using LeanCloud;
using LeanCloud.Storage.Internal;
using LeanCloud.Realtime.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace LeanCloud.Realtime
{
///
/// 代表一个实时通信的终端用户
///
public class AVIMClient
{
private readonly string clientId;
private readonly AVRealtime _realtime;
internal readonly object mutex = new object();
internal readonly object patchMutex = new object();
///
/// 一些可变的配置选项,便于应对各种需求场景
///
public struct Configuration
{
///
/// Gets or sets a value indicating whether this
/// auto read.
///
/// true if auto read; otherwise, false.
public bool AutoRead { get; set; }
}
///
/// Gets or sets the current configuration.
///
/// The current configuration.
public Configuration CurrentConfiguration
{
get; set;
}
internal AVRealtime LinkedRealtime
{
get { return _realtime; }
}
///
/// 单点登录所使用的 Tag
///
public string Tag
{
get;
private set;
}
///
/// 客户端的标识,在一个 Application 内唯一。
///
public string ClientId
{
get { return clientId; }
}
//private EventHandler m_OnNoticeReceived;
/////
///// 接收到服务器的命令时触发的事件
/////
//public event EventHandler OnNoticeReceived
//{
// add
// {
// m_OnNoticeReceived += value;
// }
// remove
// {
// m_OnNoticeReceived -= value;
// }
//}
private int onMessageReceivedCount = 0;
private EventHandler m_OnMessageReceived;
///
/// 接收到聊天消息的事件通知
///
public event EventHandler OnMessageReceived
{
add
{
onMessageReceivedCount++;
AVRealtime.PrintLog("AVIMClient.OnMessageReceived event add with " + onMessageReceivedCount + " times");
m_OnMessageReceived += value;
}
remove
{
onMessageReceivedCount--;
AVRealtime.PrintLog("AVIMClient.OnMessageReceived event remove with" + onMessageReceivedCount + " times");
m_OnMessageReceived -= value;
}
}
///
/// Occurs when on members joined.
///
public event EventHandler OnMembersJoined;
///
/// Occurs when on members left.
///
public event EventHandler OnMembersLeft;
///
/// Occurs when on kicked.
///
public event EventHandler OnKicked;
///
/// Occurs when on invited.
///
public event EventHandler OnInvited;
internal event EventHandler OnOfflineMessageReceived;
private EventHandler m_OnSessionClosed;
///
/// 当前打开的链接被迫关闭时触发的事件回调
/// 可能的原因有单点登录冲突,或者被 REST API 强制踢下线
///
public event EventHandler OnSessionClosed
{
add
{
m_OnSessionClosed += value;
}
remove
{
m_OnSessionClosed -= value;
}
}
///
/// 创建 AVIMClient 对象
///
///
///
internal AVIMClient(string clientId, AVRealtime realtime)
: this(clientId, null, realtime)
{
}
///
///
///
///
///
///
internal AVIMClient(string clientId, string tag, AVRealtime realtime)
{
this.clientId = clientId;
Tag = tag ?? tag;
_realtime = realtime;
#region sdk 强制在接收到消息之后一定要向服务端回发 ack
var ackListener = new AVIMMessageListener();
ackListener.OnMessageReceived += AckListener_OnMessageReceieved;
//this.RegisterListener(ackListener);
#endregion
#region 默认要为当前 client 绑定一个消息的监听器,用作消息的事件通知
var messageListener = new AVIMMessageListener();
messageListener.OnMessageReceived += MessageListener_OnMessageReceived;
this.RegisterListener(messageListener);
#endregion
#region 默认要为当前 client 绑定一个 session close 的监听器,用来监测单点登录冲突的事件通知
var sessionListener = new SessionListener();
sessionListener.OnSessionClosed += SessionListener_OnSessionClosed;
this.RegisterListener(sessionListener);
#endregion
#region 默认要为当前 client 监听 Ta 所出的对话中的人员变动的被动消息通知
var membersJoinedListener = new AVIMMembersJoinListener();
membersJoinedListener.OnMembersJoined += MembersJoinedListener_OnMembersJoined;
this.RegisterListener(membersJoinedListener);
var membersLeftListener = new AVIMMembersLeftListener();
membersLeftListener.OnMembersLeft += MembersLeftListener_OnMembersLeft;
this.RegisterListener(membersLeftListener);
var invitedListener = new AVIMInvitedListener();
invitedListener.OnInvited += InvitedListener_OnInvited;
this.RegisterListener(invitedListener);
var kickedListener = new AVIMKickedListener();
kickedListener.OnKicked += KickedListener_OnKicked;
this.RegisterListener(kickedListener);
#endregion
#region 当前 client id 离线的时间内,TA 所在的对话产生的普通消息会以离线消息的方式送达到 TA 下一次登录的客户端
var offlineMessageListener = new OfflineMessageListener();
offlineMessageListener.OnOfflineMessageReceived += OfflineMessageListener_OnOfflineMessageReceived;
this.RegisterListener(offlineMessageListener);
#endregion
#region 当前 client 离线期间内产生的未读消息可以通过之后调用 Conversation.SyncStateAsync 获取一下离线期间内的未读状态
var unreadListener = new ConversationUnreadListener();
this.RegisterListener(unreadListener);
#endregion
#region 消息补丁(修改或者撤回)
var messagePatchListener = new MessagePatchListener();
messagePatchListener.OnReceived = (messages) =>
{
foreach (var message in messages) {
if (message is AVIMRecalledMessage) {
m_OnMessageRecalled?.Invoke(this, new AVIMMessagePatchEventArgs(message));
} else {
m_OnMessageUpdated?.Invoke(this, new AVIMMessagePatchEventArgs(message));
}
}
};
this.RegisterListener(messagePatchListener);
#endregion
#region configuration
CurrentConfiguration = new Configuration()
{
AutoRead = true,
};
#endregion
}
private void OfflineMessageListener_OnOfflineMessageReceived(object sender, AVIMMessageEventArgs e)
{
if (OnOfflineMessageReceived != null)
{
OnOfflineMessageReceived(this, e);
}
this.AckListener_OnMessageReceieved(sender, e);
}
private void KickedListener_OnKicked(object sender, AVIMOnKickedEventArgs e)
{
if (OnKicked != null)
OnKicked(this, e);
}
private void InvitedListener_OnInvited(object sender, AVIMOnInvitedEventArgs e)
{
if (OnInvited != null)
OnInvited(this, e);
}
private void MembersLeftListener_OnMembersLeft(object sender, AVIMOnMembersLeftEventArgs e)
{
if (OnMembersLeft != null)
OnMembersLeft(this, e);
}
private void MembersJoinedListener_OnMembersJoined(object sender, AVIMOnMembersJoinedEventArgs e)
{
if (OnMembersJoined != null)
OnMembersJoined(this, e);
}
private void SessionListener_OnSessionClosed(int arg1, string arg2, string arg3)
{
if (m_OnSessionClosed != null)
{
var args = new AVIMSessionClosedEventArgs()
{
Code = arg1,
Reason = arg2,
Detail = arg3
};
if (args.Code == 4115 || args.Code == 4111)
{
this._realtime.sessionConflict = true;
}
m_OnSessionClosed(this, args);
}
AVRealtime.PrintLog("SessionListener_OnSessionClosed invoked.");
//this.LinkedRealtime.LogOut();
}
private void MessageListener_OnMessageReceived(object sender, AVIMMessageEventArgs e)
{
if (this.m_OnMessageReceived != null)
{
this.m_OnMessageReceived.Invoke(this, e);
}
this.AckListener_OnMessageReceieved(sender, e);
}
private void AckListener_OnMessageReceieved(object sender, AVIMMessageEventArgs e)
{
lock (mutex)
{
var ackCommand = new AckCommand().MessageId(e.Message.Id)
.ConversationId(e.Message.ConversationId);
// 在 v.2 协议下,只要在线收到消息,就默认是已读的,下次上线不会再把当前消息当做未读消息
if (this.LinkedRealtime.CurrentConfiguration.OfflineMessageStrategy == AVRealtime.OfflineMessageStrategy.UnreadNotice)
{
ackCommand = ackCommand.ReadAck();
}
this.RunCommandAsync(ackCommand);
}
}
private void UpdateUnreadNotice(object sender, AVIMMessageEventArgs e)
{
ConversationUnreadListener.UpdateNotice(e.Message);
}
#region listener
///
/// 注册 IAVIMListener
///
///
///
public void RegisterListener(IAVIMListener listener, Func runtimeHook = null)
{
_realtime.SubscribeNoticeReceived(listener, runtimeHook);
}
#region get client instance
///
/// Get the specified clientId.
///
/// The get.
/// Client identifier.
public static AVIMClient Get(string clientId)
{
if (AVRealtime.clients == null || !AVRealtime.clients.ContainsKey(clientId)) throw new Exception(string.Format("no client found with a id in {0}", clientId));
return AVRealtime.clients[clientId];
}
#endregion
#endregion
///
/// 创建对话
///
/// 对话
/// 是否创建唯一对话,当 isUnique 为 true 时,如果当前已经有相同成员的对话存在则返回该对话,否则会创建新的对话。该值默认为 false。
///
internal Task CreateConversationAsync(AVIMConversation conversation, bool isUnique = true)
{
var cmd = new ConversationCommand()
.Generate(conversation)
.Unique(isUnique);
var convCmd = cmd.Option("start")
.PeerId(clientId);
return LinkedRealtime.AttachSignature(convCmd, LinkedRealtime.SignatureFactory.CreateStartConversationSignature(this.clientId, conversation.MemberIds)).OnSuccess(_ =>
{
return this.RunCommandAsync(convCmd).OnSuccess(t =>
{
var result = t.Result;
if (result.Item1 < 1)
{
var members = conversation.MemberIds.ToList();
members.Add(ClientId);
conversation.MemberIds = members;
conversation.MergeFromPushServer(result.Item2);
}
return conversation;
});
}).Unwrap();
}
///
/// 创建与目标成员的对话.
///
/// 返回对话实例.
/// 目标成员.
/// 目标成员列表.
/// 对话名称.
/// 是否是系统对话.
/// 是否为暂态对话(聊天室).
/// 是否是唯一对话.
/// 自定义属性.
public Task CreateConversationAsync(string member = null,
IEnumerable members = null,
string name = "",
bool isSystem = false,
bool isTransient = false,
bool isUnique = true,
bool isTemporary = false,
int ttl = 86400,
IDictionary options = null)
{
if (member == null) member = ClientId;
var membersAsList = Concat(member, members, "创建对话时被操作的 member(s) 不可以为空。");
var conversation = new AVIMConversation(members: membersAsList,
name: name,
isUnique: isUnique,
isSystem: isSystem,
isTransient: isTransient,
isTemporary: isTemporary,
ttl: ttl,
client: this);
if (options != null)
{
foreach (var key in options.Keys)
{
conversation[key] = options[key];
}
}
return CreateConversationAsync(conversation, isUnique);
}
///
/// Creates the conversation async.
///
/// The conversation async.
/// Builder.
public Task CreateConversationAsync(IAVIMConversatioBuilder builder)
{
var conversation = builder.Build();
return CreateConversationAsync(conversation, conversation.IsUnique);
}
///
/// Gets the conversatio builder.
///
/// The conversatio builder.
public AVIMConversationBuilder GetConversationBuilder()
{
var builder = AVIMConversationBuilder.CreateDefaultBuilder();
builder.Client = this;
return builder;
}
///
/// 创建虚拟对话,对话 id 是由本地直接生成,云端根据规则消息发送给指定的 client id(s)
///
///
///
/// 过期时间,默认是一天(86400 秒),单位是秒
///
public Task CreateTemporaryConversationAsync(string member = null,
IEnumerable members = null, int ttl = 86400)
{
if (member == null) member = ClientId;
var membersAsList = Concat(member, members, "创建对话时被操作的 member(s) 不可以为空。");
return CreateConversationAsync(member, membersAsList, isTemporary: true, ttl: ttl);
}
///
/// 创建聊天室(即:暂态对话)
///
/// 聊天室名称
///
public Task CreateChatRoomAsync(string chatroomName)
{
return CreateConversationAsync(name: chatroomName, isTransient: true);
}
///
/// 获取一个对话
///
/// 对话的 ID
/// 从服务器获取
///
public Task GetConversationAsync(string id, bool noCache = true)
{
if (!noCache) return Task.FromResult(new AVIMConversation(this) { ConversationId = id });
else
{
return this.GetQuery().WhereEqualTo("objectId", id).FirstAsync();
}
}
#region send message
///
/// 向目标对话发送消息
///
/// 目标对话
/// 消息体
///
public Task SendMessageAsync(
AVIMConversation conversation,
IAVIMMessage message)
{
return this.SendMessageAsync(conversation, message, new AVIMSendOptions()
{
Receipt = true,
Transient = false,
Priority = 1,
Will = false,
PushData = null,
});
}
///
/// 向目标对话发送消息
///
/// 目标对话
/// 消息体
/// 消息的发送选项,包含了一些特殊的标记
///
public Task SendMessageAsync(
AVIMConversation conversation,
IAVIMMessage message,
AVIMSendOptions options)
{
if (this.LinkedRealtime.State != AVRealtime.Status.Online) throw new Exception("未能连接到服务器,无法发送消息。");
var messageBody = message.Serialize();
message.ConversationId = conversation.ConversationId;
message.FromClientId = this.ClientId;
var cmd = new MessageCommand()
.Message(messageBody)
.ConvId(conversation.ConversationId)
.Receipt(options.Receipt)
.Transient(options.Transient)
.Priority(options.Priority)
.Will(options.Will)
.MentionAll(message.MentionAll);
if (message is AVIMMessage)
{
cmd = ((AVIMMessage)message).BeforeSend(cmd);
}
if (options.PushData != null)
{
cmd = cmd.PushData(options.PushData);
}
if (message.MentionList != null)
{
cmd = cmd.Mention(message.MentionList);
}
var directCmd = cmd.PeerId(this.ClientId);
return this.RunCommandAsync(directCmd).OnSuccess(t =>
{
var response = t.Result.Item2;
message.Id = response["uid"].ToString();
message.ServerTimestamp = long.Parse(response["t"].ToString());
return message;
});
}
#endregion
#region mute & unmute
///
/// 当前用户对目标对话进行静音操作
///
///
///
public Task MuteConversationAsync(AVIMConversation conversation)
{
var convCmd = new ConversationCommand()
.ConversationId(conversation.ConversationId)
.Option("mute")
.PeerId(this.ClientId);
return this.RunCommandAsync(convCmd);
}
///
/// 当前用户对目标对话取消静音,恢复该对话的离线消息推送
///
///
///
public Task UnmuteConversationAsync(AVIMConversation conversation)
{
var convCmd = new ConversationCommand()
.ConversationId(conversation.ConversationId)
.Option("unmute")
.PeerId(this.ClientId);
return this.RunCommandAsync(convCmd);
}
#endregion
#region Conversation members operations
internal Task OperateMembersAsync(AVIMConversation conversation, string action, string member = null, IEnumerable members = null)
{
if (string.IsNullOrEmpty(conversation.ConversationId))
{
throw new Exception("conversation id 不可以为空。");
}
var membersAsList = Concat(member, members, "加人或者踢人的时候,被操作的 member(s) 不可以为空。");
var cmd = new ConversationCommand().ConversationId(conversation.ConversationId)
.Members(membersAsList)
.Option(action)
.PeerId(clientId);
return this.LinkedRealtime.AttachSignature(cmd, LinkedRealtime.SignatureFactory.CreateConversationSignature(conversation.ConversationId, ClientId, membersAsList, ConversationSignatureAction.Add)).OnSuccess(_ =>
{
return this.RunCommandAsync(cmd).OnSuccess(t =>
{
var result = t.Result;
if (!conversation.IsTransient)
{
if (conversation.MemberIds == null) conversation.MemberIds = new List();
conversation.MemberIds = conversation.MemberIds.Concat(membersAsList);
}
return result;
});
}).Unwrap();
}
internal IEnumerable Concat(T single, IEnumerable collection, string exString = null)
{
List asList = null;
if (collection == null)
{
collection = new List();
}
asList = collection.ToList();
if (asList.Count == 0 && single == null)
{
exString = exString ?? "can not cancat a collection with a null value.";
throw new ArgumentNullException(exString);
}
asList.Add(single);
return asList;
}
#region Join
///
/// 当前用户加入到目标的对话中
///
/// 目标对话
///
public Task JoinAsync(AVIMConversation conversation)
{
return this.OperateMembersAsync(conversation, "add", this.ClientId);
}
#endregion
#region Invite
///
/// 直接将其他人加入到目标对话
/// 被操作的人会在客户端会触发 OnInvited 事件,而已经存在于对话的用户会触发 OnMembersJoined 事件
///
/// 目标对话
/// 单个的 Client Id
/// Client Id 集合
///
public Task InviteAsync(AVIMConversation conversation, string member = null, IEnumerable members = null)
{
return this.OperateMembersAsync(conversation, "add", member, members);
}
#endregion
#region Left
///
/// 当前 Client 离开目标对话
/// 可以理解为是 QQ 群的退群操作
///
///
/// 目标对话
///
[Obsolete("use LeaveAsync instead.")]
public Task LeftAsync(AVIMConversation conversation)
{
return this.OperateMembersAsync(conversation, "remove", this.ClientId);
}
///
/// Leaves the conversation async.
///
/// The async.
/// Conversation.
public Task LeaveAsync(AVIMConversation conversation)
{
return this.OperateMembersAsync(conversation, "remove", this.ClientId);
}
#endregion
#region Kick
///
/// 从目标对话中剔除成员
///
/// 目标对话
/// 被剔除的单个成员
/// 被剔除的成员列表
///
public Task KickAsync(AVIMConversation conversation, string member = null, IEnumerable members = null)
{
return this.OperateMembersAsync(conversation, "remove", member, members);
}
#endregion
#endregion
#region Query && Message history && ack
///
/// Get conversation query.
///
///
public AVIMConversationQuery GetQuery()
{
return GetConversationQuery();
}
///
/// Get conversation query.
///
/// The conversation query.
public AVIMConversationQuery GetConversationQuery()
{
return new AVIMConversationQuery(this);
}
#region load message history
///
/// 查询目标对话的历史消息
/// 不支持聊天室(暂态对话)
///
/// 目标对话
/// 从 beforeMessageId 开始向前查询(和 beforeTimeStampPoint 共同使用,为防止某毫秒时刻有重复消息)
/// 截止到某个 afterMessageId (不包含)
/// 从 beforeTimeStampPoint 开始向前查询
/// 拉取截止到 afterTimeStampPoint 时间戳(不包含)
/// 查询方向,默认是 1,如果是 1 表示从新消息往旧消息方向, 0 则相反,其他值无效
/// 拉取消息条数,默认值 20 条,可设置为 1 - 1000 之间的任意整数
///
public Task> QueryMessageAsync(AVIMConversation conversation,
string beforeMessageId = null,
string afterMessageId = null,
DateTime? beforeTimeStampPoint = null,
DateTime? afterTimeStampPoint = null,
int direction = 1,
int limit = 20)
where T : IAVIMMessage
{
var maxLimit = 1000;
var actualLimit = limit > maxLimit ? maxLimit : limit;
var logsCmd = new AVIMCommand()
.Command("logs")
.Argument("cid", conversation.ConversationId)
.Argument("l", actualLimit);
if (beforeMessageId != null)
{
logsCmd = logsCmd.Argument("mid", beforeMessageId);
}
if (afterMessageId != null)
{
logsCmd = logsCmd.Argument("tmid", afterMessageId);
}
if (beforeTimeStampPoint != null && beforeTimeStampPoint.Value != DateTime.MinValue)
{
logsCmd = logsCmd.Argument("t", beforeTimeStampPoint.Value.ToUnixTimeStamp());
}
if (afterTimeStampPoint != null && afterTimeStampPoint.Value != DateTime.MinValue)
{
logsCmd = logsCmd.Argument("tt", afterTimeStampPoint.Value.ToUnixTimeStamp());
}
if (direction == 0)
{
logsCmd = logsCmd.Argument("direction", "NEW");
}
var subMessageType = typeof(T);
var subTypeInteger = subMessageType == typeof(AVIMTypedMessage) ? 0 : FreeStyleMessageClassInfo.GetTypedInteger(subMessageType.GetTypeInfo());
if (subTypeInteger != 0)
{
logsCmd = logsCmd.Argument("lctype", subTypeInteger);
}
return this.RunCommandAsync(logsCmd).OnSuccess(t =>
{
var rtn = new List();
var result = t.Result.Item2;
var logs = result["logs"] as List