Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Z
zhichan
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
蒋勇
zhichan
Commits
b71a342b
Commit
b71a342b
authored
Dec 07, 2019
by
蒋勇
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
d
parent
a9b0034b
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
474 additions
and
22 deletions
+474
-22
taskexecutor/app/base/db/impl/common/cacheManager.js
+35
-0
taskexecutor/app/base/db/impl/common/connection.js
+0
-2
taskexecutor/app/base/db/task.base.js
+20
-2
taskexecutor/app/base/db/task/test/testTask.js
+1
-0
taskexecutor/app/base/utils/redisClient.js
+265
-0
taskexecutor/app/base/utils/restClient.js
+136
-14
taskexecutor/app/config/objsettings.js
+1
-1
taskexecutor/app/config/settings.js
+10
-0
taskexecutor/main.js
+3
-3
taskexecutor/package.json
+3
-0
No files found.
taskexecutor/app/base/db/impl/common/cacheManager.js
0 → 100644
View file @
b71a342b
const
fs
=
require
(
"fs"
);
const
settings
=
require
(
"../../../../config/settings"
);
class
CacheManager
{
constructor
(){
//await this.buildCacheMap();
this
.
buildCacheMap
();
}
buildCacheMap
(){
var
self
=
this
;
self
.
doc
=
{};
var
cachePath
=
settings
.
basepath
+
"/app/base/db/cache/"
;
const
files
=
fs
.
readdirSync
(
cachePath
);
if
(
files
){
files
.
forEach
(
function
(
r
){
var
classObj
=
require
(
cachePath
+
"/"
+
r
);
self
[
classObj
.
name
]
=
new
classObj
();
var
refTmp
=
self
[
classObj
.
name
];
if
(
refTmp
.
prefix
){
self
.
doc
[
refTmp
.
prefix
]
=
refTmp
.
desc
;
}
else
{
console
.
log
(
"请在"
+
classObj
.
name
+
"缓存中定义prefix"
);
}
});
}
}
}
module
.
exports
=
CacheManager
;
// var cm= new CacheManager();
// cm["InitGiftCache"].cacheGlobalVal("hello").then(function(){
// cm["InitGiftCache"].cacheGlobalVal().then(x=>{
// console.log(x);
// });
// });
taskexecutor/app/base/db/impl/common/connection.js
View file @
b71a342b
...
...
@@ -6,8 +6,6 @@ var glob = require("glob");
class
DbFactory
{
constructor
(){
const
dbConfig
=
settings
.
database
();
console
.
log
(
"ppppppppppppppppppppppppp"
);
console
.
log
(
dbConfig
);
this
.
db
=
new
Sequelize
(
dbConfig
.
dbname
,
dbConfig
.
user
,
dbConfig
.
password
,
...
...
taskexecutor/app/base/db/task.base.js
View file @
b71a342b
const
system
=
require
(
"./system"
)
const
system
=
require
(
".
.
/system"
)
class
TaskBase
{
constructor
(
className
){
//
this.redisClient=system.getObject("util.redisClient");
this
.
redisClient
=
system
.
getObject
(
"util.redisClient"
);
this
.
serviceName
=
className
;
this
.
isThrough
=
false
;
this
.
cacheManager
=
system
.
getObject
(
"db.common.cacheManager"
);
this
.
db
=
system
.
getObject
(
"db.common.connection"
).
getCon
();
}
async
beforeTask
(
params
){
console
.
log
(
"前置操作......"
,
this
.
serviceName
);
...
...
@@ -44,5 +46,21 @@ class TaskBase{
static
getServiceName
(
ClassObj
){
return
ClassObj
[
"name"
];
}
async
apiCallWithAk
(
url
,
params
){
var
acckapp
=
await
this
.
cacheManager
[
"ApiAccessKeyCache"
].
cache
(
settings
.
appKey
);
var
acck
=
acckapp
.
accessKey
;
//按照访问token
var
restResult
=
await
this
.
restS
.
execPostWithAK
(
params
,
url
,
acck
);
if
(
restResult
){
if
(
restResult
.
status
==
0
){
var
resultRtn
=
restResult
.
data
;
return
resultRtn
;
}
else
{
await
this
.
cacheManager
[
"ApiAccessKeyCache"
].
invalidate
(
settings
.
appKey
);
return
null
;
}
}
return
null
;
}
}
module
.
exports
=
TaskBase
;
taskexecutor/app/base/db/task/test/testTask.js
View file @
b71a342b
...
...
@@ -6,6 +6,7 @@ class TestTask extends TaskBase{
async
beforeTask
(
params
){
console
.
log
(
"前置操作......"
,
this
.
serviceName
);
//this.isThrough=true;
//console.log(this.cacheManager);
}
async
subDoTask
(
params
){
console
.
log
(
params
);
...
...
taskexecutor/app/base/utils/redisClient.js
0 → 100644
View file @
b71a342b
const
System
=
require
(
"../system"
);
const
redis
=
require
(
"redis"
);
const
settings
=
require
(
"../../config/settings"
);
const
bluebird
=
require
(
"bluebird"
);
bluebird
.
promisifyAll
(
redis
);
// const logCtl=System.getObject("web.oplogCtl");
class
RedisClient
{
constructor
()
{
const
redisConfig
=
settings
.
redis
();
this
.
client
=
redis
.
createClient
({
host
:
redisConfig
.
host
,
port
:
redisConfig
.
port
,
password
:
redisConfig
.
password
,
db
:
redisConfig
.
db
,
retry_strategy
:
function
(
options
)
{
// if (options.error && options.error.code === 'ECONNREFUSED') {
// // End reconnecting on a specific error and flush all commands with
// // a individual error
// return new Error('The server refused the connection');
// }
if
(
options
.
total_retry_time
>
1000
*
60
*
60
)
{
// End reconnecting after a specific timeout and flush all commands
// with a individual error
return
new
Error
(
'Retry time exhausted'
);
}
if
(
options
.
attempt
>
10
)
{
// End reconnecting with built in error
return
10000
;
}
// reconnect after
return
Math
.
min
(
options
.
attempt
*
100
,
3000
);
}
});
// return client.multi().get('foo').execAsync().then(function(res) {
// console.log(res); // => 'bar'
// });
this
.
client
.
on
(
"error"
,
function
(
err
)
{
console
.
log
(
"Error "
+
err
);
// //日志记录
// logCtl.error({
// optitle:"redis this.client.on异常:",
// op:"base/utils/redisClient/this.client.on",
// content:err,
// clientIp:""
// });
});
this
.
subclient
=
this
.
client
.
duplicate
();
this
.
subclient
.
on
(
"error"
,
function
(
err
)
{
console
.
log
(
"Error "
+
err
);
// //日志记录
// logCtl.error({
// optitle:"redis this.subclient.on异常:",
// op:"base/utils/redisClient/this.subclient.on",
// content:err,
// clientIp:""
// });
});
var
self
=
this
;
this
.
subclient
.
on
(
"message"
,
async
function
(
channel
,
message
)
{
console
.
log
(
channel
,
'------------- redis message ------------------- '
);
if
(
self
.
taskmanager
)
{
if
(
channel
==
"task"
)
{
if
(
message
==
"newtask"
)
{
(
async
(
that
)
=>
{
var
msg2
=
await
that
.
rpop
(
"tasklist"
);
if
(
msg2
)
{
console
.
log
(
"taskName+++++"
+
msg2
);
var
msgs2
=
msg2
.
split
(
"_"
);
var
action
=
msgs2
[
0
];
var
taskName
=
msgs2
[
1
];
var
exp
=
msgs2
[
2
];
await
that
.
taskmanager
.
addTask
(
taskName
,
exp
);
}
})(
self
)
}
else
{
(
async
(
msg
,
that
)
=>
{
var
msgs
=
msg
.
split
(
"_"
);
var
action
=
msgs
[
0
];
if
(
action
==
"delete"
)
{
var
taskName
=
msgs
[
1
];
await
that
.
taskmanager
.
deleteTask
(
taskName
);
}
})(
message
,
self
);
}
}
}
if
(
self
.
chatserver
)
{
if
(
channel
!=
"task"
)
{
var
message
=
JSON
.
parse
(
message
);
console
.
log
(
message
,
"------------------------------------------ publish message"
);
if
(
channel
==
"brc"
)
{
//如果是广播频道,则发送广播到客户端
self
.
chatserver
.
server
.
emit
(
"brc"
,
message
);
}
else
if
(
self
.
chatserver
.
users
[
channel
])
{
if
(
message
.
type
)
{
self
.
chatserver
.
users
[
channel
].
client
.
emit
(
message
.
type
,
message
.
data
);
}
else
{
//持久化
self
.
chatserver
.
users
[
channel
].
client
.
emit
(
"chatmsg"
,
message
);
}
}
}
}
});
}
async
subscribe
(
channel
,
chatserver
)
{
if
(
!
this
.
chatserver
)
{
this
.
chatserver
=
chatserver
;
}
return
this
.
subclient
.
subscribeAsync
(
channel
);
}
async
unsubscribe
(
channel
)
{
//this.chatserver=null;
return
this
.
subclient
.
unsubscribeAsync
(
channel
);
}
async
subscribeTask
(
channel
,
taskmanager
)
{
if
(
!
this
.
taskmanager
)
{
this
.
taskmanager
=
taskmanager
;
}
return
this
.
subclient
.
subscribeAsync
(
channel
);
}
async
publish
(
channel
,
msg
)
{
console
.
log
(
channel
+
":"
+
msg
);
return
this
.
client
.
publishAsync
(
channel
,
msg
);
}
async
rpush
(
key
,
val
)
{
return
this
.
client
.
rpushAsync
(
key
,
val
);
}
async
llen
(
key
)
{
return
this
.
client
.
llenAsync
(
key
);
}
async
rpushWithEx
(
key
,
val
,
t
)
{
var
p
=
this
.
rpush
(
key
,
val
);
this
.
client
.
expire
(
key
,
t
);
return
p
;
}
async
rpop
(
key
)
{
return
this
.
client
.
rpopAsync
(
key
);
}
async
lpop
(
key
)
{
return
this
.
client
.
lpopAsync
(
key
);
}
async
lrem
(
key
,
val
)
{
return
this
.
client
.
lremAsync
(
key
,
1
,
val
);
}
async
ltrim
(
key
,
s
,
e
)
{
return
this
.
client
.
ltrimAsync
(
key
,
s
,
e
);
}
async
clearlist
(
key
)
{
await
this
.
client
.
ltrim
(
key
,
-
1
,
-
1
);
await
this
.
client
.
ltrim
(
key
,
1
,
-
1
);
return
0
;
}
async
flushall
()
{
console
.
log
(
"sss"
);
return
this
.
client
.
flushallAsync
();
}
async
keys
(
p
)
{
return
this
.
client
.
keysAsync
(
p
);
}
async
set
(
key
,
val
)
{
if
(
typeof
val
==
"undefined"
||
typeof
key
==
"undefined"
)
{
console
.
log
(
"......................cache val undefined"
);
console
.
log
(
key
);
return
null
;
}
return
this
.
client
.
setAsync
(
key
,
val
);
}
async
setWithEx
(
key
,
val
,
t
)
{
var
p
=
this
.
client
.
setAsync
(
key
,
val
);
this
.
client
.
expire
(
key
,
t
);
return
p
;
}
async
get
(
key
)
{
return
this
.
client
.
getAsync
(
key
);
}
async
delete
(
key
)
{
return
this
.
client
.
delAsync
(
key
);
}
async
hmset
(
key
,
jsonObj
)
{
return
this
.
client
.
hmsetAsync
(
key
,
jsonObj
);
}
async
hmsetWithEx
(
key
,
jsonObj
,
t
)
{
var
p
=
this
.
client
.
hmsetAsync
(
key
,
jsonObj
);
this
.
client
.
expire
(
key
,
t
);
return
p
;
}
async
hgetall
(
key
)
{
return
this
.
client
.
hgetallAsync
(
key
);
}
async
hincrby
(
key
,
f
,
n
)
{
return
this
.
client
.
hincrbyAsync
(
key
,
f
,
n
);
}
async
sadd
(
key
,
vals
)
{
await
this
.
client
.
saddAsync
(
key
,
...
vals
);
return
this
.
scard
(
key
);
}
async
scard
(
key
)
{
return
this
.
client
.
scardAsync
(
key
);
}
async
srem
(
key
,
val
)
{
return
this
.
client
.
sremAsync
(
key
,
val
);
}
async
sismember
(
key
,
val
)
{
return
this
.
client
.
sismemberAsync
(
key
,
val
);
}
async
smembers
(
key
)
{
return
this
.
client
.
smembersAsync
(
key
);
}
async
exists
(
key
)
{
return
this
.
client
.
existsAsync
(
key
);
}
async
incr
(
key
)
{
return
this
.
client
.
incrAsync
(
key
);
}
}
module
.
exports
=
RedisClient
;
// var client=new RedisClient();
// (async ()=>{
// await client.rpush("tasklist","xxx");
// await client.rpush("tasklist","xxx");
// var len=await client.llen("tasklist");
// //await client.clearlist("tasklist");
// len=await client.llen("tasklist");
// console.log(len);
// })()
// client.keys('*').then(s=>{
// console.log(s);
// });
// let clients = {};
// clients.watcher = redis.createClient({ ... } );
// clients.alterer = clients.watcher.duplicate();
// client.sadd("h",["ok","jy","ok"]).then(function(r){
// console.log(r);
// });
// client.smembers("h").then(function(r){
// console.log(r);
// });
// client.sismember("h","ok").then(function(r){
// console.log(r);
// });
// console.dir(client);ti.exec( callback )回调函数参数err:返回null或者Array,出错则返回对应命令序列链中发生错误的错误信息,这个数组中最后一个元素是源自exec本身的一个EXECABORT类型的错误
// r.set("hello","oooo").then(function(result){
// console.log(result);
// });
// r.get("hello").then(function(result){
// console.log(result);
// });
// client.hmset("user_1",{name:"jy",age:13}).then(function(r){
// console.log(r);
//
// });
// client.hincrby("user_1","age",2).then(function(r){
// console.log(r);
// setTimeout(function(){
// client.hgetall("user_1").then(function(u){
// console.log(u);
// });
// },3000);
// });
taskexecutor/app/base/utils/restClient.js
View file @
b71a342b
...
...
@@ -5,32 +5,154 @@ const querystring = require('querystring');
var
settings
=
require
(
"../../config/settings"
);
class
RestClient
{
constructor
(){
this
.
cmdGetPattern
=
"curl {-G} -k -d '{data}' {url}"
;
this
.
cmdPostPattern
=
"curl -k -H 'Content-type: application/json' -d '{data}' '{url}'"
;
this
.
cmdPostPatternWithAK
=
"curl -k -H 'Content-type: application/json' -H 'AccessKey:{ak}' -d '{data}' {url}"
;
this
.
cmdDownLoadFilePattern
=
"curl -G -o {fileName} {url}"
;
this
.
cmdPostPattern2
=
"curl -k -H 'Content-type: application/x-www-form-urlencoded' -d '{data}' {url}"
;
// form-data形式post data参数类型 md5=2&data=1
this
.
cmdPostPattern5
=
"curl -k --data '{data}' {url}"
;
}
FetchGetCmd
(
subData
,
url
)
{
var
cmd
=
this
.
cmdGetPattern
.
replace
(
/
\{\-
G
\}
/g
,
"-G"
).
replace
(
/
\{
data
\}
/g
,
subData
).
replace
(
/
\{
url
\}
/g
,
url
);
return
cmd
;
}
FetchPostCmd
(
subData
,
url
)
{
var
data
=
JSON
.
stringify
(
subData
);
var
cmd
=
this
.
cmdPostPattern
.
replace
(
/
\{
data
\}
/g
,
data
).
replace
(
/
\{
url
\}
/g
,
url
);
return
cmd
;
}
FetchPostCmdWithAK
(
subData
,
url
,
acck
)
{
var
data
=
JSON
.
stringify
(
subData
);
var
cmd
=
this
.
cmdPostPatternWithAK
.
replace
(
/
\{
data
\}
/g
,
data
).
replace
(
/
\{
url
\}
/g
,
url
).
replace
(
/
\{
ak
\}
/g
,
acck
);
return
cmd
;
}
FetchPostCmd2
(
subData
,
url
)
{
var
data
=
subData
;
var
cmd
=
this
.
cmdPostPattern2
.
replace
(
/
\{
data
\}
/g
,
data
).
replace
(
/
\{
url
\}
/g
,
url
);
return
cmd
;
}
FetchPostCmd3
(
subData
,
url
)
{
var
data
=
subData
;
var
cmd
=
this
.
cmdPostPattern3
.
replace
(
/
\{
data
\}
/g
,
data
).
replace
(
/
\{
url
\}
/g
,
url
);
return
cmd
;
}
FetchGetCmd3
(
url
)
{
var
cmd
=
this
.
cmdGetPattern3
.
replace
(
/
\{\-
G
\}
/g
,
"-G"
).
replace
(
/
\{
url
\}
/g
,
url
);
return
cmd
;
}
FetchPostCmd4
(
subData
,
url
)
{
var
data
=
subData
;
var
cmd
=
this
.
cmdPostPattern4
.
replace
(
/
\{
data
\}
/g
,
data
).
replace
(
/
\{
url
\}
/g
,
url
);
return
cmd
;
}
FetchPostCmd5
(
subData
,
url
)
{
var
data
=
subData
;
var
cmd
=
this
.
cmdPostPattern5
.
replace
(
/
\{
data
\}
/g
,
data
).
replace
(
/
\{
url
\}
/g
,
url
);
return
cmd
;
}
FetchDownLoadCmd
(
outfname
,
url
)
{
// console.log(this.cmdPattern);
var
cmd
=
this
.
cmdDownLoadFilePattern
.
replace
(
/
\{
fileName
\}
/g
,
outfname
).
replace
(
/
\{
url
\}
/g
,
url
);
return
cmd
;
}
async
exec
(
cmd
,
options
)
{
//await后面表达式返回的promise对象,是then的语法糖,await返回then函数的返回值
//异常需要try/catch自己捕获或外部catch捕获
if
(
options
){
const
{
stdout
,
stderr
}
=
await
exec
(
cmd
,
options
);
return
{
stdout
,
stderr
};
}
else
{
const
{
stdout
,
stderr
}
=
await
exec
(
cmd
);
return
{
stdout
,
stderr
};
}
}
async
exec
(
cmd
)
{
//await后面表达式返回的promise对象,是then的语法糖,await返回then函数的返回值
//异常需要try/catch自己捕获或外部catch捕获
const
{
stdout
,
stderr
}
=
await
exec
(
cmd
,{
maxBuffer
:
1024
*
1024
*
15
});
return
{
stdout
,
stderr
};
}
async
execDownload
(
url
,
outfname
){
let
cmd
=
this
.
FetchDownLoadCmd
(
outfname
,
url
);
var
result
=
await
this
.
exec
(
cmd
);
return
result
;
}
async
execGet
(
subData
,
url
){
let
cmd
=
this
.
FetchGetCmd
(
subData
,
url
);
var
result
=
await
this
.
exec
(
cmd
);
return
result
;
}
async
execGet2
(
subData
,
url
){
var
data
=
querystring
.
stringify
(
subData
);
let
cmd
=
this
.
FetchGetCmd
(
data
,
url
);
var
result
=
await
this
.
exec
(
cmd
);
return
result
;
}
async
execPost
(
subData
,
url
){
let
cmd
=
this
.
FetchPostCmd
(
subData
,
url
);
var
result
=
await
this
.
exec
(
cmd
,{
maxBuffer
:
10000
*
1024
});
return
result
;
}
async
execPostWithAK
(
subData
,
url
,
ak
){
let
cmd
=
this
.
FetchPostCmdWithAK
(
subData
,
url
,
ak
);
var
result
=
await
this
.
exec
(
cmd
,{
maxBuffer
:
1024
*
1024
*
15
});
var
rtn
=
result
.
stdout
;
if
(
rtn
){
return
JSON
.
parse
(
rtn
);
}
else
{
return
null
;
}
}
async
execPost2
(
subData
,
url
){
let
cmd
=
this
.
FetchPostCmd2
(
subData
,
url
);
console
.
log
(
cmd
);
var
result
=
await
this
.
exec
(
cmd
);
return
result
;
}
async
execPost3
(
subData
,
url
){
let
cmd
=
this
.
FetchPostCmd3
(
subData
,
url
);
console
.
log
(
cmd
);
var
result
=
await
this
.
exec
(
cmd
);
return
result
;
}
async
execGet3
(
url
){
let
cmd
=
this
.
FetchGetCmd3
(
url
);
console
.
log
(
"execGet3-----01"
);
console
.
log
(
cmd
);
var
result
=
await
this
.
exec
(
cmd
);
return
result
;
}
async
execPostESignBao
(
subData
,
url
){
let
cmd
=
this
.
FetchPostCmd4
(
subData
,
url
);
console
.
log
(
cmd
);
var
result
=
await
this
.
exec
(
cmd
);
return
result
;
}
async
execPostForm
(
subData
,
url
){
let
cmd
=
this
.
FetchPostCmd5
(
subData
,
url
);
console
.
log
(
cmd
);
var
result
=
await
this
.
exec
(
cmd
);
return
result
;
}
async
execCustomPostESignBao
(
cmd
){
console
.
log
(
cmd
);
var
result
=
await
this
.
exec
(
cmd
);
return
result
;
}
test
(){
console
.
log
(
"hello"
);
}
}
module
.
exports
=
RestClient
;
// var x=new RestClient();
// x.execGet("","http://www.163.com").then(function(r){
// console.log(r.stdout);
// console.log(r.stderr);
// });
taskexecutor/app/config/objsettings.js
View file @
b71a342b
...
...
@@ -3,6 +3,6 @@ var basepath=path.normalize(path.join(__dirname, '../..'));
var
settings
=
{
db
:
path
.
join
(
basepath
,
"app/base/db/impl"
),
util
:
path
.
join
(
basepath
,
"app/base/utils"
),
task
:
path
.
join
(
basepath
,
"app/base/task"
),
task
:
path
.
join
(
basepath
,
"app/base/
db/
task"
),
};
module
.
exports
=
settings
;
taskexecutor/app/config/settings.js
View file @
b71a342b
...
...
@@ -15,6 +15,16 @@ var settings = {
env
:
ENVINPUT
.
APP_ENV
,
basepath
:
path
.
normalize
(
path
.
join
(
__dirname
,
'../..'
)),
port
:
process
.
env
.
NODE_PORT
||
3001
,
appKey
:
"0f74f4021cd34189a9fa020be1dfac9c"
,
paasKey
:
"wx76a324c5d201d1a4"
,
secret
:
"fcf106b9ca5f4cd7ad8b9d34a3f7b29d"
,
paasUrl
:
function
(){
if
(
this
.
env
==
"dev"
){
return
"http://p.apps.com:4001/"
;
}
else
{
return
"http://open.gongsibao.com/"
;
}
},
redis
:
function
(){
if
(
this
.
env
==
"dev"
){
var
localsettings
=
require
(
"./localsettings"
);
...
...
taskexecutor/main.js
View file @
b71a342b
const
system
=
require
(
"./app/base/system"
);
const
fs
=
require
(
"fs"
);
var
dbf
=
system
.
getObject
(
"db.common.connection"
);
var
downClient
=
system
.
getObject
(
"util.restClient
"
);
con
=
dbf
.
getCon
();
//数据库支持暂缓支持
// var dbf=system.getObject("db.common.connection
");
//
con=dbf.getCon();
var
taskName
=
process
.
env
.
TASK_NAME
;
var
params
=
process
.
env
.
TASK_PARAM
;
if
(
taskName
){
...
...
taskexecutor/package.json
View file @
b71a342b
...
...
@@ -12,5 +12,8 @@
"
glob
"
:
"^7.1.4"
,
"
mysql2
"
:
"^1.5.3"
,
"
sequelize
"
:
"^4.37.8"
},
"devDependencies"
:
{
"
redis
"
:
"^2.8.0"
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment