Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wecloud_im_server
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
hewei
wecloud_im_server
Commits
c22b869b
Commit
c22b869b
authored
Dec 20, 2021
by
hweeeeeei
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
编码优化
parent
60209023
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
52 additions
and
49 deletions
+52
-49
core/src/main/java/com/wecloud/im/netty/handler/NettyApiRequest.java
+1
-1
core/src/main/java/com/wecloud/im/push/AndroidNotification.java
+1
-0
core/src/main/java/com/wecloud/im/push/PushClient.java
+6
-5
core/src/main/java/com/wecloud/im/register/GetIpUtils.java
+5
-4
core/src/main/java/com/wecloud/im/ws/receive/ReadWsData.java
+2
-2
core/src/main/java/com/wecloud/im/ws/sender/PushTask.java
+6
-6
core/src/main/java/com/wecloud/im/ws/service/impl/MangerChannelServiceImpl.java
+8
-8
core/src/main/java/com/wecloud/im/ws/strategy/AbstractImCmd.java
+1
-1
core/src/main/java/com/wecloud/im/ws/strategy/ImCmdContext.java
+2
-2
core/src/main/java/com/wecloud/im/ws/strategy/concrete/ImChatConcrete.java
+2
-2
core/src/main/java/com/wecloud/im/ws/utils/KeyGenerator.java
+3
-3
framework/src/main/java/io/geekidea/springbootplus/framework/config/il8n/I18nMessageUtil.java
+2
-2
framework/src/main/java/io/geekidea/springbootplus/framework/core/util/RequestDetailThreadLocal.java
+4
-4
framework/src/main/java/io/geekidea/springbootplus/framework/shiro/jwt/JwtFilter.java
+1
-1
framework/src/main/java/io/geekidea/springbootplus/framework/shiro/service/ShiroLoginService.java
+1
-1
framework/src/main/java/io/geekidea/springbootplus/framework/shiro/service/impl/ShiroLoginServiceImpl.java
+1
-1
framework/src/main/java/io/geekidea/springbootplus/framework/util/AnsiUtil.java
+3
-3
framework/src/main/java/io/geekidea/springbootplus/framework/util/Jackson.java
+3
-3
No files found.
core/src/main/java/com/wecloud/im/netty/handler/NettyApiRequest.java
View file @
c22b869b
...
...
@@ -103,7 +103,7 @@ public class NettyApiRequest {
}
// 从redis获取jwt的token 验签token
JwtToken
jwtToken
=
shiroLoginService
.
get
T
JwtTokenForRedis
(
token
);
JwtToken
jwtToken
=
shiroLoginService
.
getJwtTokenForRedis
(
token
);
if
(
jwtToken
==
null
)
{
log
.
info
(
"jwtToken == null ,token和redis不一致, clientId:"
+
clientId
+
",token:"
+
token
);
...
...
core/src/main/java/com/wecloud/im/push/AndroidNotification.java
View file @
c22b869b
...
...
@@ -203,6 +203,7 @@ public abstract class AndroidNotification extends UmengNotification {
setPredefinedKeyValue
(
"custom"
,
custom
);
}
@SuppressWarnings
(
"AliMissingOverrideAnnotation"
)
public
enum
DisplayType
{
NOTIFICATION
{
public
String
getValue
()
{
...
...
core/src/main/java/com/wecloud/im/push/PushClient.java
View file @
c22b869b
...
...
@@ -15,20 +15,21 @@ import java.nio.charset.StandardCharsets;
public
class
PushClient
{
// The host
protected
static
final
String
host
=
"http://msg.umeng.com"
;
protected
static
final
String
HOST
=
"http://msg.umeng.com"
;
// The upload path
protected
static
final
String
uploadPath
=
"/upload"
;
protected
static
final
String
UPLOAD_PATH
=
"/upload"
;
// The post path
protected
static
final
String
postPath
=
"/api/send"
;
protected
static
final
String
POST_PATH
=
"/api/send"
;
// The user agent
protected
final
String
USER_AGENT
=
"Mozilla/5.0"
;
// This object is used for sending the post request to Umeng
@SuppressWarnings
(
"AliDeprecation"
)
protected
HttpClient
client
=
new
DefaultHttpClient
();
public
boolean
send
(
UmengNotification
msg
)
throws
Exception
{
String
timestamp
=
Integer
.
toString
((
int
)
(
System
.
currentTimeMillis
()
/
1000
));
msg
.
setPredefinedKeyValue
(
"timestamp"
,
timestamp
);
String
url
=
host
+
postPath
;
String
url
=
HOST
+
POST_PATH
;
String
postBody
=
msg
.
getPostBody
();
String
sign
=
DigestUtils
.
md5Hex
((
"POST"
+
url
+
postBody
+
msg
.
getAppMasterSecret
()).
getBytes
(
StandardCharsets
.
UTF_8
));
url
=
url
+
"?sign="
+
sign
;
...
...
@@ -64,7 +65,7 @@ public class PushClient {
uploadJson
.
put
(
"timestamp"
,
timestamp
);
uploadJson
.
put
(
"content"
,
contents
);
// Construct the request
String
url
=
host
+
uploadPath
;
String
url
=
HOST
+
UPLOAD_PATH
;
String
postBody
=
uploadJson
.
toString
();
String
sign
=
DigestUtils
.
md5Hex
((
"POST"
+
url
+
postBody
+
appMasterSecret
).
getBytes
(
StandardCharsets
.
UTF_8
));
url
=
url
+
"?sign="
+
sign
;
...
...
core/src/main/java/com/wecloud/im/register/GetIpUtils.java
View file @
c22b869b
...
...
@@ -37,7 +37,7 @@ public class GetIpUtils {
* 服务器运营商local,aws,huawei
*/
@Value
(
"${load-blance.server-type}"
)
private
String
SERVER_TYPE
;
private
String
serverType
;
/**
* 判断是否为虚拟mac地址
...
...
@@ -53,7 +53,7 @@ public class GetIpUtils {
/*
* 排除无效的mac地址
*/
byte
[][]
INVALID_MACS
=
{
byte
[][]
invalidMacs
=
{
{
0x00
,
0x05
,
0x69
},
// VMWare
{
0x00
,
0x1C
,
0x14
},
// VMWare
{
0x00
,
0x0C
,
0x29
},
// VMWare
...
...
@@ -64,7 +64,7 @@ public class GetIpUtils {
{
0x00
,
0x15
,
0x5D
}
// Hyper-V
};
for
(
byte
[]
invalid
:
INVALID_MACS
)
{
for
(
byte
[]
invalid
:
invalidMacs
)
{
if
(
invalid
[
0
]
==
mac
[
0
]
&&
invalid
[
1
]
==
mac
[
1
]
&&
invalid
[
2
]
==
mac
[
2
])
{
return
true
;
}
...
...
@@ -133,7 +133,7 @@ public class GetIpUtils {
public
String
getPublicIp
()
{
if
(
PUBLIC_IP
==
null
)
{
switch
(
SERVER_TYPE
)
{
switch
(
serverType
)
{
case
LOCAL:
PUBLIC_IP
=
getlanIp
();
break
;
...
...
@@ -143,6 +143,7 @@ public class GetIpUtils {
case
HUAWEI_CLOUD:
PUBLIC_IP
=
HttpUtil
.
get
(
"http://169.254.169.254/latest/meta-data/public-ipv4"
,
30
);
break
;
default
:
}
}
return
PUBLIC_IP
;
...
...
core/src/main/java/com/wecloud/im/ws/receive/ReadWsData.java
View file @
c22b869b
...
...
@@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.json.JsonMapper;
import
com.wecloud.im.ws.enums.WsRequestCmdEnum
;
import
com.wecloud.im.ws.model.request.ReceiveModel
;
import
com.wecloud.im.ws.service.WriteDataService
;
import
com.wecloud.im.ws.strategy.
ImCmdAbstract
;
import
com.wecloud.im.ws.strategy.
AbstractImCmd
;
import
com.wecloud.im.ws.strategy.ImCmdContext
;
import
io.geekidea.springbootplus.framework.common.exception.BusinessException
;
import
io.netty.channel.ChannelHandlerContext
;
...
...
@@ -51,7 +51,7 @@ public class ReadWsData {
WsRequestCmdEnum
wsRequestUriPathEnum
=
WsRequestCmdEnum
.
getByCode
(
receiveModel
.
getCmd
());
// 使用策略模式, 根据不同类型请求调用不同实现类
ImCmdAbstract
cmdStrategy
=
receiveStrategyContext
.
getStrategy
(
wsRequestUriPathEnum
);
AbstractImCmd
cmdStrategy
=
receiveStrategyContext
.
getStrategy
(
wsRequestUriPathEnum
);
cmdStrategy
.
process
(
receiveModel
,
ctx
,
data
,
appKey
,
clientId
);
}
...
...
core/src/main/java/com/wecloud/im/ws/sender/PushTask.java
View file @
c22b869b
...
...
@@ -45,8 +45,8 @@ public class PushTask {
* 点击查看
*/
private
static
final
String
PUSH_BODY
=
"Click to view"
;
private
static
final
String
title
=
"title"
;
private
static
final
String
subTitle
=
"subTitle"
;
private
static
final
String
TITLE
=
"title"
;
private
static
final
String
SUB_TITLE
=
"subTitle"
;
@Autowired
private
ImIosApnsService
imIosApnsService
;
@Autowired
...
...
@@ -103,8 +103,8 @@ public class PushTask {
pushModel
.
setTitle
(
PUSH_TITLE
);
pushModel
.
setSubTitle
(
PUSH_BODY
);
}
else
{
pushModel
.
setTitle
(
pushMap
.
get
(
title
));
pushModel
.
setSubTitle
(
pushMap
.
get
(
subTitle
));
pushModel
.
setTitle
(
pushMap
.
get
(
TITLE
));
pushModel
.
setSubTitle
(
pushMap
.
get
(
SUB_TITLE
));
}
this
.
push
(
pushModel
,
imClientReceiver
,
imApplication
);
...
...
@@ -125,9 +125,9 @@ public class PushTask {
PushUtils
pushUtils
=
new
PushUtils
(
imApplication
.
getUmengKey
(),
imApplication
.
getUmengSecret
());
// 安卓 单推
String
deviceToken
IOS
=
imClientReceiver
.
getDeviceToken
();
String
deviceToken
=
imClientReceiver
.
getDeviceToken
();
try
{
pushUtils
.
sendIOSUnicast
(
deviceToken
IOS
,
pushModel
.
getTitle
(),
pushModel
.
getSubTitle
(),
PUSH_BODY
);
pushUtils
.
sendIOSUnicast
(
deviceToken
,
pushModel
.
getTitle
(),
pushModel
.
getSubTitle
(),
PUSH_BODY
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
...
...
core/src/main/java/com/wecloud/im/ws/service/impl/MangerChannelServiceImpl.java
View file @
c22b869b
...
...
@@ -34,7 +34,7 @@ public class MangerChannelServiceImpl implements MangerChannelService {
// @Override
// public NioSocketChannel get(String appKey, String clientId) {
//
// return
this
.CHANNEL_MAP.get(appKey + clientId);
// return
MangerChannelService
.CHANNEL_MAP.get(appKey + clientId);
// }
@Override
...
...
@@ -47,7 +47,7 @@ public class MangerChannelServiceImpl implements MangerChannelService {
// nioSocketChannel.close();
// }
//
this
.CHANNEL_MAP.put(appKey + clientId, channel);
//
MangerChannelService
.CHANNEL_MAP.put(appKey + clientId, channel);
String
longChannelId
=
channel
.
id
().
asLongText
();
this
.
putClientsMap
(
appKey
,
clientId
,
longChannelId
);
...
...
@@ -61,26 +61,26 @@ public class MangerChannelServiceImpl implements MangerChannelService {
clientInfo
.
setDeviceId
(
""
);
clientInfo
.
setNioSocketChannel
(
channel
);
clientInfo
.
setToken
(
""
);
this
.
SESSION_INFO_MAP
.
put
(
longChannelId
,
clientInfo
);
MangerChannelService
.
SESSION_INFO_MAP
.
put
(
longChannelId
,
clientInfo
);
}
void
putClientsMap
(
String
appKey
,
String
clientId
,
String
longChannelId
)
{
Set
<
String
>
set
=
this
.
CLIENTS_MAP
.
get
(
appKey
+
":"
+
clientId
);
Set
<
String
>
set
=
MangerChannelService
.
CLIENTS_MAP
.
get
(
appKey
+
":"
+
clientId
);
if
(
set
==
null
||
set
.
isEmpty
())
{
HashSet
<
String
>
hashSet
=
new
HashSet
<>();
hashSet
.
add
(
longChannelId
);
this
.
CLIENTS_MAP
.
put
(
appKey
+
":"
+
clientId
,
hashSet
);
MangerChannelService
.
CLIENTS_MAP
.
put
(
appKey
+
":"
+
clientId
,
hashSet
);
}
else
{
set
.
add
(
longChannelId
);
}
}
void
delSessionInfoMap
(
String
longChannelId
)
{
this
.
SESSION_INFO_MAP
.
remove
(
longChannelId
);
MangerChannelService
.
SESSION_INFO_MAP
.
remove
(
longChannelId
);
}
void
delClientsMap
(
String
appKey
,
String
clientId
,
String
longChannelId
)
{
Set
<
String
>
set
=
this
.
CLIENTS_MAP
.
get
(
appKey
+
":"
+
clientId
);
Set
<
String
>
set
=
MangerChannelService
.
CLIENTS_MAP
.
get
(
appKey
+
":"
+
clientId
);
if
(
set
!=
null
)
{
set
.
remove
(
longChannelId
);
}
...
...
@@ -121,7 +121,7 @@ public class MangerChannelServiceImpl implements MangerChannelService {
// @Override
// public Boolean isOnLocal(Long userId) {
// NioSocketChannel nioSocketChannel =
this
.get(String.valueOf(userId));
// NioSocketChannel nioSocketChannel =
MangerChannelService
.get(String.valueOf(userId));
// return null != nioSocketChannel;
// }
...
...
core/src/main/java/com/wecloud/im/ws/strategy/
ImCmdAbstract
.java
→
core/src/main/java/com/wecloud/im/ws/strategy/
AbstractImCmd
.java
View file @
c22b869b
...
...
@@ -11,7 +11,7 @@ import io.netty.channel.ChannelHandlerContext;
* @Author hewei hwei1233@163.com
* @Date 2020-01-02
*/
public
abstract
class
ImCmdAbstract
{
public
abstract
class
AbstractImCmd
{
/**
* 处理业务流程
...
...
core/src/main/java/com/wecloud/im/ws/strategy/ImCmdContext.java
View file @
c22b869b
...
...
@@ -19,7 +19,7 @@ public class ImCmdContext {
this
.
strategyMap
=
strategyMap
;
}
public
ImCmdAbstract
getStrategy
(
WsRequestCmdEnum
wsRequestPathEnum
)
{
public
AbstractImCmd
getStrategy
(
WsRequestCmdEnum
wsRequestPathEnum
)
{
if
(
wsRequestPathEnum
==
null
)
{
throw
new
IllegalArgumentException
(
"not fond enum"
);
...
...
@@ -35,6 +35,6 @@ public class ImCmdContext {
throw
new
IllegalArgumentException
(
"not fond strategy for type:"
+
wsRequestPathEnum
.
getCmdCode
());
}
return
(
ImCmdAbstract
)
SpringBeanUtils
.
getBean
(
aClass
);
return
(
AbstractImCmd
)
SpringBeanUtils
.
getBean
(
aClass
);
}
}
core/src/main/java/com/wecloud/im/ws/strategy/concrete/ImChatConcrete.java
View file @
c22b869b
...
...
@@ -22,7 +22,7 @@ import com.wecloud.im.ws.model.WsResponseModel;
import
com.wecloud.im.ws.model.request.ReceiveModel
;
import
com.wecloud.im.ws.sender.PushTask
;
import
com.wecloud.im.ws.service.WriteDataService
;
import
com.wecloud.im.ws.strategy.
ImCmdAbstract
;
import
com.wecloud.im.ws.strategy.
AbstractImCmd
;
import
io.geekidea.springbootplus.framework.common.api.ApiCode
;
import
io.geekidea.springbootplus.framework.common.api.ApiResult
;
import
io.geekidea.springbootplus.framework.shiro.util.SnowflakeUtil
;
...
...
@@ -43,7 +43,7 @@ import java.util.List;
@CmdTypeAnnotation
(
type
=
WsRequestCmdEnum
.
DATA
)
@Service
@Slf4j
public
class
ImChatConcrete
extends
ImCmdAbstract
{
public
class
ImChatConcrete
extends
AbstractImCmd
{
public
static
final
String
PUSH_KEY
=
"push"
;
public
static
final
String
MSG_ID
=
"msgId"
;
...
...
core/src/main/java/com/wecloud/im/ws/utils/KeyGenerator.java
View file @
c22b869b
...
...
@@ -30,13 +30,13 @@ public class KeyGenerator {
//生成32位appSecret
public
static
String
getAppSecret
(
String
appId
)
{
String
E
ncryoAppSecret
=
""
;
String
e
ncryoAppSecret
=
""
;
try
{
EncrypDES
des1
=
new
EncrypDES
();
// 使用默认密钥
E
ncryoAppSecret
=
des1
.
encrypt
(
appId
);
e
ncryoAppSecret
=
des1
.
encrypt
(
appId
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
return
E
ncryoAppSecret
;
return
e
ncryoAppSecret
;
}
}
framework/src/main/java/io/geekidea/springbootplus/framework/config/il8n/I18nMessageUtil.java
View file @
c22b869b
...
...
@@ -18,7 +18,7 @@ public class I18nMessageUtil {
// 后缀
private
static
final
String
SUFFIX
=
".properties"
;
// 分解器
private
static
final
ResourcePatternResolver
resourcePatternResolver
=
new
PathMatchingResourcePatternResolver
();
private
static
final
ResourcePatternResolver
RESOURCE_PATTERN_RESOLVER
=
new
PathMatchingResourcePatternResolver
();
// 存取器
private
static
MessageSourceAccessor
accessor
;
...
...
@@ -35,7 +35,7 @@ public class I18nMessageUtil {
/*
* 获取配置文件名
*/
Resource
resource
=
resourcePatternResolver
.
getResource
(
PATH_PARENT
+
language
+
SUFFIX
);
Resource
resource
=
RESOURCE_PATTERN_RESOLVER
.
getResource
(
PATH_PARENT
+
language
+
SUFFIX
);
String
fileName
=
resource
.
getURL
().
toString
();
int
lastIndex
=
fileName
.
lastIndexOf
(
"."
);
String
baseName
=
fileName
.
substring
(
0
,
lastIndex
);
...
...
framework/src/main/java/io/geekidea/springbootplus/framework/core/util/RequestDetailThreadLocal.java
View file @
c22b869b
...
...
@@ -26,13 +26,13 @@ import io.geekidea.springbootplus.framework.core.bean.RequestDetail;
**/
public
class
RequestDetailThreadLocal
{
private
static
final
ThreadLocal
<
RequestDetail
>
threadLocal
=
new
ThreadLocal
<>();
private
static
final
ThreadLocal
<
RequestDetail
>
THREAD_LOCAL
=
new
ThreadLocal
<>();
/**
* 从当前线程中获取请求信息
*/
public
static
RequestDetail
getRequestDetail
()
{
return
threadLocal
.
get
();
return
THREAD_LOCAL
.
get
();
}
/**
...
...
@@ -41,14 +41,14 @@ public class RequestDetailThreadLocal {
* @param requestDetail
*/
public
static
void
setRequestDetail
(
RequestDetail
requestDetail
)
{
threadLocal
.
set
(
requestDetail
);
THREAD_LOCAL
.
set
(
requestDetail
);
}
/**
* 销毁
*/
public
static
void
remove
()
{
threadLocal
.
remove
();
THREAD_LOCAL
.
remove
();
}
}
framework/src/main/java/io/geekidea/springbootplus/framework/shiro/jwt/JwtFilter.java
View file @
c22b869b
...
...
@@ -72,7 +72,7 @@ public class JwtFilter extends AuthenticatingFilter {
}
// 从redis 获取jwt数据
JwtToken
jwtToken
=
shiroLoginService
.
get
T
JwtTokenForRedis
(
token
);
JwtToken
jwtToken
=
shiroLoginService
.
getJwtTokenForRedis
(
token
);
if
(
jwtToken
==
null
)
{
throw
new
AuthenticationException
(
"Redis Token不存在,token:"
+
token
);
}
...
...
framework/src/main/java/io/geekidea/springbootplus/framework/shiro/service/ShiroLoginService.java
View file @
c22b869b
...
...
@@ -53,7 +53,7 @@ public interface ShiroLoginService {
/**
* 从redis获取token信息
*/
JwtToken
get
T
JwtTokenForRedis
(
String
token
);
JwtToken
getJwtTokenForRedis
(
String
token
);
/**
* 获取盐
...
...
framework/src/main/java/io/geekidea/springbootplus/framework/shiro/service/impl/ShiroLoginServiceImpl.java
View file @
c22b869b
...
...
@@ -128,7 +128,7 @@ public class ShiroLoginServiceImpl implements ShiroLoginService {
}
@Override
public
JwtToken
get
T
JwtTokenForRedis
(
String
token
)
{
public
JwtToken
getJwtTokenForRedis
(
String
token
)
{
Object
jwtTokenRedisVo
=
null
;
...
...
framework/src/main/java/io/geekidea/springbootplus/framework/util/AnsiUtil.java
View file @
c22b869b
...
...
@@ -27,7 +27,7 @@ import org.springframework.core.env.Environment;
@Slf4j
public
class
AnsiUtil
{
private
static
final
boolean
enableAnsi
;
private
static
final
boolean
ENABLE_ANSI
;
static
{
Boolean
value
=
false
;
...
...
@@ -38,12 +38,12 @@ public class AnsiUtil {
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
enableAnsi
=
value
;
ENABLE_ANSI
=
value
;
}
public
static
String
getAnsi
(
Ansi
.
Color
color
,
String
text
)
{
if
(
enableAnsi
)
{
if
(
ENABLE_ANSI
)
{
return
Ansi
.
ansi
().
eraseScreen
().
fg
(
color
).
a
(
text
).
reset
().
toString
();
}
return
text
;
...
...
framework/src/main/java/io/geekidea/springbootplus/framework/util/Jackson.java
View file @
c22b869b
...
...
@@ -34,7 +34,7 @@ public class Jackson {
/**
* 时区
*/
private
static
final
TimeZone
timeZone
=
TimeZone
.
getTimeZone
(
"GMT"
);
private
static
final
TimeZone
TIME_ZONE
=
TimeZone
.
getTimeZone
(
"GMT"
);
/**
* 键按自然顺序输出
...
...
@@ -64,7 +64,7 @@ public class Jackson {
// 键按自然顺序输出
objectMapper
.
configure
(
SerializationFeature
.
ORDER_MAP_ENTRIES_BY_KEYS
,
true
);
// 设置时区
objectMapper
.
setTimeZone
(
timeZone
);
objectMapper
.
setTimeZone
(
TIME_ZONE
);
return
objectMapper
.
writeValueAsString
(
object
);
}
catch
(
JsonProcessingException
e
)
{
e
.
printStackTrace
();
...
...
@@ -103,7 +103,7 @@ public class Jackson {
// 为空的序列化
objectMapper
.
setSerializationInclusion
(
JsonInclude
.
Include
.
NON_NULL
);
// 设置时区
objectMapper
.
setTimeZone
(
timeZone
);
objectMapper
.
setTimeZone
(
TIME_ZONE
);
return
objectMapper
.
writeValueAsString
(
object
);
}
catch
(
JsonProcessingException
e
)
{
e
.
printStackTrace
();
...
...
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