From aff76ee059690b3e43896e6ac6062fa772553821 Mon Sep 17 00:00:00 2001 From: oneRain Date: Thu, 30 Apr 2020 13:48:59 +0800 Subject: [PATCH] chore --- AppRouter/AppRouter/AppRouter.csproj | 15 - RTM/RTM.PCL/Properties/AssemblyInfo.cs | 26 - RTM/RTM.PCL/RTM.PCL.csproj | 210 --- RTM/RTM.PCL/packages.config | 4 - RTM/RTM.Test/RTM.Test.csproj | 41 - RTM/RTM.Test/Test.cs | 10 - RTM/RTM.Test/packages.config | 4 - RTM/RTM.Unity/Properties/AssemblyInfo.cs | 26 - RTM/RTM.Unity/RTM.Unity.csproj | 219 --- RTM/Source/Internal/AVIMCorePlugins.cs | 56 - RTM/Source/Internal/Command/AVIMCommand.cs | 176 -- .../Internal/Command/AVIMCommandRunner.cs | 92 - RTM/Source/Internal/Command/AckCommand.cs | 63 - .../Internal/Command/ConversationCommand.cs | 129 -- .../Internal/Command/IAVIMCommandRunner.cs | 17 - RTM/Source/Internal/Command/MessageCommand.cs | 83 - RTM/Source/Internal/Command/PatchCommand.cs | 98 -- RTM/Source/Internal/Command/ReadCommand.cs | 91 - RTM/Source/Internal/Command/SessionCommand.cs | 57 - .../DataEngine/Controller/DateTimeEngine.cs | 30 - .../DataEngine/Controller/DictionaryEngine.cs | 47 - .../DataEngine/Controller/StringEngine.cs | 36 - RTM/Source/Internal/IAVIMPlatformHooks.cs | 15 - .../Subclassing/FreeStyleMessageClassInfo.cs | 75 - .../FreeStyleMessageClassingController.cs | 210 --- .../IFreeStyleMessageClassingController.cs | 18 - RTM/Source/Internal/Protocol/AVIMProtocol.cs | 19 - .../Internal/Router/AVRouterController.cs | 173 -- .../Internal/Router/IAVRouterController.cs | 13 - .../Internal/Router/State/RouterState.cs | 20 - RTM/Source/Internal/Timer/IAVTimer.cs | 49 - .../Timer/Portable/AVTimer.Portable.cs | 107 -- .../Internal/Timer/Unity/AVTimer.Unity.cs | 82 - .../Internal/WebSocket/IWebSocketClient.cs | 56 - .../NetCore/DefaultWebSocketClient.NetCore.cs | 174 -- .../NetFx45/WebSocketClient.NetFx45.cs | 80 - .../DefaultWebSocketClient.Portable.cs | 164 -- .../Unity/DefaultWebSocketClient.Unity.cs | 149 -- .../WebSocket/Unity/websocket-sharp.dll | Bin 254464 -> 0 bytes RTM/Source/Public/AVIMAudioMessage.cs | 28 - RTM/Source/Public/AVIMBinaryMessage.cs | 42 - RTM/Source/Public/AVIMClient.cs | 1195 ------------- RTM/Source/Public/AVIMConversation.cs | 1545 ----------------- RTM/Source/Public/AVIMConversationQuery.cs | 181 -- RTM/Source/Public/AVIMEnumerator.cs | 248 --- RTM/Source/Public/AVIMEventArgs.cs | 251 --- RTM/Source/Public/AVIMException.cs | 235 --- RTM/Source/Public/AVIMImageMessage.cs | 246 --- RTM/Source/Public/AVIMMessage.cs | 162 -- .../Public/AVIMMessageClassNameAttribute.cs | 19 - .../Public/AVIMMessageFieldNameAttribute.cs | 18 - RTM/Source/Public/AVIMMessageListener.cs | 143 -- RTM/Source/Public/AVIMNotice.cs | 57 - RTM/Source/Public/AVIMRecalledMessage.cs | 12 - RTM/Source/Public/AVIMSignature.cs | 42 - .../Public/AVIMTemporaryConversation.cs | 37 - RTM/Source/Public/AVIMTextMessage.cs | 47 - RTM/Source/Public/AVIMTypedMessage.cs | 205 --- .../AVIMTypedMessageTypeIntAttribute.cs | 14 - RTM/Source/Public/AVRealtime.cs | 1282 -------------- RTM/Source/Public/IAVIMListener.cs | 33 - RTM/Source/Public/IAVIMMessage.cs | 83 - RTM/Source/Public/ICacheEngine.cs | 13 - RTM/Source/Public/ISignatureFactory.cs | 131 -- .../Listener/AVIMConversationListener.cs | 256 --- .../Listener/ConversationUnreadListener.cs | 145 -- RTM/Source/Public/Listener/GoAwayListener.cs | 28 - .../Public/Listener/MessagePatchListener.cs | 48 - .../Public/Listener/OfflineMessageListener.cs | 42 - RTM/Source/Public/Listener/SessionListener.cs | 58 - RTM/Source/Public/Unity/AVRealtimeBehavior.cs | 101 -- 71 files changed, 9881 deletions(-) delete mode 100644 AppRouter/AppRouter/AppRouter.csproj delete mode 100644 RTM/RTM.PCL/Properties/AssemblyInfo.cs delete mode 100644 RTM/RTM.PCL/RTM.PCL.csproj delete mode 100644 RTM/RTM.PCL/packages.config delete mode 100644 RTM/RTM.Test/RTM.Test.csproj delete mode 100644 RTM/RTM.Test/Test.cs delete mode 100644 RTM/RTM.Test/packages.config delete mode 100644 RTM/RTM.Unity/Properties/AssemblyInfo.cs delete mode 100644 RTM/RTM.Unity/RTM.Unity.csproj delete mode 100644 RTM/Source/Internal/AVIMCorePlugins.cs delete mode 100644 RTM/Source/Internal/Command/AVIMCommand.cs delete mode 100644 RTM/Source/Internal/Command/AVIMCommandRunner.cs delete mode 100644 RTM/Source/Internal/Command/AckCommand.cs delete mode 100644 RTM/Source/Internal/Command/ConversationCommand.cs delete mode 100644 RTM/Source/Internal/Command/IAVIMCommandRunner.cs delete mode 100644 RTM/Source/Internal/Command/MessageCommand.cs delete mode 100644 RTM/Source/Internal/Command/PatchCommand.cs delete mode 100644 RTM/Source/Internal/Command/ReadCommand.cs delete mode 100644 RTM/Source/Internal/Command/SessionCommand.cs delete mode 100644 RTM/Source/Internal/DataEngine/Controller/DateTimeEngine.cs delete mode 100644 RTM/Source/Internal/DataEngine/Controller/DictionaryEngine.cs delete mode 100644 RTM/Source/Internal/DataEngine/Controller/StringEngine.cs delete mode 100644 RTM/Source/Internal/IAVIMPlatformHooks.cs delete mode 100644 RTM/Source/Internal/Message/Subclassing/FreeStyleMessageClassInfo.cs delete mode 100644 RTM/Source/Internal/Message/Subclassing/FreeStyleMessageClassingController.cs delete mode 100644 RTM/Source/Internal/Message/Subclassing/IFreeStyleMessageClassingController.cs delete mode 100644 RTM/Source/Internal/Protocol/AVIMProtocol.cs delete mode 100644 RTM/Source/Internal/Router/AVRouterController.cs delete mode 100644 RTM/Source/Internal/Router/IAVRouterController.cs delete mode 100644 RTM/Source/Internal/Router/State/RouterState.cs delete mode 100644 RTM/Source/Internal/Timer/IAVTimer.cs delete mode 100644 RTM/Source/Internal/Timer/Portable/AVTimer.Portable.cs delete mode 100644 RTM/Source/Internal/Timer/Unity/AVTimer.Unity.cs delete mode 100644 RTM/Source/Internal/WebSocket/IWebSocketClient.cs delete mode 100644 RTM/Source/Internal/WebSocket/NetCore/DefaultWebSocketClient.NetCore.cs delete mode 100644 RTM/Source/Internal/WebSocket/NetFx45/WebSocketClient.NetFx45.cs delete mode 100644 RTM/Source/Internal/WebSocket/Portable/DefaultWebSocketClient.Portable.cs delete mode 100644 RTM/Source/Internal/WebSocket/Unity/DefaultWebSocketClient.Unity.cs delete mode 100644 RTM/Source/Internal/WebSocket/Unity/websocket-sharp.dll delete mode 100644 RTM/Source/Public/AVIMAudioMessage.cs delete mode 100644 RTM/Source/Public/AVIMBinaryMessage.cs delete mode 100644 RTM/Source/Public/AVIMClient.cs delete mode 100644 RTM/Source/Public/AVIMConversation.cs delete mode 100644 RTM/Source/Public/AVIMConversationQuery.cs delete mode 100644 RTM/Source/Public/AVIMEnumerator.cs delete mode 100644 RTM/Source/Public/AVIMEventArgs.cs delete mode 100644 RTM/Source/Public/AVIMException.cs delete mode 100644 RTM/Source/Public/AVIMImageMessage.cs delete mode 100644 RTM/Source/Public/AVIMMessage.cs delete mode 100644 RTM/Source/Public/AVIMMessageClassNameAttribute.cs delete mode 100644 RTM/Source/Public/AVIMMessageFieldNameAttribute.cs delete mode 100644 RTM/Source/Public/AVIMMessageListener.cs delete mode 100644 RTM/Source/Public/AVIMNotice.cs delete mode 100644 RTM/Source/Public/AVIMRecalledMessage.cs delete mode 100644 RTM/Source/Public/AVIMSignature.cs delete mode 100644 RTM/Source/Public/AVIMTemporaryConversation.cs delete mode 100644 RTM/Source/Public/AVIMTextMessage.cs delete mode 100644 RTM/Source/Public/AVIMTypedMessage.cs delete mode 100644 RTM/Source/Public/AVIMTypedMessageTypeIntAttribute.cs delete mode 100644 RTM/Source/Public/AVRealtime.cs delete mode 100644 RTM/Source/Public/IAVIMListener.cs delete mode 100644 RTM/Source/Public/IAVIMMessage.cs delete mode 100644 RTM/Source/Public/ICacheEngine.cs delete mode 100644 RTM/Source/Public/ISignatureFactory.cs delete mode 100644 RTM/Source/Public/Listener/AVIMConversationListener.cs delete mode 100644 RTM/Source/Public/Listener/ConversationUnreadListener.cs delete mode 100644 RTM/Source/Public/Listener/GoAwayListener.cs delete mode 100644 RTM/Source/Public/Listener/MessagePatchListener.cs delete mode 100644 RTM/Source/Public/Listener/OfflineMessageListener.cs delete mode 100644 RTM/Source/Public/Listener/SessionListener.cs delete mode 100644 RTM/Source/Public/Unity/AVRealtimeBehavior.cs diff --git a/AppRouter/AppRouter/AppRouter.csproj b/AppRouter/AppRouter/AppRouter.csproj deleted file mode 100644 index 722603d..0000000 --- a/AppRouter/AppRouter/AppRouter.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - netstandard2.0 - 0.1.0 - - - - - - - - - - diff --git a/RTM/RTM.PCL/Properties/AssemblyInfo.cs b/RTM/RTM.PCL/Properties/AssemblyInfo.cs deleted file mode 100644 index 669b160..0000000 --- a/RTM/RTM.PCL/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. - -[assembly: AssemblyTitle("RTM.PCL")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("${AuthorCopyright}")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. - -[assembly: AssemblyVersion("1.0.*")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("")] diff --git a/RTM/RTM.PCL/RTM.PCL.csproj b/RTM/RTM.PCL/RTM.PCL.csproj deleted file mode 100644 index a67701e..0000000 --- a/RTM/RTM.PCL/RTM.PCL.csproj +++ /dev/null @@ -1,210 +0,0 @@ - - - - Debug - AnyCPU - {92B2B40E-A3CD-4672-AC84-2E894E1A6CE5} - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - RTM.PCL - RTM.PCL - v4.5 - Profile111 - 0.1.0 - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - - - true - bin\Release - prompt - 4 - - - - - Internal\AVIMCorePlugins.cs - - - Internal\IAVIMPlatformHooks.cs - - - Internal\Command\AVIMCommand.cs - - - Internal\Command\AVIMCommandRunner.cs - - - Internal\Command\AckCommand.cs - - - Internal\Command\ConversationCommand.cs - - - Internal\Command\IAVIMCommandRunner.cs - - - Internal\Command\MessageCommand.cs - - - Internal\Command\PatchCommand.cs - - - Internal\Command\ReadCommand.cs - - - Internal\Command\SessionCommand.cs - - - Internal\DataEngine\Controller\DateTimeEngine.cs - - - Internal\DataEngine\Controller\DictionaryEngine.cs - - - Internal\DataEngine\Controller\StringEngine.cs - - - Internal\Message\Subclassing\FreeStyleMessageClassInfo.cs - - - Internal\Message\Subclassing\FreeStyleMessageClassingController.cs - - - Internal\Message\Subclassing\IFreeStyleMessageClassingController.cs - - - Internal\Protocol\AVIMProtocol.cs - - - Internal\Router\AVRouterController.cs - - - Internal\Router\IAVRouterController.cs - - - Internal\Router\State\RouterState.cs - - - Internal\Timer\IAVTimer.cs - - - Internal\Timer\Portable\AVTimer.Portable.cs - - - Internal\WebSocket\IWebSocketClient.cs - - - Internal\WebSocket\Portable\DefaultWebSocketClient.Portable.cs - - - Public\AVIMAudioMessage.cs - - - Public\AVIMBinaryMessage.cs - - - Public\AVIMClient.cs - - - Public\AVIMConversation.cs - - - Public\AVIMConversationQuery.cs - - - Public\AVIMEnumerator.cs - - - Public\AVIMEventArgs.cs - - - Public\AVIMException.cs - - - Public\AVIMImageMessage.cs - - - Public\AVIMMessage.cs - - - Public\AVIMMessageClassNameAttribute.cs - - - Public\AVIMMessageFieldNameAttribute.cs - - - Public\AVIMMessageListener.cs - - - Public\AVIMNotice.cs - - - Public\AVIMRecalledMessage.cs - - - Public\AVIMSignature.cs - - - Public\AVIMTemporaryConversation.cs - - - Public\AVIMTextMessage.cs - - - Public\AVIMTypedMessage.cs - - - Public\AVIMTypedMessageTypeIntAttribute.cs - - - Public\AVRealtime.cs - - - Public\IAVIMListener.cs - - - Public\IAVIMMessage.cs - - - Public\ICacheEngine.cs - - - Public\ISignatureFactory.cs - - - Public\Listener\AVIMConversationListener.cs - - - Public\Listener\ConversationUnreadListener.cs - - - Public\Listener\GoAwayListener.cs - - - Public\Listener\MessagePatchListener.cs - - - Public\Listener\OfflineMessageListener.cs - - - Public\Listener\SessionListener.cs - - - - - - - - ..\..\packages\Websockets.Pcl.1.1.9\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10\WebSockets.PCL.dll - - - - \ No newline at end of file diff --git a/RTM/RTM.PCL/packages.config b/RTM/RTM.PCL/packages.config deleted file mode 100644 index 105fd5f..0000000 --- a/RTM/RTM.PCL/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/RTM/RTM.Test/RTM.Test.csproj b/RTM/RTM.Test/RTM.Test.csproj deleted file mode 100644 index 66eada9..0000000 --- a/RTM/RTM.Test/RTM.Test.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - - Debug - AnyCPU - {A1BBD0B5-41C6-4579-B9A3-5EF778BE7F95} - Library - RTM.Test - RTM.Test - v4.7 - 0.1.0 - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - - - true - bin\Release - prompt - 4 - - - - - ..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll - - - - - - - - - - \ No newline at end of file diff --git a/RTM/RTM.Test/Test.cs b/RTM/RTM.Test/Test.cs deleted file mode 100644 index 4f8a269..0000000 --- a/RTM/RTM.Test/Test.cs +++ /dev/null @@ -1,10 +0,0 @@ -using NUnit.Framework; -using System; -namespace RTM.Test { - [TestFixture()] - public class Test { - [Test()] - public void TestCase() { - } - } -} diff --git a/RTM/RTM.Test/packages.config b/RTM/RTM.Test/packages.config deleted file mode 100644 index bbb222f..0000000 --- a/RTM/RTM.Test/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/RTM/RTM.Unity/Properties/AssemblyInfo.cs b/RTM/RTM.Unity/Properties/AssemblyInfo.cs deleted file mode 100644 index 5485e9a..0000000 --- a/RTM/RTM.Unity/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. - -[assembly: AssemblyTitle("RTM.Unity")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("${AuthorCopyright}")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. - -[assembly: AssemblyVersion("1.0.*")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("")] diff --git a/RTM/RTM.Unity/RTM.Unity.csproj b/RTM/RTM.Unity/RTM.Unity.csproj deleted file mode 100644 index 2d18b38..0000000 --- a/RTM/RTM.Unity/RTM.Unity.csproj +++ /dev/null @@ -1,219 +0,0 @@ - - - - Debug - AnyCPU - {1E608FCD-9039-4FF7-8EE7-BA8B00E15D1C} - Library - RTM.Unity - RTM.Unity - v4.7 - 0.1.0 - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - false - - - true - bin\Release - prompt - 4 - false - - - - - ..\..\Libs\UnityEngine.dll - - - ..\..\Libs\websocket-sharp.dll - - - - - - Internal\AVIMCorePlugins.cs - - - Internal\IAVIMPlatformHooks.cs - - - Internal\Command\AVIMCommand.cs - - - Internal\Command\AVIMCommandRunner.cs - - - Internal\Command\AckCommand.cs - - - Internal\Command\ConversationCommand.cs - - - Internal\Command\IAVIMCommandRunner.cs - - - Internal\Command\MessageCommand.cs - - - Internal\Command\PatchCommand.cs - - - Internal\Command\ReadCommand.cs - - - Internal\Command\SessionCommand.cs - - - Internal\DataEngine\Controller\DateTimeEngine.cs - - - Internal\DataEngine\Controller\DictionaryEngine.cs - - - Internal\DataEngine\Controller\StringEngine.cs - - - Internal\Message\Subclassing\FreeStyleMessageClassInfo.cs - - - Internal\Message\Subclassing\FreeStyleMessageClassingController.cs - - - Internal\Message\Subclassing\IFreeStyleMessageClassingController.cs - - - Internal\Protocol\AVIMProtocol.cs - - - Internal\Router\AVRouterController.cs - - - Internal\Router\IAVRouterController.cs - - - Internal\Router\State\RouterState.cs - - - Internal\Timer\IAVTimer.cs - - - Internal\Timer\Unity\AVTimer.Unity.cs - - - Internal\WebSocket\IWebSocketClient.cs - - - Internal\WebSocket\Unity\DefaultWebSocketClient.Unity.cs - - - Public\AVIMAudioMessage.cs - - - Public\AVIMBinaryMessage.cs - - - Public\AVIMClient.cs - - - Public\AVIMConversation.cs - - - Public\AVIMConversationQuery.cs - - - Public\AVIMEnumerator.cs - - - Public\AVIMEventArgs.cs - - - Public\AVIMException.cs - - - Public\AVIMImageMessage.cs - - - Public\AVIMMessage.cs - - - Public\AVIMMessageClassNameAttribute.cs - - - Public\AVIMMessageFieldNameAttribute.cs - - - Public\AVIMMessageListener.cs - - - Public\AVIMNotice.cs - - - Public\AVIMRecalledMessage.cs - - - Public\AVIMSignature.cs - - - Public\AVIMTemporaryConversation.cs - - - Public\AVIMTextMessage.cs - - - Public\AVIMTypedMessage.cs - - - Public\AVIMTypedMessageTypeIntAttribute.cs - - - Public\AVRealtime.cs - - - Public\IAVIMListener.cs - - - Public\IAVIMMessage.cs - - - Public\ICacheEngine.cs - - - Public\ISignatureFactory.cs - - - Public\Listener\AVIMConversationListener.cs - - - Public\Listener\ConversationUnreadListener.cs - - - Public\Listener\GoAwayListener.cs - - - Public\Listener\MessagePatchListener.cs - - - Public\Listener\OfflineMessageListener.cs - - - Public\Listener\SessionListener.cs - - - Public\Unity\AVRealtimeBehavior.cs - - - - - Internal\WebSocket\Unity\websocket-sharp.dll - - - - \ No newline at end of file diff --git a/RTM/Source/Internal/AVIMCorePlugins.cs b/RTM/Source/Internal/AVIMCorePlugins.cs deleted file mode 100644 index 859e0bc..0000000 --- a/RTM/Source/Internal/AVIMCorePlugins.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - internal class AVIMCorePlugins - { - private static readonly AVIMCorePlugins instance = new AVIMCorePlugins(); - public static AVIMCorePlugins Instance - { - get - { - return instance; - } - } - - private readonly object mutex = new object(); - - private IAVRouterController routerController; - public IAVRouterController RouterController - { - get - { - lock (mutex) - { - routerController = routerController ?? new AVRouterController(); - return routerController; - } - } - internal set - { - lock (mutex) - { - routerController = value; - } - } - } - - - private IFreeStyleMessageClassingController freeStyleClassingController; - public IFreeStyleMessageClassingController FreeStyleClassingController - { - get - { - lock (mutex) - { - freeStyleClassingController = freeStyleClassingController ?? new FreeStyleMessageClassingController(); - return freeStyleClassingController; - } - } - } - } -} diff --git a/RTM/Source/Internal/Command/AVIMCommand.cs b/RTM/Source/Internal/Command/AVIMCommand.cs deleted file mode 100644 index 18300e5..0000000 --- a/RTM/Source/Internal/Command/AVIMCommand.cs +++ /dev/null @@ -1,176 +0,0 @@ -using LeanCloud; -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - /// - /// Command. - /// - public class AVIMCommand - { - protected readonly string cmd; - protected readonly string op; - protected string appId; - protected string peerId; - protected AVIMSignature signature; - protected readonly IDictionary arguments; - - public int TimeoutInSeconds { get; set; } - - protected readonly IDictionary estimatedData = new Dictionary(); - internal readonly object mutex = new object(); - internal static readonly object Mutex = new object(); - - public AVIMCommand() : - this(arguments: new Dictionary()) - { - - } - protected AVIMCommand(string cmd = null, - string op = null, - string appId = null, - string peerId = null, - AVIMSignature signature = null, - IDictionary arguments = null) - { - this.cmd = cmd; - this.op = op; - this.arguments = arguments == null ? new Dictionary() : arguments; - this.peerId = peerId; - this.signature = signature; - } - - protected AVIMCommand(AVIMCommand source, - string cmd = null, - string op = null, - string appId = null, - string peerId = null, - IDictionary arguments = null, - AVIMSignature signature = null) - { - if (source == null) - { - throw new ArgumentNullException("source", "Source can not be null"); - } - this.cmd = source.cmd; - this.op = source.op; - this.arguments = source.arguments; - this.peerId = source.peerId; - this.appId = source.appId; - this.signature = source.signature; - - if (cmd != null) - { - this.cmd = cmd; - } - if (op != null) - { - this.op = op; - } - if (arguments != null) - { - this.arguments = arguments; - } - if (peerId != null) - { - this.peerId = peerId; - } - if (appId != null) - { - this.appId = appId; - } - if (signature != null) - { - this.signature = signature; - } - } - - public AVIMCommand Command(string cmd) - { - return new AVIMCommand(this, cmd: cmd); - } - public AVIMCommand Option(string op) - { - return new AVIMCommand(this, op: op); - } - public AVIMCommand Argument(string key, object value) - { - lock (mutex) - { - this.arguments[key] = value; - return new AVIMCommand(this); - } - } - public AVIMCommand AppId(string appId) - { - this.appId = appId; - return new AVIMCommand(this, appId: appId); - } - - public AVIMCommand PeerId(string peerId) - { - this.peerId = peerId; - return new AVIMCommand(this, peerId: peerId); - } - - public AVIMCommand IDlize() - { - this.Argument("i", AVIMCommand.NextCmdId); - return this; - } - - public virtual IDictionary Encode() - { - lock (mutex) - { - estimatedData.Clear(); - estimatedData.Merge(arguments); - estimatedData.Add("cmd", cmd); - estimatedData.Add("appId", this.appId); - if (!string.IsNullOrEmpty(op)) - estimatedData.Add("op", op); - if (!string.IsNullOrEmpty(peerId)) - estimatedData.Add("peerId", peerId); - - return estimatedData; - } - } - - public virtual string EncodeJsonString() - { - var json = this.Encode(); - return Json.Encode(json); - } - - public bool IsValid - { - get - { - return !string.IsNullOrEmpty(this.cmd); - } - } - - private static Int32 lastCmdId = -65536; - internal static Int32 NextCmdId - { - get - { - lock (Mutex) - { - lastCmdId++; - - if (lastCmdId > ushort.MaxValue) - { - lastCmdId = -65536; - } - return lastCmdId; - } - } - } - } -} diff --git a/RTM/Source/Internal/Command/AVIMCommandRunner.cs b/RTM/Source/Internal/Command/AVIMCommandRunner.cs deleted file mode 100644 index 2052c46..0000000 --- a/RTM/Source/Internal/Command/AVIMCommandRunner.cs +++ /dev/null @@ -1,92 +0,0 @@ -using LeanCloud; -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - public class AVIMCommandRunner : IAVIMCommandRunner - { - private readonly IWebSocketClient webSocketClient; - public AVIMCommandRunner(IWebSocketClient webSocketClient) - { - this.webSocketClient = webSocketClient; - } - - public void RunCommand(AVIMCommand command) - { - command.IDlize(); - var requestString = command.EncodeJsonString(); - AVRealtime.PrintLog("websocket=>" + requestString); - webSocketClient.Send(requestString); - } - - /// - /// - /// - /// - /// - /// - public Task>> RunCommandAsync(AVIMCommand command, CancellationToken cancellationToken = default(CancellationToken)) - { - var tcs = new TaskCompletionSource>>(); - - command.IDlize(); - - var requestString = command.EncodeJsonString(); - if (!command.IsValid) - { - requestString = "{}"; - } - AVRealtime.PrintLog("websocket=>" + requestString); - webSocketClient.Send(requestString); - var requestJson = command.Encode(); - - - Action onMessage = null; - onMessage = (response) => - { - //AVRealtime.PrintLog("response<=" + response); - var responseJson = Json.Parse(response) as IDictionary; - if (responseJson.Keys.Contains("i")) - { - if (requestJson["i"].ToString() == responseJson["i"].ToString()) - { - var result = new Tuple>(-1, responseJson); - if (responseJson.Keys.Contains("code")) - { - var errorCode = int.Parse(responseJson["code"].ToString()); - var reason = string.Empty; - int appCode = 0; - - if (responseJson.Keys.Contains("reason")) - { - reason = responseJson["reason"].ToString(); - } - if (responseJson.Keys.Contains("appCode")) - { - appCode = int.Parse(responseJson["appCode"].ToString()); - } - tcs.SetException(new AVIMException(errorCode, appCode, reason, null)); - } - if (tcs.Task.Exception == null) - { - tcs.SetResult(result); - } - webSocketClient.OnMessage -= onMessage; - } - else - { - - } - } - }; - webSocketClient.OnMessage += onMessage; - return tcs.Task; - } - } -} diff --git a/RTM/Source/Internal/Command/AckCommand.cs b/RTM/Source/Internal/Command/AckCommand.cs deleted file mode 100644 index 938f078..0000000 --- a/RTM/Source/Internal/Command/AckCommand.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - internal class AckCommand : AVIMCommand - { - public AckCommand() - : base(cmd: "ack") - { - - } - - public AckCommand(AVIMCommand source) - : base(source) - { - - } - - public AckCommand Message(IAVIMMessage message) - { - return new AckCommand() - .ConversationId(message.ConversationId) - .MessageId(message.Id); - } - - public AckCommand MessageId(string messageId) - { - if (string.IsNullOrEmpty(messageId)) - { - messageId = ""; - } - return new AckCommand(this.Argument("mid", messageId)); - } - - public AckCommand ConversationId(string conversationId) - { - if (string.IsNullOrEmpty(conversationId)) - { - conversationId = ""; - } - return new AckCommand(this.Argument("cid", conversationId)); - } - - public AckCommand FromTimeStamp(long startTimeStamp) - { - return new AckCommand(this.Argument("fromts", startTimeStamp)); - } - - public AckCommand ToTimeStamp(long endTimeStamp) - { - return new AckCommand(this.Argument("tots", endTimeStamp)); - } - - public AckCommand ReadAck() - { - return new AckCommand(this.Argument("read", true)); - } - } -} diff --git a/RTM/Source/Internal/Command/ConversationCommand.cs b/RTM/Source/Internal/Command/ConversationCommand.cs deleted file mode 100644 index ab4de2d..0000000 --- a/RTM/Source/Internal/Command/ConversationCommand.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - internal class ConversationCommand : AVIMCommand - { - protected IList members; - public ConversationCommand() - : base(cmd: "conv") - { - - } - - public ConversationCommand(AVIMCommand source) - : base(source: source) - { - } - - public ConversationCommand Member(string clientId) - { - if (members == null) - { - members = new List(); - } - members.Add(clientId); - return Members(members); - } - - public ConversationCommand Members(IEnumerable members) - { - this.members = members.ToList(); - return new ConversationCommand(this.Argument("m", members)); - } - - public ConversationCommand Transient(bool isTransient) - { - return new ConversationCommand(this.Argument("transient", isTransient)); - } - - public ConversationCommand Unique(bool isUnique) - { - return new ConversationCommand(this.Argument("unique", isUnique)); - } - - public ConversationCommand Temporary(bool isTemporary) - { - return new ConversationCommand(this.Argument("tempConv", isTemporary)); - } - - public ConversationCommand TempConvTTL(double tempConvTTL) - { - return new ConversationCommand(this.Argument("tempConvTTL", tempConvTTL)); - } - - public ConversationCommand Attr(IDictionary attr) - { - return new ConversationCommand(this.Argument("attr", attr)); - } - - public ConversationCommand Set(string key, object value) - { - return new ConversationCommand(this.Argument(key, value)); - } - - public ConversationCommand ConversationId(string conversationId) - { - return new ConversationCommand(this.Argument("cid", conversationId)); - } - - public ConversationCommand Generate(AVIMConversation conversation) - { - var attr = conversation.EncodeAttributes(); - var cmd = new ConversationCommand() - .ConversationId(conversation.ConversationId) - .Attr(attr) - .Members(conversation.MemberIds) - .Transient(conversation.IsTransient) - .Temporary(conversation.IsTemporary); - - if (conversation.IsTemporary) - { - var ttl = (conversation.expiredAt.Value - DateTime.Now).TotalSeconds; - cmd = cmd.TempConvTTL(ttl); - } - - return cmd; - } - - public ConversationCommand Where(object encodedQueryString) - { - return new ConversationCommand(this.Argument("where", encodedQueryString)); - } - - public ConversationCommand Limit(int limit) - { - return new ConversationCommand(this.Argument("limit", limit)); - } - - public ConversationCommand Skip(int skip) - { - return new ConversationCommand(this.Argument("skip", skip)); - } - - public ConversationCommand Count() - { - return new ConversationCommand(this.Argument("count", 1)); - } - - public ConversationCommand Sort(string sort) - { - return new ConversationCommand(this.Argument("sort", sort)); - } - - public ConversationCommand TargetClientId(string targetClientId) - { - return new ConversationCommand(this.Argument("targetClientId", targetClientId)); - } - - public ConversationCommand QueryAllMembers(bool queryAllMembers) - { - return new ConversationCommand(this.Argument("queryAllMembers", queryAllMembers)); - } - - } -} diff --git a/RTM/Source/Internal/Command/IAVIMCommandRunner.cs b/RTM/Source/Internal/Command/IAVIMCommandRunner.cs deleted file mode 100644 index a904f97..0000000 --- a/RTM/Source/Internal/Command/IAVIMCommandRunner.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - public interface IAVIMCommandRunner - { - Task>> RunCommandAsync(AVIMCommand command, - CancellationToken cancellationToken = default(CancellationToken)); - - void RunCommand(AVIMCommand command); - } -} diff --git a/RTM/Source/Internal/Command/MessageCommand.cs b/RTM/Source/Internal/Command/MessageCommand.cs deleted file mode 100644 index ec76d94..0000000 --- a/RTM/Source/Internal/Command/MessageCommand.cs +++ /dev/null @@ -1,83 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - internal class MessageCommand : AVIMCommand - { - public MessageCommand() - : base(cmd: "direct") - { - - } - - public MessageCommand(AVIMCommand source) - : base(source: source) - { - - } - - public MessageCommand ConvId(string convId) - { - return new MessageCommand(this.Argument("cid", convId)); - } - - public MessageCommand Receipt(bool receipt) - { - return new MessageCommand(this.Argument("r", receipt)); - } - - public MessageCommand Transient(bool transient) - { - if (transient) return new MessageCommand(this.Argument("transient", transient)); - return new MessageCommand(this); - } - public MessageCommand Priority(int priority) - { - if (priority > 1) return new MessageCommand(this.Argument("level", priority)); - return new MessageCommand(this); - } - public MessageCommand Will(bool will) - { - if (will) return new MessageCommand(this.Argument("will", will)); - return new MessageCommand(this); - } - public MessageCommand Distinct(string token) - { - return new MessageCommand(this.Argument("dt", token)); - } - public MessageCommand Message(string msg) - { - return new MessageCommand(this.Argument("msg", msg)); - } - public MessageCommand BinaryEncode(bool binaryEncode) - { - return new MessageCommand(this.Argument("bin", binaryEncode)); - } - - public MessageCommand PushData(IDictionary pushData) - { - return new MessageCommand(this.Argument("pushData", Json.Encode(pushData))); - } - - public MessageCommand Mention(IEnumerable clientIds) - { - var mentionedMembers = clientIds.ToList(); - return new MessageCommand(this.Argument("mentionPids", mentionedMembers)); - } - - public MessageCommand MentionAll(bool mentionAll) - { - return new MessageCommand(this.Argument("mentionAll", mentionAll)); - } - - public MessageCommand Binary(byte[] data) - { - return new MessageCommand(this.Argument("binaryMsg", data)); - } - } -} diff --git a/RTM/Source/Internal/Command/PatchCommand.cs b/RTM/Source/Internal/Command/PatchCommand.cs deleted file mode 100644 index 113a0db..0000000 --- a/RTM/Source/Internal/Command/PatchCommand.cs +++ /dev/null @@ -1,98 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - internal class PatchCommand : AVIMCommand - { - - internal struct Patch - { - public string MessageId { get; set; } - public string ConvId { get; set; } - public string From { get; set; } - public long MetaTimestamp { get; set; } - public long PatchTimestamp { get; set; } - public string PatchData { get; set; } - public bool Recall { get; set; } - public byte[] BinaryData { get; set; } - public bool MentionAll { get; set; } - public IEnumerable MentionIds { get; set; } - - public IDictionary Encode() - { - return new Dictionary() - { - { "cid",this.ConvId}, - { "mid",this.MessageId}, - { "from",this.From}, - { "timestamp",this.MetaTimestamp}, - { "recall",this.Recall}, - { "data",this.PatchData}, - { "patchTimestamp",this.PatchTimestamp}, - { "binaryMsg",this.BinaryData}, - { "mentionAll",this.MentionAll}, - { "meintonPids",this.MentionIds} - } as IDictionary; - } - } - - public PatchCommand() - : base(cmd: "patch", op: "modify") - { - this.Patches = new List(); - } - - public PatchCommand(AVIMCommand source, ICollection sourcePatchs) - : base(source: source) - { - this.Patches = sourcePatchs; - } - - public ICollection Patches { get; set; } - - public IList> EncodePatches() - { - return this.Patches.Select(p => p.Encode().Trim()).ToList(); - } - - public PatchCommand Recall(IAVIMMessage message) - { - var patch = new Patch() - { - ConvId = message.ConversationId, - From = message.FromClientId, - MessageId = message.Id, - MetaTimestamp = message.ServerTimestamp, - Recall = true, - PatchTimestamp = DateTime.Now.ToUnixTimeStamp() - }; - - this.Patches.Add(patch); - this.Argument("patches", this.EncodePatches()); - return new PatchCommand(this, this.Patches); - } - - public PatchCommand Modify(IAVIMMessage oldMessage, IAVIMMessage newMessage) - { - var patch = new Patch() - { - ConvId = oldMessage.ConversationId, - From = oldMessage.FromClientId, - MessageId = oldMessage.Id, - MetaTimestamp = oldMessage.ServerTimestamp, - Recall = false, - PatchTimestamp = DateTime.Now.ToUnixTimeStamp(), - PatchData = newMessage.Serialize() - }; - - this.Patches.Add(patch); - this.Argument("patches", this.EncodePatches()); - return new PatchCommand(this, this.Patches); - } - } -} diff --git a/RTM/Source/Internal/Command/ReadCommand.cs b/RTM/Source/Internal/Command/ReadCommand.cs deleted file mode 100644 index a627d00..0000000 --- a/RTM/Source/Internal/Command/ReadCommand.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - internal class ReadCommand : AVIMCommand - { - internal class ConvRead - { - internal string ConvId { get; set; } - internal string MessageId { get; set; } - internal long Timestamp { get; set; } - public override bool Equals(object obj) - { - ConvRead cr = obj as ConvRead; - return cr.ConvId == this.ConvId; - } - public override int GetHashCode() - { - return this.ConvId.GetHashCode() ^ this.MessageId.GetHashCode() ^ this.Timestamp.GetHashCode(); - } - } - - public ReadCommand() - : base(cmd: "read") - { - - } - - public ReadCommand(AVIMCommand source) - : base(source) - { - - } - - public ReadCommand ConvId(string convId) - { - return new ReadCommand(this.Argument("cid", convId)); - } - - public ReadCommand ConvIds(IEnumerable convIds) - { - if (convIds != null) - { - if (convIds.Count() > 0) - { - return new ReadCommand(this.Argument("cids", convIds.ToList())); - } - } - return this; - - } - - public ReadCommand Conv(ConvRead conv) - { - return Convs(new ConvRead[] { conv }); - } - - public ReadCommand Convs(IEnumerable convReads) - { - if (convReads != null) - { - if (convReads.Count() > 0) - { - IList> payload = new List>(); - - foreach (var convRead in convReads) - { - var convDic = new Dictionary(); - convDic.Add("cid", convRead.ConvId); - if (!string.IsNullOrEmpty(convRead.MessageId)) - { - convDic.Add("mid", convRead.MessageId); - } - if (convRead.Timestamp != 0) - { - convDic.Add("timestamp", convRead.Timestamp); - } - payload.Add(convDic); - } - - return new ReadCommand(this.Argument("convs", payload)); - } - } - return this; - } - } -} diff --git a/RTM/Source/Internal/Command/SessionCommand.cs b/RTM/Source/Internal/Command/SessionCommand.cs deleted file mode 100644 index f18b7b9..0000000 --- a/RTM/Source/Internal/Command/SessionCommand.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - internal class SessionCommand : AVIMCommand - { - static readonly int MESSAGE_RECALL_AND_MODIFY = 0x1; - - public SessionCommand() - : base(cmd: "session") - { - arguments.Add("configBitmap", MESSAGE_RECALL_AND_MODIFY); - } - - public SessionCommand(AVIMCommand source) - :base(source: source) - { - - } - - public SessionCommand UA(string ua) - { - return new SessionCommand(this.Argument("ua", ua)); - } - - public SessionCommand Tag(string tag) - { - if (string.IsNullOrEmpty(tag)) return new SessionCommand(this); - return new SessionCommand(this.Argument("tag", tag)); - } - - public SessionCommand DeviceId(string deviceId) - { - if (string.IsNullOrEmpty(deviceId)) return new SessionCommand(this); - return new SessionCommand(this.Argument("deviceId", deviceId)); - } - - public SessionCommand R(int r) - { - return new SessionCommand(this.Argument("r", r)); - } - - public SessionCommand SessionToken(string st) - { - return new SessionCommand(this.Argument("st", st)); - } - - public SessionCommand SessionPeerIds(IEnumerable sessionPeerIds) - { - return new SessionCommand(this.Argument("sessionPeerIds", sessionPeerIds.ToList())); - } - } -} diff --git a/RTM/Source/Internal/DataEngine/Controller/DateTimeEngine.cs b/RTM/Source/Internal/DataEngine/Controller/DateTimeEngine.cs deleted file mode 100644 index 25c920a..0000000 --- a/RTM/Source/Internal/DataEngine/Controller/DateTimeEngine.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - internal enum UnixTimeStampUnit - { - Second = 1, - Milisecond = 1000, - } - internal static class DateTimeEngine - { - public static long ToUnixTimeStamp(this DateTime date, UnixTimeStampUnit unit = UnixTimeStampUnit.Milisecond) - { - long unixTimestamp = (long)(date.ToUniversalTime().Subtract(new DateTime(1970, 1, 1))).TotalSeconds; - return (unixTimestamp * (int)unit); - } - - public static DateTime ToDateTime(this long timestamp, UnixTimeStampUnit unit = UnixTimeStampUnit.Milisecond) - { - var timespan = timestamp * 1000 / (int)(unit); - DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - dtDateTime = dtDateTime.AddMilliseconds(timespan).ToLocalTime(); - return dtDateTime; - } - } -} diff --git a/RTM/Source/Internal/DataEngine/Controller/DictionaryEngine.cs b/RTM/Source/Internal/DataEngine/Controller/DictionaryEngine.cs deleted file mode 100644 index a1da600..0000000 --- a/RTM/Source/Internal/DataEngine/Controller/DictionaryEngine.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - internal static class DictionaryEngine - { - internal static IDictionary Merge(this IDictionary dataLeft, IDictionary dataRight) - { - if (dataRight == null) - return dataLeft; - foreach (var kv in dataRight) - { - if (dataLeft.ContainsKey(kv.Key)) - { - dataLeft[kv.Key] = kv.Value; - } - else - { - dataLeft.Add(kv); - } - } - return dataLeft; - } - - internal static object Grab(this IDictionary data, string path) - { - var keys = path.Split('.').ToList(); - if (keys.Count == 1) return data[keys[0]]; - - var deep = data[keys[0]] as IDictionary; - - keys.RemoveAt(0); - string deepPath = string.Join(".", keys.ToArray()); - - return Grab(deep, deepPath); - } - - internal static IDictionary Trim(this IDictionary data) - { - return data.Where(kvp => kvp.Value != null).ToDictionary(k => k.Key, v => v.Value); - } - } -} diff --git a/RTM/Source/Internal/DataEngine/Controller/StringEngine.cs b/RTM/Source/Internal/DataEngine/Controller/StringEngine.cs deleted file mode 100644 index 62e902a..0000000 --- a/RTM/Source/Internal/DataEngine/Controller/StringEngine.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.Linq.Expressions; -using System.Text; - -namespace LeanCloud.Realtime.Internal -{ - internal static class StringEngine - { - internal static string Random(this string str, int length) - { - const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; - var random = new Random(); - return new string(Enumerable.Repeat(chars, length) - .Select(s => s[random.Next(s.Length)]).ToArray()); - } - - internal static string TempConvId(this IEnumerable objs) - { - var orderedBase64Strs = objs.Select(obj => Encoding.UTF8.ToBase64(obj.ToString())).OrderBy(a => a, StringComparer.Ordinal).ToArray(); - return "_tmp:" + string.Join("$", orderedBase64Strs); - } - - internal static string ToBase64(this System.Text.Encoding encoding, string text) - { - if (text == null) - { - return null; - } - - byte[] textAsBytes = encoding.GetBytes(text); - return Convert.ToBase64String(textAsBytes); - } - } -} diff --git a/RTM/Source/Internal/IAVIMPlatformHooks.cs b/RTM/Source/Internal/IAVIMPlatformHooks.cs deleted file mode 100644 index 53d737c..0000000 --- a/RTM/Source/Internal/IAVIMPlatformHooks.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - interface IAVIMPlatformHooks - { - IWebSocketClient WebSocketClient { get; } - - string ua { get; } - } -} diff --git a/RTM/Source/Internal/Message/Subclassing/FreeStyleMessageClassInfo.cs b/RTM/Source/Internal/Message/Subclassing/FreeStyleMessageClassInfo.cs deleted file mode 100644 index e1a1e56..0000000 --- a/RTM/Source/Internal/Message/Subclassing/FreeStyleMessageClassInfo.cs +++ /dev/null @@ -1,75 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; - -namespace LeanCloud.Realtime.Internal -{ - internal class FreeStyleMessageClassInfo - { - public TypeInfo TypeInfo { get; private set; } - public IDictionary PropertyMappings { get; private set; } - private ConstructorInfo Constructor { get; set; } - //private MethodInfo ValidateMethod { get; set; } - - public int TypeInt { get; set; } - - public FreeStyleMessageClassInfo(Type type, ConstructorInfo constructor) - { - TypeInfo = type.GetTypeInfo(); - Constructor = constructor; - PropertyMappings = ReflectionHelpers.GetProperties(type) - .Select(prop => Tuple.Create(prop, prop.GetCustomAttribute(true))) - .Where(t => t.Item2 != null) - .Select(t => Tuple.Create(t.Item1, t.Item2.FieldName)) - .ToDictionary(t => t.Item1.Name, t => t.Item2); - } - public bool Validate(string msgStr) - { - var instance = Instantiate(msgStr); - if (instance is AVIMTypedMessage) - { - try - { - var msgDic = Json.Parse(msgStr) as IDictionary; - if (msgDic != null) - { - if (msgDic.ContainsKey(AVIMProtocol.LCTYPE)) - { - return msgDic[AVIMProtocol.LCTYPE].ToString() == TypeInt.ToString(); - } - } - } - catch (Exception ex) - { - if (ex is ArgumentException) - { - return instance.Validate(msgStr); - } - } - - } - return instance.Validate(msgStr); - } - - public IAVIMMessage Instantiate(string msgStr) - { - var rtn = (IAVIMMessage)Constructor.Invoke(null); - return rtn; - } - - public static string GetMessageClassName(TypeInfo type) - { - var attribute = type.GetCustomAttribute(); - return attribute != null ? attribute.ClassName : null; - } - - public static int GetTypedInteger(TypeInfo type) - { - var attribute = type.GetCustomAttribute(); - return attribute != null ? attribute.TypeInteger : 0; - } - } -} diff --git a/RTM/Source/Internal/Message/Subclassing/FreeStyleMessageClassingController.cs b/RTM/Source/Internal/Message/Subclassing/FreeStyleMessageClassingController.cs deleted file mode 100644 index 04bd105..0000000 --- a/RTM/Source/Internal/Message/Subclassing/FreeStyleMessageClassingController.cs +++ /dev/null @@ -1,210 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading; - -namespace LeanCloud.Realtime.Internal -{ - internal class FreeStyleMessageClassingController : IFreeStyleMessageClassingController - { - private static readonly string messageClassName = "_AVIMMessage"; - private readonly IDictionary registeredInterfaces; - private readonly ReaderWriterLockSlim mutex; - - public FreeStyleMessageClassingController() - { - mutex = new ReaderWriterLockSlim(); - registeredInterfaces = new Dictionary(); - } - - public Type GetType(IDictionary msg) - { - throw new NotImplementedException(); - } - - public IAVIMMessage Instantiate(string msgStr, IDictionary buildInData) - { - FreeStyleMessageClassInfo info = null; - mutex.EnterReadLock(); - bool bin = false; - if (buildInData.ContainsKey("bin")) - { - bool.TryParse(buildInData["bin"].ToString(), out bin); - } - - if (bin) - { - var binMessage = new AVIMBinaryMessage(); - this.DecodeProperties(binMessage, buildInData); - return binMessage; - } - - var reverse = registeredInterfaces.Values.Reverse(); - foreach (var subInterface in reverse) - { - if (subInterface.Validate(msgStr)) - { - info = subInterface; - break; - } - } - - mutex.ExitReadLock(); - - var message = info != null ? info.Instantiate(msgStr) : new AVIMMessage(); - - this.DecodeProperties(message, buildInData); - - message.Deserialize(msgStr); - - return message; - } - - public IAVIMMessage DecodeProperties(IAVIMMessage message, IDictionary buildInData) - { - long timestamp; - if (buildInData.ContainsKey("timestamp")) - { - if (long.TryParse(buildInData["timestamp"].ToString(), out timestamp)) - { - message.ServerTimestamp = timestamp; - } - } - long ackAt; - if (buildInData.ContainsKey("ackAt")) - { - if (long.TryParse(buildInData["ackAt"].ToString(), out ackAt)) - { - message.RcpTimestamp = ackAt; - } - } - - if (buildInData.ContainsKey("from")) - { - message.FromClientId = buildInData["from"].ToString(); - } - if (buildInData.ContainsKey("msgId")) - { - message.Id = buildInData["msgId"].ToString(); - } - if (buildInData.ContainsKey("cid")) - { - message.ConversationId = buildInData["cid"].ToString(); - } - if (buildInData.ContainsKey("fromPeerId")) - { - message.FromClientId = buildInData["fromPeerId"].ToString(); - } - if (buildInData.ContainsKey("id")) - { - message.Id = buildInData["id"].ToString(); - } - if (buildInData.ContainsKey("mid")) - { - message.Id = buildInData["mid"].ToString(); - } - if (buildInData.ContainsKey("mentionPids")) - { - message.MentionList = AVDecoder.Instance.DecodeList(buildInData["mentionPids"]); - } - if (buildInData.TryGetValue("patchTimestamp", out object patchTimestampObj)) { - if (long.TryParse(patchTimestampObj.ToString(), out long patchTimestamp)) { - message.UpdatedAt = patchTimestamp; - } - } - - bool mentionAll; - if (buildInData.ContainsKey("mentionAll")) - { - if (bool.TryParse(buildInData["mentionAll"].ToString(), out mentionAll)) - { - message.MentionAll = mentionAll; - } - } - return message; - } - - public IDictionary EncodeProperties(IAVIMMessage subclass) - { - var type = subclass.GetType(); - var result = new Dictionary(); - var className = GetClassName(type); - var typeInt = GetTypeInt(type); - var propertMappings = GetPropertyMappings(className); - foreach (var propertyPair in propertMappings) - { - var propertyInfo = ReflectionHelpers.GetProperty(type, propertyPair.Key); - var operation = propertyInfo.GetValue(subclass, null); - if (operation != null) - result[propertyPair.Value] = PointerOrLocalIdEncoder.Instance.Encode(operation); - } - if (typeInt != 0) - { - result[AVIMProtocol.LCTYPE] = typeInt; - } - return result; - } - - public bool IsTypeValid(IDictionary msg, Type type) - { - return true; - } - - public void RegisterSubclass(Type type) - { - TypeInfo typeInfo = type.GetTypeInfo(); - - if (!typeof(IAVIMMessage).GetTypeInfo().IsAssignableFrom(typeInfo)) - { - throw new ArgumentException("Cannot register a type that is not a implementation of IAVIMMessage"); - } - var className = GetClassName(type); - var typeInt = GetTypeInt(type); - try - { - mutex.EnterWriteLock(); - ConstructorInfo constructor = type.FindConstructor(); - if (constructor == null) - { - throw new ArgumentException("Cannot register a type that does not implement the default constructor!"); - } - var classInfo = new FreeStyleMessageClassInfo(type, constructor); - if (typeInt != 0) - { - classInfo.TypeInt = typeInt; - } - registeredInterfaces[className] = classInfo; - } - finally - { - mutex.ExitWriteLock(); - } - } - public String GetClassName(Type type) - { - return type == typeof(IAVIMMessage) - ? messageClassName - : FreeStyleMessageClassInfo.GetMessageClassName(type.GetTypeInfo()); - } - public int GetTypeInt(Type type) - { - return type == typeof(AVIMTypedMessage) ? 0 : FreeStyleMessageClassInfo.GetTypedInteger(type.GetTypeInfo()); - } - public IDictionary GetPropertyMappings(String className) - { - FreeStyleMessageClassInfo info = null; - mutex.EnterReadLock(); - registeredInterfaces.TryGetValue(className, out info); - if (info == null) - { - registeredInterfaces.TryGetValue(messageClassName, out info); - } - mutex.ExitReadLock(); - - return info.PropertyMappings; - } - } -} diff --git a/RTM/Source/Internal/Message/Subclassing/IFreeStyleMessageClassingController.cs b/RTM/Source/Internal/Message/Subclassing/IFreeStyleMessageClassingController.cs deleted file mode 100644 index 0fc3912..0000000 --- a/RTM/Source/Internal/Message/Subclassing/IFreeStyleMessageClassingController.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace LeanCloud.Realtime.Internal -{ - interface IFreeStyleMessageClassingController - { - bool IsTypeValid(IDictionary msg, Type type); - void RegisterSubclass(Type t); - IAVIMMessage Instantiate(string msgStr,IDictionary buildInData); - IDictionary EncodeProperties(IAVIMMessage subclass); - Type GetType(IDictionary msg); - String GetClassName(Type type); - IDictionary GetPropertyMappings(String className); - } -} diff --git a/RTM/Source/Internal/Protocol/AVIMProtocol.cs b/RTM/Source/Internal/Protocol/AVIMProtocol.cs deleted file mode 100644 index 6c71044..0000000 --- a/RTM/Source/Internal/Protocol/AVIMProtocol.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - internal class AVIMProtocol - { - #region msg format - static internal readonly string LCTYPE = "_lctype"; - static internal readonly string LCFILE = "_lcfile"; - static internal readonly string LCTEXT = "_lctext"; - static internal readonly string LCATTRS = "_lcattrs"; - static internal readonly string LCLOC = "_lcloc"; - #endregion - } -} diff --git a/RTM/Source/Internal/Router/AVRouterController.cs b/RTM/Source/Internal/Router/AVRouterController.cs deleted file mode 100644 index dc47848..0000000 --- a/RTM/Source/Internal/Router/AVRouterController.cs +++ /dev/null @@ -1,173 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - internal class AVRouterController : IAVRouterController - { - const string routerUrl = "http://router.g0.push.leancloud.cn/v1/route?appId={0}"; - const string routerKey = "LeanCloud_RouterState"; - public Task GetAsync(string pushRouter = null, bool secure = true, CancellationToken cancellationToken = default(CancellationToken)) - { - //return Task.FromResult(new PushRouterState() - //{ - // server = "wss://rtm57.leancloud.cn/" - //}); - return LoadAysnc(cancellationToken).OnSuccess(_ => - { - var cache = _.Result; - var task = Task.FromResult(cache); - - if (cache == null || cache.expire < DateTime.Now.ToUnixTimeStamp()) - { - task = QueryAsync(pushRouter, secure, cancellationToken); - } - - return task; - }).Unwrap(); - } - - /// - /// 清理地址缓存 - /// - /// The cache. - public Task ClearCache() { - var tcs = new TaskCompletionSource(); - AVPlugins.Instance.StorageController.LoadAsync().ContinueWith(t => { - if (t.IsFaulted) { - tcs.SetResult(true); - } else { - var storage = t.Result; - if (storage.ContainsKey(routerKey)) { - storage.RemoveAsync(routerKey).ContinueWith(_ => tcs.SetResult(true)); - } else { - tcs.SetResult(true); - } - } - }); - return tcs.Task; - } - - Task LoadAysnc(CancellationToken cancellationToken) - { - try - { - return AVPlugins.Instance.StorageController.LoadAsync().OnSuccess(_ => - { - var currentCache = _.Result; - object routeCacheStr = null; - if (currentCache.TryGetValue(routerKey, out routeCacheStr)) - { - var routeCache = routeCacheStr as IDictionary; - var routerState = new PushRouterState() - { - groupId = routeCache["groupId"] as string, - server = routeCache["server"] as string, - secondary = routeCache["secondary"] as string, - ttl = long.Parse(routeCache["ttl"].ToString()), - expire = long.Parse(routeCache["expire"].ToString()), - source = "localCache" - }; - return routerState; - } - return null; - }); - } - catch - { - return Task.FromResult(null); - } - } - - Task QueryAsync(string pushRouter, bool secure, CancellationToken cancellationToken) - { - var routerHost = pushRouter; - if (routerHost == null) { - var appRouter = AVPlugins.Instance.AppRouterController.Get(); - routerHost = string.Format("https://{0}/v1/route?appId={1}", appRouter.RealtimeRouterServer, AVClient.CurrentConfiguration.ApplicationId) ?? appRouter.RealtimeRouterServer ?? string.Format(routerUrl, AVClient.CurrentConfiguration.ApplicationId); - } - AVRealtime.PrintLog($"router: {routerHost}"); - AVRealtime.PrintLog($"push: {pushRouter}"); - if (!string.IsNullOrEmpty(pushRouter)) - { - var rtmUri = new Uri(pushRouter); - if (!string.IsNullOrEmpty(rtmUri.Scheme)) - { - var url = new Uri(rtmUri, "v1/route").ToString(); - routerHost = string.Format("{0}?appId={1}", url, AVClient.CurrentConfiguration.ApplicationId); - } - else - { - routerHost = string.Format("https://{0}/v1/route?appId={1}", pushRouter, AVClient.CurrentConfiguration.ApplicationId); - } - } - if (secure) - { - routerHost += "&secure=1"; - } - - AVRealtime.PrintLog("use push router url:" + routerHost); - - return AVClient.RequestAsync(uri: new Uri(routerHost), - method: "GET", - headers: null, - data: null, - contentType: "application/json", - cancellationToken: CancellationToken.None).ContinueWith(t => - { - if (t.Exception != null) - { - var innnerException = t.Exception.InnerException; - AVRealtime.PrintLog(innnerException.Message); - throw innnerException; - } - var httpStatus = (int)t.Result.Item1; - if (httpStatus != 200) - { - return null; - } - try - { - var result = t.Result.Item2; - - var routerState = Json.Parse(result) as IDictionary; - if (routerState.Keys.Count == 0) - { - throw new KeyNotFoundException("Can not get websocket url from server,please check the appId."); - } - var ttl = long.Parse(routerState["ttl"].ToString()); - var expire = DateTime.Now.AddSeconds(ttl); - routerState["expire"] = expire.ToUnixTimeStamp(); - - //save to local cache async. - AVPlugins.Instance.StorageController.LoadAsync().OnSuccess(storage => storage.Result.AddAsync(routerKey, routerState)); - var routerStateObj = new PushRouterState() - { - groupId = routerState["groupId"] as string, - server = routerState["server"] as string, - secondary = routerState["secondary"] as string, - ttl = long.Parse(routerState["ttl"].ToString()), - expire = expire.ToUnixTimeStamp(), - source = "online" - }; - - return routerStateObj; - } - catch (Exception e) - { - if (e is KeyNotFoundException) - { - throw e; - } - return null; - } - - }); - } - } -} diff --git a/RTM/Source/Internal/Router/IAVRouterController.cs b/RTM/Source/Internal/Router/IAVRouterController.cs deleted file mode 100644 index b7eebfb..0000000 --- a/RTM/Source/Internal/Router/IAVRouterController.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - interface IAVRouterController - { - Task GetAsync(string pushRouter, bool secure, CancellationToken cancellationToken = default(CancellationToken)); - Task ClearCache(); - } -} diff --git a/RTM/Source/Internal/Router/State/RouterState.cs b/RTM/Source/Internal/Router/State/RouterState.cs deleted file mode 100644 index 66eaea9..0000000 --- a/RTM/Source/Internal/Router/State/RouterState.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - internal class PushRouterState - { - public string groupId { get; internal set; } - public string server { get; internal set; } - public long ttl { get; internal set; } - public long expire { get; internal set; } - public string secondary { get; internal set; } - public string groupUrl { get; internal set; } - - public string source { get; internal set; } - } -} diff --git a/RTM/Source/Internal/Timer/IAVTimer.cs b/RTM/Source/Internal/Timer/IAVTimer.cs deleted file mode 100644 index 5573041..0000000 --- a/RTM/Source/Internal/Timer/IAVTimer.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -namespace LeanCloud.Realtime.Internal -{ - public interface IAVTimer - { - /// - /// Start this timer. - /// - void Start(); - - /// - /// Stop this timer. - /// - void Stop(); - - bool Enabled { get; set; } - - /// - /// The number of milliseconds between timer events. - /// - /// The interval. - double Interval { get; set; } - - /// - /// 已经执行了多少次 - /// - long Executed { get; } - - /// - /// Occurs when elapsed. - /// - event EventHandler Elapsed; - } - /// - /// Timer event arguments. - /// - public class TimerEventArgs : EventArgs - { - public TimerEventArgs(DateTime signalTime) - { - SignalTime = signalTime; - } - public DateTime SignalTime - { - get; - private set; - } - } -} diff --git a/RTM/Source/Internal/Timer/Portable/AVTimer.Portable.cs b/RTM/Source/Internal/Timer/Portable/AVTimer.Portable.cs deleted file mode 100644 index e981dbd..0000000 --- a/RTM/Source/Internal/Timer/Portable/AVTimer.Portable.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Threading; - -namespace LeanCloud.Realtime.Internal -{ - internal delegate void TimerCallback(); - - internal sealed class Timer : CancellationTokenSource, IDisposable - { - TimerCallback exe; - int Interval { get; set; } - - internal Timer(TimerCallback callback, int interval, bool enable) - { - exe = callback; - Interval = interval; - - Enabled = enable; - Execute(); - } - - Task Execute() - { - if (Enabled) - return Task.Delay(Interval).ContinueWith(t => - { - if (!Enabled) - return null; - exe(); - return this.Execute(); - }); - else - return Task.FromResult(0); - } - - volatile bool enabled; - public bool Enabled - { - get { - return enabled; - } set { - enabled = value; - } - } - } - - public class AVTimer : IAVTimer - { - public AVTimer() - { - - } - - Timer timer; - - public bool Enabled - { - get - { - return timer.Enabled; - } - set - { - timer.Enabled = value; - } - } - - public double Interval - { - get; set; - } - - long executed; - - public long Executed - { - get - { - return executed; - } - - internal set - { - executed = value; - } - } - - public void Start() - { - if (timer == null) - { - timer = new Timer(() => - { - Elapsed(this, new TimerEventArgs(DateTime.Now)); - }, (int)Interval, true); - } - } - - public void Stop() - { - if (timer != null) timer.Enabled = false; - } - - public event EventHandler Elapsed; - } -} diff --git a/RTM/Source/Internal/Timer/Unity/AVTimer.Unity.cs b/RTM/Source/Internal/Timer/Unity/AVTimer.Unity.cs deleted file mode 100644 index 8298433..0000000 --- a/RTM/Source/Internal/Timer/Unity/AVTimer.Unity.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Timers; - -namespace LeanCloud.Realtime.Internal -{ - public class AVTimer : IAVTimer - { - public AVTimer() - { - timer = new Timer(); - } - - Timer timer; - - public bool Enabled - { - get - { - return timer.Enabled; - } - set - { - timer.Enabled = value; - } - } - - public double Interval - { - get - { - return timer.Interval; - } - set - { - timer.Interval = value; - } - } - - long executed; - - public long Executed - { - get - { - return executed; - } - - internal set - { - executed = value; - } - } - - public void Start() - { - timer.Start(); - } - - public void Stop() - { - timer.Stop(); - } - - public event EventHandler Elapsed - { - add - { - timer.Elapsed += (object sender, ElapsedEventArgs e) => - { - value(this, new TimerEventArgs(e.SignalTime)); - }; - } - remove - { - timer.Elapsed -= (object sender, ElapsedEventArgs e) => - { - value(this, new TimerEventArgs(e.SignalTime)); - }; ; - } - } - } -} diff --git a/RTM/Source/Internal/WebSocket/IWebSocketClient.cs b/RTM/Source/Internal/WebSocket/IWebSocketClient.cs deleted file mode 100644 index 175ffa0..0000000 --- a/RTM/Source/Internal/WebSocket/IWebSocketClient.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - /// - /// LeanCloud WebSocket 客户端接口 - /// - public interface IWebSocketClient - { - /// - /// 客户端 WebSocket 长连接是否打开 - /// - bool IsOpen { get; } - - /// - /// WebSocket 长连接关闭时触发的事件回调 - /// - event Action OnClosed; - - /// - /// 云端发送数据包给客户端,WebSocket 接受到时触发的事件回调 - /// - event Action OnMessage; - - /// - /// 客户端 WebSocket 长连接成功打开时,触发的事件回调 - /// - event Action OnOpened; - - /// - /// 主动关闭连接 - /// - void Close(); - - void Disconnect(); - - /// - /// 打开连接 - /// - /// wss 地址 - /// 子协议 - void Open(string url, string protocol = null); - /// - /// 发送数据包的接口 - /// - /// - void Send(string message); - - Task Connect(string url, string protocol = null); - } -} diff --git a/RTM/Source/Internal/WebSocket/NetCore/DefaultWebSocketClient.NetCore.cs b/RTM/Source/Internal/WebSocket/NetCore/DefaultWebSocketClient.NetCore.cs deleted file mode 100644 index bf27232..0000000 --- a/RTM/Source/Internal/WebSocket/NetCore/DefaultWebSocketClient.NetCore.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - public class DefaultWebSocketClient : IWebSocketClient - { - private const int ReceiveChunkSize = 1024; - private const int SendChunkSize = 1024; - - private ClientWebSocket _ws; - private Uri _uri; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - private readonly CancellationToken _cancellationToken; - - /// - /// Occurs when on closed. - /// - public event Action OnClosed; - /// - /// Occurs when on error. - /// - public event Action OnError; - /// - /// Occurs when on log. - /// - public event Action OnLog; - /// - /// Occurs when on opened. - /// - public event Action OnOpened; - - public bool IsOpen => _ws.State == WebSocketState.Open; - - public DefaultWebSocketClient() - { - _ws = NewWebSocket(); - _cancellationToken = _cancellationTokenSource.Token; - } - - public event Action OnMessage; - - private async void StartListen() - { - var buffer = new byte[8192]; - - try - { - while (_ws.State == WebSocketState.Open) - { - var stringResult = new StringBuilder(); - - WebSocketReceiveResult result; - do - { - result = await _ws.ReceiveAsync(new ArraySegment(buffer), _cancellationToken); - - if (result.MessageType == WebSocketMessageType.Close) - { - await - _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); - CallOnDisconnected(); - } - else - { - var str = Encoding.UTF8.GetString(buffer, 0, result.Count); - stringResult.Append(str); - } - - } while (!result.EndOfMessage); - - CallOnMessage(stringResult); - - } - } - catch (Exception) - { - CallOnDisconnected(); - } - finally - { - _ws.Dispose(); - } - } - - private void CallOnMessage(StringBuilder stringResult) - { - if (OnMessage != null) - RunInTask(() => OnMessage(stringResult.ToString())); - } - - private void CallOnDisconnected() - { - AVRealtime.PrintLog("PCL websocket closed without parameters."); - if (OnClosed != null) - RunInTask(() => this.OnClosed(0, "", "")); - } - - private void CallOnConnected() - { - if (OnOpened != null) - RunInTask(() => this.OnOpened()); - } - - private static void RunInTask(Action action) - { - Task.Factory.StartNew(action); - } - - public async void Close() - { - if (_ws != null) - { - try - { - await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); - CallOnDisconnected(); - } - catch (Exception ex) - { - CallOnDisconnected(); - } - } - } - - public void Disconnect() { - _ws?.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); - } - - public async void Open(string url, string protocol = null) - { - _uri = new Uri(url); - if (_ws.State == WebSocketState.Open || _ws.State == WebSocketState.Connecting) - { - _ws = NewWebSocket(); - } - try - { - await _ws.ConnectAsync(_uri, _cancellationToken); - CallOnConnected(); - StartListen(); - } - catch (Exception ex) - { - if (ex is ObjectDisposedException) - { - OnError($"can NOT connect server with url: {url}"); - } - } - } - - public async void Send(string message) - { - if (_ws.State != WebSocketState.Open) - { - throw new Exception("Connection is not open."); - } - - var encoded = Encoding.UTF8.GetBytes(message); - var buffer = new ArraySegment(encoded, 0, encoded.Length); - await _ws.SendAsync(buffer, WebSocketMessageType.Text, true, _cancellationToken); - } - - ClientWebSocket NewWebSocket() - { - var result = new ClientWebSocket(); - result.Options.KeepAliveInterval = TimeSpan.FromSeconds(20); - return result; - } - } -} \ No newline at end of file diff --git a/RTM/Source/Internal/WebSocket/NetFx45/WebSocketClient.NetFx45.cs b/RTM/Source/Internal/WebSocket/NetFx45/WebSocketClient.NetFx45.cs deleted file mode 100644 index dfa5ac9..0000000 --- a/RTM/Source/Internal/WebSocket/NetFx45/WebSocketClient.NetFx45.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using Websockets; -using System.Net.WebSockets; -using LeanCloud.Realtime.Internal; - -namespace LeanCloud.Realtime -{ - public class WebSocketClient: IWebSocketClient - { - internal readonly IWebSocketConnection connection; - public WebSocketClient() - { - Websockets.Net.WebsocketConnection.Link(); - connection = WebSocketFactory.Create(); - } - - public event Action OnClosed; - public event Action OnError; - public event Action OnLog; - - public event Action OnOpened - { - add - { - connection.OnOpened += value; - } - remove - { - connection.OnOpened -= value; - } - } - - public event Action OnMessage - { - add - { - connection.OnMessage += value; - } - remove - { - connection.OnMessage -= value; - } - } - - public bool IsOpen - { - get - { - return connection.IsOpen; - } - } - - public void Close() - { - if (connection != null) - { - connection.Close(); - } - } - - public void Open(string url, string protocol = null) - { - if (connection != null) - { - connection.Open(url, protocol); - } - } - - public void Send(string message) - { - if (connection != null) - { - if (this.IsOpen) - { - connection.Send(message); - } - } - } - } -} diff --git a/RTM/Source/Internal/WebSocket/Portable/DefaultWebSocketClient.Portable.cs b/RTM/Source/Internal/WebSocket/Portable/DefaultWebSocketClient.Portable.cs deleted file mode 100644 index 85face2..0000000 --- a/RTM/Source/Internal/WebSocket/Portable/DefaultWebSocketClient.Portable.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Websockets; - -namespace LeanCloud.Realtime.Internal -{ - /// - /// LeanCloud Realtime SDK for .NET Portable 内置默认的 WebSocketClient - /// - public class DefaultWebSocketClient : IWebSocketClient - { - internal IWebSocketConnection connection; - /// - /// LeanCluod .NET Realtime SDK 内置默认的 WebSocketClient - /// 开发者可以在初始化的时候指定自定义的 WebSocketClient - /// - public DefaultWebSocketClient() - { - - } - - public event Action OnClosed; - public event Action OnOpened; - public event Action OnMessage; - - public bool IsOpen - { - get - { - return connection != null && connection.IsOpen; - } - } - - public void Close() - { - if (connection != null) - { - connection.OnOpened -= Connection_OnOpened; - connection.OnMessage -= Connection_OnMessage; - connection.OnClosed -= Connection_OnClosed; - connection.OnError -= Connection_OnError; - try { - connection.Close(); - } catch (Exception e) { - AVRealtime.PrintLog(string.Format("close websocket error: {0}", e.Message)); - } - } - } - - public void Disconnect() { - connection?.Close(); - } - - public void Open(string url, string protocol = null) - { - // 在每次打开时,重新创建 WebSocket 对象 - connection = WebSocketFactory.Create(); - connection.OnOpened += Connection_OnOpened; - connection.OnMessage += Connection_OnMessage; - connection.OnClosed += Connection_OnClosed; - connection.OnError += Connection_OnError; - if (!string.IsNullOrEmpty(protocol)) - { - url = string.Format("{0}?subprotocol={1}", url, protocol); - } - connection.Open(url, protocol); - } - - private void Connection_OnOpened() - { - OnOpened?.Invoke(); - } - - private void Connection_OnMessage(string obj) - { - AVRealtime.PrintLog("websocket<=" + obj); - OnMessage?.Invoke(obj); - } - - private void Connection_OnClosed() - { - AVRealtime.PrintLog("PCL websocket closed without parameters.."); - OnClosed?.Invoke(0, "", ""); - } - - private void Connection_OnError(string obj) - { - AVRealtime.PrintLog($"PCL websocket error: {obj}"); - connection?.Close(); - } - - public void Send(string message) - { - if (connection != null) - { - if (this.IsOpen) - { - connection.Send(message); - } - else - { - var log = "Connection is NOT open when send message"; - AVRealtime.PrintLog(log); - connection?.Close(); - } - } - else { - AVRealtime.PrintLog("Connection is NULL"); - } - } - - public Task Connect(string url, string protocol = null) { - var tcs = new TaskCompletionSource(); - Action onOpen = null; - Action onClose = null; - Action onError = null; - onOpen = () => { - AVRealtime.PrintLog("PCL websocket opened"); - connection.OnOpened -= onOpen; - connection.OnClosed -= onClose; - connection.OnError -= onError; - // 注册事件 - connection.OnMessage += Connection_OnMessage; - connection.OnClosed += Connection_OnClosed; - connection.OnError += Connection_OnError; - tcs.SetResult(true); - }; - onClose = () => { - connection.OnOpened -= onOpen; - connection.OnClosed -= onClose; - connection.OnError -= onError; - tcs.SetException(new Exception("连接关闭")); - }; - onError = (err) => { - AVRealtime.PrintLog(string.Format("连接错误:{0}", err)); - connection.OnOpened -= onOpen; - connection.OnClosed -= onClose; - connection.OnError -= onError; - try { - connection.Close(); - } catch (Exception e) { - AVRealtime.PrintLog(string.Format("关闭错误的 WebSocket 异常:{0}", e.Message)); - } finally { - tcs.SetException(new Exception(string.Format("连接错误:{0}", err))); - } - }; - - // 在每次打开时,重新创建 WebSocket 对象 - connection = WebSocketFactory.Create(); - connection.OnOpened += onOpen; - connection.OnClosed += onClose; - connection.OnError += onError; - // - if (!string.IsNullOrEmpty(protocol)) { - url = string.Format("{0}?subprotocol={1}", url, protocol); - } - connection.Open(url, protocol); - return tcs.Task; - } - } -} diff --git a/RTM/Source/Internal/WebSocket/Unity/DefaultWebSocketClient.Unity.cs b/RTM/Source/Internal/WebSocket/Unity/DefaultWebSocketClient.Unity.cs deleted file mode 100644 index c0769b4..0000000 --- a/RTM/Source/Internal/WebSocket/Unity/DefaultWebSocketClient.Unity.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using WebSocketSharp; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime.Internal -{ - - /// - /// LeanCluod Unity Realtime SDK 内置默认的 WebSocketClient - /// 开发者可以在初始化的时候指定自定义的 WebSocketClient - /// - public class DefaultWebSocketClient : IWebSocketClient - { - WebSocket ws; - public bool IsOpen - { - get - { - if (ws == null) { return false; } - return ws.IsAlive; - } - } - - public event Action OnClosed; - public event Action OnMessage - { - add - { - onMesssageCount++; - AVRealtime.PrintLog("DefaultWebSocketClient.OnMessage event add with " + onMesssageCount + " times"); - m_OnMessage += value; - - } - remove - { - onMesssageCount--; - AVRealtime.PrintLog("DefaultWebSocketClient.OnMessage event remove with " + onMesssageCount + " times"); - m_OnMessage -= value; - } - } - private Action m_OnMessage; - private int onMesssageCount = 0; - public event Action OnOpened; - - public void Close() - { - ws.CloseAsync(); - ws.OnOpen -= OnOpen; - ws.OnMessage -= OnWebSokectMessage; - ws.OnClose -= OnClose; - } - - public void Disconnect() { - ws.CloseAsync(); - } - - public void Open(string url, string protocol = null) - { - if (!string.IsNullOrEmpty(protocol)) - { - url = string.Format("{0}?subprotocol={1}", url, protocol); - } - ws = new WebSocket(url); - ws.OnOpen += OnOpen; - ws.OnMessage += OnWebSokectMessage; - ws.OnClose += OnClose; - ws.ConnectAsync(); - } - - private void OnWebSokectMessage(object sender, MessageEventArgs e) - { - AVRealtime.PrintLog("websocket<=" + e.Data); - m_OnMessage?.Invoke(e.Data); - } - - private void OnClose(object sender, CloseEventArgs e) { - AVRealtime.PrintLog(string.Format("Unity websocket closed with {0}, {1}", e.Code, e.Reason)); - OnClosed?.Invoke(e.Code, e.Reason, null); - } - - void OnWebSocketError(object sender, ErrorEventArgs e) { - AVRealtime.PrintLog($"PCL websocket error: {e.Message}"); - ws?.Close(); - } - - private void OnOpen(object sender, EventArgs e) { - OnOpened?.Invoke(); - } - - public void Send(string message) - { - ws.SendAsync(message, (b) => - { - - }); - } - - public Task Connect(string url, string protocol = null) { - var tcs = new TaskCompletionSource(); - EventHandler onOpen = null; - EventHandler onClose = null; - EventHandler onError = null; - onOpen = (sender, e) => { - AVRealtime.PrintLog("PCL websocket opened"); - ws.OnOpen -= onOpen; - ws.OnClose -= onClose; - ws.OnError -= onError; - // 注册事件 - ws.OnMessage += OnWebSokectMessage; - ws.OnClose += OnClose; - ws.OnError += OnWebSocketError; - tcs.SetResult(true); - }; - onClose = (sender, e) => { - ws.OnOpen -= onOpen; - ws.OnClose -= onClose; - ws.OnError -= onError; - tcs.SetException(new Exception("连接关闭")); - }; - onError = (sender, e) => { - AVRealtime.PrintLog(string.Format("连接错误:{0}", e.Message)); - ws.OnOpen -= onOpen; - ws.OnClose -= onClose; - ws.OnError -= onError; - try { - ws.Close(); - } catch (Exception ex) { - AVRealtime.PrintLog(string.Format("关闭错误的 WebSocket 异常:{0}", ex.Message)); - } finally { - tcs.SetException(new Exception(string.Format("连接错误:{0}", e.Message))); - } - }; - - // 在每次打开时,重新创建 WebSocket 对象 - if (!string.IsNullOrEmpty(protocol)) { - url = string.Format("{0}?subprotocol={1}", url, protocol); - } - ws = new WebSocket(url); - ws.OnOpen += onOpen; - ws.OnClose += onClose; - ws.OnError += onError; - ws.ConnectAsync(); - return tcs.Task; - } - } -} diff --git a/RTM/Source/Internal/WebSocket/Unity/websocket-sharp.dll b/RTM/Source/Internal/WebSocket/Unity/websocket-sharp.dll deleted file mode 100644 index 06740f7226730aa01542e3cd6fdedfcc619ad07f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 254464 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 j^5)itpDHFYgeS9@VN(D|>qpnrfh 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 - /// Audio message. - /// - [AVIMMessageClassName("_AVIMAudioMessage")] - [AVIMTypedMessageTypeInt(-3)] - public class AVIMAudioMessage : AVIMFileMessage - { - - } - - /// - /// Video message. - /// - [AVIMMessageClassName("_AVIMVideoMessage")] - [AVIMTypedMessageTypeInt(-4)] - public class AVIMVideoMessage: AVIMFileMessage - { - - } -} diff --git a/RTM/Source/Public/AVIMBinaryMessage.cs b/RTM/Source/Public/AVIMBinaryMessage.cs deleted file mode 100644 index 103d4b3..0000000 --- a/RTM/Source/Public/AVIMBinaryMessage.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using LeanCloud.Realtime.Internal; - -namespace LeanCloud.Realtime -{ - /// - /// 基于二进制数据的消息类型,可以直接发送 Byte 数组 - /// - [AVIMMessageClassName("_AVIMBinaryMessage")] - public class AVIMBinaryMessage : AVIMMessage - { - - /// - /// Initializes a new instance of the class. - /// - public AVIMBinaryMessage() - { - - } - /// - /// create new instance of AVIMBinnaryMessage - /// - /// - public AVIMBinaryMessage(byte[] data) - { - this.BinaryData = data; - } - - /// - /// Gets or sets the binary data. - /// - /// The binary data. - public byte[] BinaryData { get; set; } - - internal override MessageCommand BeforeSend(MessageCommand cmd) - { - var result = base.BeforeSend(cmd); - result = result.Binary(this.BinaryData); - return result; - } - } -} diff --git a/RTM/Source/Public/AVIMClient.cs b/RTM/Source/Public/AVIMClient.cs deleted file mode 100644 index 4715c07..0000000 --- a/RTM/Source/Public/AVIMClient.cs +++ /dev/null @@ -1,1195 +0,0 @@ -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; - if (logs != null) - { - foreach (var log in logs) - { - var logMap = log as IDictionary; - if (logMap != null) - { - var msgStr = logMap["data"].ToString(); - var messageObj = AVRealtime.FreeStyleMessageClassingController.Instantiate(msgStr, logMap); - messageObj.ConversationId = conversation.ConversationId; - rtn.Add(messageObj); - } - } - } - - conversation.OnMessageLoad(rtn); - - return rtn.AsEnumerable().OfType(); - }); - } - #endregion - - - //public Task MarkAsReadAsync(string conversationId = null, string messageId = null, AVIMConversation conversation = null, AVIMMessage message = null) - //{ - // var msgId = messageId != null ? messageId : message.Id; - // var convId = conversationId != null ? conversationId : conversation.ConversationId; - // if (convId == null && msgId == null) throw new ArgumentNullException("发送已读回执的时候,必须指定 conversation id 或者 message id"); - // lock (mutex) - // { - // var ackCommand = new AckCommand() - // .ReadAck().MessageId(msgId) - // .ConversationId(convId) - // .PeerId(this.ClientId); - - // return this.RunCommandAsync(ackCommand); - // } - //} - #region 查询对话中对方的接收状态,也就是已读回执 - private Task> FetchAllReceiptTimestampsAsync(string targetClientId = null, string conversationId = null, AVIMConversation conversation = null, bool queryAllMembers = false) - { - var convId = conversationId != null ? conversationId : conversation.ConversationId; - if (convId == null) throw new ArgumentNullException("conversationId 和 conversation 不可以同时为 null"); - - var cmd = new ConversationCommand().ConversationId(convId) - .TargetClientId(targetClientId) - .QueryAllMembers(queryAllMembers) - .Option("max-read") - .PeerId(clientId); - - return this.RunCommandAsync(cmd).OnSuccess(t => - { - var result = t.Result; - long maxReadTimestamp = -1; - long maxAckTimestamp = -1; - - if (result.Item2.ContainsKey("maxReadTimestamp")) - { - long.TryParse(result.Item2["maxReadTimestamp"].ToString(), out maxReadTimestamp); - } - if (result.Item2.ContainsKey("maxAckTimestamp")) - { - long.TryParse(result.Item2["maxAckTimestamp"].ToString(), out maxAckTimestamp); - } - return new Tuple(maxAckTimestamp, maxReadTimestamp); - - }); - } - #endregion - - #region 查询对方是否在线 - /// - /// 查询对方 client Id 是否在线 - /// - /// 单个 client Id - /// 多个 client Id 集合 - /// - public Task>> PingAsync(string targetClientId = null, IEnumerable targetClientIds = null) - { - List queryIds = null; - if (targetClientIds != null) queryIds = targetClientIds.ToList(); - if (queryIds == null && string.IsNullOrEmpty(targetClientId)) throw new ArgumentNullException("必须查询至少一个 client id 的状态,targetClientId 和 targetClientIds 不可以同时为空"); - queryIds.Add(targetClientId); - - var cmd = new SessionCommand() - .SessionPeerIds(queryIds) - .Option("query"); - - return this.RunCommandAsync(cmd).OnSuccess(t => - { - var result = t.Result; - List> rtn = new List>(); - var onlineSessionPeerIds = AVDecoder.Instance.DecodeList(result.Item2["onlineSessionPeerIds"]); - foreach (var peerId in targetClientIds) - { - rtn.Add(new Tuple(peerId, onlineSessionPeerIds.Contains(peerId))); - } - return rtn.AsEnumerable(); - }); - } - #endregion - #region 获取暂态对话在线人数 - /// - /// 获取暂态对话(聊天室)在线人数,依赖缓存,并不一定 100% 与真实数据一致。 - /// - /// - /// - public Task CountOnlineClientsAsync(string chatroomId) - { - var command = new AVCommand(relativeUri: "rtm/transient_group/onlines?gid=" + chatroomId, method: "GET", - sessionToken: null, - headers: null, - data: null); - - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).OnSuccess(t => - { - var result = t.Result.Item2; - if (result.ContainsKey("result")) - { - return int.Parse(result["result"].ToString()); - } - return -1; - }); - } - #endregion - #endregion - - #region mark as read - - /// - /// - /// - /// - /// - /// - /// - public Task ReadAsync(AVIMConversation conversation, IAVIMMessage message = null, DateTime? readAt = null) - { - var convRead = new ReadCommand.ConvRead() - { - ConvId = conversation.ConversationId, - }; - - if (message != null) - { - convRead.MessageId = message.Id; - convRead.Timestamp = message.ServerTimestamp; - } - - if (readAt != null && readAt.Value != DateTime.MinValue) - { - convRead.Timestamp = readAt.Value.ToUnixTimeStamp(); - } - - var readCmd = new ReadCommand().Conv(convRead).PeerId(this.ClientId); - - this.RunCommandAsync(readCmd); - - return Task.FromResult(true); - } - - /// - /// mark the conversation as read with conversation id. - /// - /// conversation id - /// - public Task ReadAsync(string conversationId) - { - var conv = AVIMConversation.CreateWithoutData(conversationId, this); - return this.ReadAsync(conv); - } - - /// - /// mark all conversations as read. - /// - /// - public Task ReadAllAsync() - { - var cids = ConversationUnreadListener.FindAllConvIds(); - var readCmd = new ReadCommand().ConvIds(cids).PeerId(this.ClientId); - return this.RunCommandAsync(readCmd); - } - #endregion - - #region recall & modify - - /// - /// Recalls the async. - /// - /// The async. - /// Message. - public Task RecallAsync(IAVIMMessage message) - { - var tcs = new TaskCompletionSource(); - var patchCmd = new PatchCommand().Recall(message); - RunCommandAsync(patchCmd) - .OnSuccess(t => { - var recalledMsg = new AVIMRecalledMessage(); - AVIMMessage.CopyMetaData(message, recalledMsg); - tcs.SetResult(recalledMsg); - }); - return tcs.Task; - } - - /// - /// Modifies the aysnc. - /// - /// The aysnc. - /// 要修改的消息对象 - /// 新的消息对象 - public Task UpdateAsync(IAVIMMessage oldMessage, IAVIMMessage newMessage) - { - var tcs = new TaskCompletionSource(); - var patchCmd = new PatchCommand().Modify(oldMessage, newMessage); - this.RunCommandAsync(patchCmd) - .OnSuccess(t => { - // 从旧消息对象中拷贝数据 - AVIMMessage.CopyMetaData(oldMessage, newMessage); - // 获取更新时间戳 - var response = t.Result.Item2; - if (response.TryGetValue("lastPatchTime", out object updatedAtObj) && - long.TryParse(updatedAtObj.ToString(), out long updatedAt)) { - newMessage.UpdatedAt = updatedAt; - } - tcs.SetResult(newMessage); - }); - return tcs.Task; - } - - internal EventHandler m_OnMessageRecalled; - /// - /// Occurs when on message recalled. - /// - public event EventHandler OnMessageRecalled - { - add - { - this.m_OnMessageRecalled += value; - } - remove - { - this.m_OnMessageRecalled -= value; - } - } - internal EventHandler m_OnMessageUpdated; - /// - /// Occurs when on message modified. - /// - public event EventHandler OnMessageUpdated - { - add - { - this.m_OnMessageUpdated += value; - } - remove - { - this.m_OnMessageUpdated -= value; - } - } - - #endregion - - #region log out - /// - /// 退出登录或者切换账号 - /// - /// - public Task CloseAsync() - { - var cmd = new SessionCommand().Option("close"); - return this.RunCommandAsync(cmd).ContinueWith(t => - { - m_OnSessionClosed(this, null); - }); - } - #endregion - - /// - /// Run command async. - /// - /// The command async. - /// Command. - public Task>> RunCommandAsync(AVIMCommand command) - { - command.PeerId(this.ClientId); - return this.LinkedRealtime.RunCommandAsync(command); - } - - /// - /// Run command. - /// - /// Command. - public void RunCommand(AVIMCommand command) - { - command.PeerId(this.ClientId); - this.LinkedRealtime.RunCommand(command); - } - } - - /// - /// AVIMClient extensions. - /// - public static class AVIMClientExtensions - { - /// - /// Create conversation async. - /// - /// The conversation async. - /// Client. - /// Members. - public static Task CreateConversationAsync(this AVIMClient client, IEnumerable members) - { - return client.CreateConversationAsync(members: members); - } - - public static Task CreateConversationAsync(this AVIMClient client, IEnumerable members, string conversationName) - { - return client.CreateConversationAsync(members: members, name: conversationName); - } - - /// - /// Get conversation. - /// - /// The conversation. - /// Client. - /// Conversation identifier. - public static AVIMConversation GetConversation(this AVIMClient client, string conversationId) - { - return AVIMConversation.CreateWithoutData(conversationId, client); - } - - /// - /// Join conversation async. - /// - /// The async. - /// Client. - /// Conversation identifier. - public static Task JoinAsync(this AVIMClient client, string conversationId) - { - var conversation = client.GetConversation(conversationId); - return client.JoinAsync(conversation); - } - - /// - /// Leave conversation async. - /// - /// The async. - /// Client. - /// Conversation identifier. - public static Task LeaveAsync(this AVIMClient client, string conversationId) - { - var conversation = client.GetConversation(conversationId); - return client.LeaveAsync(conversation); - } - - /// - /// Query messages. - /// - /// The message async. - /// Client. - /// Conversation. - /// Before message identifier. - /// After message identifier. - /// Before time stamp point. - /// After time stamp point. - /// Direction. - /// Limit. - public static Task> QueryMessageAsync(this AVIMClient client, - AVIMConversation conversation, - string beforeMessageId = null, - string afterMessageId = null, - DateTime? beforeTimeStampPoint = null, - DateTime? afterTimeStampPoint = null, - int direction = 1, - int limit = 20) - { - return client.QueryMessageAsync(conversation, - beforeMessageId, - afterMessageId, - beforeTimeStampPoint, - afterTimeStampPoint, - direction, - limit); - } - - /// - /// Get the chat room query. - /// - /// The chat room query. - /// Client. - public static AVIMConversationQuery GetChatRoomQuery(this AVIMClient client) - { - return client.GetQuery().WhereEqualTo("tr", true); - } - } -} diff --git a/RTM/Source/Public/AVIMConversation.cs b/RTM/Source/Public/AVIMConversation.cs deleted file mode 100644 index 1bdf1cf..0000000 --- a/RTM/Source/Public/AVIMConversation.cs +++ /dev/null @@ -1,1545 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using LeanCloud.Realtime.Internal; -using LeanCloud; -using LeanCloud.Storage.Internal; -using System.Collections; -using System.IO; - -namespace LeanCloud.Realtime -{ - /// - /// 对话 - /// - public class AVIMConversation : IEnumerable>, IAVObject - { - private DateTime? updatedAt; - - private DateTime? createdAt; - - private DateTime? lastMessageAt; - - internal DateTime? expiredAt; - - private string name; - - private AVObject convState; - - internal readonly Object mutex = new Object(); - //private readonly IDictionary estimatedData = new Dictionary(); - - internal AVIMClient _currentClient; - - IEnumerator> IEnumerable>.GetEnumerator() - { - lock (mutex) - { - return ((IEnumerable>)convState).GetEnumerator(); - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - lock (mutex) - { - return ((IEnumerable>)convState).GetEnumerator(); - } - } - - virtual public object this[string key] - { - get - { - return convState[key]; - } - set - { - convState[key] = value; - } - } - public ICollection Keys - { - get - { - lock (mutex) - { - return convState.Keys; - } - } - } - public T Get(string key) - { - return this.convState.Get(key); - } - public bool ContainsKey(string key) - { - return this.convState.ContainsKey(key); - } - - internal IDictionary EncodeAttributes() - { - var currentOperations = convState.StartSave(); - var jsonToSave = AVObject.ToJSONObjectForSaving(currentOperations); - return jsonToSave; - } - - internal void MergeFromPushServer(IDictionary json) - { - if (json.Keys.Contains("cdate")) - { - createdAt = DateTime.Parse(json["cdate"].ToString()); - updatedAt = DateTime.Parse(json["cdate"].ToString()); - json.Remove("cdate"); - } - if (json.Keys.Contains("lm")) - { - var ts = long.Parse(json["lm"].ToString()); - updatedAt = ts.ToDateTime(); - lastMessageAt = ts.ToDateTime(); - json.Remove("lm"); - } - if (json.Keys.Contains("c")) - { - Creator = json["c"].ToString(); - json.Remove("c"); - } - if (json.Keys.Contains("m")) - { - MemberIds = json["m"] as IList; - json.Remove("m"); - } - if (json.Keys.Contains("mu")) - { - MuteMemberIds = json["mu"] as IList; - json.Remove("mu"); - } - if (json.Keys.Contains("tr")) - { - IsTransient = bool.Parse(json["tr"].ToString()); - json.Remove("tr"); - } - if (json.Keys.Contains("sys")) - { - IsSystem = bool.Parse(json["sys"].ToString()); - json.Remove("sys"); - } - if (json.Keys.Contains("cid")) - { - ConversationId = json["cid"].ToString(); - json.Remove("cid"); - } - - if (json.Keys.Contains("name")) - { - Name = json["name"].ToString(); - json.Remove("name"); - } - } - - /// - /// 当前的AVIMClient,一个对话理论上只存在一个AVIMClient。 - /// - public AVIMClient CurrentClient - { - get - { - if (_currentClient == null) throw new NullReferenceException("当前对话没有关联有效的 AVIMClient。"); - return _currentClient; - } - //set - //{ - // _currentClient = value; - //} - } - /// - /// 对话的唯一ID - /// - public string ConversationId { get; internal set; } - - /// - /// 对话在全局的唯一的名字 - /// - public string Name - { - get - { - if (convState.ContainsKey("name")) - { - name = this.convState.Get("name"); - } - return name; - } - set - { - if (value == null) - this["name"] = ""; - else - { - this["name"] = value; - } - } - } - - /// - /// 对话中存在的 Client 的 ClientId 列表 - /// - public IEnumerable MemberIds { get; internal set; } - - /// - /// 对该对话静音的成员列表 - /// - /// 对该对话设置了静音的成员,将不会收到离线消息的推送。 - /// - /// - public IEnumerable MuteMemberIds { get; internal set; } - - /// - /// 对话的创建者 - /// - public string Creator { get; private set; } - - /// - /// 是否为聊天室 - /// - public bool IsTransient { get; internal set; } - - /// - /// 是否系统对话 - /// - public bool IsSystem { get; internal set; } - - /// - /// 是否是唯一对话 - /// - public bool IsUnique { get; internal set; } - - /// - /// 对话是否为虚拟对话 - /// - public bool IsTemporary { get; internal set; } - - /// - /// 对话创建的时间 - /// - public DateTime? CreatedAt - { - get - { - DateTime? nullable; - lock (this.mutex) - { - nullable = this.createdAt; - } - return nullable; - } - private set - { - lock (this.mutex) - { - this.createdAt = value; - } - } - } - - /// - /// 对话更新的时间 - /// - public DateTime? UpdatedAt - { - get - { - DateTime? nullable; - lock (this.mutex) - { - nullable = this.updatedAt; - } - return nullable; - } - private set - { - lock (this.mutex) - { - this.updatedAt = value; - } - } - } - - /// - /// 对话中最后一条消息的时间,可以用此判断对话的最后活跃时间 - /// - public DateTime? LastMessageAt - { - get - { - DateTime? nullable; - lock (this.mutex) - { - nullable = this.lastMessageAt; - } - return nullable; - } - private set - { - lock (this.mutex) - { - this.lastMessageAt = value; - } - } - } - - /// - /// 已知 id,在本地构建一个 AVIMConversation 对象 - /// - public AVIMConversation(string id) - : this(id, null) - { - - } - - /// - /// 已知 id 在本地构建一个对话 - /// - /// 对话 id - /// AVIMClient 实例,必须是登陆成功的 - public AVIMConversation(string id, AVIMClient client) : this(client) - { - this.ConversationId = id; - } - - internal AVIMConversation(AVIMClient client) - { - this._currentClient = client; - this.CurrentClient.OnMessageReceived += CurrentClient_OnMessageReceived; - } - - /// - /// AVIMConversation Build 驱动器 - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - internal AVIMConversation(AVIMConversation source = null, - string name = null, - string creator = null, - IEnumerable members = null, - IEnumerable muteMembers = null, - bool isTransient = false, - bool isSystem = false, - IEnumerable> attributes = null, - AVObject state = null, - bool isUnique = true, - bool isTemporary = false, - int ttl = 86400, - AVIMClient client = null) : - this(client) - { - convState = source != null ? source.convState : new AVObject("_Conversation"); - - - this.Name = source?.Name; - this.MemberIds = source?.MemberIds; - this.Creator = source?.Creator; - this.MuteMemberIds = source?.MuteMemberIds; - - if (!string.IsNullOrEmpty(name)) - { - this.Name = name; - } - if (!string.IsNullOrEmpty(creator)) - { - this.Creator = creator; - } - if (members != null) - { - this.MemberIds = members.ToList(); - } - if (muteMembers != null) - { - this.MuteMemberIds = muteMembers.ToList(); - } - - this.IsTransient = isTransient; - this.IsSystem = isSystem; - this.IsUnique = isUnique; - this.IsTemporary = isTemporary; - this.expiredAt = DateTime.Now + new TimeSpan(0, 0, ttl); - - if (state != null) - { - convState = state; - this.ConversationId = state.ObjectId; - this.CreatedAt = state.CreatedAt; - this.UpdatedAt = state.UpdatedAt; - this.MergeMagicFields(state.ToDictionary(x => x.Key, x => x.Value)); - } - - if (attributes != null) - { - this.MergeMagicFields(attributes.ToDictionary(x => x.Key, x => x.Value)); - } - } - - /// - /// 从本地构建一个对话 - /// - /// 对话的 objectId - /// - /// - public static AVIMConversation CreateWithoutData(string convId, AVIMClient client) - { - return new AVIMConversation(client) - { - ConversationId = convId, - }; - } - - /// - /// - /// - /// - /// - /// - public static AVIMConversation CreateWithData(IEnumerable> magicFields, AVIMClient client) - { - if (magicFields is AVObject) - { - return new AVIMConversation(state: (AVObject)magicFields, client: client); - } - return new AVIMConversation(attributes: magicFields, client: client); - } - - #region save to cloud - /// - /// 将修改保存到云端 - /// - /// - public Task SaveAsync() - { - var cmd = new ConversationCommand() - .Generate(this); - - var convCmd = cmd.Option("update") - .PeerId(this.CurrentClient.ClientId); - - return this.CurrentClient.RunCommandAsync(convCmd); - } - #endregion - - #region send message - /// - /// 向该对话发送消息。 - /// - /// 消息体 - /// 是否需要送达回执 - /// 是否是暂态消息,暂态消息不返回送达回执(ack),不保留离线消息,不触发离线推送 - /// 消息等级,默认是1,可选值还有 2 ,3 - /// 标记该消息是否为下线通知消息 - /// 如果消息的接收者已经下线了,这个字段的内容就会被离线推送到接收者 - /// 例如,一张图片消息的离线消息内容可以类似于:[您收到一条图片消息,点击查看] 这样的推送内容,参照微信的做法 - /// - /// - public Task SendMessageAsync(IAVIMMessage avMessage, - bool receipt = true, - bool transient = false, - int priority = 1, - bool will = false, - IDictionary pushData = null) - { - return this.SendMessageAsync(avMessage, new AVIMSendOptions() - { - Receipt = receipt, - Transient = transient, - Priority = priority, - Will = will, - PushData = pushData - }); - } - - /// - /// 发送消息 - /// - /// 消息体 - /// 消息的发送选项,包含了一些特殊的标记 - /// - public Task SendMessageAsync(IAVIMMessage avMessage, AVIMSendOptions options) - { - if (this.CurrentClient == null) throw new Exception("当前对话未指定有效 AVIMClient,无法发送消息。"); - return this.CurrentClient.SendMessageAsync(this, avMessage, options); - } - #endregion - - #region recall message - - #endregion - - #region message listener and event notify - - /// - /// Registers the listener. - /// - /// Listener. - public void RegisterListener(IAVIMListener listener) - { - this.CurrentClient.RegisterListener(listener, this.ConversationIdHook); - } - - internal bool ConversationIdHook(AVIMNotice notice) - { - if (!notice.RawData.ContainsKey("cid")) return false; - return notice.RawData["cid"].ToString() == this.ConversationId; - } - #endregion - - #region mute && unmute - /// - /// 当前用户针对对话做静音操作 - /// - /// - public Task MuteAsync() - { - return this.CurrentClient.MuteConversationAsync(this); - } - /// - /// 当前用户取消对话的静音,恢复该对话的离线消息推送 - /// - /// - public Task UnmuteAsync() - { - return this.CurrentClient.UnmuteConversationAsync(this); - } - #endregion - - #region 成员操作相关接口 - - /// - /// Joins the async. - /// - /// The async. - public Task JoinAsync() - { - return AddMembersAsync(CurrentClient.ClientId); - } - - - /// - /// Adds the members async. - /// - /// The members async. - /// Client identifier. - /// Client identifiers. - public Task AddMembersAsync(string clientId = null, IEnumerable clientIds = null) - { - return this.CurrentClient.InviteAsync(this, clientId, clientIds); - } - - /// - /// Removes the members async. - /// - /// The members async. - /// Client identifier. - /// Client identifiers. - public Task RemoveMembersAsync(string clientId = null, IEnumerable clientIds = null) - { - return this.CurrentClient.KickAsync(this, clientId, clientIds); - } - - /// - /// Quits the async. - /// - /// The async. - public Task QuitAsync() - { - return RemoveMembersAsync(CurrentClient.ClientId); - } - #endregion - - #region load message history - /// - /// 获取当前对话的历史消息 - /// 不支持聊天室(暂态对话) - /// - /// 从 beforeMessageId 开始向前查询(和 beforeTimeStampPoint 共同使用,为防止某毫秒时刻有重复消息) - /// 截止到某个 afterMessageId (不包含) - /// 从 beforeTimeStampPoint 开始向前查询 - /// 拉取截止到 afterTimeStampPoint 时间戳(不包含) - /// 查询方向,默认是 1,如果是 1 表示从新消息往旧消息方向, 0 则相反,其他值无效 - /// 获取的消息数量 - /// - public Task> QueryMessageAsync( - string beforeMessageId = null, - string afterMessageId = null, - DateTime? beforeTimeStampPoint = null, - DateTime? afterTimeStampPoint = null, - int direction = 1, - int limit = 20) - where T : IAVIMMessage - { - return this.CurrentClient.QueryMessageAsync(this, beforeMessageId, afterMessageId, beforeTimeStampPoint, afterTimeStampPoint, direction, limit) - .OnSuccess(t => - { - //OnMessageLoad(t.Result); - return t.Result; - }); - } - - /// - /// Gets the message query. - /// - /// The message query. - public AVIMMessageQuery GetMessageQuery() - { - return new AVIMMessageQuery(this); - } - - /// - /// Gets the message pager. - /// - /// The message pager. - public AVIMMessagePager GetMessagePager() - { - return new AVIMMessagePager(this); - } - - #endregion - - #region 字典与对象之间的转换 - internal virtual void MergeMagicFields(IDictionary data) - { - lock (this.mutex) - { - if (data.ContainsKey("objectId")) - { - this.ConversationId = (data["objectId"] as String); - data.Remove("objectId"); - } - if (data.ContainsKey("createdAt")) - { - this.CreatedAt = AVDecoder.ParseDate(data["createdAt"] as string); - data.Remove("createdAt"); - } - if (data.ContainsKey("updatedAt")) - { - this.updatedAt = AVDecoder.ParseDate(data["updatedAt"] as string); - data.Remove("updatedAt"); - } - if (data.ContainsKey("name")) - { - this.Name = (data["name"] as String); - data.Remove("name"); - } - if (data.ContainsKey("lm")) - { - this.LastMessageAt = AVDecoder.Instance.Decode(data["lm"]) as DateTime?; - data.Remove("lm"); - } - if (data.ContainsKey("m")) - { - this.MemberIds = AVDecoder.Instance.DecodeList(data["m"]); - data.Remove("m"); - } - if (data.ContainsKey("mu")) - { - this.MuteMemberIds = AVDecoder.Instance.DecodeList(data["mu"]); - data.Remove("mu"); - } - if (data.ContainsKey("c")) - { - this.Creator = data["c"].ToString(); - data.Remove("c"); - } - if (data.ContainsKey("unique")) - { - if (data["unique"] is bool) - { - this.IsUnique = (bool)data["unique"]; - } - data.Remove("unique"); - } - foreach (var kv in data) - { - this[kv.Key] = kv.Value; - } - } - } - #endregion - - #region SyncStateAsync & unread & mark as read - /// - /// sync state from server.suhc unread state .etc; - /// - /// - public Task SyncStateAsync() - { - lock (mutex) - { - var rtn = new AggregatedState(); - rtn.Unread = GetUnreadStateFromLocal(); - return Task.FromResult(rtn); - } - } - - private UnreadState _unread; - private UnreadState _lastUnreadWhenOpenSession; - public UnreadState Unread - { - get - { - _lastUnreadWhenOpenSession = GetUnreadStateFromLocal(); - - // v.2 协议,只给出上次离线之后的未读消息,本次在线的收到的消息均视为已读 - if (this.CurrentClient.LinkedRealtime.CurrentConfiguration.OfflineMessageStrategy == AVRealtime.OfflineMessageStrategy.UnreadNotice) - { - _unread = _lastUnreadWhenOpenSession; - } - else if (this.CurrentClient.LinkedRealtime.CurrentConfiguration.OfflineMessageStrategy == AVRealtime.OfflineMessageStrategy.UnreadAck) - { - if (_lastUnreadWhenOpenSession == null) _unread = new UnreadState().MergeReceived(this.Received); - else _unread = _lastUnreadWhenOpenSession.MergeReceived(this.Received); - } - - return _unread; - } - - internal set - { - _unread = value; - } - } - - private readonly object receivedMutex = new object(); - public ReceivedState Received - { - get; set; - } - public ReadState Read - { - get; set; - } - - UnreadState GetUnreadStateFromLocal() - { - lock (mutex) - { - var notice = ConversationUnreadListener.Get(this.ConversationId); - if (notice != null) - { - var unreadState = new UnreadState() - { - LastMessage = notice.LastUnreadMessage, - SyncdAt = ConversationUnreadListener.NotifTime, - Count = notice.UnreadCount - }; - return unreadState; - } - - return null; - } - } - - internal void OnMessageLoad(IEnumerable messages) - { - var lastestInCollection = messages.OrderByDescending(m => m.ServerTimestamp).FirstOrDefault(); - if (lastestInCollection != null) - { - if (CurrentClient.CurrentConfiguration.AutoRead) - { - this.ReadAsync(lastestInCollection); - } - } - } - - /// - /// mark this conversation as read - /// - /// - public Task ReadAsync(IAVIMMessage message = null, DateTime? readAt = null) - { - // 标记已读必须至少是从上一次离线产生的最后一条消息开始,否则无法计算 Count - if (_lastUnreadWhenOpenSession != null) - { - if (_lastUnreadWhenOpenSession.LastMessage != null) - { - message = _lastUnreadWhenOpenSession.LastMessage; - } - } - return this.CurrentClient.ReadAsync(this, message, readAt).OnSuccess(t => - { - Received = null; - _lastUnreadWhenOpenSession = null; - Read = new ReadState() - { - ReadAt = readAt != null ? readAt.Value.ToUnixTimeStamp() : 0, - LastMessage = message, - SyncdAt = DateTime.Now.ToUnixTimeStamp() - }; - - }); - } - - /// - /// aggregated state for the conversation - /// - public class AggregatedState - { - /// - /// Unread state - /// - public UnreadState Unread { get; internal set; } - } - - /// - /// UnreadState recoder for the conversation - /// - public class UnreadState - { - /// - /// unread count - /// - public int Count { get; internal set; } - /// - /// last unread message - /// - public IAVIMMessage LastMessage { get; internal set; } - - /// - /// last sync timestamp - /// - public long SyncdAt { get; internal set; } - - internal UnreadState MergeReceived(ReceivedState receivedState) - { - if (receivedState == null) return this; - var count = Count + receivedState.Count; - var lastMessage = this.LastMessage; - if (receivedState.LastMessage != null) - { - lastMessage = receivedState.LastMessage; - } - var syncdAt = this.SyncdAt > receivedState.SyncdAt ? this.SyncdAt : receivedState.SyncdAt; - return new UnreadState() - { - Count = count, - LastMessage = lastMessage, - SyncdAt = syncdAt - }; - } - } - - public class ReceivedState - { - public int Count { get; internal set; } - /// - /// last received message - /// - public IAVIMMessage LastMessage { get; internal set; } - - /// - /// last sync timestamp - /// - public long SyncdAt { get; internal set; } - } - - public class ReadState - { - public long ReadAt { get; set; } - public IAVIMMessage LastMessage { get; internal set; } - public long SyncdAt { get; internal set; } - } - - #endregion - - #region on client message received to update unread - private void CurrentClient_OnMessageReceived(object sender, AVIMMessageEventArgs e) - { - if (this.CurrentClient.CurrentConfiguration.AutoRead) - { - this.ReadAsync(e.Message); - return; - } - lock (receivedMutex) - { - if (this.Received == null) this.Received = new ReceivedState(); - this.Received.Count++; - this.Received.LastMessage = e.Message; - this.Received.SyncdAt = DateTime.Now.ToUnixTimeStamp(); - } - } - #endregion - } - - /// - /// AVIMConversation extensions. - /// - public static class AVIMConversationExtensions - { - - /// - /// Send message async. - /// - /// The async. - /// Conversation. - /// Message. - /// Options. - public static Task SendAsync(this AVIMConversation conversation, IAVIMMessage message, AVIMSendOptions options) - { - return conversation.SendMessageAsync(message, options); - } - - /// - /// Send message async. - /// - /// The async. - /// Conversation. - /// Message. - /// Options. - /// The 1st type parameter. - public static Task SendAsync(this AVIMConversation conversation, T message, AVIMSendOptions options) - where T : IAVIMMessage - { - return conversation.SendMessageAsync(message, options).OnSuccess(t => - { - return (T)t.Result; - }); - } - - /// - /// Send message async. - /// - /// The async. - /// Conversation. - /// Message. - /// The 1st type parameter. - public static Task SendAsync(this AVIMConversation conversation, T message) - where T : IAVIMMessage - { - return conversation.SendMessageAsync(message).OnSuccess(t => - { - return (T)t.Result; - }); - } - - /// - /// Send text message. - /// - /// The text async. - /// Conversation. - /// Text. - public static Task SendTextAsync(this AVIMConversation conversation, string text) - { - return conversation.SendAsync(new AVIMTextMessage(text)); - } - - #region Image messages - - /// - /// Send image message async. - /// - /// The image async. - /// Conversation. - /// URL. - /// Name. - /// Text title. - /// Custom attributes. - public static Task SendImageAsync(this AVIMConversation conversation, string url, string name = null, string textTitle = null, IDictionary customAttributes = null) - { - return conversation.SendFileMessageAsync(url, name, textTitle, customAttributes); - } - - /// - /// Send image message async. - /// - /// The image async. - /// Conversation. - /// File name. - /// Data. - /// MIME type. - /// Text title. - /// Meta data. - /// Custom attributes. - public static Task SendImageAsync(this AVIMConversation conversation, string fileName, Stream data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) - { - return conversation.SendFileMessageAsync(fileName, data, mimeType, textTitle, metaData, customAttributes); - } - - /// - /// Send image message async. - /// - /// The image async. - /// Conversation. - /// File name. - /// Data. - /// MIME type. - /// Text title. - /// Meta data. - /// Custom attributes. - public static Task SendImageAsync(this AVIMConversation conversation, string fileName, byte[] data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) - { - return conversation.SendFileMessageAsync(fileName, data, mimeType, textTitle, metaData, customAttributes); - } - #endregion - - #region audio message - - /// - /// Send audio message async. - /// - /// The audio async. - /// Conversation. - /// URL. - /// Name. - /// Text title. - /// Custom attributes. - public static Task SendAudioAsync(this AVIMConversation conversation, string url, string name = null, string textTitle = null, IDictionary customAttributes = null) - { - return conversation.SendFileMessageAsync(name, url, textTitle, customAttributes); - } - - /// - /// Send audio message async. - /// - /// The audio async. - /// Conversation. - /// File name. - /// Data. - /// MIME type. - /// Text title. - /// Meta data. - /// Custom attributes. - public static Task SendAudioAsync(this AVIMConversation conversation, string fileName, Stream data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) - { - return conversation.SendFileMessageAsync(fileName, data, mimeType, textTitle, metaData, customAttributes); - } - - /// - /// Send audio message async. - /// - /// The audio async. - /// Conversation. - /// File name. - /// Data. - /// MIME type. - /// Text title. - /// Meta data. - /// Custom attributes. - public static Task SendAudioAsync(this AVIMConversation conversation, string fileName, byte[] data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) - { - return conversation.SendFileMessageAsync(fileName, data, mimeType, textTitle, metaData, customAttributes); - } - #endregion - - #region video message - /// - /// Send video message async. - /// - /// The video async. - /// Conversation. - /// URL. - /// Name. - /// Text title. - /// Custom attributes. - public static Task SendVideoAsync(this AVIMConversation conversation, string url, string name = null, string textTitle = null, IDictionary customAttributes = null) - { - return conversation.SendFileMessageAsync(name, url, textTitle, customAttributes); - } - /// - /// Send video message async. - /// - /// The video async. - /// Conversation. - /// File name. - /// Data. - /// MIME type. - /// Text title. - /// Meta data. - /// Custom attributes. - public static Task SendVideoAsync(this AVIMConversation conversation, string fileName, Stream data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) - { - return conversation.SendFileMessageAsync(fileName, data, mimeType, textTitle, metaData, customAttributes); - } - - /// - /// Send video message async. - /// - /// The video async. - /// Conversation. - /// File name. - /// Data. - /// MIME type. - /// Text title. - /// Meta data. - /// Custom attributes. - public static Task SendVideoAsync(this AVIMConversation conversation, string fileName, byte[] data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) - { - return conversation.SendFileMessageAsync(fileName, data, mimeType, textTitle, metaData, customAttributes); - } - #endregion - - /// - /// Send file message async. - /// - /// The file message async. - /// Conversation. - /// URL. - /// Name. - /// Text title. - /// Custom attributes. - /// The 1st type parameter. - public static Task SendFileMessageAsync(this AVIMConversation conversation, string url, string name = null, string textTitle = null, IDictionary customAttributes = null) - where T : AVIMFileMessage, new() - { - var fileMessage = AVIMFileMessage.FromUrl(name, url, textTitle, customAttributes); - return conversation.SendAsync(fileMessage); - } - - /// - /// Send file message async. - /// - /// The file message async. - /// Conversation. - /// File name. - /// Data. - /// MIME type. - /// Text title. - /// Meta data. - /// Custom attributes. - /// The 1st type parameter. - public static Task SendFileMessageAsync(this AVIMConversation conversation, string fileName, Stream data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) - where T : AVIMFileMessage, new() - { - var fileMessage = AVIMFileMessage.FromStream(fileName, data, mimeType, textTitle, metaData, customAttributes); - - return fileMessage.File.SaveAsync().OnSuccess(fileUploaded => - { - return conversation.SendAsync(fileMessage); - }).Unwrap(); - } - - /// - /// Send file message async. - /// - /// The file message async. - /// Conversation. - /// File name. - /// Data. - /// MIME type. - /// Text title. - /// Meta data. - /// Custom attributes. - /// The 1st type parameter. - public static Task SendFileMessageAsync(this AVIMConversation conversation, string fileName, byte[] data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) - where T : AVIMFileMessage, new() - { - var fileMessage = AVIMFileMessage.FromStream(fileName, new MemoryStream(data), mimeType, textTitle, metaData, customAttributes); - - return conversation.SendFileMessageAsync(fileName, new MemoryStream(data), mimeType, textTitle, metaData, customAttributes); - } - - /// - /// Send location async. - /// - /// The location async. - /// Conversation. - /// Point. - public static Task SendLocationAsync(this AVIMConversation conversation, AVGeoPoint point) - { - var locationMessage = new AVIMLocationMessage(point); - return conversation.SendAsync(locationMessage); - } - - /// - /// Query the message async. - /// - /// The message async. - /// Conversation. - /// Before message identifier. - /// After message identifier. - /// Before time stamp. - /// After time stamp. - /// Direction. - /// Limit. - public static Task> QueryMessageAsync( - this AVIMConversation conversation, - string beforeMessageId = null, - string afterMessageId = null, - long? beforeTimeStamp = null, - long? afterTimeStamp = null, - int direction = 1, - int limit = 20) - { - - return conversation.QueryMessageAsync(beforeMessageId, - afterMessageId, - beforeTimeStamp, - afterTimeStamp, - direction, - limit); - } - - /// - /// Query message with speciafic subclassing type. - /// - /// The message async. - /// Conversation. - /// Before message identifier. - /// After message identifier. - /// Before time stamp. - /// After time stamp. - /// Direction. - /// Limit. - /// The 1st type parameter. - public static Task> QueryMessageAsync( - this AVIMConversation conversation, - string beforeMessageId = null, - string afterMessageId = null, - long? beforeTimeStamp = null, - long? afterTimeStamp = null, - int direction = 1, - int limit = 20) - where T : IAVIMMessage - { - - return conversation.QueryMessageAsync(beforeMessageId, - afterMessageId, - beforeTimeStampPoint: beforeTimeStamp.HasValue ? beforeTimeStamp.Value.ToDateTime() : DateTime.MinValue, - afterTimeStampPoint: afterTimeStamp.HasValue ? afterTimeStamp.Value.ToDateTime() : DateTime.MinValue, - direction: direction, - limit: limit); - } - - /// - /// Query message record before the given message async. - /// - /// The message before async. - /// Conversation. - /// Message. - /// Limit. - public static Task> QueryMessageBeforeAsync( - this AVIMConversation conversation, - IAVIMMessage message, - int limit = 20) - { - return conversation.QueryMessageAsync(beforeMessageId: message.Id, beforeTimeStamp: message.ServerTimestamp, limit: limit); - } - - /// - /// Query message record after the given message async. - /// - /// The message after async. - /// Conversation. - /// Message. - /// Limit. - public static Task> QueryMessageAfterAsync( - this AVIMConversation conversation, - IAVIMMessage message, - int limit = 20) - { - return conversation.QueryMessageAsync(afterMessageId: message.Id, afterTimeStamp: message.ServerTimestamp, limit: limit); - } - - /// - /// Query messages after conversation created. - /// - /// The message after async. - /// Conversation. - /// Limit. - public static Task> QueryMessageFromOldToNewAsync( - this AVIMConversation conversation, - int limit = 20) - { - return conversation.QueryMessageAsync(afterTimeStamp: conversation.CreatedAt.Value.ToUnixTimeStamp(), limit: limit); - } - - - /// - /// Query messages in interval async. - /// - /// The message interval async. - /// Conversation. - /// Start. - /// End. - /// Limit. - public static Task> QueryMessageInIntervalAsync( - this AVIMConversation conversation, - IAVIMMessage start, - IAVIMMessage end, - int limit = 20) - { - return conversation.QueryMessageAsync( - beforeMessageId: end.Id, - beforeTimeStamp: end.ServerTimestamp, - afterMessageId: start.Id, - afterTimeStamp: start.ServerTimestamp, - limit: limit); - } - - /// - /// Recall message async. - /// - /// The async. - /// Conversation. - /// Message. - public static Task RecallAsync(this AVIMConversation conversation, IAVIMMessage message) - { - return conversation.CurrentClient.RecallAsync(message); - } - - /// - /// Modifiy message async. - /// - /// The async. - /// Conversation. - /// 要修改的消息对象 - /// 新的消息对象 - public static Task UpdateAsync(this AVIMConversation conversation, IAVIMMessage oldMessage, IAVIMMessage newMessage) - { - return conversation.CurrentClient.UpdateAsync(oldMessage, newMessage); - } - } - - /// - /// AVIMConversatio builder. - /// - public interface IAVIMConversatioBuilder - { - /// - /// Build this instance. - /// - /// The build. - AVIMConversation Build(); - } - - /// - /// AVIMConversation builder. - /// - public class AVIMConversationBuilder : IAVIMConversatioBuilder - { - /// - /// Gets or sets the client. - /// - /// The client. - public AVIMClient Client { get; internal set; } - private bool isUnique; - private Dictionary properties; - - private string name; - private bool isTransient; - private bool isSystem; - private List members; - - - internal static AVIMConversationBuilder CreateDefaultBuilder() - { - return new AVIMConversationBuilder(); - } - - /// - /// Build this instance. - /// - /// The build. - public AVIMConversation Build() - { - var result = new AVIMConversation( - members: members, - name: name, - isUnique: isUnique, - isSystem: isSystem, - isTransient: isTransient, - client: Client); - - if (properties != null) - { - foreach (var key in properties.Keys) - { - result[key] = properties[key]; - } - } - - return result; - } - - /// - /// Sets the unique. - /// - /// The unique. - /// If set to true toggle. - public AVIMConversationBuilder SetUnique(bool toggle = true) - { - this.isUnique = toggle; - return this; - } - - /// - /// Sets the system. - /// - /// The system. - /// If set to true toggle. - public AVIMConversationBuilder SetSystem(bool toggle = true) - { - this.isSystem = toggle; - return this; - } - - /// - /// Sets the transient. - /// - /// The transient. - /// If set to true toggle. - public AVIMConversationBuilder SetTransient(bool toggle = true) - { - this.isTransient = toggle; - return this; - } - - /// - /// Sets the name. - /// - /// The name. - /// Name. - public AVIMConversationBuilder SetName(string name) - { - this.name = name; - return this; - } - - /// - /// Sets the members. - /// - /// The members. - /// Member client identifiers. - public AVIMConversationBuilder SetMembers(IEnumerable memberClientIds) - { - this.members = memberClientIds.ToList(); - return this; - } - - /// - /// Adds the member. - /// - /// The member. - /// Member client identifier. - public AVIMConversationBuilder AddMember(string memberClientId) - { - return AddMembers(new string[] { memberClientId }); - } - - /// - /// Adds the members. - /// - /// The members. - /// Member client identifiers. - public AVIMConversationBuilder AddMembers(IEnumerable memberClientIds) - { - if (this.members == null) this.members = new List(); - this.members.AddRange(memberClientIds); - return this; - } - - /// - /// Sets the property. - /// - /// The property. - /// Key. - /// Value. - public AVIMConversationBuilder SetProperty(string key, object value) - { - if (this.properties == null) this.properties = new Dictionary(); - this.properties[key] = value; - return this; - } - } - - /// - /// AVIMM essage emitter builder. - /// - public class AVIMMessageEmitterBuilder - { - private AVIMConversation _conversation; - /// - /// Sets the conversation. - /// - /// The conversation. - /// Conversation. - public AVIMMessageEmitterBuilder SetConversation(AVIMConversation conversation) - { - _conversation = conversation; - return this; - } - /// - /// Gets the conversation. - /// - /// The conversation. - public AVIMConversation Conversation - { - get - { - return _conversation; - } - } - - private AVIMSendOptions _sendOptions; - /// - /// Gets the send options. - /// - /// The send options. - public AVIMSendOptions SendOptions - { - get - { - return _sendOptions; - } - } - /// - /// Sets the send options. - /// - /// The send options. - /// Send options. - public AVIMMessageEmitterBuilder SetSendOptions(AVIMSendOptions sendOptions) - { - _sendOptions = sendOptions; - return this; - } - - private IAVIMMessage _message; - /// - /// Gets the message. - /// - /// The message. - public IAVIMMessage Message - { - get - { - return _message; - } - } - /// - /// Sets the message. - /// - /// The message. - /// Message. - public AVIMMessageEmitterBuilder SetMessage(IAVIMMessage message) - { - _message = message; - return this; - } - - /// - /// Send async. - /// - /// The async. - public Task SendAsync() - { - return this.Conversation.SendAsync(this.Message, this.SendOptions); - } - } -} diff --git a/RTM/Source/Public/AVIMConversationQuery.cs b/RTM/Source/Public/AVIMConversationQuery.cs deleted file mode 100644 index cbb0f5f..0000000 --- a/RTM/Source/Public/AVIMConversationQuery.cs +++ /dev/null @@ -1,181 +0,0 @@ -using LeanCloud.Realtime.Internal; -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime -{ - /// - /// 对话查询类 - /// - public class AVIMConversationQuery : AVQueryPair, IAVQuery - { - internal AVIMClient CurrentClient { get; set; } - internal AVIMConversationQuery(AVIMClient _currentClient) - : base() - { - CurrentClient = _currentClient; - } - - bool compact; - bool withLastMessageRefreshed; - - private AVIMConversationQuery(AVIMConversationQuery source, - IDictionary where = null, - IEnumerable replacementOrderBy = null, - IEnumerable thenBy = null, - int? skip = null, - int? limit = null, - IEnumerable includes = null, - IEnumerable selectedKeys = null, - String redirectClassNameForKey = null) - : base(source, where, replacementOrderBy, thenBy, skip, limit, includes, selectedKeys, redirectClassNameForKey) - { - - } - - /// - /// Creates the instance. - /// - /// The instance. - /// Where. - /// Replacement order by. - /// Then by. - /// Skip. - /// Limit. - /// Includes. - /// Selected keys. - /// Redirect class name for key. - public override AVIMConversationQuery CreateInstance( - IDictionary where = null, - IEnumerable replacementOrderBy = null, - IEnumerable thenBy = null, - int? skip = null, - int? limit = null, - IEnumerable includes = null, - IEnumerable selectedKeys = null, - String redirectClassNameForKey = null) - { - var rtn = new AVIMConversationQuery(this, where, replacementOrderBy, thenBy, skip, limit, includes); - rtn.CurrentClient = this.CurrentClient; - rtn.compact = this.compact; - rtn.withLastMessageRefreshed = this.withLastMessageRefreshed; - return rtn; - } - - /// - /// Withs the last message refreshed. - /// - /// The last message refreshed. - /// If set to true enabled. - public AVIMConversationQuery WithLastMessageRefreshed(bool enabled) - { - this.withLastMessageRefreshed = enabled; - return this; - } - - public AVIMConversationQuery Compact(bool enabled) - { - this.compact = enabled; - return this; - } - - - internal ConversationCommand GenerateQueryCommand() - { - var cmd = new ConversationCommand(); - - var queryParameters = this.BuildParameters(false); - if (queryParameters != null) - { - if (queryParameters.Keys.Contains("where")) - cmd.Where(queryParameters["where"]); - - if (queryParameters.Keys.Contains("skip")) - cmd.Skip(int.Parse(queryParameters["skip"].ToString())); - - if (queryParameters.Keys.Contains("limit")) - cmd.Limit(int.Parse(queryParameters["limit"].ToString())); - - if (queryParameters.Keys.Contains("sort")) - cmd.Sort(queryParameters["order"].ToString()); - } - - return cmd; - } - - public override Task CountAsync(CancellationToken cancellationToken) - { - var convCmd = this.GenerateQueryCommand(); - convCmd.Count(); - convCmd.Limit(0); - var cmd = convCmd.Option("query"); - return CurrentClient.RunCommandAsync(convCmd).OnSuccess(t => - { - var result = t.Result.Item2; - - if (result.ContainsKey("count")) - { - return int.Parse(result["count"].ToString()); - } - return 0; - }); - } - - - /// - /// 查找符合条件的对话 - /// - /// - /// - public override Task> FindAsync(CancellationToken cancellationToken) - { - var convCmd = this.GenerateQueryCommand().Option("query"); - return CurrentClient.RunCommandAsync(convCmd).OnSuccess(t => - { - var result = t.Result.Item2; - - IList rtn = new List(); - var conList = result["results"] as IList; - if (conList != null) - { - foreach (var c in conList) - { - var cData = c as IDictionary; - if (cData != null) - { - var con = AVIMConversation.CreateWithData(cData, CurrentClient); - rtn.Add(con); - } - } - } - return rtn.AsEnumerable(); - }); - } - - public override Task FirstAsync(CancellationToken cancellationToken) - { - return this.FirstOrDefaultAsync(); - } - - public override Task FirstOrDefaultAsync(CancellationToken cancellationToken) - { - var firstQuery = this.Limit(1); - return firstQuery.FindAsync().OnSuccess(t => - { - return t.Result.FirstOrDefault(); - }); - } - - public override Task GetAsync(string objectId, CancellationToken cancellationToken) - { - var idQuery = this.WhereEqualTo("objectId", objectId); - return idQuery.FirstAsync(); - } - } - -} diff --git a/RTM/Source/Public/AVIMEnumerator.cs b/RTM/Source/Public/AVIMEnumerator.cs deleted file mode 100644 index d9368bc..0000000 --- a/RTM/Source/Public/AVIMEnumerator.cs +++ /dev/null @@ -1,248 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using LeanCloud.Realtime.Internal; - -namespace LeanCloud.Realtime -{ - - /// - /// AVIMM essage pager. - /// - public class AVIMMessagePager - { - /// - /// Gets the query. - /// - /// The query. - public AVIMMessageQuery Query { get; private set; } - - /// - /// Gets the size of the page. - /// - /// The size of the page. - public int PageSize - { - get - { - return Query.Limit; - } - - private set - { - Query.Limit = value; - } - } - - /// - /// Gets the current message identifier flag. - /// - /// The current message identifier flag. - public string CurrentMessageIdFlag - { - get - { - return Query.StartMessageId; - } - private set - { - Query.StartMessageId = value; - } - } - - /// - /// Gets the current date time flag. - /// - /// The current date time flag. - public DateTime CurrentDateTimeFlag - { - get - { - return Query.From; - } - private set - { - Query.From = value; - } - } - - internal AVIMMessagePager() - { - - } - - /// - /// Initializes a new instance of the class. - /// - /// Conversation. - public AVIMMessagePager(AVIMConversation conversation) - : this() - { - Query = conversation.GetMessageQuery(); - PageSize = 20; - CurrentDateTimeFlag = DateTime.Now; - } - - /// - /// Sets the size of the page. - /// - /// The page size. - /// Page size. - public AVIMMessagePager SetPageSize(int pageSize) - { - PageSize = pageSize; - return this; - } - - /// - /// Previouses the async. - /// - /// The async. - public Task> PreviousAsync() - { - return Query.FindAsync().OnSuccess(t => - { - var headerMessage = t.Result.FirstOrDefault(); - if (headerMessage != null) - { - CurrentMessageIdFlag = headerMessage.Id; - CurrentDateTimeFlag = headerMessage.ServerTimestamp.ToDateTime(); - } - return t.Result; - }); - } - - /// - /// from previous to lastest. - /// - /// - public Task> NextAsync() - { - return Query.ReverseFindAsync().OnSuccess(t => - { - var tailMessage = t.Result.LastOrDefault(); - if (tailMessage != null) - { - CurrentMessageIdFlag = tailMessage.Id; - CurrentDateTimeFlag = tailMessage.ServerTimestamp.ToDateTime(); - } - return t.Result; - }); - } - } - - /// - /// history message interator. - /// - public class AVIMMessageQuery - { - /// - /// Gets or sets the convsersation. - /// - /// The convsersation. - public AVIMConversation Convsersation { get; set; } - /// - /// Gets or sets the limit. - /// - /// The limit. - public int Limit { get; set; } - /// - /// Gets or sets from. - /// - /// From. - public DateTime From { get; set; } - /// - /// Gets or sets to. - /// - /// To. - public DateTime To { get; set; } - /// - /// Gets or sets the end message identifier. - /// - /// The end message identifier. - public string EndMessageId { get; set; } - /// - /// Gets or sets the start message identifier. - /// - /// The start message identifier. - public string StartMessageId { get; set; } - - - internal AVIMMessageQuery() - { - Limit = 20; - From = DateTime.Now; - } - - /// - /// Initializes a new instance of the class. - /// - /// Conversation. - public AVIMMessageQuery(AVIMConversation conversation) - : this() - { - Convsersation = conversation; - } - - /// - /// Sets the limit. - /// - /// The limit. - /// Limit. - public AVIMMessageQuery SetLimit(int limit) - { - Limit = limit; - return this; - } - - /// - /// from lastest to previous. - /// - /// - public Task> FindAsync() - { - return FindAsync(); - } - - /// - /// from lastest to previous. - /// - /// - public Task> ReverseFindAsync() - { - return ReverseFindAsync(); - } - - /// - /// Finds the async. - /// - /// The async. - /// set direction to reverse,it means query direct is from old to new. - /// The 1st type parameter. - public Task> FindAsync(bool reverse = false) - where T : IAVIMMessage - { - return Convsersation.QueryMessageAsync( - beforeTimeStampPoint: From, - afterTimeStampPoint: To, - limit: Limit, - afterMessageId: EndMessageId, - beforeMessageId: StartMessageId, - direction: reverse ? 0 : 1); - } - - /// - /// from previous to lastest. - /// - /// - public Task> ReverseFindAsync() - where T : IAVIMMessage - { - return FindAsync(true); - } - } -} diff --git a/RTM/Source/Public/AVIMEventArgs.cs b/RTM/Source/Public/AVIMEventArgs.cs deleted file mode 100644 index 2b91b23..0000000 --- a/RTM/Source/Public/AVIMEventArgs.cs +++ /dev/null @@ -1,251 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace LeanCloud.Realtime -{ - /// - /// - /// - public class AVIMEventArgs : EventArgs - { - public AVIMEventArgs() - { - - } - public AVIMException.ErrorCode ErrorCode { get; internal set; } - /// - /// LeanCloud 服务端发往客户端消息通知 - /// - public string Message { get; set; } - } - - public class AVIMDisconnectEventArgs : EventArgs - { - public int Code { get; private set; } - - public string Reason { get; private set; } - - public string Detail { get; private set; } - - public AVIMDisconnectEventArgs() - { - - } - public AVIMDisconnectEventArgs(int _code, string _reason, string _detail) - { - this.Code = _code; - this.Reason = _reason; - this.Detail = _detail; - } - } - - /// - /// 开始重连之后触发正在重连的事件通知,提供给监听者的事件参数 - /// - public class AVIMReconnectingEventArgs : EventArgs - { - /// - /// 是否由 SDK 内部机制启动的自动重连 - /// - public bool IsAuto { get; set; } - - /// - /// 重连的 client Id - /// - public string ClientId { get; set; } - - /// - /// 重连时使用的 SessionToken - /// - public string SessionToken { get; set; } - } - - /// - /// 重连成功之后的事件回调 - /// - public class AVIMReconnectedEventArgs : EventArgs - { - /// - /// 是否由 SDK 内部机制启动的自动重连 - /// - public bool IsAuto { get; set; } - - /// - /// 重连的 client Id - /// - public string ClientId { get; set; } - - /// - /// 重连时使用的 SessionToken - /// - public string SessionToken { get; set; } - } - - /// - /// 重连失败之后的事件回调参数 - /// - public class AVIMReconnectFailedArgs : EventArgs - { - /// - /// 是否由 SDK 内部机制启动的自动重连 - /// - public bool IsAuto { get; set; } - - /// - /// 重连的 client Id - /// - public string ClientId { get; set; } - - /// - /// 重连时使用的 SessionToken - /// - public string SessionToken { get; set; } - - /// - /// 失败的原因 - /// 0. 客户端网络断开 - /// 1. sessionToken 错误或者失效,需要重新创建 client - /// - public int FailedCode { get; set; } - } - - /// - /// AVIMM essage event arguments. - /// - public class AVIMMessageEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// I message. - public AVIMMessageEventArgs(IAVIMMessage iMessage) - { - Message = iMessage; - } - /// - /// Gets or sets the message. - /// - /// The message. - public IAVIMMessage Message { get; internal set; } - } - - /// - /// AVIMMessage event arguments. - /// - public class AVIMMessagePatchEventArgs : EventArgs - { - public AVIMMessagePatchEventArgs(IAVIMMessage message) - { - Message = message; - } - - /// - /// Gets or sets the message. - /// - /// The message. - public IAVIMMessage Message { get; internal set; } - } - - /// - /// AVIMT ext message event arguments. - /// - public class AVIMTextMessageEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// Raw. - public AVIMTextMessageEventArgs(AVIMTextMessage raw) - { - TextMessage = raw; - } - /// - /// Gets or sets the text message. - /// - /// The text message. - public AVIMTextMessage TextMessage { get; internal set; } - } - - /// - /// 当对话中有人加入时,触发 时所携带的事件参数 - /// - public class AVIMOnMembersJoinedEventArgs : EventArgs - { - /// - /// 加入到对话的 Client Id(s) - /// - public IEnumerable JoinedMembers { get; internal set; } - - /// - /// 邀请的操作人 - /// - public string InvitedBy { get; internal set; } - - /// - /// 此次操作针对的对话 Id - /// - public string ConversationId { get; internal set; } - } - - /// - /// 当对话中有人加入时,触发 AVIMMembersJoinListener 时所携带的事件参数 - /// - public class AVIMOnMembersLeftEventArgs : EventArgs - { - /// - /// 离开对话的 Client Id(s) - /// - public IEnumerable LeftMembers { get; internal set; } - - /// - /// 踢出的操作人 - /// - public string KickedBy { get; internal set; } - - /// - /// 此次操作针对的对话 Id - /// - public string ConversationId { get; internal set; } - } - /// - /// 当前用户被邀请加入到对话 - /// - public class AVIMOnInvitedEventArgs : EventArgs - { - /// - /// 邀请的操作人 - /// - public string InvitedBy { get; internal set; } - - /// - /// 此次操作针对的对话 Id - /// - public string ConversationId { get; internal set; } - } - - /// - /// 当前用户被他人从对话中踢出 - /// - public class AVIMOnKickedEventArgs : EventArgs - { - /// - /// 踢出的操作人 - /// - public string KickedBy { get; internal set; } - - /// - /// 此次操作针对的对话 Id - /// - public string ConversationId { get; internal set; } - } - - public class AVIMSessionClosedEventArgs : EventArgs - { - public int Code { get; internal set; } - - public string Reason { get; internal set; } - - public string Detail { get; internal set; } - } -} diff --git a/RTM/Source/Public/AVIMException.cs b/RTM/Source/Public/AVIMException.cs deleted file mode 100644 index f473746..0000000 --- a/RTM/Source/Public/AVIMException.cs +++ /dev/null @@ -1,235 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime -{ - /// - /// 实时通信的异常 - /// - public class AVIMException : Exception - { - /// - /// 错误代码 - /// - public enum ErrorCode - { - /// - /// Error code indicating that an unknown error or an error unrelated to LeanCloud - /// occurred. - /// - OtherCause = -1, - - /// - /// 服务端错误 - /// - FromServer = 4000, - - - /// - /// websocket 连接非正常关闭,通常见于路由器配置对长连接限制的情况。SDK 会自动重连,无需人工干预。 - /// - UnknownError = 1006, - - /// - /// 应用不存在或应用禁用了实时通信服务 - /// - APP_NOT_AVAILABLE = 4100, - - - /// - /// 登录签名验证失败 - /// - SIGNATURE_FAILED = 4102, - - /// - /// Client ClientId 格式错误,超过 64 个字符。 - /// - INVALID_LOGIN = 4103, - - /// - /// Session 没有打开就发送消息,或执行其他操作。常见的错误场景是调用 open session 后直接发送消息,正确的用法是在 Session 打开的回调里执行。 - /// - SESSION_REQUIRED = 4105, - - /// - /// 读超时,服务器端长时间没有收到客户端的数据,切断连接。SDK 包装了心跳包的机制,出现此错误通常是网络问题。SDK 会自动重连,无需人工干预。 - /// - READ_TIMEOUT = 4107, - - /// - /// 登录超时,连接后长时间没有完成 session open。通常是登录被拒绝等原因,出现此问题可能是使用方式有误,可以 创建工单,由我们技术顾问来给出建议。 - /// - LOGIN_TIMEOUT = 4108, - - /// - /// 包过长。消息大小超过 5 KB,请缩短消息或者拆分消息。 - /// - FRAME_TOO_LONG = 4109, - - /// - /// 设置安全域名后,当前登录的域名与安全域名不符合。 - /// - INVALID_ORIGIN = 4110, - - - /// - /// 设置单设备登录后,客户端被其他设备挤下线。 - /// - SESSION_CONFLICT = 4111, - - /// - /// 应用容量超限,当天登录用户数已经超过应用设定的最大值。 - /// - APP_QUOTA_EXCEEDED = 4113, - - /// - /// 客户端发送的序列化数据服务器端无法解析。 - /// - UNPARSEABLE_RAW_MESSAGE = 4114, - - /// - /// 客户端被 REST API 管理接口强制下线。 - /// - KICKED_BY_APP = 4115, - - /// - /// 应用单位时间内发送消息量超过限制,消息被拒绝。 - /// - MESSAGE_SENT_QUOTA_EXCEEDED = 4116, - - /// - /// 服务器内部错误,如果反复出现请收集相关线索并 创建工单,我们会尽快解决。 - /// - INTERNAL_ERROR = 4200, - - /// - /// 通过 API 发送消息超时 - /// - SEND_MESSAGE_TIMEOUT = 4201, - - /// - /// 上游 API 调用异常,请查看报错信息了解错误详情 - /// - CONVERSATION_API_FAILED = 4301, - - /// - /// 对话相关操作签名错误 - /// - CONVERSATION_SIGNATURE_FAILED = 4302, - - /// - /// 发送消息,或邀请等操作对应的对话不存在。 - /// - CONVERSATION_NOT_FOUND = 4303, - - /// - /// 对话成员已满,不能再添加。 - /// - CONVERSATION_FULL = 4304, - - /// - /// 对话操作被应用的云引擎 Hook 拒绝 - /// - CONVERSATION_REJECTED_BY_APP = 4305, - - /// - /// 更新对话操作失败 - /// - CONVERSATION_UPDATE_FAILED = 4306, - - /// - /// 该对话为只读,不能更新或增删成员。 - /// - CONVERSATION_READ_ONLY = 4307, - - /// - /// 该对话禁止当前用户发送消息 - /// - CONVERSATION_NOT_ALLOWED = 4308, - - /// - /// 更新对话的请求被拒绝,当前用户不在对话中 - /// - CONVERSATION_UPDATE_REJECT = 4309, - - /// - /// 查询对话失败,常见于慢查询导致的超时或受其他慢查询导致的数据库响应慢 - /// - CONVERSATION_QUERY_FAILED = 4310, - - /// - /// 拉取对话消息记录失败,常见与超时的情况 - /// - CONVERSATION_LOG_FAILED = 4311, - - /// - /// 拉去对话消息记录被拒绝,当前用户不再对话中 - /// - CONVERSATION_LOG_REJECT = 4312, - - /// - /// 该功能仅对系统对话有效 - /// - SYSTEM_CONVERSATION_REQUIRED = 4313, - - - /// - /// 该功能仅对普通对话有效。 - /// - NORMAL_CONVERSATION_REQUIRED = 4314, - - - /// - /// 当前用户被加入此对话的黑名单,无法发送消息。 - /// - CONVERSATION_BLACKLISTED = 4315, - - - /// - /// 该功能仅对暂态对话有效。 - /// - TRANSIENT_CONVERSATION_REQUIRED = 4316, - - /// - /// 发送消息的对话不存在,或当前用户不在对话中 - /// - INVALID_MESSAGING_TARGET = 4401, - - /// - /// 发送的消息被应用的云引擎 Hook 拒绝 - /// - MESSAGE_REJECTED_BY_APP = 4402, - - /// - /// 客户端无法通过 WebSocket 发送数据包 - /// - CAN_NOT_EXCUTE_COMMAND = 1002, - - } - /// - /// 用户云代码返回的错误码 - /// - public int AppCode { get; private set; } - - - internal AVIMException(ErrorCode code, string message, Exception cause = null) - : base(message, cause) - { - this.Code = code; - } - - internal AVIMException(int code, int appCode, string message, Exception cause = null) - : this((ErrorCode)code, message, cause) - { - this.AppCode = appCode; - } - - /// - /// The LeanCloud error code associated with the exception. - /// - public ErrorCode Code { get; private set; } - } -} diff --git a/RTM/Source/Public/AVIMImageMessage.cs b/RTM/Source/Public/AVIMImageMessage.cs deleted file mode 100644 index 9e00b67..0000000 --- a/RTM/Source/Public/AVIMImageMessage.cs +++ /dev/null @@ -1,246 +0,0 @@ -using LeanCloud; -using LeanCloud.Storage.Internal; -using LeanCloud.Realtime.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.IO; - -namespace LeanCloud.Realtime -{ - /// - /// 图像消息 - /// - [AVIMMessageClassName("_AVIMImageMessage")] - [AVIMTypedMessageTypeInt(-2)] - public class AVIMImageMessage : AVIMFileMessage - { - - } - - /// - /// File message. - /// - [AVIMMessageClassName("_AVIMFileMessage")] - [AVIMTypedMessageTypeInt(-6)] - public class AVIMFileMessage : AVIMMessageDecorator - { - /// - /// Initializes a new instance of the class. - /// - public AVIMFileMessage() - : base(new AVIMTypedMessage()) - { - - } - - /// - /// Initializes a new instance of the class. - /// - /// Message. - public AVIMFileMessage(AVIMTypedMessage message) - : base(message) - { - - } - - /// - /// Gets or sets the file. - /// - /// The file. - public AVFile File { get; set; } - - /// - /// Froms the URL. - /// - /// The URL. - /// URL. - /// The 1st type parameter. - public static T FromUrl(string url) where T : AVIMFileMessage, new() - { - return FromUrl(string.Empty.Random(8), url, null); - } - - /// - /// From the URL. - /// - /// The URL. - /// File name. - /// External URL. - /// Text title. - /// Custom attributes. - /// The 1st type parameter. - public static T FromUrl(string fileName, string externalUrl, string textTitle, IDictionary customAttributes = null) where T : AVIMFileMessage, new() - { - T message = new T(); - message.File = new AVFile(fileName, externalUrl); - message.TextContent = textTitle; - message.MergeCustomAttributes(customAttributes); - return message; - } - - /// - /// From the stream. - /// - /// The stream. - /// File name. - /// Data. - /// MIME type. - /// Text title. - /// Meta data. - /// Custom attributes. - /// The 1st type parameter. - public static T FromStream(string fileName, Stream data, string mimeType, string textTitle, IDictionary metaData, IDictionary customAttributes = null) where T : AVIMFileMessage, new() - { - T message = new T(); - message.File = new AVFile(fileName, data, mimeType, metaData); - message.TextContent = textTitle; - message.MergeCustomAttributes(customAttributes); - return message; - } - - /// - /// Encodes the decorator. - /// - /// The decorator. - public override IDictionary EncodeDecorator() - { - if (File.Url == null) throw new InvalidOperationException("File.Url can not be null before it can be sent."); - File.MetaData["name"] = File.Name; - File.MetaData["format"] = File.MimeType; - var fileData = new Dictionary - { - { "url", File.Url.ToString()}, - { "objId", File.ObjectId }, - { "metaData", File.MetaData } - }; - - return new Dictionary - { - { AVIMProtocol.LCFILE, fileData } - }; - } - - /// - /// Deserialize the specified msgStr. - /// - /// The deserialize. - /// Message string. - public override IAVIMMessage Deserialize(string msgStr) - { - var msg = Json.Parse(msgStr) as IDictionary; - var fileData = msg[AVIMProtocol.LCFILE] as IDictionary; - string mimeType = null; - string url = null; - string name = null; - string objId = null; - IDictionary metaData = null; - if (fileData != null) - { - object metaDataObj = null; - - if (fileData.TryGetValue("metaData", out metaDataObj)) - { - metaData = metaDataObj as IDictionary; - object fileNameObj = null; - if (metaData != null) - { - if (metaData.TryGetValue("name", out fileNameObj)) - { - name = fileNameObj.ToString(); - } - } - object mimeTypeObj = null; - if (metaData != null) - { - if (metaData.TryGetValue("format", out mimeTypeObj)) - { - if (mimeTypeObj != null) - mimeType = mimeTypeObj.ToString(); - } - } - } - - object objIdObj = null; - if (fileData.TryGetValue("objId", out objIdObj)) - { - if (objIdObj != null) - objId = objIdObj.ToString(); - } - - object urlObj = null; - if (fileData.TryGetValue("url", out urlObj)) - { - url = urlObj.ToString(); - } - File = AVFile.CreateWithData(objId, name, url, metaData); - } - - return base.Deserialize(msgStr); - } - } - - /// - /// Location message. - /// - [AVIMMessageClassName("_AVIMMessageClassName")] - [AVIMTypedMessageTypeInt(-5)] - public class AVIMLocationMessage : AVIMMessageDecorator - { - /// - /// Initializes a new instance of the class. - /// - public AVIMLocationMessage() - : base(new AVIMTypedMessage()) - { - - } - - /// - /// Gets or sets the location. - /// - /// The location. - public AVGeoPoint Location { get; set; } - - public AVIMLocationMessage(AVGeoPoint location) - : this() - { - Location = location; - } - - /// - /// Encodes the decorator. - /// - /// The decorator. - public override IDictionary EncodeDecorator() - { - var locationData = new Dictionary - { - { "longitude", Location.Longitude}, - { "latitude", Location.Latitude } - }; - return new Dictionary - { - { AVIMProtocol.LCLOC, locationData } - }; - } - - /// - /// Deserialize the specified msgStr. - /// - /// The deserialize. - /// Message string. - public override IAVIMMessage Deserialize(string msgStr) - { - var msg = Json.Parse(msgStr) as IDictionary; - var locationData = msg[AVIMProtocol.LCLOC] as IDictionary; - if (locationData != null) - { - Location = new AVGeoPoint(double.Parse(locationData["latitude"].ToString()), double.Parse(locationData["longitude"].ToString())); - } - return base.Deserialize(msgStr); - } - } -} diff --git a/RTM/Source/Public/AVIMMessage.cs b/RTM/Source/Public/AVIMMessage.cs deleted file mode 100644 index 21fb38d..0000000 --- a/RTM/Source/Public/AVIMMessage.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using LeanCloud; -using System.Reflection; -using LeanCloud.Storage.Internal; -using System.Threading; -using System.Collections; -using LeanCloud.Realtime.Internal; - -namespace LeanCloud.Realtime -{ - /// - /// 实时消息的核心基类,它是 Json schema 消息的父类 - /// - [AVIMMessageClassName("_AVIMMessage")] - public class AVIMMessage : IAVIMMessage - { - /// - /// 默认的构造函数 - /// - public AVIMMessage() - { - - } - internal readonly object mutex = new object(); - - /// - /// 对话的Id - /// - public string ConversationId { get; set; } - - /// - /// 发送消息的 ClientId - /// - public string FromClientId { get; set; } - - /// - /// 消息在全局的唯一标识Id - /// - public string Id { get; set; } - - /// - /// 服务器端的时间戳 - /// - public long ServerTimestamp { get; set; } - - /// - /// Gets or sets the content. - /// - /// The content. - public string Content { get; set; } - - /// - /// 对方收到消息的时间戳,如果是多人聊天,那以最早收到消息的人回发的 ACK 为准 - /// - public long RcpTimestamp { get; set; } - - public long UpdatedAt { get; set; } - - internal string cmdId { get; set; } - - #region - /// - /// Gets or sets a value indicating whether this mention all. - /// - /// true if mention all; otherwise, false. - public bool MentionAll { get; set; } - - /// - /// Gets or sets the mention list. - /// - /// The mention list. - public IEnumerable MentionList { get; set; } - - #endregion - - #region register convertor for custom typed message - - /// - /// Serialize this message. - /// - /// The serialize. - public virtual string Serialize() - { - return Content; - } - - /// - /// Validate the specified msgStr. - /// - /// The validate. - /// Message string. - public virtual bool Validate(string msgStr) - { - return true; - } - - /// - /// Deserialize the specified msgStr to message subclass instance - /// - /// The deserialize. - /// Message string. - public virtual IAVIMMessage Deserialize(string msgStr) - { - Content = msgStr; - return this; - } - - internal virtual MessageCommand BeforeSend(MessageCommand cmd) - { - return cmd; - } - - internal static IAVIMMessage CopyMetaData(IAVIMMessage srcMsg, IAVIMMessage desMsg) { - if (srcMsg == null) - return desMsg; - - desMsg.ConversationId = srcMsg.ConversationId; - desMsg.FromClientId = srcMsg.FromClientId; - desMsg.Id = srcMsg.Id; - desMsg.ServerTimestamp = srcMsg.ServerTimestamp; - desMsg.RcpTimestamp = srcMsg.RcpTimestamp; - desMsg.UpdatedAt = srcMsg.UpdatedAt; - return desMsg; - } - - #endregion - } - - - /// - /// 消息的发送选项 - /// - public struct AVIMSendOptions - { - /// - /// 是否需要送达回执 - /// - public bool Receipt; - /// - /// 是否是暂态消息,暂态消息不返回送达回执(ack),不保留离线消息,不触发离线推送 - /// - public bool Transient; - /// - /// 消息的优先级,默认是1,可选值还有 2|3 - /// - public int Priority; - /// - /// 是否为 Will 类型的消息,这条消息会被缓存在服务端,一旦当前客户端下线,这条消息会被发送到对话内的其他成员 - /// - public bool Will; - - /// - /// 如果消息的接收者已经下线了,这个字段的内容就会被离线推送到接收者 - ///例如,一张图片消息的离线消息内容可以类似于:[您收到一条图片消息,点击查看] 这样的推送内容,参照微信的做法 - /// - public IDictionary PushData; - } -} diff --git a/RTM/Source/Public/AVIMMessageClassNameAttribute.cs b/RTM/Source/Public/AVIMMessageClassNameAttribute.cs deleted file mode 100644 index e4d7228..0000000 --- a/RTM/Source/Public/AVIMMessageClassNameAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime -{ - [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] - public sealed class AVIMMessageClassNameAttribute: Attribute - { - public AVIMMessageClassNameAttribute(string className) - { - this.ClassName = className; - } - public string ClassName { get; private set; } - - } -} diff --git a/RTM/Source/Public/AVIMMessageFieldNameAttribute.cs b/RTM/Source/Public/AVIMMessageFieldNameAttribute.cs deleted file mode 100644 index 0e155b5..0000000 --- a/RTM/Source/Public/AVIMMessageFieldNameAttribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace LeanCloud.Realtime -{ - [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] - public sealed class AVIMMessageFieldNameAttribute: Attribute - { - public AVIMMessageFieldNameAttribute(string fieldName) - { - FieldName = fieldName; - } - - public string FieldName { get; private set; } - } -} diff --git a/RTM/Source/Public/AVIMMessageListener.cs b/RTM/Source/Public/AVIMMessageListener.cs deleted file mode 100644 index d0d91cf..0000000 --- a/RTM/Source/Public/AVIMMessageListener.cs +++ /dev/null @@ -1,143 +0,0 @@ -using LeanCloud.Realtime.Internal; -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime -{ - /// - /// 默认的消息监听器,它主要承担的指责是回执的发送与用户自定义的监听器不冲突 - /// - public class AVIMMessageListener : IAVIMListener - { - /// - /// 默认的 AVIMMessageListener 只会监听 direct 协议,但是并不会触发针对消息类型的判断的监听器 - /// - public AVIMMessageListener() - { - - } - - /// - /// Protocols the hook. - /// - /// true, if hook was protocoled, false otherwise. - /// Notice. - public virtual bool ProtocolHook(AVIMNotice notice) - { - if (notice.CommandName != "direct") return false; - if (notice.RawData.ContainsKey("offline")) return false; - return true; - } - - private EventHandler m_OnMessageReceived; - /// - /// 接收到聊天消息的事件通知 - /// - public event EventHandler OnMessageReceived - { - add - { - m_OnMessageReceived += value; - } - remove - { - m_OnMessageReceived -= value; - } - } - internal virtual void OnMessage(AVIMNotice notice) - { - if (m_OnMessageReceived != null) - { - var msgStr = notice.RawData["msg"].ToString(); - var iMessage = AVRealtime.FreeStyleMessageClassingController.Instantiate(msgStr, notice.RawData); - //var messageNotice = new AVIMMessageNotice(notice.RawData); - //var messaegObj = AVIMMessage.Create(messageNotice); - var args = new AVIMMessageEventArgs(iMessage); - m_OnMessageReceived.Invoke(this, args); - } - } - - /// - /// Ons the notice received. - /// - /// Notice. - public virtual void OnNoticeReceived(AVIMNotice notice) - { - this.OnMessage(notice); - } - - } - - /// - /// 文本消息监听器 - /// - public class AVIMTextMessageListener : IAVIMListener - { - /// - /// 构建默认的文本消息监听器 - /// - public AVIMTextMessageListener() - { - - } - - /// - /// 构建文本消息监听者 - /// - /// - public AVIMTextMessageListener(Action textMessageReceived) - { - OnTextMessageReceived += (sender, textMessage) => - { - textMessageReceived(textMessage.TextMessage); - }; - } - - private EventHandler m_OnTextMessageReceived; - public event EventHandler OnTextMessageReceived - { - add - { - m_OnTextMessageReceived += value; - } - remove - { - m_OnTextMessageReceived -= value; - } - } - - public virtual bool ProtocolHook(AVIMNotice notice) - { - if (notice.CommandName != "direct") return false; - try - { - var msg = Json.Parse(notice.RawData["msg"].ToString()) as IDictionary; - if (!msg.Keys.Contains(AVIMProtocol.LCTYPE)) return false; - var typInt = 0; - int.TryParse(msg[AVIMProtocol.LCTYPE].ToString(), out typInt); - if (typInt != -1) return false; - return true; - } - catch(ArgumentException) - { - - } - return false; - - } - - public virtual void OnNoticeReceived(AVIMNotice notice) - { - if (m_OnTextMessageReceived != null) - { - var textMessage = new AVIMTextMessage(); - textMessage.Deserialize(notice.RawData["msg"].ToString()); - m_OnTextMessageReceived(this, new AVIMTextMessageEventArgs(textMessage)); - } - } - } -} diff --git a/RTM/Source/Public/AVIMNotice.cs b/RTM/Source/Public/AVIMNotice.cs deleted file mode 100644 index 65f4053..0000000 --- a/RTM/Source/Public/AVIMNotice.cs +++ /dev/null @@ -1,57 +0,0 @@ -using LeanCloud; -using LeanCloud.Realtime.Internal; -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime -{ - /// - /// - /// - public interface IAVIMNotice - { - /// - /// - /// - /// - /// - AVIMNotice Restore(IDictionary estimatedData); - } - /// - /// 从服务端接受到的通知 - /// 通知泛指消息,对话信息变更(例如加人和被踢等),服务器的 ACK,消息回执等 - /// - public class AVIMNotice : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - public AVIMNotice() - { - - } - - /// - /// The name of the command. - /// - public readonly string CommandName; - public readonly IDictionary RawData; - public AVIMNotice(IDictionary estimatedData) - { - this.RawData = estimatedData; - this.CommandName = estimatedData["cmd"].ToString(); - } - - public static bool IsValidLeanCloudProtocol(IDictionary estimatedData) - { - if (estimatedData == null) return false; - if (estimatedData.Count == 0) return false; - return true; - } - } -} diff --git a/RTM/Source/Public/AVIMRecalledMessage.cs b/RTM/Source/Public/AVIMRecalledMessage.cs deleted file mode 100644 index 1e863fd..0000000 --- a/RTM/Source/Public/AVIMRecalledMessage.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace LeanCloud.Realtime { - /// - /// 撤回消息 - /// - [AVIMMessageClassName("_AVIMRecalledMessagee")] - [AVIMTypedMessageTypeInt(-127)] - public class AVIMRecalledMessage : AVIMTypedMessage { - - } -} diff --git a/RTM/Source/Public/AVIMSignature.cs b/RTM/Source/Public/AVIMSignature.cs deleted file mode 100644 index c5bd0d3..0000000 --- a/RTM/Source/Public/AVIMSignature.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime -{ - /// - /// 签名 - /// - public class AVIMSignature - { - /// - /// 经过 SHA1 以及相关操作参数计算出来的加密字符串 - /// - public string SignatureContent { get; set; } - - /// - /// 服务端时间戳 - /// - public long Timestamp { get; set; } - - /// - /// 随机字符串 - /// - public string Nonce { get; set; } - - /// - /// 构造一个签名 - /// - /// - /// - /// - public AVIMSignature(string s,long t,string n) - { - this.Nonce = n; - this.SignatureContent = s; - this.Timestamp = t; - } - } -} diff --git a/RTM/Source/Public/AVIMTemporaryConversation.cs b/RTM/Source/Public/AVIMTemporaryConversation.cs deleted file mode 100644 index 1e06769..0000000 --- a/RTM/Source/Public/AVIMTemporaryConversation.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime -{ - /// - /// Temporary conversation. - /// - public class AVIMTemporaryConversation : AVIMConversation - { - public DateTime ExpiredAt - { - get - { - if (expiredAt == null) - return DateTime.Now.AddDays(1); - return expiredAt.Value; - } - - set - { - expiredAt = value; - } - } - - internal AVIMTemporaryConversation(long ttl) - : base(isTemporary: true) - { - this.expiredAt = DateTime.Now.AddDays(1); - } - } - - -} diff --git a/RTM/Source/Public/AVIMTextMessage.cs b/RTM/Source/Public/AVIMTextMessage.cs deleted file mode 100644 index 7bc1d34..0000000 --- a/RTM/Source/Public/AVIMTextMessage.cs +++ /dev/null @@ -1,47 +0,0 @@ -using LeanCloud.Realtime.Internal; -using LeanCloud.Storage.Internal; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime -{ - /// - /// 纯文本信息 - /// - [AVIMMessageClassName("_AVIMTextMessage")] - [AVIMTypedMessageTypeInt(-1)] - public class AVIMTextMessage : AVIMTypedMessage - { - /// - /// 构建一个文本信息 class. - /// - public AVIMTextMessage() - { - - } - - /// - /// 文本类型标记 - /// - [Obsolete("LCType is deprecated, please use AVIMTypedMessageTypeInt instead.")] - [AVIMMessageFieldName("_lctype")] - public int LCType - { - get; set; - } - - /// - /// 构造一个纯文本信息 - /// - /// - public AVIMTextMessage(string textContent) - : this() - { - TextContent = textContent; - } - } -} diff --git a/RTM/Source/Public/AVIMTypedMessage.cs b/RTM/Source/Public/AVIMTypedMessage.cs deleted file mode 100644 index c756e1f..0000000 --- a/RTM/Source/Public/AVIMTypedMessage.cs +++ /dev/null @@ -1,205 +0,0 @@ -using LeanCloud.Storage.Internal; -using LeanCloud.Realtime.Internal; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace LeanCloud.Realtime -{ - /// - /// - /// - [AVIMMessageClassName("_AVIMTypedMessage")] - [AVIMTypedMessageTypeInt(0)] - public class AVIMTypedMessage : AVIMMessage, IEnumerable> - { - /// - /// Initializes a new instance of the class. - /// - public AVIMTypedMessage() - { - - } - - /// - /// 文本内容 - /// - [AVIMMessageFieldName("_lctext")] - public string TextContent - { - get; set; - } - - private IDictionary estimatedData = new Dictionary(); - /// - /// Serialize this instance. - /// - /// The serialize. - public override string Serialize() - { - var result = Encode(); - var resultStr = Json.Encode(result); - this.Content = resultStr; - return resultStr; - } - - /// - /// Encode this instance. - /// - /// The encode. - public virtual IDictionary Encode() - { - var result = AVRealtime.FreeStyleMessageClassingController.EncodeProperties(this); - var encodedAttrs = PointerOrLocalIdEncoder.Instance.Encode(estimatedData); - result[AVIMProtocol.LCATTRS] = estimatedData; - return result; - } - - /// - /// Validate the specified msgStr. - /// - /// The validate. - /// Message string. - public override bool Validate(string msgStr) - { - try - { - var msg = Json.Parse(msgStr) as IDictionary; - return msg.ContainsKey(AVIMProtocol.LCTYPE); - } - catch - { - - } - return false; - } - - /// - /// Deserialize the specified msgStr. - /// - /// The deserialize. - /// Message string. - public override IAVIMMessage Deserialize(string msgStr) - { - var msg = Json.Parse(msgStr) as IDictionary; - var className = AVRealtime.FreeStyleMessageClassingController.GetClassName(this.GetType()); - var PropertyMappings = AVRealtime.FreeStyleMessageClassingController.GetPropertyMappings(className); - var messageFieldProperties = PropertyMappings.Where(prop => msg.ContainsKey(prop.Value)) - .Select(prop => Tuple.Create(ReflectionHelpers.GetProperty(this.GetType(), prop.Key), msg[prop.Value])); - - foreach (var property in messageFieldProperties) - { - property.Item1.SetValue(this, property.Item2, null); - } - - if (msg.ContainsKey(AVIMProtocol.LCATTRS)) - { - object attrs = msg[AVIMProtocol.LCATTRS]; - this.estimatedData = AVDecoder.Instance.Decode(attrs) as Dictionary; - } - - return base.Deserialize(msgStr); - } - - /// - /// Gets or sets the with the specified key. - /// - /// Key. - public virtual object this[string key] - { - get - { - if (estimatedData.TryGetValue(key, out object value)) { - return value; - } - return null; - } - set - { - estimatedData[key] = value; - } - } - - /// - /// Merges the custom attributes. - /// - /// Custom attributes. - public void MergeCustomAttributes(IDictionary customAttributes) - { - this.estimatedData = this.estimatedData.Merge(customAttributes); - } - - /// - /// Gets the enumerator. - /// - /// The enumerator. - public IEnumerator> GetEnumerator() - { - return estimatedData.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable>)this).GetEnumerator(); - } - } - - /// - /// AVIMMessage decorator. - /// - public abstract class AVIMMessageDecorator : AVIMTypedMessage - { - /// - /// Gets or sets the message. - /// - /// The message. - public AVIMTypedMessage Message { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// Message. - protected AVIMMessageDecorator(AVIMTypedMessage message) - { - this.Message = message; - } - - /// - /// Gets or sets the content of the message. - /// - /// The content of the message. - public virtual IDictionary MessageContent { get; set; } - - /// - /// Encodes the decorated. - /// - /// The decorated. - public virtual IDictionary EncodeDecorated() - { - return Message.Encode(); - } - - /// - /// Encode this instance. - /// - /// The encode. - public override IDictionary Encode() - { - var decoratedMessageEncoded = EncodeDecorated(); - var selfEncoded = base.Encode(); - var decoratoEncoded = this.EncodeDecorator(); - var resultEncoed = decoratedMessageEncoded.Merge(selfEncoded).Merge(decoratoEncoded); - return resultEncoed; - } - - /// - /// Encodes the decorator. - /// - /// The decorator. - public abstract IDictionary EncodeDecorator(); - } - - -} diff --git a/RTM/Source/Public/AVIMTypedMessageTypeIntAttribute.cs b/RTM/Source/Public/AVIMTypedMessageTypeIntAttribute.cs deleted file mode 100644 index 49c4b76..0000000 --- a/RTM/Source/Public/AVIMTypedMessageTypeIntAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -namespace LeanCloud.Realtime -{ - [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] - public sealed class AVIMTypedMessageTypeIntAttribute : Attribute - { - public AVIMTypedMessageTypeIntAttribute(int typeInt) - { - this.TypeInteger = typeInt; - } - - public int TypeInteger { get; private set; } - } -} diff --git a/RTM/Source/Public/AVRealtime.cs b/RTM/Source/Public/AVRealtime.cs deleted file mode 100644 index 49bd9ec..0000000 --- a/RTM/Source/Public/AVRealtime.cs +++ /dev/null @@ -1,1282 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -using LeanCloud; -using System.Reflection; -using LeanCloud.Realtime.Internal; -using LeanCloud.Storage.Internal; -using System.Threading; - -#if UNITY -using UnityEngine; -#endif -namespace LeanCloud.Realtime -{ - - /// - /// 实时消息的框架类 - /// 包含了 WebSocket 连接以及事件通知的管理 - /// - public class AVRealtime - { - internal static IDictionary clients = null; - - private static readonly object mutex = new object(); - private string _wss; - private string _secondaryWss; - private string _sesstionToken; - private long _sesstionTokenExpire; - private string _clientId; - private string _deviceId; - private bool _secure; - private string _tag; - private string subprotocolPrefix = "lc.json."; - - static readonly int RECONNECT_DELAY = 5 * 1000; - static readonly int RECONNECT_USE_SECONDARY_TIMES = 6; - static readonly int RECONNECT_FROM_APP_ROUTER = 12; - - int reconnectTimes; - - public bool IsSesstionTokenExpired - { - get - { - return DateTime.Now.ToUnixTimeStamp() > _sesstionTokenExpire; - } - } - - - - private IAVIMCommandRunner avIMCommandRunner; - - - /// - /// - /// - public IAVIMCommandRunner AVIMCommandRunner - { - get - { - lock (mutex) - { - avIMCommandRunner = avIMCommandRunner ?? new AVIMCommandRunner(this.AVWebSocketClient); - return avIMCommandRunner; - } - } - } - - private IWebSocketClient webSocketController; - internal IWebSocketClient AVWebSocketClient - { - get - { - lock (mutex) - { - webSocketController = webSocketController ?? new DefaultWebSocketClient(); - return webSocketController; - } - } - set - { - lock (mutex) - { - webSocketController = value; - } - } - } - - internal static IAVRouterController RouterController - { - get - { - return AVIMCorePlugins.Instance.RouterController; - } - } - - internal static IFreeStyleMessageClassingController FreeStyleMessageClassingController - { - get - { - return AVIMCorePlugins.Instance.FreeStyleClassingController; - } - } - - /// - /// - /// - public event EventHandler OnOfflineMessageReceived; - - /// - /// 与云端通讯的状态 - /// - public enum Status - { - /// - /// 未初始化 - /// - None = -1, - - /// - /// 正在连接 - /// - Connecting = 0, - - /// - /// 已连接 - /// - Online = 1, - - /// - /// 连接已断开 - /// - Offline = 2, - - /// - /// 正在重连 - /// - Reconnecting = 3, - - /// - /// websocket 连接已被打开 - /// - Opened = 98, - - /// - /// 已主动关闭 - /// - Closed = 99, - } - - private AVRealtime.Status state = Status.None; - public AVRealtime.Status State - { - get - { - return state; - } - private set - { - state = value; - } - } - - private struct NetworkStateOptions - { - public bool Available { get; set; } - } - - private NetworkStateOptions NetworkState { get; set; } - - private struct WebSocketStateOptions - { - public int ClosedCode { get; set; } - } - - private WebSocketStateOptions WebSocketState { get; set; } - - /// - /// - /// - public struct AVIMReconnectOptions - { - /// - /// 重连的时间间隔,单位是秒 - /// - public long Interval { get; set; } - - /// - /// 重连的次数 - /// - public int Retry { get; set; } - } - - internal string Subprotocol - { - get - { - return subprotocolPrefix + (int)CurrentConfiguration.OfflineMessageStrategy; - } - } - - /// - /// 重连选项 - /// - public AVIMReconnectOptions ReconnectOptions { get; set; } - - private ISignatureFactory _signatureFactory; - - /// - /// 签名接口 - /// - public ISignatureFactory SignatureFactory - { - get - { - _signatureFactory = _signatureFactory ?? new DefaulSiganatureFactory(); - return _signatureFactory; - } - set - { - _signatureFactory = value; - } - } - - private bool useLeanEngineSignaturFactory; - /// - /// 启用 LeanEngine 云函数签名 - /// - public void UseLeanEngineSignatureFactory() - { - useLeanEngineSignaturFactory = true; - this.SignatureFactory = new LeanEngineSignatureFactory(); - } - - private EventHandler m_OnDisconnected; - /// - /// 连接断开触发的事件 - /// 如果其他客户端使用了相同的 Tag 登录,就会导致当前用户被服务端断开 - /// - public event EventHandler OnDisconnected - { - add - { - m_OnDisconnected += value; - } - remove - { - m_OnDisconnected -= value; - } - } - - private EventHandler m_OnReconnecting; - /// - /// 正在重连时触发的事件 - /// - public event EventHandler OnReconnecting - { - add - { - m_OnReconnecting += value; - } - remove - { - m_OnReconnecting -= value; - } - } - - private EventHandler m_OnReconnected; - /// - /// 重连之后触发的事件 - /// - public event EventHandler OnReconnected - { - add - { - m_OnReconnected += value; - } - remove - { - m_OnReconnected -= value; - } - } - - private EventHandler m_OnReconnectFailed; - - /// - /// 重连失败之后触发的事件 - /// - public event EventHandler OnReconnectFailed - { - add - { - m_OnReconnectFailed += value; - } - remove - { - m_OnReconnectFailed -= value; - } - } - - /// - /// Invokes the state of the network. - /// - /// If set to true broken. - internal void InvokeNetworkState(bool available = false) - { - if (this.NetworkState.Available == available) return; - SetNetworkState(available); - PrintLog(string.Format("network connectivity is {0} now", available)); - // 如果断线产生的原因是客户端掉线而不是服务端踢下线,则应该开始自动重连 - var reasonShouldReconnect = new int[] { 0, 1006, 4107 }; - if (this.NetworkState.Available && reasonShouldReconnect.Contains(this.WebSocketState.ClosedCode)) - { - StartAutoReconnect(); - } - } - - internal void SetNetworkState(bool available = true) - { - this.NetworkState = new NetworkStateOptions() - { - Available = available - }; - } - - private EventHandler m_NoticeReceived; - public event EventHandler NoticeReceived - { - add - { - m_NoticeReceived += value; - } - remove - { - m_NoticeReceived -= value; - } - } - - private void WebSocketClient_OnMessage(string obj) - { - try - { - var estimatedData = Json.Parse(obj) as IDictionary; - var validator = AVIMNotice.IsValidLeanCloudProtocol(estimatedData); - if (validator) - { - var notice = new AVIMNotice(estimatedData); - m_NoticeReceived?.Invoke(this, notice); - } - } - catch (Exception ex) - { - if (ex.InnerException != null) - { - PrintLog(ex.InnerException.Source); - } - if (ex.Source != null) - { - PrintLog(ex.Source); - } - - PrintLog(ex.Message); - } - } - - /// - /// 设定监听者 - /// - /// - /// - public void SubscribeNoticeReceived(IAVIMListener listener, Func runtimeHook = null) - { - this.NoticeReceived += new EventHandler((sender, notice) => - { - var approved = runtimeHook == null ? listener.ProtocolHook(notice) : runtimeHook(notice) && listener.ProtocolHook(notice); - if (approved) - { - listener.OnNoticeReceived(notice); - } - }); - } - - /// - /// 初始化配置项 - /// - public struct Configuration - { - /// - /// 签名工厂 - /// - public ISignatureFactory SignatureFactory { get; set; } - /// - /// 自定义 WebSocket 客户端 - /// - public IWebSocketClient WebSocketClient { get; set; } - /// - /// LeanCloud App Id - /// - public string ApplicationId { get; set; } - /// - /// LeanCloud App Key - /// - public string ApplicationKey { get; set; } - - /// - /// 登录的时候告知服务器,本次登录所使用的离线消息策略 - /// - public OfflineMessageStrategy OfflineMessageStrategy { get; set; } - } - - /// - ///登录时的离线消息下发策略 - /// - public enum OfflineMessageStrategy - { - /// - /// 服务器将所有离线消息一次性在登录之后马上下发下来 - /// - Default = 1, - - /// - /// 不再下发未读消息,而是下发对话的未读通知,告知客户端有哪些对话处于未读状态 - /// - [Obsolete("该策略已被废弃,请使用 UnreadAck")] - UnreadNotice = 2, - - /// - /// ack 和 read 分离, ack 不会清理未读消息 - /// - UnreadAck = 3 - } - - /// - /// 当前配置 - /// - public Configuration CurrentConfiguration { get; internal set; } - /// - /// 初始化实时消息客户端 - /// - /// - public AVRealtime(Configuration config) - { - lock (mutex) - { - if ((int)config.OfflineMessageStrategy == 0) - { - config.OfflineMessageStrategy = OfflineMessageStrategy.UnreadAck; - } - - CurrentConfiguration = config; - if (CurrentConfiguration.WebSocketClient != null) - { - webSocketController = CurrentConfiguration.WebSocketClient; - } - if (CurrentConfiguration.SignatureFactory != null) - { - this.SignatureFactory = CurrentConfiguration.SignatureFactory; - } - ReconnectOptions = new AVIMReconnectOptions() - { - Interval = 5, - Retry = 120 - }; - - - RegisterMessageType(); - RegisterMessageType(); - RegisterMessageType(); - RegisterMessageType(); - RegisterMessageType(); - RegisterMessageType(); - RegisterMessageType(); - RegisterMessageType(); - RegisterMessageType(); - - // 注册服务端 goaway 指令 - var goAwayListener = new GoAwayListener(); - goAwayListener.OnGoAway += () => { - RouterController.ClearCache().ContinueWith(_ => { - reborn = true; - // 关闭 WebSocket - AVWebSocketClient.Disconnect(); - }); - }; - SubscribeNoticeReceived(goAwayListener); - - reconnectTimes = 0; - } - } - - /// - /// 初始化实时消息客户端 - /// - /// - /// - public AVRealtime(string applicationId, string applicationKey) - : this(new Configuration() - { - ApplicationId = applicationId, - ApplicationKey = applicationKey, - OfflineMessageStrategy = OfflineMessageStrategy.UnreadAck - }) - { - - } - - #region websocket log - internal static Action LogTracker { get; private set; } - /// - /// 打开 WebSocket 日志 - /// - /// - public static void WebSocketLog(Action trace) - { - LogTracker = trace; - } - public static void PrintLog(string log) - { - if (AVRealtime.LogTracker != null) - { - AVRealtime.LogTracker(log); - } - } - #endregion - - /// - /// 创建 Client - /// - /// - /// - /// 设备唯一的 Id。如果是 iOS 设备,需要将 iOS 推送使用的 DeviceToken 作为 deviceId 传入 - /// 是否强制加密 wss 链接 - /// - /// - public Task CreateClientAsync(string clientId, - string tag = null, - string deviceId = null, - bool secure = true, - CancellationToken cancellationToken = default(CancellationToken)) - { - lock (mutex) - { - var client = PreLogIn(clientId, tag, deviceId); - - AVRealtime.PrintLog("begin OpenAsync."); - return OpenAsync(secure, Subprotocol, true, cancellationToken).OnSuccess(t => - { - if (!t.Result) - { - return Task.FromResult(null); - } - AVRealtime.PrintLog("websocket server connected, begin to open sesstion."); - SetNetworkState(); - var cmd = new SessionCommand() - .UA(VersionString) - .Tag(tag) - .DeviceId(deviceId) - .Option("open") - .PeerId(clientId); - - ToggleNotification(true); - return AttachSignature(cmd, this.SignatureFactory.CreateConnectSignature(clientId)); - - }).Unwrap().OnSuccess(x => - { - var cmd = x.Result; - if (cmd == null) - { - return Task.FromResult>>(null); - } - return this.RunCommandAsync(cmd); - }).Unwrap().OnSuccess(s => - { - if (s.Result == null) - { - return null; - } - AVRealtime.PrintLog("sesstion opened."); - state = Status.Online; - ToggleHeartBeating(true); - var response = s.Result.Item2; - if (response.ContainsKey("st")) - { - _sesstionToken = response["st"] as string; - } - if (response.ContainsKey("stTtl")) - { - var stTtl = long.Parse(response["stTtl"].ToString()); - _sesstionTokenExpire = DateTime.Now.ToUnixTimeStamp() + stTtl * 1000; - } - AfterLogIn(client); - return client; - }); - } - } - - - - /// - /// Creates the client async. - /// - /// The client async. - /// User. - /// Tag. - /// Device identifier. - /// If set to true secure. - public Task CreateClientAsync(AVUser user = null, - string tag = null, - string deviceId = null, - bool secure = true, - CancellationToken cancellationToken = default(CancellationToken)) - { - AVIMClient client = null; - AVRealtime.PrintLog("begin OpenAsync."); - return OpenAsync(secure, Subprotocol, true, cancellationToken).OnSuccess(openTask => - { - AVRealtime.PrintLog("OpenAsync OnSuccess. begin send open sesstion cmd."); - var userTask = Task.FromResult(user); - if (user == null) - userTask = AVUser.GetCurrentUserAsync(); - - return userTask; - }).Unwrap().OnSuccess(u => - { - var theUser = u.Result; - return AVCloud.RequestRealtimeSignatureAsync(theUser); - }).Unwrap().OnSuccess(signTask => - { - var signResult = signTask.Result; - var clientId = signResult.ClientId; - var nonce = signResult.Nonce; - var singnature = signResult.Signature; - var ts = signResult.Timestamp; - - client = PreLogIn(clientId, tag, deviceId); - ToggleNotification(true); - return this.OpenSessionAsync(clientId, tag, deviceId, nonce, ts, singnature, secure); - }).Unwrap().OnSuccess(s => - { - ToggleHeartBeating(true); - AfterLogIn(client); - return client; - }); - } - - #region pre-login - internal AVIMClient PreLogIn(string clientId, - string tag = null, - string deviceId = null) - { - var client = new AVIMClient(clientId, tag, this); - if (this.OnOfflineMessageReceived != null) - { - client.OnOfflineMessageReceived += this.OnOfflineMessageReceived; - } - _clientId = clientId; - _tag = tag; - _deviceId = deviceId; - if (_tag != null) - { - if (deviceId == null) - throw new ArgumentNullException(deviceId, "当 tag 不为空时,必须传入当前设备不变的唯一 id(deviceId)"); - } - - if (string.IsNullOrEmpty(clientId)) throw new Exception("当前 ClientId 为空,无法登录服务器。"); - - return client; - } - - internal void AfterLogIn(AVIMClient client) - { - if (clients == null) clients = new Dictionary(); - client.OnSessionClosed += (sender, e) => { - string clientId = (sender as AVIMClient).ClientId; - clients.Remove(clientId); - if (clients.Count == 0) { - LogOut(); - } - }; - clients[client.ClientId] = client; - } - - #endregion - - #region after-login - - - #endregion - - /// - /// 创建 Client - /// - /// - /// - /// 设备唯一的 Id。如果是 iOS 设备,需要将 iOS 推送使用的 DeviceToken 作为 deviceId 传入 - /// 是否强制加密 wss 链接 - /// - /// - [Obsolete("CreateClient is deprecated, please use CreateClientAsync instead.")] - public Task CreateClient( - string clientId, - string tag = null, - string deviceId = null, - bool secure = true, - CancellationToken cancellationToken = default(CancellationToken)) - { - return this.CreateClientAsync(clientId, tag, deviceId, secure, cancellationToken); - } - - private bool _listening = false; - /// - /// websocket 事件的监听的开关 - /// - /// 是否打开 - public void ToggleNotification(bool toggle) - { - AVRealtime.PrintLog("ToggleNotification| toggle:" + toggle + "|listening: " + _listening); - if (toggle && !_listening) - { - AVWebSocketClient.OnClosed += WebsocketClient_OnClosed; - AVWebSocketClient.OnMessage += WebSocketClient_OnMessage; - _listening = true; - } - else if (!toggle && _listening) - { - AVWebSocketClient.OnClosed -= WebsocketClient_OnClosed; - AVWebSocketClient.OnMessage -= WebSocketClient_OnMessage; - _listening = false; - } - } - - //public void ToggleOfflineNotification(bool toggle) - //{ - // if (toggle) - // { - // PCLWebsocketClient.OnMessage += WebSocketClient_OnMessage_On_Session_Opening; - // } - // else - // { - // PCLWebsocketClient.OnMessage -= WebSocketClient_OnMessage_On_Session_Opening; - // } - //} - - //private void WebSocketClient_OnMessage_On_Session_Opening(string obj) - //{ - // AVRealtime.PrintLog("offline<=" + obj); - //} - - - string _beatPacket = "{}"; - bool _heartBeatingToggle = true; - IAVTimer _heartBeatingTimer; - /// - /// 主动发送心跳包 - /// - /// 是否开启 - /// 时间间隔 - /// 心跳包的内容,默认是个空的 {} - public void ToggleHeartBeating(bool toggle = true, double interval = 60000, string beatPacket = "{}") - { - this._heartBeatingToggle = toggle; - if (!string.Equals(_beatPacket, beatPacket)) _beatPacket = beatPacket; - - if (_heartBeatingTimer == null && this._heartBeatingToggle) - { - _heartBeatingTimer = new AVTimer(); - _heartBeatingTimer.Elapsed += SendHeartBeatingPacket; - _heartBeatingTimer.Interval = interval; - _heartBeatingTimer.Start(); - PrintLog("auto heart beating started."); - } - if (!this._heartBeatingToggle && _heartBeatingTimer != null) - { - _heartBeatingTimer.Stop(); - _heartBeatingTimer = null; - } - } - - void SendHeartBeatingPacket(object sender, TimerEventArgs e) - { - PrintLog("auto heart beating ticked by timer."); -#if MONO || UNITY - Dispatcher.Instance.Post(() => - { - KeepAlive(); - }); -#else - KeepAlive(); -#endif - } - - /// - /// Keeps the alive. - /// - public void KeepAlive() - { - try - { - var cmd = new AVIMCommand(); - RunCommandAsync(cmd).ContinueWith(t => - { - if (t.IsCanceled || t.IsFaulted || t.Exception != null) - { - InvokeNetworkState(); - } - }); - } - catch (Exception) - { - InvokeNetworkState(); - } - } - - internal bool sessionConflict = false; - internal bool loggedOut = false; - - internal bool CanReconnect - { - get - { - return !sessionConflict && !loggedOut && state == Status.Offline; - } - } - - /// - /// 开始自动重连 - /// - public void StartAutoReconnect() - { - - } - internal bool useSecondary = false; - internal bool reborn = false; - - internal Task LogInAsync(string clientId, - string tag = null, - string deviceId = null, - bool secure = true, - CancellationToken cancellationToken = default(CancellationToken)) - { - lock (mutex) - { - var cmd = new SessionCommand() - .UA(VersionString) - .Tag(tag) - .DeviceId(deviceId) - .Option("open") - .PeerId(clientId); - - var result = AttachSignature(cmd, this.SignatureFactory.CreateConnectSignature(clientId)).OnSuccess(_ => - { - return RunCommandAsync(cmd); - }).Unwrap().OnSuccess(t => - { - AVRealtime.PrintLog("sesstion opened."); - if (t.Exception != null) - { - var imException = t.Exception.InnerException as AVIMException; - throw imException; - } - state = Status.Online; - var response = t.Result.Item2; - if (response.ContainsKey("st")) - { - _sesstionToken = response["st"] as string; - } - if (response.ContainsKey("stTtl")) - { - var stTtl = long.Parse(response["stTtl"].ToString()); - _sesstionTokenExpire = DateTime.Now.ToUnixTimeStamp() + stTtl * 1000; - } - return t.Result; - }); - - return result; - } - - } - - internal Task OpenSessionAsync(string clientId, - string tag = null, - string deviceId = null, - string nonce = null, - long timestamp = 0, - string signature = null, - bool secure = true) - { - var cmd = new SessionCommand() - .UA(VersionString) - .Tag(tag) - .DeviceId(deviceId) - .Option("open") - .PeerId(clientId) - .Argument("n", nonce) - .Argument("t", timestamp) - .Argument("s", signature); - - return RunCommandAsync(cmd).OnSuccess(t => - { - AVRealtime.PrintLog("sesstion opened."); - if (t.Exception != null) - { - var imException = t.Exception.InnerException as AVIMException; - throw imException; - } - state = Status.Online; - var response = t.Result.Item2; - if (response.ContainsKey("st")) - { - _sesstionToken = response["st"] as string; - } - if (response.ContainsKey("stTtl")) - { - var stTtl = long.Parse(response["stTtl"].ToString()); - _sesstionTokenExpire = DateTime.Now.ToUnixTimeStamp() + stTtl * 1000; - } - return t.Result; - }); - - } - - /// - /// 自动重连 - /// - /// - Task AutoReconnect() - { - AVRealtime.PrintLog("AutoReconnect started."); - var reconnectingArgs = new AVIMReconnectingEventArgs() - { - ClientId = _clientId, - IsAuto = true, - SessionToken = _sesstionToken - }; - m_OnReconnecting?.Invoke(this, reconnectingArgs); - - var tcs = new TaskCompletionSource(); - Task task; - if (reborn) - { - AVRealtime.PrintLog("both preferred and secondary websockets are expired, so try to request RTM router to get a new pair"); - task = OpenAsync(this._secure, Subprotocol, true); - } else { - var websocketServer = _wss; - if (useSecondary) { - AVRealtime.PrintLog(string.Format("preferred websocket server ({0}) network broken, take secondary server({1}) :", _wss, _secondaryWss)); - websocketServer = _secondaryWss; - } - task = OpenAsync(websocketServer, Subprotocol, true); - } - - task.ContinueWith(t => - { - if (t.IsFaulted || t.IsCanceled) { - state = Status.Reconnecting; - var reconnectFailedArgs = new AVIMReconnectFailedArgs() { - ClientId = _clientId, - IsAuto = true, - SessionToken = _sesstionToken, - FailedCode = 0// network broken. - }; - m_OnReconnectFailed?.Invoke(this, reconnectFailedArgs); - state = Status.Offline; - tcs.SetException(t.Exception); - throw t.Exception; - } else { - state = Status.Opened; - SetNetworkState(); - - void onClose(int code, string reason, string detail) { - AVRealtime.PrintLog("disconnect when open session"); - var ex = new Exception("connection is closed"); - tcs.SetException(ex); - AVWebSocketClient.OnClosed -= onClose; - throw ex; - }; - AVWebSocketClient.OnClosed += onClose; - - if (this.IsSesstionTokenExpired) { - AVRealtime.PrintLog("session is expired, auto relogin with clientId :" + _clientId); - return this.LogInAsync(_clientId, this._tag, this._deviceId, this._secure).ContinueWith(o => { - AVWebSocketClient.OnClosed -= onClose; - return !o.IsFaulted; - }); - } else { - var sessionCMD = new SessionCommand().UA(VersionString).R(1); - - if (string.IsNullOrEmpty(_tag)) { - sessionCMD = sessionCMD.Tag(_tag).SessionToken(this._sesstionToken); - } - - var cmd = sessionCMD.Option("open") - .PeerId(_clientId); - - AVRealtime.PrintLog("reopen session with session token :" + _sesstionToken); - return RunCommandAsync(cmd).ContinueWith(o => { - AVWebSocketClient.OnClosed -= onClose; - return !o.IsFaulted; - }); - } - } - }).Unwrap().ContinueWith(s => - { - if (s.IsFaulted || s.Exception != null) - { - var reconnectFailedArgs = new AVIMReconnectFailedArgs() - { - ClientId = _clientId, - IsAuto = true, - SessionToken = _sesstionToken, - FailedCode = 1 - }; - m_OnReconnectFailed?.Invoke(this, reconnectFailedArgs); - state = Status.Offline; - tcs.SetException(s.Exception); - } - else - { - var reconnectedArgs = new AVIMReconnectedEventArgs() { - ClientId = _clientId, - IsAuto = true, - SessionToken = _sesstionToken, - }; - state = Status.Online; - m_OnReconnected?.Invoke(this, reconnectedArgs); - ToggleNotification(true); - ToggleHeartBeating(true); - tcs.SetResult(true); - } - }); - - return tcs.Task; - } - - - - #region register IAVIMMessage - /// - /// Registers the subtype of the message. - /// - /// The 1st type parameter. - public void RegisterMessageType() where T : IAVIMMessage - { - AVIMCorePlugins.Instance.FreeStyleClassingController.RegisterSubclass(typeof(T)); - } - #endregion - - /// - /// open websocket with default configurations. - /// - /// - public Task OpenAsync(bool secure = true) - { - return this.OpenAsync(secure, null); - } - - /// - /// Open websocket connection. - /// - /// The async. - /// If set to true secure. - /// Subprotocol. - /// If set to true enforce. - /// Cancellation token. - public Task OpenAsync(bool secure, string subprotocol = null, bool enforce = false, CancellationToken cancellationToken = default(CancellationToken)) - { - _secure = secure; - if (state == Status.Online && !enforce) - { - AVRealtime.PrintLog("state is Status.Online."); - return Task.FromResult(true); - } - - if (AVClient.CurrentConfiguration.RealtimeServer != null) - { - _wss = AVClient.CurrentConfiguration.RealtimeServer; - AVRealtime.PrintLog("use configuration realtime server with url: " + _wss); - return OpenAsync(_wss, subprotocol, enforce); - } - var routerUrl = AVClient.CurrentConfiguration.RTMServer; - return RouterController.GetAsync(routerUrl, secure, cancellationToken).OnSuccess(r => - { - var routerState = r.Result; - if (routerState == null) - { - return Task.FromResult(false); - } - _wss = routerState.server; - _secondaryWss = routerState.secondary; - state = Status.Connecting; - AVRealtime.PrintLog("push router give a url :" + _wss); - return OpenAsync(routerState.server, subprotocol, enforce); - }).Unwrap(); - } - - /// - /// open webcoket connection with cloud. - /// - /// wss address - /// subprotocol for websocket - /// - /// - public Task OpenAsync(string url, string subprotocol = null, bool enforce = false, CancellationToken cancellationToken = default(CancellationToken)) - { - if (AVWebSocketClient.IsOpen && !enforce) - { - AVRealtime.PrintLog(url + "is already connectd."); - return Task.FromResult(true); - } - - AVRealtime.PrintLog("websocket try to connect url :" + url + " with subprotocol: " + subprotocol); - AVRealtime.PrintLog(url + " \tconnecting..."); - - return AVWebSocketClient.Connect(url, subprotocol); - } - - /// - /// send websocket command to Realtime server. - /// - /// - /// - public Task>> RunCommandAsync(AVIMCommand command) - { - command.AppId(AVClient.CurrentConfiguration.ApplicationId); - return this.AVIMCommandRunner.RunCommandAsync(command); - } - - /// - /// - /// - /// - public void RunCommand(AVIMCommand command) - { - command.AppId(AVClient.CurrentConfiguration.ApplicationId); - this.AVIMCommandRunner.RunCommand(command); - } - - internal Task AttachSignature(AVIMCommand command, Task SignatureTask) - { - AVRealtime.PrintLog("begin to attach singature."); - var tcs = new TaskCompletionSource(); - if (SignatureTask == null) - { - tcs.SetResult(command); - return tcs.Task; - } - return SignatureTask.OnSuccess(_ => - { - if (_.Result != null) - { - var signature = _.Result; - command.Argument("t", signature.Timestamp); - command.Argument("n", signature.Nonce); - command.Argument("s", signature.SignatureContent); - AVRealtime.PrintLog("AttachSignature ended.t:" + signature.Timestamp + ";n:" + signature.Nonce + ";s:" + signature.SignatureContent); - } - return command; - }); - } - - #region log out and clean event subscribtion - private void WebsocketClient_OnClosed(int errorCode, string reason, string detail) - { - PrintLog(string.Format("websocket closed with code is {0},reason is {1} and detail is {2}", errorCode, reason, detail)); - state = Status.Offline; - - ToggleNotification(false); - ToggleHeartBeating(false); - - var disconnectEventArgs = new AVIMDisconnectEventArgs(errorCode, reason, detail); - m_OnDisconnected?.Invoke(this, disconnectEventArgs); - - this.WebSocketState = new WebSocketStateOptions() - { - ClosedCode = errorCode - }; - PrepareReconnect(); - } - - private void WebsocketClient_OnError(string obj) - { - PrintLog("error:" + obj); - // 如果遇到 WebSocket 错误之后,先关闭,再按断线处理 - AVWebSocketClient.Close(); - WebsocketClient_OnClosed(0, obj, string.Empty); - } - - void PrepareReconnect() { - AVRealtime.PrintLog("Prepare Reconnect"); - Task.Delay(RECONNECT_DELAY).ContinueWith(_ => { - // 开启重连 - AutoReconnect().ContinueWith(t => { - if (t.IsFaulted) { - // 重连失败,延迟再次重连 - reconnectTimes++; - AVRealtime.PrintLog(String.Format("reconnect {0} times", reconnectTimes)); - if (reconnectTimes >= RECONNECT_FROM_APP_ROUTER) { - // 如果大于当前服务地址的最大重连次数,则清空 Router 后重新重连 - RouterController.ClearCache().ContinueWith(__ => { - reborn = true; - PrepareReconnect(); - }); - - } else if (reconnectTimes >= RECONNECT_USE_SECONDARY_TIMES) { - // 如果大于单台 IM 服务器的重连次数,则启用备用服务器 - useSecondary = true; - PrepareReconnect(); - } else { - PrepareReconnect(); - } - } else { - // 重连成功 - reconnectTimes = 0; - reborn = false; - useSecondary = false; - } - }); - }); - } - - internal void LogOut() - { - State = Status.Closed; - loggedOut = true; - Dispose(); - AVWebSocketClient.Close(); - } - - internal void Dispose() - { - var toggle = false; - ToggleNotification(toggle); - ToggleHeartBeating(toggle); - - if (m_NoticeReceived != null) - { - foreach (Delegate d in m_NoticeReceived.GetInvocationList()) - { - m_NoticeReceived -= (EventHandler)d; - } - } - if (m_OnDisconnected != null) - { - foreach (Delegate d in m_OnDisconnected.GetInvocationList()) - { - m_OnDisconnected -= (EventHandler)d; - } - } - } - #endregion - - static AVRealtime() - { -#if MONO || UNITY - versionString = "net-unity/" + Version; -#else - versionString = "net-universal/" + Version; -#endif - } - - private static readonly string versionString; - internal static string VersionString - { - get - { - return versionString; - } - } - - internal static System.Version Version - { - get - { - AssemblyName assemblyName = new AssemblyName(typeof(AVRealtime).GetTypeInfo().Assembly.FullName); - return assemblyName.Version; - } - } - } -} diff --git a/RTM/Source/Public/IAVIMListener.cs b/RTM/Source/Public/IAVIMListener.cs deleted file mode 100644 index 366f4a2..0000000 --- a/RTM/Source/Public/IAVIMListener.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime -{ - /// - /// WebSocket 监听服务端事件通知的接口 - /// 所有基于协议层的事件监听都需要实现这个接口,然后自定义监听协议。 - /// - public interface IAVIMListener - { - /// - /// 监听的协议 Hook - /// 例如,消息的协议是 direct 命令,因此消息监听需要判断 == "direct" 才可以调用 - /// - /// - /// - bool ProtocolHook(AVIMNotice notice); - - ///// - ///// 如果 返回 true,则会启动 NoticeAction 里面的回调逻辑 - ///// - //Action NoticeAction { get; set; } - - /// - /// 如果 返回 true,则会启动 NoticeAction 里面的回调逻辑 - /// - void OnNoticeReceived(AVIMNotice notice); - } -} diff --git a/RTM/Source/Public/IAVIMMessage.cs b/RTM/Source/Public/IAVIMMessage.cs deleted file mode 100644 index f797485..0000000 --- a/RTM/Source/Public/IAVIMMessage.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime -{ - /// - /// 消息接口 - /// 所有消息必须实现这个接口 - /// - public interface IAVIMMessage - { - /// - /// Serialize this instance. - /// - /// The serialize. - string Serialize(); - - /// - /// Validate the specified msgStr. - /// - /// The validate. - /// Message string. - bool Validate(string msgStr); - - /// - /// Deserialize the specified msgStr. - /// - /// The deserialize. - /// Message string. - IAVIMMessage Deserialize(string msgStr); - - /// - /// Gets or sets the conversation identifier. - /// - /// The conversation identifier. - string ConversationId { get; set; } - - /// - /// Gets or sets from client identifier. - /// - /// From client identifier. - string FromClientId { get; set; } - - /// - /// Gets or sets the identifier. - /// - /// The identifier. - string Id { get; set; } - - /// - /// Gets or sets the server timestamp. - /// - /// The server timestamp. - long ServerTimestamp { get; set; } - - /// - /// Gets or sets the rcp timestamp. - /// - /// The rcp timestamp. - long RcpTimestamp { get; set; } - - long UpdatedAt { get; set; } - - - #region mention features. - /// - /// Gets or sets a value indicating whether this mention all. - /// - /// true if mention all; otherwise, false. - bool MentionAll { get; set; } - - /// - /// Gets or sets the mention list. - /// - /// The mention list. - IEnumerable MentionList { get; set; } - #endregion - - } -} diff --git a/RTM/Source/Public/ICacheEngine.cs b/RTM/Source/Public/ICacheEngine.cs deleted file mode 100644 index 1c20ec7..0000000 --- a/RTM/Source/Public/ICacheEngine.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime -{ - public interface ISQLStorage - { - - } -} diff --git a/RTM/Source/Public/ISignatureFactory.cs b/RTM/Source/Public/ISignatureFactory.cs deleted file mode 100644 index ffda11a..0000000 --- a/RTM/Source/Public/ISignatureFactory.cs +++ /dev/null @@ -1,131 +0,0 @@ -using LeanCloud; -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - - -namespace LeanCloud.Realtime -{ - /// - /// 对话操作的签名类型,比如讲一个 client id 加入到对话中 - /// - /// - public enum ConversationSignatureAction - { - /// - /// add 加入对话和邀请对方加入对话 - /// - Add, - /// - /// remove 当前 client Id 离开对话和将其他人踢出对话 - /// - Remove - } - - /// - /// - /// - public interface ISignatureFactory - { - - /// - /// 构建登录签名 - /// - /// 需要登录到云端服务器的 client Id - /// - Task CreateConnectSignature(string clientId); - - /// - /// - /// - /// - /// - /// - Task CreateStartConversationSignature(string clientId, IEnumerable targetIds); - - /// - /// - /// - /// - /// - /// - /// 需要签名的操作 - /// - Task CreateConversationSignature(string conversationId, string clientId, IEnumerable targetIds, ConversationSignatureAction action); - } - - internal class DefaulSiganatureFactory : ISignatureFactory - { - Task ISignatureFactory.CreateConnectSignature(string clientId) - { - return Task.FromResult(null); - } - - Task ISignatureFactory.CreateConversationSignature(string conversationId, string clientId, IEnumerable targetIds, ConversationSignatureAction action) - { - return Task.FromResult(null); - } - - Task ISignatureFactory.CreateStartConversationSignature(string clientId, IEnumerable targetIds) - { - return Task.FromResult(null); - } - } - - public class LeanEngineSignatureFactory : ISignatureFactory - { - public Task CreateConnectSignature(string clientId) - { - var data = new Dictionary(); - data.Add("client_id", clientId); - return AVCloud.CallFunctionAsync>("connect", data).OnSuccess(_ => - { - var jsonData = _.Result; - var s = jsonData["signature"].ToString(); - var n = jsonData["nonce"].ToString(); - var t = long.Parse(jsonData["timestamp"].ToString()); - var signature = new AVIMSignature(s, t, n); - return signature; - }); - } - - public Task CreateStartConversationSignature(string clientId, IEnumerable targetIds) - { - var data = new Dictionary(); - data.Add("client_id", clientId); - data.Add("members", targetIds.ToList()); - return AVCloud.CallFunctionAsync>("startConversation", data).OnSuccess(_ => - { - var jsonData = _.Result; - var s = jsonData["signature"].ToString(); - var n = jsonData["nonce"].ToString(); - var t = long.Parse(jsonData["timestamp"].ToString()); - var signature = new AVIMSignature(s, t, n); - return signature; - }); - } - - public Task CreateConversationSignature(string conversationId, string clientId, IEnumerable targetIds, ConversationSignatureAction action) - { - var actionList = new string[] { "invite", "kick" }; - var data = new Dictionary(); - data.Add("client_id", clientId); - data.Add("conv_id", conversationId); - data.Add("members", targetIds.ToList()); - data.Add("action", actionList[(int)action]); - return AVCloud.CallFunctionAsync>("oprateConversation", data).OnSuccess(_ => - { - var jsonData = _.Result; - var s = jsonData["signature"].ToString(); - var n = jsonData["nonce"].ToString(); - var t = long.Parse(jsonData["timestamp"].ToString()); - var signature = new AVIMSignature(s, t, n); - return signature; - }); - } - - } -} diff --git a/RTM/Source/Public/Listener/AVIMConversationListener.cs b/RTM/Source/Public/Listener/AVIMConversationListener.cs deleted file mode 100644 index 77f931a..0000000 --- a/RTM/Source/Public/Listener/AVIMConversationListener.cs +++ /dev/null @@ -1,256 +0,0 @@ -using LeanCloud.Storage.Internal; -using LeanCloud.Realtime.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime -{ - /// - /// 对话中成员变动的事件参数,它提供被操作的对话(Conversation),操作类型(AffectedType) - /// 受影响的成员列表(AffectedMembers) - /// - public class AVIMOnMembersChangedEventArgs : EventArgs - { - /// - /// 本次成员变动中被操作的具体对话(AVIMConversation)的对象 - /// - public AVIMConversation Conversation { get; set; } - - /// - /// 变动的类型 - /// - public AVIMConversationEventType AffectedType { get; internal set; } - - /// - /// 受影响的成员的 Client Ids - /// - public IList AffectedMembers { get; set; } - - /// - /// 操作人的 Client ClientId - /// - public string Oprator { get; set; } - - /// - /// 操作的时间,已转化为本地时间 - /// - public DateTime OpratedTime { get; set; } - } - - /// - /// 变动的类型,目前支持如下: - /// 1、Joined:当前 Client 主动加入,案例:当 A 主动加入到对话,A 将收到 Joined 事件响应,其余的成员收到 MembersJoined 事件响应 - /// 2、Left:当前 Client 主动退出,案例:当 A 从对话中退出,A 将收到 Left 事件响应,其余的成员收到 MembersLeft 事件响应 - /// 3、MembersJoined:某个成员加入(区别于Joined和Kicked),案例:当 A 把 B 加入到对话中,C 将收到 MembersJoined 事件响应 - /// 4、MembersLeft:某个成员加入(区别于Joined和Kicked),案例:当 A 把 B 从对话中剔除,C 将收到 MembersLeft 事件响应 - /// 5、Invited:当前 Client 被邀请加入,案例:当 A 被 B 邀请加入到对话中,A 将收到 Invited 事件响应,B 将收到 Joined ,其余的成员收到 MembersJoined 事件响应 - /// 6、Kicked:当前 Client 被剔除,案例:当 A 被 B 从对话中剔除,A 将收到 Kicked 事件响应,B 将收到 Left,其余的成员收到 MembersLeft 事件响应 - /// - public enum AVIMConversationEventType - { - /// - /// 自身主动加入 - /// - Joined = 1, - /// - /// 自身主动离开 - /// - Left, - /// - /// 他人加入 - /// - MembersJoined, - /// - /// 他人离开 - /// - MembersLeft, - /// - /// 自身被邀请加入 - /// - Invited, - /// - /// 自身被他人剔除 - /// - Kicked - } - - #region AVIMMembersJoinListener - //when Members joined or invited by member,this listener will invoke AVIMOnMembersJoinedEventArgs event. - /// - /// 对话中有成员加入的时候,在改对话中的其他成员都会触发 事件 - /// - public class AVIMMembersJoinListener : IAVIMListener - { - - private EventHandler m_OnMembersJoined; - /// - /// 有成员加入到对话时,触发的事件 - /// - public event EventHandler OnMembersJoined - { - add - { - m_OnMembersJoined += value; - } - remove - { - m_OnMembersJoined -= value; - } - } - - public virtual void OnNoticeReceived(AVIMNotice notice) - { - if (m_OnMembersJoined != null) - { - var joinedMembers = AVDecoder.Instance.DecodeList(notice.RawData["m"]); - var ivitedBy = notice.RawData["initBy"].ToString(); - var conersationId = notice.RawData["cid"].ToString(); - var args = new AVIMOnMembersJoinedEventArgs() - { - ConversationId = conersationId, - InvitedBy = ivitedBy, - JoinedMembers = joinedMembers - }; - m_OnMembersJoined.Invoke(this, args); - } - } - - public virtual bool ProtocolHook(AVIMNotice notice) - { - if (notice.CommandName != "conv") return false; - if (!notice.RawData.ContainsKey("op")) return false; - var op = notice.RawData["op"].ToString(); - if (!op.Equals("members-joined")) return false; - return true; - } - } - #endregion - - #region AVIMMembersLeftListener - // when Members left or kicked by member,this listener will invoke AVIMOnMembersJoinedEventArgs event. - /// - /// 对话中有成员加入的时候,在改对话中的其他成员都会触发 OnMembersJoined 事件 - /// - public class AVIMMembersLeftListener : IAVIMListener - { - private EventHandler m_OnMembersLeft; - /// - /// 有成员加入到对话时,触发的事件 - /// - public event EventHandler OnMembersLeft - { - add - { - m_OnMembersLeft += value; - } - remove - { - m_OnMembersLeft -= value; - } - } - public virtual void OnNoticeReceived(AVIMNotice notice) - { - if (m_OnMembersLeft != null) - { - var leftMembers = AVDecoder.Instance.DecodeList(notice.RawData["m"]); - var kickedBy = notice.RawData["initBy"].ToString(); - var conersationId = notice.RawData["cid"].ToString(); - var args = new AVIMOnMembersLeftEventArgs() - { - ConversationId = conersationId, - KickedBy = kickedBy, - LeftMembers = leftMembers - }; - m_OnMembersLeft.Invoke(this, args); - } - } - - public virtual bool ProtocolHook(AVIMNotice notice) - { - if (notice.CommandName != "conv") return false; - if (!notice.RawData.ContainsKey("op")) return false; - var op = notice.RawData["op"].ToString(); - if (!op.Equals("members-left")) return false; - return true; - } - } - #endregion - - #region AVIMInvitedListener - public class AVIMInvitedListener : IAVIMListener - { - private EventHandler m_OnInvited; - public event EventHandler OnInvited { - add { - m_OnInvited += value; - } remove { - m_OnInvited -= value; - } - } - public void OnNoticeReceived(AVIMNotice notice) - { - if (m_OnInvited != null) - { - var ivitedBy = notice.RawData["initBy"].ToString(); - var conersationId = notice.RawData["cid"].ToString(); - var args = new AVIMOnInvitedEventArgs() - { - ConversationId = conersationId, - InvitedBy = ivitedBy, - }; - m_OnInvited.Invoke(this, args); - } - } - - public bool ProtocolHook(AVIMNotice notice) - { - if (notice.CommandName != "conv") return false; - if (!notice.RawData.ContainsKey("op")) return false; - var op = notice.RawData["op"].ToString(); - if (!op.Equals("joined")) return false; - return true; - } - } - #endregion - - #region AVIMKickedListener - public class AVIMKickedListener : IAVIMListener - { - private EventHandler m_OnKicked; - public event EventHandler OnKicked { - add { - m_OnKicked += value; - } remove { - m_OnKicked -= value; - } - } - public void OnNoticeReceived(AVIMNotice notice) - { - if (m_OnKicked != null) - { - var kickcdBy = notice.RawData["initBy"].ToString(); - var conersationId = notice.RawData["cid"].ToString(); - var args = new AVIMOnKickedEventArgs() - { - ConversationId = conersationId, - KickedBy = kickcdBy, - }; - m_OnKicked.Invoke(this, args); - } - } - - public bool ProtocolHook(AVIMNotice notice) - { - if (notice.CommandName != "conv") return false; - if (!notice.RawData.ContainsKey("op")) return false; - var op = notice.RawData["op"].ToString(); - if (!op.Equals("left")) return false; - return true; - } - } - #endregion - -} diff --git a/RTM/Source/Public/Listener/ConversationUnreadListener.cs b/RTM/Source/Public/Listener/ConversationUnreadListener.cs deleted file mode 100644 index bd48b89..0000000 --- a/RTM/Source/Public/Listener/ConversationUnreadListener.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using LeanCloud.Realtime.Internal; - -namespace LeanCloud.Realtime -{ - internal class ConversationUnreadListener : IAVIMListener - { - internal class UnreadConversationNotice : IEqualityComparer - { - internal readonly object mutex = new object(); - internal IAVIMMessage LastUnreadMessage { get; set; } - internal string ConvId { get; set; } - internal int UnreadCount { get; set; } - - public bool Equals(UnreadConversationNotice x, UnreadConversationNotice y) - { - return x.ConvId == y.ConvId; - } - - public int GetHashCode(UnreadConversationNotice obj) - { - return obj.ConvId.GetHashCode(); - } - - internal void AutomicIncrement() - { - lock (mutex) - { - UnreadCount++; - } - } - } - internal static readonly object sMutex = new object(); - internal static long NotifTime; - internal static HashSet UnreadConversations; - static ConversationUnreadListener() - { - UnreadConversations = new HashSet(new UnreadConversationNotice()); - NotifTime = DateTime.Now.ToUnixTimeStamp(); - } - - internal static void UpdateNotice(IAVIMMessage message) - { - lock (sMutex) - { - var convValidators = UnreadConversations.Where(c => c.ConvId == message.ConversationId); - if (convValidators != null) - { - if (convValidators.Count() > 0) - { - var currentNotice = convValidators.FirstOrDefault(); - currentNotice.AutomicIncrement(); - currentNotice.LastUnreadMessage = message; - } - else - { - var currentThread = new UnreadConversationNotice(); - currentThread.ConvId = message.ConversationId; - currentThread.LastUnreadMessage = message; - currentThread.AutomicIncrement(); - UnreadConversations.Add(currentThread); - } - } - } - } - internal static void ClearUnread(string convId) - { - UnreadConversations.Remove(Get(convId)); - } - internal static IEnumerable FindAllConvIds() - { - lock (sMutex) - { - return ConversationUnreadListener.UnreadConversations.Select(c => c.ConvId); - } - } - - internal static UnreadConversationNotice Get(string convId) - { - lock (sMutex) - { - var unreadValidator = ConversationUnreadListener.UnreadConversations.Where(c => c.ConvId == convId); - if (unreadValidator != null) - { - if (unreadValidator.Count() > 0) - { - var notice = unreadValidator.FirstOrDefault(); - return notice; - } - } - return null; - } - } - - public void OnNoticeReceived(AVIMNotice notice) - { - lock (sMutex) - { - if (notice.RawData.ContainsKey("convs")) - { - var unreadRawData = notice.RawData["convs"] as List; - if (notice.RawData.ContainsKey("notifTime")) - { - long.TryParse(notice.RawData["notifTime"].ToString(), out NotifTime); - } - foreach (var data in unreadRawData) - { - var dataMap = data as IDictionary; - if (dataMap != null) - { - var convId = dataMap["cid"].ToString(); - var ucn = Get(convId); - if (ucn == null) ucn = new UnreadConversationNotice(); - - ucn.ConvId = convId; - var unreadCount = 0; - Int32.TryParse(dataMap["unread"].ToString(), out unreadCount); - ucn.UnreadCount = unreadCount; - - #region restore last message for the conversation - if (dataMap.ContainsKey("data")) - { - var msgStr = dataMap["data"].ToString(); - var messageObj = AVRealtime.FreeStyleMessageClassingController.Instantiate(msgStr, dataMap); - ucn.LastUnreadMessage = messageObj; - } - - UnreadConversations.Add(ucn); - #endregion - } - } - } - } - } - - public bool ProtocolHook(AVIMNotice notice) - { - return notice.CommandName == "unread"; - } - } -} diff --git a/RTM/Source/Public/Listener/GoAwayListener.cs b/RTM/Source/Public/Listener/GoAwayListener.cs deleted file mode 100644 index 57ecb62..0000000 --- a/RTM/Source/Public/Listener/GoAwayListener.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace LeanCloud.Realtime { - /// - /// 强制被踢下线处理 - /// - internal class GoAwayListener : IAVIMListener { - Action onGoAway; - - public event Action OnGoAway { - add { - onGoAway += value; - } - remove { - onGoAway -= value; - } - } - - public void OnNoticeReceived(AVIMNotice notice) { - // TODO 退出并清理路由缓存 - onGoAway?.Invoke(); - } - - public bool ProtocolHook(AVIMNotice notice) { - return notice.CommandName == "goaway"; - } - } -} diff --git a/RTM/Source/Public/Listener/MessagePatchListener.cs b/RTM/Source/Public/Listener/MessagePatchListener.cs deleted file mode 100644 index b588190..0000000 --- a/RTM/Source/Public/Listener/MessagePatchListener.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime -{ - internal delegate void OnMessagePatch(IEnumerable messages); - internal class MessagePatchListener : IAVIMListener - { - public OnMessagePatch OnReceived { get; set; } - - public void OnNoticeReceived(AVIMNotice notice) - { - ICollection patchedMessages = new List(); - var msgObjs = notice.RawData["patches"] as IList; - if (msgObjs != null) - { - foreach (var msgObj in msgObjs) - { - var msgData = msgObj as IDictionary; - if (msgData != null) - { - var msgStr = msgData["data"] as string; - var message = AVRealtime.FreeStyleMessageClassingController.Instantiate(msgStr, msgData); - patchedMessages.Add(message); - } - } - } - if (OnReceived != null) - { - if (patchedMessages.Count > 0) - { - this.OnReceived(patchedMessages); - } - } - } - - public bool ProtocolHook(AVIMNotice notice) - { - if (notice.CommandName != "patch") return false; - if (!notice.RawData.ContainsKey("op")) return false; - if (notice.RawData["op"].ToString() != "modify") return false; - return true; - } - } -} diff --git a/RTM/Source/Public/Listener/OfflineMessageListener.cs b/RTM/Source/Public/Listener/OfflineMessageListener.cs deleted file mode 100644 index 983ad8a..0000000 --- a/RTM/Source/Public/Listener/OfflineMessageListener.cs +++ /dev/null @@ -1,42 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace LeanCloud.Realtime -{ - internal class OfflineMessageListener : IAVIMListener - { - private EventHandler m_OnOfflineMessageReceived; - public event EventHandler OnOfflineMessageReceived - { - add - { - m_OnOfflineMessageReceived += value; - } - remove - { - m_OnOfflineMessageReceived -= value; - } - } - public void OnNoticeReceived(AVIMNotice notice) - { - if (m_OnOfflineMessageReceived != null) - { - var msgStr = notice.RawData["msg"].ToString(); - var iMessage = AVRealtime.FreeStyleMessageClassingController.Instantiate(msgStr, notice.RawData); - var args = new AVIMMessageEventArgs(iMessage); - m_OnOfflineMessageReceived.Invoke(this, args); - } - - } - - public bool ProtocolHook(AVIMNotice notice) - { - if (notice.CommandName != "direct") return false; - if (!notice.RawData.ContainsKey("offline")) return false; - return true; - } - } -} diff --git a/RTM/Source/Public/Listener/SessionListener.cs b/RTM/Source/Public/Listener/SessionListener.cs deleted file mode 100644 index aad4c54..0000000 --- a/RTM/Source/Public/Listener/SessionListener.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace LeanCloud.Realtime -{ - internal class SessionListener : IAVIMListener - { - private Action _onSessionClosed; - public event Action OnSessionClosed - { - add - { - _onSessionClosed += value; - } - remove - { - _onSessionClosed -= value; - } - } - public void OnNoticeReceived(AVIMNotice notice) - { - var code = 0; - if (notice.RawData.ContainsKey("code")) - { - int.TryParse(notice.RawData["code"].ToString(), out code); - } - - var reason = ""; - if (notice.RawData.ContainsKey("reason")) - { - reason = notice.RawData["reason"].ToString(); - } - - var detail = ""; - if (notice.RawData.ContainsKey("detail")) - { - detail = notice.RawData["detail"].ToString(); - } - - if (_onSessionClosed != null) - { - _onSessionClosed(code, reason, detail); - } - } - - public bool ProtocolHook(AVIMNotice notice) - { - if (notice.CommandName != "session") return false; - if (!notice.RawData.ContainsKey("op")) return false; - if (notice.RawData.ContainsKey("i")) return false; - if (notice.RawData["op"].ToString() != "closed") return false; - - return true; - } - } -} diff --git a/RTM/Source/Public/Unity/AVRealtimeBehavior.cs b/RTM/Source/Public/Unity/AVRealtimeBehavior.cs deleted file mode 100644 index fe84b52..0000000 --- a/RTM/Source/Public/Unity/AVRealtimeBehavior.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using LeanCloud.Realtime.Internal; -using LeanCloud.Storage.Internal; -using UnityEngine; -using UnityEngine.Networking; - -namespace LeanCloud.Realtime -{ - /// - /// AVRealtime initialize behavior. - /// - public class AVRealtimeBehavior : AVInitializeBehaviour - { - public string RTMRouter = null; - - //void OnApplicationQuit() - //{ - // if (AVRealtime.clients != null) - // { - // foreach (var item in AVRealtime.clients) - // { - // item.Value.LinkedRealtime.LogOut(); - // } - // } - //} - - //private void Update() - //{ - // var available = Application.internetReachability != NetworkReachability.NotReachable; - // if (AVRealtime.clients != null) - // foreach (var item in AVRealtime.clients) - // { - // if (item.Value != null) - // if (item.Value.LinkedRealtime != null) - // item.Value.LinkedRealtime.InvokeNetworkState(available); - // } - //} - - //public override void Awake() - //{ - // base.Awake(); - // StartCoroutine(InitializeRealtime()); - // gameObject.name = "AVRealtimeInitializeBehavior"; - //} - - //public IEnumerator InitializeRealtime() - //{ - // if (isRealtimeInitialized) - // { - // yield break; - // } - // isRealtimeInitialized = true; - // yield return FetchRouter(); - //} - - - //[SerializeField] - //public bool secure; - //private static bool isRealtimeInitialized = false; - //public string Server; - //private IDictionary routerState; - - //public IEnumerator FetchRouter() - //{ - // var router = RTMRouter; - // if (string.IsNullOrEmpty(router)) { - // var state = AVPlugins.Instance.AppRouterController.Get(); - // router = state.RealtimeRouterServer; - // } - // var url = string.Format("https://{0}/v1/route?appId={1}", router, applicationID); - // if (secure) - // { - // url += "&secure=1"; - // } - - // var request = new UnityWebRequest(url); - // request.downloadHandler = new DownloadHandlerBuffer(); - // yield return request.Send(); - - // if (request.isError) - // { - // throw new AVException(AVException.ErrorCode.ConnectionFailed, "can not reach router.", null); - // } - - // var result = request.downloadHandler.text; - // routerState = Json.Parse(result) as IDictionary; - // if (routerState.Keys.Count == 0) - // { - // throw new KeyNotFoundException("Can not get websocket url from server,please check the appId."); - // } - // var ttl = long.Parse(routerState["ttl"].ToString()); - // var expire = DateTime.Now.AddSeconds(ttl); - // routerState["expire"] = expire.ToUnixTimeStamp(UnixTimeStampUnit.Second); - // Server = routerState["server"].ToString(); - //} - - - } -}