From 6fb2750b678ed6d25226fbde1ec31bfbf6083d73 Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 4 Mar 2020 14:31:48 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E6=95=B4=E7=90=86=E5=B7=A5=E7=A8=8B?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Libs/UnityEngine.dll | Bin 1345536 -> 0 bytes Libs/websocket-sharp.dll | Bin 253440 -> 0 bytes LiveQuery/LiveQuery.PCL/LiveQuery.PCL.csproj | 53 - .../LiveQuery.PCL/Properties/AssemblyInfo.cs | 26 - .../LiveQuery.Test/LiveQuery.Test.csproj | 41 - LiveQuery/LiveQuery.Test/Test.cs | 10 - LiveQuery/LiveQuery.Test/packages.config | 4 - .../LiveQuery.Unity/LiveQuery.Unity.csproj | 56 - .../Properties/AssemblyInfo.cs | 26 - LiveQuery/Source/AVLiveQuery.cs | 225 -- LiveQuery/Source/AVLiveQueryEventArgs.cs | 34 - LiveQuery/Source/AVLiveQueryExtensions.cs | 25 - RTM/RTM.PCL/RTM.PCL.csproj | 6 - RTM/RTM.Unity/RTM.Unity.csproj | 6 - RTM/RTM/Internal/AVIMCorePlugins.cs | 56 - RTM/RTM/Internal/Command/AVIMCommand.cs | 176 -- RTM/RTM/Internal/Command/AVIMCommandRunner.cs | 92 - RTM/RTM/Internal/Command/AckCommand.cs | 63 - .../Internal/Command/ConversationCommand.cs | 129 - .../Internal/Command/IAVIMCommandRunner.cs | 17 - RTM/RTM/Internal/Command/MessageCommand.cs | 83 - RTM/RTM/Internal/Command/PatchCommand.cs | 98 - RTM/RTM/Internal/Command/ReadCommand.cs | 91 - RTM/RTM/Internal/Command/SessionCommand.cs | 57 - .../DataEngine/Controller/DateTimeEngine.cs | 30 - .../DataEngine/Controller/DictionaryEngine.cs | 47 - .../DataEngine/Controller/StringEngine.cs | 36 - RTM/RTM/Internal/IAVIMPlatformHooks.cs | 15 - .../Subclassing/FreeStyleMessageClassInfo.cs | 75 - .../FreeStyleMessageClassingController.cs | 210 -- .../IFreeStyleMessageClassingController.cs | 18 - RTM/RTM/Internal/Protocol/AVIMProtocol.cs | 19 - RTM/RTM/Internal/Router/AVRouterController.cs | 173 -- .../Internal/Router/IAVRouterController.cs | 13 - RTM/RTM/Internal/Router/State/RouterState.cs | 20 - RTM/RTM/Internal/Timer/IAVTimer.cs | 49 - .../Timer/Portable/AVTimer.Portable.cs | 107 - .../Internal/WebSocket/IWebSocketClient.cs | 55 - .../DefaultWebSocketClient.Portable.cs | 107 - RTM/RTM/Public/AVIMAudioMessage.cs | 28 - RTM/RTM/Public/AVIMBinaryMessage.cs | 42 - RTM/RTM/Public/AVIMClient.cs | 1195 ---------- RTM/RTM/Public/AVIMConversation.cs | 1545 ------------ RTM/RTM/Public/AVIMConversationQuery.cs | 299 --- RTM/RTM/Public/AVIMEnumerator.cs | 248 -- RTM/RTM/Public/AVIMEventArgs.cs | 251 -- RTM/RTM/Public/AVIMException.cs | 235 -- RTM/RTM/Public/AVIMImageMessage.cs | 246 -- RTM/RTM/Public/AVIMMessage.cs | 162 -- .../Public/AVIMMessageClassNameAttribute.cs | 19 - .../Public/AVIMMessageFieldNameAttribute.cs | 18 - RTM/RTM/Public/AVIMMessageListener.cs | 143 -- RTM/RTM/Public/AVIMNotice.cs | 57 - RTM/RTM/Public/AVIMRecalledMessage.cs | 12 - RTM/RTM/Public/AVIMSignature.cs | 42 - RTM/RTM/Public/AVIMTemporaryConversation.cs | 37 - RTM/RTM/Public/AVIMTextMessage.cs | 47 - RTM/RTM/Public/AVIMTypedMessage.cs | 205 -- .../AVIMTypedMessageTypeIntAttribute.cs | 14 - RTM/RTM/Public/AVRealtime.cs | 1282 ---------- RTM/RTM/Public/IAVIMListener.cs | 33 - RTM/RTM/Public/IAVIMMessage.cs | 83 - RTM/RTM/Public/ICacheEngine.cs | 13 - RTM/RTM/Public/ISignatureFactory.cs | 131 - .../Listener/AVIMConversationListener.cs | 256 -- .../Listener/ConversationUnreadListener.cs | 145 -- RTM/RTM/Public/Listener/GoAwayListener.cs | 28 - .../Public/Listener/MessagePatchListener.cs | 48 - .../Public/Listener/OfflineMessageListener.cs | 42 - RTM/RTM/Public/Listener/SessionListener.cs | 58 - RTM/RTM/RTM.csproj | 17 - .../{Storage => }/Internal/Codec/LCDecoder.cs | 0 .../{Storage => }/Internal/Codec/LCEncoder.cs | 0 .../Internal/File/LCAWSUploader.cs | 0 .../Internal/File/LCMimeTypeMap.cs | 0 .../File/LCProgressableStreamContent.cs | 0 .../Internal/File/LCQiniuUploader.cs | 0 .../Internal/Http/LCHttpClient.cs | 0 .../Internal/Http/LeanCloudJsonConverter.cs | 0 .../{Storage => }/Internal/Object/LCBatch.cs | 0 .../Internal/Object/LCObjectData.cs | 0 .../Internal/Object/LCSubClassInfo.cs | 0 .../Internal/Operation/ILCOperation.cs | 0 .../Internal/Operation/LCAddOperation.cs | 0 .../Operation/LCAddRelationOperation.cs | 0 .../Operation/LCAddUniqueOperation.cs | 0 .../Internal/Operation/LCDeleteOperation.cs | 0 .../Internal/Operation/LCNumberOperation.cs | 0 .../Internal/Operation/LCRemoveOperation.cs | 0 .../Operation/LCRemoveRelationOperation.cs | 0 .../Internal/Operation/LCSetOperation.cs | 0 .../Internal/Query/ILCQueryCondition.cs | 0 .../Query/LCCompositionalCondition.cs | 0 .../Internal/Query/LCEqualCondition.cs | 0 .../Internal/Query/LCOperationCondition.cs | 0 .../Internal/Query/LCRelatedCondition.cs | 0 Storage/{Storage => }/LCACL.cs | 0 Storage/{Storage => }/LCCloud.cs | 0 Storage/{Storage => }/LCException.cs | 2 +- Storage/{Storage => }/LCFile.cs | 0 Storage/{Storage => }/LCGeoPoint.cs | 0 Storage/{Storage => }/LCObject.cs | 0 Storage/{Storage => }/LCQuery.cs | 0 Storage/{Storage => }/LCRelation.cs | 0 Storage/{Storage => }/LCRole.cs | 0 Storage/{Storage => }/LCUser.cs | 0 .../LCUserAuthDataLoginOption.cs | 0 Storage/{Storage => }/LeanCloud.cs | 0 Storage/Source/Internal/AVCorePlugins.cs | 384 --- .../Internal/AppRouter/AppRouterController.cs | 103 - .../Internal/AppRouter/AppRouterState.cs | 85 - .../AppRouter/IAppRouterController.cs | 22 - .../IAVAuthenticationProvider.cs | 36 - .../Cloud/Controller/AVCloudCodeController.cs | 58 - .../Controller/IAVCloudCodeController.cs | 19 - Storage/Source/Internal/Command/AVCommand.cs | 118 - .../Internal/Command/AVCommandRunner.cs | 167 -- .../Internal/Command/IAVCommandRunner.cs | 24 - .../Config/Controller/AVConfigController.cs | 40 - .../Controller/AVCurrentConfigController.cs | 76 - .../Config/Controller/IAVConfigController.cs | 21 - .../Controller/IAVCurrentConfigController.cs | 31 - .../Dispatcher/Unity/UnityDispatcher.cs | 102 - Storage/Source/Internal/Encoding/AVDecoder.cs | 164 -- Storage/Source/Internal/Encoding/AVEncoder.cs | 138 -- .../Source/Internal/Encoding/AVObjectCoder.cs | 105 - .../Internal/Encoding/NoObjectsEncoder.cs | 23 - .../Encoding/PointerOrLocalIdEncoder.cs | 72 - .../File/Controller/AVFileController.cs | 133 -- .../File/Controller/AWSS3FileController.cs | 54 - .../File/Controller/IAVFileController.cs | 24 - .../Controller/QCloudCosFileController.cs | 250 -- .../File/Controller/QiniuFileController.cs | 332 --- .../Internal/File/Cryptography/MD5/MD5.cs | 566 ----- .../SHA1/SHA1CryptoServiceProvider.cs | 495 ---- .../Source/Internal/File/State/FileState.cs | 27 - .../Source/Internal/HttpClient/HttpRequest.cs | 25 - .../Source/Internal/HttpClient/IHttpClient.cs | 24 - .../Portable/HttpClient.Portable.cs | 163 -- .../HttpClient/Unity/HttpClient.Unity.cs | 168 -- Storage/Source/Internal/IAVCorePlugins.cs | 26 - .../Controller/IInstallationIdController.cs | 24 - .../Controller/InstallationIdController.cs | 66 - .../Object/Controller/AVObjectController.cs | 248 -- .../Object/Controller/IAVObjectController.cs | 37 - .../Controller/IAVObjectCurrentController.cs | 51 - .../Internal/Object/State/IObjectState.cs | 19 - .../Object/State/MutableObjectState.cs | 114 - .../IObjectSubclassingController.cs | 24 - .../Object/Subclassing/ObjectSubclassInfo.cs | 42 - .../ObjectSubclassingController.cs | 171 -- .../Internal/Operation/AVAddOperation.cs | 53 - .../Operation/AVAddUniqueOperation.cs | 69 - .../Internal/Operation/AVDeleteOperation.cs | 38 - .../Internal/Operation/AVFieldOperations.cs | 40 - .../Operation/AVIncrementOperation.cs | 166 -- .../Internal/Operation/AVRelationOperation.cs | 118 - .../Internal/Operation/AVRemoveOperation.cs | 68 - .../Internal/Operation/AVSetOperation.cs | 21 - .../Internal/Operation/IAVFieldOperation.cs | 42 - .../Query/Controller/AVQueryController.cs | 110 - .../Query/Controller/IAVQueryController.cs | 21 - .../Session/Controller/AVSessionController.cs | 50 - .../Controller/IAVSessionController.cs | 15 - .../Internal/Storage/IStorageController.cs | 54 - .../Storage/NetCore/StorageController.cs | 184 -- .../Storage/Portable/StorageController.cs | 192 -- .../Storage/Unity/StorageController.cs | 250 -- .../Controller/AVCurrentUserController.cs | 159 -- .../User/Controller/AVUserController.cs | 161 -- .../Controller/IAVCurrentUserController.cs | 11 - .../User/Controller/IAVUserController.cs | 42 - .../Internal/Utilities/AVConfigExtensions.cs | 20 - .../Internal/Utilities/AVFileExtensions.cs | 20 - .../Internal/Utilities/AVObjectExtensions.cs | 225 -- .../Internal/Utilities/AVQueryExtensions.cs | 28 - .../Utilities/AVRelationExtensions.cs | 28 - .../Internal/Utilities/AVSessionExtensions.cs | 26 - .../Internal/Utilities/AVUserExtensions.cs | 22 - .../Utilities/FlexibleDictionaryWrapper.cs | 104 - .../Internal/Utilities/FlexibleListWrapper.cs | 81 - .../Internal/Utilities/IJsonConvertible.cs | 15 - .../Utilities/IdentityEqualityComparer.cs | 23 - .../Internal/Utilities/InternalExtensions.cs | 105 - Storage/Source/Internal/Utilities/Json.cs | 554 ----- Storage/Source/Internal/Utilities/LockSet.cs | 41 - .../Internal/Utilities/ReflectionHelpers.cs | 123 - .../Utilities/SynchronizedEventHandler.cs | 66 - .../Source/Internal/Utilities/TaskQueue.cs | 68 - .../Internal/Utilities/XamarinAttributes.cs | 426 ---- Storage/Source/Public/AVACL.cs | 284 --- Storage/Source/Public/AVClassNameAttribute.cs | 29 - Storage/Source/Public/AVClient.cs | 506 ---- Storage/Source/Public/AVCloud.cs | 583 ----- Storage/Source/Public/AVConfig.cs | 143 -- .../Public/AVDownloadProgressEventArgs.cs | 15 - Storage/Source/Public/AVException.cs | 275 --- Storage/Source/Public/AVExtensions.cs | 160 -- Storage/Source/Public/AVFieldNameAttribute.cs | 30 - Storage/Source/Public/AVFile.cs | 728 ------ Storage/Source/Public/AVGeoDistance.cs | 78 - Storage/Source/Public/AVGeoPoint.cs | 107 - Storage/Source/Public/AVObject.cs | 2099 ----------------- Storage/Source/Public/AVQuery.cs | 360 --- Storage/Source/Public/AVQueryExtensions.cs | 818 ------- Storage/Source/Public/AVRelation.cs | 175 -- Storage/Source/Public/AVRole.cs | 111 - Storage/Source/Public/AVSession.cs | 111 - Storage/Source/Public/AVStatus.cs | 24 - .../Public/AVUploadProgressEventArgs.cs | 15 - Storage/Source/Public/AVUser.cs | 1310 ---------- .../Public/AVUserAuthDataLogInOption.cs | 33 - Storage/Source/Public/IAVQuery.cs | 2068 ---------------- .../Public/LeaderBoard/AVLeaderboard.cs | 479 ---- .../LeaderBoard/AVLeaderboardArchive.cs | 76 - .../Source/Public/LeaderBoard/AVRanking.cs | 72 - .../Source/Public/LeaderBoard/AVStatistic.cs | 52 - .../Public/Unity/AVInitializeBehaviour.cs | 75 - Storage/Source/Public/Utilities/Conversion.cs | 138 -- .../Storage.PCL/Properties/AssemblyInfo.cs | 26 - Storage/Storage.PCL/Storage.PCL.csproj | 363 --- Storage/Storage.PCL/packages.config | 4 - Storage/Storage.Test/Utils.cs | 69 - .../Storage.Unity/Properties/AssemblyInfo.cs | 26 - Storage/Storage.Unity/Storage.Unity.csproj | 368 --- Storage/{Storage => }/Storage.csproj | 7 +- Storage/Storage/Internal_/AVCorePlugins.cs | 170 -- .../Cloud/Controller/AVCloudCodeController.cs | 40 - .../Storage/Internal_/Command/AVCommand.cs | 32 - .../Internal_/Command/AVCommandRunner.cs | 180 -- .../Internal_/Command/EngineCommand.cs | 7 - .../Storage/Internal_/Command/RTMCommand.cs | 7 - .../Storage/Internal_/Encoding/AVDecoder.cs | 95 - .../Storage/Internal_/Encoding/AVEncoder.cs | 87 - .../Internal_/Encoding/AVObjectCoder.cs | 104 - .../Encoding/PointerOrLocalIdEncoder.cs | 69 - .../File/Controller/AVFileController.cs | 129 - .../Internal_/File/Controller/AWSUploader.cs | 39 - .../File/Controller/QCloudUploader.cs | 216 -- .../File/Controller/QiniuUploader.cs | 294 --- .../Internal_/File/Cryptography/MD5/MD5.cs | 566 ----- .../SHA1/SHA1CryptoServiceProvider.cs | 495 ---- .../Storage/Internal_/File/State/FileState.cs | 38 - .../Controller/InstallationIdController.cs | 39 - .../Internal_/Object/State/IObjectState.cs | 17 - .../Object/State/MutableObjectState.cs | 82 - .../Internal_/Object/State/ObjectData.cs | 69 - .../Object/Subclassing/ObjectSubclassInfo.cs | 33 - .../ObjectSubclassingController.cs | 134 -- .../Internal_/Operation/AVAddOperation.cs | 53 - .../Operation/AVAddUniqueOperation.cs | 68 - .../Internal_/Operation/AVDeleteOperation.cs | 33 - .../Internal_/Operation/AVFieldOperations.cs | 37 - .../Operation/AVIncrementOperation.cs | 147 -- .../Operation/AVRelationOperation.cs | 118 - .../Internal_/Operation/AVRemoveOperation.cs | 55 - .../Internal_/Operation/AVSetOperation.cs | 21 - .../Internal_/Operation/IAVFieldOperation.cs | 42 - .../Query/Controller/AVQueryController.cs | 46 - .../Internal_/Query/IQueryCondition.cs | 10 - .../Query/QueryCompositionalCondition.cs | 270 --- .../Internal_/Query/QueryEqualCondition.cs | 26 - .../Query/QueryOperationCondition.cs | 30 - .../Internal_/Query/QueryRelatedCondition.cs | 29 - .../User/Controller/AVUserController.cs | 114 - .../Internal_/Utilities/AVObjectExtensions.cs | 113 - .../Utilities/AVRelationExtensions.cs | 28 - .../Utilities/FlexibleDictionaryWrapper.cs | 104 - .../Utilities/FlexibleListWrapper.cs | 81 - .../Internal_/Utilities/IJsonConvertible.cs | 15 - .../Utilities/IdentityEqualityComparer.cs | 21 - .../Internal_/Utilities/InternalExtensions.cs | 105 - Storage/Storage/Internal_/Utilities/Json.cs | 554 ----- .../Storage/Internal_/Utilities/LockSet.cs | 41 - .../Internal_/Utilities/ReflectionHelpers.cs | 123 - .../Utilities/SynchronizedEventHandler.cs | 66 - .../Storage/Internal_/Utilities/TaskQueue.cs | 68 - .../Internal_/Utilities/XamarinAttributes.cs | 426 ---- Storage/Storage/Public/AVACL.cs | 284 --- .../Storage/Public/AVClassNameAttribute.cs | 29 - Storage/Storage/Public/AVClient.cs | 198 -- Storage/Storage/Public/AVCloud.cs | 311 --- Storage/Storage/Public/AVException.cs | 285 --- Storage/Storage/Public/AVExtensions.cs | 104 - .../Storage/Public/AVFieldNameAttribute.cs | 23 - Storage/Storage/Public/AVFile.cs | 565 ----- Storage/Storage/Public/AVGeoDistance.cs | 68 - Storage/Storage/Public/AVGeoPoint.cs | 91 - Storage/Storage/Public/AVObject.cs | 802 ------- Storage/Storage/Public/AVQuery.cs | 347 --- Storage/Storage/Public/AVQueryExtensions.cs | 780 ------ Storage/Storage/Public/AVRelation.cs | 147 -- Storage/Storage/Public/AVRole.cs | 93 - .../Public/AVUploadProgressEventArgs.cs | 14 - Storage/Storage/Public/AVUser.cs | 646 ----- .../Public/AVUserAuthDataLogInOption.cs | 33 - .../Public/LeaderBoard/AVLeaderboard.cs | 498 ---- .../LeaderBoard/AVLeaderboardArchive.cs | 76 - .../Storage/Public/LeaderBoard/AVRanking.cs | 72 - .../Storage/Public/LeaderBoard/AVStatistic.cs | 52 - .../Storage/Public/Utilities/Conversion.cs | 102 - Test/Common.Test/AppRouterTest.cs | 14 +- {Storage => Test}/Storage.Test/ACLTest.cs | 0 {Storage => Test}/Storage.Test/CloudTest.cs | 0 .../Storage.Test/ExceptionTest.cs | 0 {Storage => Test}/Storage.Test/FileTest.cs | 0 {Storage => Test}/Storage.Test/GeoTest.cs | 0 {Storage => Test}/Storage.Test/ObjectTest.cs | 0 .../Storage.Test/OperationTest.cs | 0 {Storage => Test}/Storage.Test/QueryTest.cs | 0 .../Storage.Test/RelationTest.cs | 0 {Storage => Test}/Storage.Test/RoleTest.cs | 0 .../Storage.Test/Storage.Test.csproj | 2 +- .../Storage.Test/SubClassTest.cs | 0 {Storage => Test}/Storage.Test/UserTest.cs | 0 Test/Storage.Test/Utils.cs | 25 + .../Storage.Test/assets/hello.png | Bin .../Storage.Test/assets/test.apk | Bin csharp-sdk.sln | 74 +- 319 files changed, 40 insertions(+), 41339 deletions(-) delete mode 100644 Libs/UnityEngine.dll delete mode 100644 Libs/websocket-sharp.dll delete mode 100644 LiveQuery/LiveQuery.PCL/LiveQuery.PCL.csproj delete mode 100644 LiveQuery/LiveQuery.PCL/Properties/AssemblyInfo.cs delete mode 100644 LiveQuery/LiveQuery.Test/LiveQuery.Test.csproj delete mode 100644 LiveQuery/LiveQuery.Test/Test.cs delete mode 100644 LiveQuery/LiveQuery.Test/packages.config delete mode 100644 LiveQuery/LiveQuery.Unity/LiveQuery.Unity.csproj delete mode 100644 LiveQuery/LiveQuery.Unity/Properties/AssemblyInfo.cs delete mode 100644 LiveQuery/Source/AVLiveQuery.cs delete mode 100644 LiveQuery/Source/AVLiveQueryEventArgs.cs delete mode 100644 LiveQuery/Source/AVLiveQueryExtensions.cs delete mode 100644 RTM/RTM/Internal/AVIMCorePlugins.cs delete mode 100644 RTM/RTM/Internal/Command/AVIMCommand.cs delete mode 100644 RTM/RTM/Internal/Command/AVIMCommandRunner.cs delete mode 100644 RTM/RTM/Internal/Command/AckCommand.cs delete mode 100644 RTM/RTM/Internal/Command/ConversationCommand.cs delete mode 100644 RTM/RTM/Internal/Command/IAVIMCommandRunner.cs delete mode 100644 RTM/RTM/Internal/Command/MessageCommand.cs delete mode 100644 RTM/RTM/Internal/Command/PatchCommand.cs delete mode 100644 RTM/RTM/Internal/Command/ReadCommand.cs delete mode 100644 RTM/RTM/Internal/Command/SessionCommand.cs delete mode 100644 RTM/RTM/Internal/DataEngine/Controller/DateTimeEngine.cs delete mode 100644 RTM/RTM/Internal/DataEngine/Controller/DictionaryEngine.cs delete mode 100644 RTM/RTM/Internal/DataEngine/Controller/StringEngine.cs delete mode 100644 RTM/RTM/Internal/IAVIMPlatformHooks.cs delete mode 100644 RTM/RTM/Internal/Message/Subclassing/FreeStyleMessageClassInfo.cs delete mode 100644 RTM/RTM/Internal/Message/Subclassing/FreeStyleMessageClassingController.cs delete mode 100644 RTM/RTM/Internal/Message/Subclassing/IFreeStyleMessageClassingController.cs delete mode 100644 RTM/RTM/Internal/Protocol/AVIMProtocol.cs delete mode 100644 RTM/RTM/Internal/Router/AVRouterController.cs delete mode 100644 RTM/RTM/Internal/Router/IAVRouterController.cs delete mode 100644 RTM/RTM/Internal/Router/State/RouterState.cs delete mode 100644 RTM/RTM/Internal/Timer/IAVTimer.cs delete mode 100644 RTM/RTM/Internal/Timer/Portable/AVTimer.Portable.cs delete mode 100644 RTM/RTM/Internal/WebSocket/IWebSocketClient.cs delete mode 100644 RTM/RTM/Internal/WebSocket/Portable/DefaultWebSocketClient.Portable.cs delete mode 100644 RTM/RTM/Public/AVIMAudioMessage.cs delete mode 100644 RTM/RTM/Public/AVIMBinaryMessage.cs delete mode 100644 RTM/RTM/Public/AVIMClient.cs delete mode 100644 RTM/RTM/Public/AVIMConversation.cs delete mode 100644 RTM/RTM/Public/AVIMConversationQuery.cs delete mode 100644 RTM/RTM/Public/AVIMEnumerator.cs delete mode 100644 RTM/RTM/Public/AVIMEventArgs.cs delete mode 100644 RTM/RTM/Public/AVIMException.cs delete mode 100644 RTM/RTM/Public/AVIMImageMessage.cs delete mode 100644 RTM/RTM/Public/AVIMMessage.cs delete mode 100644 RTM/RTM/Public/AVIMMessageClassNameAttribute.cs delete mode 100644 RTM/RTM/Public/AVIMMessageFieldNameAttribute.cs delete mode 100644 RTM/RTM/Public/AVIMMessageListener.cs delete mode 100644 RTM/RTM/Public/AVIMNotice.cs delete mode 100644 RTM/RTM/Public/AVIMRecalledMessage.cs delete mode 100644 RTM/RTM/Public/AVIMSignature.cs delete mode 100644 RTM/RTM/Public/AVIMTemporaryConversation.cs delete mode 100644 RTM/RTM/Public/AVIMTextMessage.cs delete mode 100644 RTM/RTM/Public/AVIMTypedMessage.cs delete mode 100644 RTM/RTM/Public/AVIMTypedMessageTypeIntAttribute.cs delete mode 100644 RTM/RTM/Public/AVRealtime.cs delete mode 100644 RTM/RTM/Public/IAVIMListener.cs delete mode 100644 RTM/RTM/Public/IAVIMMessage.cs delete mode 100644 RTM/RTM/Public/ICacheEngine.cs delete mode 100644 RTM/RTM/Public/ISignatureFactory.cs delete mode 100644 RTM/RTM/Public/Listener/AVIMConversationListener.cs delete mode 100644 RTM/RTM/Public/Listener/ConversationUnreadListener.cs delete mode 100644 RTM/RTM/Public/Listener/GoAwayListener.cs delete mode 100644 RTM/RTM/Public/Listener/MessagePatchListener.cs delete mode 100644 RTM/RTM/Public/Listener/OfflineMessageListener.cs delete mode 100644 RTM/RTM/Public/Listener/SessionListener.cs delete mode 100644 RTM/RTM/RTM.csproj rename Storage/{Storage => }/Internal/Codec/LCDecoder.cs (100%) rename Storage/{Storage => }/Internal/Codec/LCEncoder.cs (100%) rename Storage/{Storage => }/Internal/File/LCAWSUploader.cs (100%) rename Storage/{Storage => }/Internal/File/LCMimeTypeMap.cs (100%) rename Storage/{Storage => }/Internal/File/LCProgressableStreamContent.cs (100%) rename Storage/{Storage => }/Internal/File/LCQiniuUploader.cs (100%) rename Storage/{Storage => }/Internal/Http/LCHttpClient.cs (100%) rename Storage/{Storage => }/Internal/Http/LeanCloudJsonConverter.cs (100%) rename Storage/{Storage => }/Internal/Object/LCBatch.cs (100%) rename Storage/{Storage => }/Internal/Object/LCObjectData.cs (100%) rename Storage/{Storage => }/Internal/Object/LCSubClassInfo.cs (100%) rename Storage/{Storage => }/Internal/Operation/ILCOperation.cs (100%) rename Storage/{Storage => }/Internal/Operation/LCAddOperation.cs (100%) rename Storage/{Storage => }/Internal/Operation/LCAddRelationOperation.cs (100%) rename Storage/{Storage => }/Internal/Operation/LCAddUniqueOperation.cs (100%) rename Storage/{Storage => }/Internal/Operation/LCDeleteOperation.cs (100%) rename Storage/{Storage => }/Internal/Operation/LCNumberOperation.cs (100%) rename Storage/{Storage => }/Internal/Operation/LCRemoveOperation.cs (100%) rename Storage/{Storage => }/Internal/Operation/LCRemoveRelationOperation.cs (100%) rename Storage/{Storage => }/Internal/Operation/LCSetOperation.cs (100%) rename Storage/{Storage => }/Internal/Query/ILCQueryCondition.cs (100%) rename Storage/{Storage => }/Internal/Query/LCCompositionalCondition.cs (100%) rename Storage/{Storage => }/Internal/Query/LCEqualCondition.cs (100%) rename Storage/{Storage => }/Internal/Query/LCOperationCondition.cs (100%) rename Storage/{Storage => }/Internal/Query/LCRelatedCondition.cs (100%) rename Storage/{Storage => }/LCACL.cs (100%) rename Storage/{Storage => }/LCCloud.cs (100%) rename Storage/{Storage => }/LCException.cs (89%) rename Storage/{Storage => }/LCFile.cs (100%) rename Storage/{Storage => }/LCGeoPoint.cs (100%) rename Storage/{Storage => }/LCObject.cs (100%) rename Storage/{Storage => }/LCQuery.cs (100%) rename Storage/{Storage => }/LCRelation.cs (100%) rename Storage/{Storage => }/LCRole.cs (100%) rename Storage/{Storage => }/LCUser.cs (100%) rename Storage/{Storage => }/LCUserAuthDataLoginOption.cs (100%) rename Storage/{Storage => }/LeanCloud.cs (100%) delete mode 100644 Storage/Source/Internal/AVCorePlugins.cs delete mode 100644 Storage/Source/Internal/AppRouter/AppRouterController.cs delete mode 100644 Storage/Source/Internal/AppRouter/AppRouterState.cs delete mode 100644 Storage/Source/Internal/AppRouter/IAppRouterController.cs delete mode 100644 Storage/Source/Internal/Authentication/IAVAuthenticationProvider.cs delete mode 100644 Storage/Source/Internal/Cloud/Controller/AVCloudCodeController.cs delete mode 100644 Storage/Source/Internal/Cloud/Controller/IAVCloudCodeController.cs delete mode 100644 Storage/Source/Internal/Command/AVCommand.cs delete mode 100644 Storage/Source/Internal/Command/AVCommandRunner.cs delete mode 100644 Storage/Source/Internal/Command/IAVCommandRunner.cs delete mode 100644 Storage/Source/Internal/Config/Controller/AVConfigController.cs delete mode 100644 Storage/Source/Internal/Config/Controller/AVCurrentConfigController.cs delete mode 100644 Storage/Source/Internal/Config/Controller/IAVConfigController.cs delete mode 100644 Storage/Source/Internal/Config/Controller/IAVCurrentConfigController.cs delete mode 100644 Storage/Source/Internal/Dispatcher/Unity/UnityDispatcher.cs delete mode 100644 Storage/Source/Internal/Encoding/AVDecoder.cs delete mode 100644 Storage/Source/Internal/Encoding/AVEncoder.cs delete mode 100644 Storage/Source/Internal/Encoding/AVObjectCoder.cs delete mode 100644 Storage/Source/Internal/Encoding/NoObjectsEncoder.cs delete mode 100644 Storage/Source/Internal/Encoding/PointerOrLocalIdEncoder.cs delete mode 100644 Storage/Source/Internal/File/Controller/AVFileController.cs delete mode 100644 Storage/Source/Internal/File/Controller/AWSS3FileController.cs delete mode 100644 Storage/Source/Internal/File/Controller/IAVFileController.cs delete mode 100644 Storage/Source/Internal/File/Controller/QCloudCosFileController.cs delete mode 100644 Storage/Source/Internal/File/Controller/QiniuFileController.cs delete mode 100644 Storage/Source/Internal/File/Cryptography/MD5/MD5.cs delete mode 100644 Storage/Source/Internal/File/Cryptography/SHA1/SHA1CryptoServiceProvider.cs delete mode 100644 Storage/Source/Internal/File/State/FileState.cs delete mode 100644 Storage/Source/Internal/HttpClient/HttpRequest.cs delete mode 100644 Storage/Source/Internal/HttpClient/IHttpClient.cs delete mode 100644 Storage/Source/Internal/HttpClient/Portable/HttpClient.Portable.cs delete mode 100644 Storage/Source/Internal/HttpClient/Unity/HttpClient.Unity.cs delete mode 100644 Storage/Source/Internal/IAVCorePlugins.cs delete mode 100644 Storage/Source/Internal/InstallationId/Controller/IInstallationIdController.cs delete mode 100644 Storage/Source/Internal/InstallationId/Controller/InstallationIdController.cs delete mode 100644 Storage/Source/Internal/Object/Controller/AVObjectController.cs delete mode 100644 Storage/Source/Internal/Object/Controller/IAVObjectController.cs delete mode 100644 Storage/Source/Internal/Object/Controller/IAVObjectCurrentController.cs delete mode 100644 Storage/Source/Internal/Object/State/IObjectState.cs delete mode 100644 Storage/Source/Internal/Object/State/MutableObjectState.cs delete mode 100644 Storage/Source/Internal/Object/Subclassing/IObjectSubclassingController.cs delete mode 100644 Storage/Source/Internal/Object/Subclassing/ObjectSubclassInfo.cs delete mode 100644 Storage/Source/Internal/Object/Subclassing/ObjectSubclassingController.cs delete mode 100644 Storage/Source/Internal/Operation/AVAddOperation.cs delete mode 100644 Storage/Source/Internal/Operation/AVAddUniqueOperation.cs delete mode 100644 Storage/Source/Internal/Operation/AVDeleteOperation.cs delete mode 100644 Storage/Source/Internal/Operation/AVFieldOperations.cs delete mode 100644 Storage/Source/Internal/Operation/AVIncrementOperation.cs delete mode 100644 Storage/Source/Internal/Operation/AVRelationOperation.cs delete mode 100644 Storage/Source/Internal/Operation/AVRemoveOperation.cs delete mode 100644 Storage/Source/Internal/Operation/AVSetOperation.cs delete mode 100644 Storage/Source/Internal/Operation/IAVFieldOperation.cs delete mode 100644 Storage/Source/Internal/Query/Controller/AVQueryController.cs delete mode 100644 Storage/Source/Internal/Query/Controller/IAVQueryController.cs delete mode 100644 Storage/Source/Internal/Session/Controller/AVSessionController.cs delete mode 100644 Storage/Source/Internal/Session/Controller/IAVSessionController.cs delete mode 100644 Storage/Source/Internal/Storage/IStorageController.cs delete mode 100644 Storage/Source/Internal/Storage/NetCore/StorageController.cs delete mode 100644 Storage/Source/Internal/Storage/Portable/StorageController.cs delete mode 100644 Storage/Source/Internal/Storage/Unity/StorageController.cs delete mode 100644 Storage/Source/Internal/User/Controller/AVCurrentUserController.cs delete mode 100644 Storage/Source/Internal/User/Controller/AVUserController.cs delete mode 100644 Storage/Source/Internal/User/Controller/IAVCurrentUserController.cs delete mode 100644 Storage/Source/Internal/User/Controller/IAVUserController.cs delete mode 100644 Storage/Source/Internal/Utilities/AVConfigExtensions.cs delete mode 100644 Storage/Source/Internal/Utilities/AVFileExtensions.cs delete mode 100644 Storage/Source/Internal/Utilities/AVObjectExtensions.cs delete mode 100644 Storage/Source/Internal/Utilities/AVQueryExtensions.cs delete mode 100644 Storage/Source/Internal/Utilities/AVRelationExtensions.cs delete mode 100644 Storage/Source/Internal/Utilities/AVSessionExtensions.cs delete mode 100644 Storage/Source/Internal/Utilities/AVUserExtensions.cs delete mode 100644 Storage/Source/Internal/Utilities/FlexibleDictionaryWrapper.cs delete mode 100644 Storage/Source/Internal/Utilities/FlexibleListWrapper.cs delete mode 100644 Storage/Source/Internal/Utilities/IJsonConvertible.cs delete mode 100644 Storage/Source/Internal/Utilities/IdentityEqualityComparer.cs delete mode 100644 Storage/Source/Internal/Utilities/InternalExtensions.cs delete mode 100644 Storage/Source/Internal/Utilities/Json.cs delete mode 100644 Storage/Source/Internal/Utilities/LockSet.cs delete mode 100644 Storage/Source/Internal/Utilities/ReflectionHelpers.cs delete mode 100644 Storage/Source/Internal/Utilities/SynchronizedEventHandler.cs delete mode 100644 Storage/Source/Internal/Utilities/TaskQueue.cs delete mode 100644 Storage/Source/Internal/Utilities/XamarinAttributes.cs delete mode 100644 Storage/Source/Public/AVACL.cs delete mode 100644 Storage/Source/Public/AVClassNameAttribute.cs delete mode 100644 Storage/Source/Public/AVClient.cs delete mode 100644 Storage/Source/Public/AVCloud.cs delete mode 100644 Storage/Source/Public/AVConfig.cs delete mode 100644 Storage/Source/Public/AVDownloadProgressEventArgs.cs delete mode 100644 Storage/Source/Public/AVException.cs delete mode 100644 Storage/Source/Public/AVExtensions.cs delete mode 100644 Storage/Source/Public/AVFieldNameAttribute.cs delete mode 100644 Storage/Source/Public/AVFile.cs delete mode 100644 Storage/Source/Public/AVGeoDistance.cs delete mode 100644 Storage/Source/Public/AVGeoPoint.cs delete mode 100644 Storage/Source/Public/AVObject.cs delete mode 100644 Storage/Source/Public/AVQuery.cs delete mode 100644 Storage/Source/Public/AVQueryExtensions.cs delete mode 100644 Storage/Source/Public/AVRelation.cs delete mode 100644 Storage/Source/Public/AVRole.cs delete mode 100644 Storage/Source/Public/AVSession.cs delete mode 100644 Storage/Source/Public/AVStatus.cs delete mode 100644 Storage/Source/Public/AVUploadProgressEventArgs.cs delete mode 100644 Storage/Source/Public/AVUser.cs delete mode 100644 Storage/Source/Public/AVUserAuthDataLogInOption.cs delete mode 100644 Storage/Source/Public/IAVQuery.cs delete mode 100644 Storage/Source/Public/LeaderBoard/AVLeaderboard.cs delete mode 100644 Storage/Source/Public/LeaderBoard/AVLeaderboardArchive.cs delete mode 100644 Storage/Source/Public/LeaderBoard/AVRanking.cs delete mode 100644 Storage/Source/Public/LeaderBoard/AVStatistic.cs delete mode 100644 Storage/Source/Public/Unity/AVInitializeBehaviour.cs delete mode 100644 Storage/Source/Public/Utilities/Conversion.cs delete mode 100644 Storage/Storage.PCL/Properties/AssemblyInfo.cs delete mode 100644 Storage/Storage.PCL/Storage.PCL.csproj delete mode 100644 Storage/Storage.PCL/packages.config delete mode 100644 Storage/Storage.Test/Utils.cs delete mode 100644 Storage/Storage.Unity/Properties/AssemblyInfo.cs delete mode 100644 Storage/Storage.Unity/Storage.Unity.csproj rename Storage/{Storage => }/Storage.csproj (85%) delete mode 100644 Storage/Storage/Internal_/AVCorePlugins.cs delete mode 100644 Storage/Storage/Internal_/Cloud/Controller/AVCloudCodeController.cs delete mode 100644 Storage/Storage/Internal_/Command/AVCommand.cs delete mode 100644 Storage/Storage/Internal_/Command/AVCommandRunner.cs delete mode 100644 Storage/Storage/Internal_/Command/EngineCommand.cs delete mode 100644 Storage/Storage/Internal_/Command/RTMCommand.cs delete mode 100644 Storage/Storage/Internal_/Encoding/AVDecoder.cs delete mode 100644 Storage/Storage/Internal_/Encoding/AVEncoder.cs delete mode 100644 Storage/Storage/Internal_/Encoding/AVObjectCoder.cs delete mode 100644 Storage/Storage/Internal_/Encoding/PointerOrLocalIdEncoder.cs delete mode 100644 Storage/Storage/Internal_/File/Controller/AVFileController.cs delete mode 100644 Storage/Storage/Internal_/File/Controller/AWSUploader.cs delete mode 100644 Storage/Storage/Internal_/File/Controller/QCloudUploader.cs delete mode 100644 Storage/Storage/Internal_/File/Controller/QiniuUploader.cs delete mode 100644 Storage/Storage/Internal_/File/Cryptography/MD5/MD5.cs delete mode 100644 Storage/Storage/Internal_/File/Cryptography/SHA1/SHA1CryptoServiceProvider.cs delete mode 100644 Storage/Storage/Internal_/File/State/FileState.cs delete mode 100644 Storage/Storage/Internal_/InstallationId/Controller/InstallationIdController.cs delete mode 100644 Storage/Storage/Internal_/Object/State/IObjectState.cs delete mode 100644 Storage/Storage/Internal_/Object/State/MutableObjectState.cs delete mode 100644 Storage/Storage/Internal_/Object/State/ObjectData.cs delete mode 100644 Storage/Storage/Internal_/Object/Subclassing/ObjectSubclassInfo.cs delete mode 100644 Storage/Storage/Internal_/Object/Subclassing/ObjectSubclassingController.cs delete mode 100644 Storage/Storage/Internal_/Operation/AVAddOperation.cs delete mode 100644 Storage/Storage/Internal_/Operation/AVAddUniqueOperation.cs delete mode 100644 Storage/Storage/Internal_/Operation/AVDeleteOperation.cs delete mode 100644 Storage/Storage/Internal_/Operation/AVFieldOperations.cs delete mode 100644 Storage/Storage/Internal_/Operation/AVIncrementOperation.cs delete mode 100644 Storage/Storage/Internal_/Operation/AVRelationOperation.cs delete mode 100644 Storage/Storage/Internal_/Operation/AVRemoveOperation.cs delete mode 100644 Storage/Storage/Internal_/Operation/AVSetOperation.cs delete mode 100644 Storage/Storage/Internal_/Operation/IAVFieldOperation.cs delete mode 100644 Storage/Storage/Internal_/Query/Controller/AVQueryController.cs delete mode 100644 Storage/Storage/Internal_/Query/IQueryCondition.cs delete mode 100644 Storage/Storage/Internal_/Query/QueryCompositionalCondition.cs delete mode 100644 Storage/Storage/Internal_/Query/QueryEqualCondition.cs delete mode 100644 Storage/Storage/Internal_/Query/QueryOperationCondition.cs delete mode 100644 Storage/Storage/Internal_/Query/QueryRelatedCondition.cs delete mode 100644 Storage/Storage/Internal_/User/Controller/AVUserController.cs delete mode 100644 Storage/Storage/Internal_/Utilities/AVObjectExtensions.cs delete mode 100644 Storage/Storage/Internal_/Utilities/AVRelationExtensions.cs delete mode 100644 Storage/Storage/Internal_/Utilities/FlexibleDictionaryWrapper.cs delete mode 100644 Storage/Storage/Internal_/Utilities/FlexibleListWrapper.cs delete mode 100644 Storage/Storage/Internal_/Utilities/IJsonConvertible.cs delete mode 100644 Storage/Storage/Internal_/Utilities/IdentityEqualityComparer.cs delete mode 100644 Storage/Storage/Internal_/Utilities/InternalExtensions.cs delete mode 100644 Storage/Storage/Internal_/Utilities/Json.cs delete mode 100644 Storage/Storage/Internal_/Utilities/LockSet.cs delete mode 100644 Storage/Storage/Internal_/Utilities/ReflectionHelpers.cs delete mode 100644 Storage/Storage/Internal_/Utilities/SynchronizedEventHandler.cs delete mode 100644 Storage/Storage/Internal_/Utilities/TaskQueue.cs delete mode 100644 Storage/Storage/Internal_/Utilities/XamarinAttributes.cs delete mode 100644 Storage/Storage/Public/AVACL.cs delete mode 100644 Storage/Storage/Public/AVClassNameAttribute.cs delete mode 100644 Storage/Storage/Public/AVClient.cs delete mode 100644 Storage/Storage/Public/AVCloud.cs delete mode 100644 Storage/Storage/Public/AVException.cs delete mode 100644 Storage/Storage/Public/AVExtensions.cs delete mode 100644 Storage/Storage/Public/AVFieldNameAttribute.cs delete mode 100644 Storage/Storage/Public/AVFile.cs delete mode 100644 Storage/Storage/Public/AVGeoDistance.cs delete mode 100644 Storage/Storage/Public/AVGeoPoint.cs delete mode 100644 Storage/Storage/Public/AVObject.cs delete mode 100644 Storage/Storage/Public/AVQuery.cs delete mode 100644 Storage/Storage/Public/AVQueryExtensions.cs delete mode 100644 Storage/Storage/Public/AVRelation.cs delete mode 100644 Storage/Storage/Public/AVRole.cs delete mode 100644 Storage/Storage/Public/AVUploadProgressEventArgs.cs delete mode 100644 Storage/Storage/Public/AVUser.cs delete mode 100644 Storage/Storage/Public/AVUserAuthDataLogInOption.cs delete mode 100644 Storage/Storage/Public/LeaderBoard/AVLeaderboard.cs delete mode 100644 Storage/Storage/Public/LeaderBoard/AVLeaderboardArchive.cs delete mode 100644 Storage/Storage/Public/LeaderBoard/AVRanking.cs delete mode 100644 Storage/Storage/Public/LeaderBoard/AVStatistic.cs delete mode 100644 Storage/Storage/Public/Utilities/Conversion.cs rename {Storage => Test}/Storage.Test/ACLTest.cs (100%) rename {Storage => Test}/Storage.Test/CloudTest.cs (100%) rename {Storage => Test}/Storage.Test/ExceptionTest.cs (100%) rename {Storage => Test}/Storage.Test/FileTest.cs (100%) rename {Storage => Test}/Storage.Test/GeoTest.cs (100%) rename {Storage => Test}/Storage.Test/ObjectTest.cs (100%) rename {Storage => Test}/Storage.Test/OperationTest.cs (100%) rename {Storage => Test}/Storage.Test/QueryTest.cs (100%) rename {Storage => Test}/Storage.Test/RelationTest.cs (100%) rename {Storage => Test}/Storage.Test/RoleTest.cs (100%) rename {Storage => Test}/Storage.Test/Storage.Test.csproj (89%) rename {Storage => Test}/Storage.Test/SubClassTest.cs (100%) rename {Storage => Test}/Storage.Test/UserTest.cs (100%) create mode 100644 Test/Storage.Test/Utils.cs rename {Storage => Test}/Storage.Test/assets/hello.png (100%) rename {Storage => Test}/Storage.Test/assets/test.apk (100%) diff --git a/Libs/UnityEngine.dll b/Libs/UnityEngine.dll deleted file mode 100644 index 80cf67e5076e96996396fc4ed3a1e49a80251c4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1345536 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
r>Efl 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 z≪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�wo2Yygxe;VGQTaIXOvPE1Pr@9n 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�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%0De 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 z

?r;KqnSn*V&NYpTIaElv{FV5XKqBx*2In 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 - - - Debug - AnyCPU - {EA1C601E-D853-41F7-B9EB-276CBF7D1FA5} - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - LiveQuery.PCL - LiveQuery.PCL - v4.5 - Profile111 - 0.1.0 - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - - - true - bin\Release - prompt - 4 - - - - - Source\AVLiveQuery.cs - - - Source\AVLiveQueryEventArgs.cs - - - Source\AVLiveQueryExtensions.cs - - - - - {92B2B40E-A3CD-4672-AC84-2E894E1A6CE5} - RTM.PCL - - - {659D19F0-9A40-42C0-886C-555E64F16848} - Storage.PCL - - - - \ No newline at end of file diff --git a/LiveQuery/LiveQuery.PCL/Properties/AssemblyInfo.cs b/LiveQuery/LiveQuery.PCL/Properties/AssemblyInfo.cs deleted file mode 100644 index 5cf7adb..0000000 --- a/LiveQuery/LiveQuery.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("LiveQuery.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/LiveQuery/LiveQuery.Test/LiveQuery.Test.csproj b/LiveQuery/LiveQuery.Test/LiveQuery.Test.csproj deleted file mode 100644 index c382c64..0000000 --- a/LiveQuery/LiveQuery.Test/LiveQuery.Test.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - - Debug - AnyCPU - {F907012C-74DF-4575-AFE6-E8DAACC26D24} - Library - LiveQuery.Test - LiveQuery.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/LiveQuery/LiveQuery.Test/Test.cs b/LiveQuery/LiveQuery.Test/Test.cs deleted file mode 100644 index 9bd89d3..0000000 --- a/LiveQuery/LiveQuery.Test/Test.cs +++ /dev/null @@ -1,10 +0,0 @@ -using NUnit.Framework; -using System; -namespace LiveQuery.Test { - [TestFixture()] - public class Test { - [Test()] - public void TestCase() { - } - } -} diff --git a/LiveQuery/LiveQuery.Test/packages.config b/LiveQuery/LiveQuery.Test/packages.config deleted file mode 100644 index bbb222f..0000000 --- a/LiveQuery/LiveQuery.Test/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/LiveQuery/LiveQuery.Unity/LiveQuery.Unity.csproj b/LiveQuery/LiveQuery.Unity/LiveQuery.Unity.csproj deleted file mode 100644 index 2e35ef5..0000000 --- a/LiveQuery/LiveQuery.Unity/LiveQuery.Unity.csproj +++ /dev/null @@ -1,56 +0,0 @@ - - - - Debug - AnyCPU - {3251B4D8-D11A-4D90-8626-27FEE266B066} - Library - LiveQuery.Unity - LiveQuery.Unity - v4.7 - 0.1.0 - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - false - - - true - bin\Release - prompt - 4 - false - - - - - - - - Source\AVLiveQuery.cs - - - Source\AVLiveQueryEventArgs.cs - - - Source\AVLiveQueryExtensions.cs - - - - - {1E608FCD-9039-4FF7-8EE7-BA8B00E15D1C} - RTM.Unity - - - {A0D50BCB-E50E-4AAE-8E7D-24BF5AE33DAC} - Storage.Unity - - - - \ No newline at end of file diff --git a/LiveQuery/LiveQuery.Unity/Properties/AssemblyInfo.cs b/LiveQuery/LiveQuery.Unity/Properties/AssemblyInfo.cs deleted file mode 100644 index e93ac80..0000000 --- a/LiveQuery/LiveQuery.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("LiveQuery.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/LiveQuery/Source/AVLiveQuery.cs b/LiveQuery/Source/AVLiveQuery.cs deleted file mode 100644 index b414d4d..0000000 --- a/LiveQuery/Source/AVLiveQuery.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using System.Collections; -using System.Collections.Generic; -using LeanCloud.Storage.Internal; -using LeanCloud.Realtime; -using LeanCloud.Realtime.Internal; -using System.Linq; -using System.Linq.Expressions; - -namespace LeanCloud.LiveQuery -{ - ///

- /// AVLiveQuery 类 - /// - public static class AVLiveQuery - { - /// - /// LiveQuery 传输数据的 AVRealtime 实例 - /// - public static AVRealtime Channel { - get; set; - } - - internal static long ClientTs { - get; set; - } - - internal static bool Inited { - get; set; - } - - internal static string InstallationId { - get; set; - } - } - - /// - /// AVLiveQuery 对象 - /// - /// - public class AVLiveQuery where T : AVObject - { - internal static Dictionary>> liveQueryDict = new Dictionary>>(); - - - /// - /// 当前 AVLiveQuery 对象的 Id - /// - public string Id { get; set; } - - /// - /// 根据 AVQuery 创建 AVLiveQuery 对象 - /// - /// - public AVLiveQuery(AVQuery query) { - this.Query = query; - } - /// - /// AVLiveQuery 对应的 AVQuery 对象 - /// - public AVQuery Query { get; set; } - - /// - /// 数据推送的触发的事件通知 - /// - public event EventHandler> OnLiveQueryReceived; - - /// - /// 推送抵达时触发事件通知 - /// - /// 产生这条推送的原因。 - /// - /// create:符合查询条件的对象创建; - /// update:符合查询条件的对象属性修改。 - /// enter:对象修改事件,从不符合查询条件变成符合。 - /// leave:对象修改时间,从符合查询条件变成不符合。 - /// delete:对象删除 - /// login:只对 _User 对象有效,表示用户登录。 - /// - /// - /// - public void On(string scope, Action onRecevived) - { - this.OnLiveQueryReceived += (sender, e) => - { - if (e.Scope == scope) - { - onRecevived.Invoke(e.Payload); - } - }; - } - - /// - /// 订阅操作 - /// - /// - /// - public async Task> SubscribeAsync(CancellationToken cancellationToken = default(CancellationToken)) { - if (Query == null) { - throw new Exception("Query can not be null when subcribe."); - } - if (!AVLiveQuery.Inited) { - await Login(); - AVLiveQuery.Channel.OnReconnected += OnChannelReconnected; - AVLiveQuery.Channel.NoticeReceived += OnChannelNoticeReceived; - AVLiveQuery.Inited = true; - } - await InternalSubscribe(); - var liveQueryRef = new WeakReference>(this); - liveQueryDict.Add(Id, liveQueryRef); - return this; - } - - static async void OnChannelReconnected(object sender, AVIMReconnectedEventArgs e) { - await Login(); - lock (liveQueryDict) { - foreach (var kv in liveQueryDict) { - if (kv.Value.TryGetTarget(out var liveQuery)) { - liveQuery.InternalSubscribe().ContinueWith(_ => { }); - } - } - } - } - - static async Task Login() { - var installation = await AVPlugins.Instance.InstallationIdController.GetAsync(); - AVLiveQuery.InstallationId = installation.ToString(); - AVLiveQuery.Channel.ToggleNotification(true); - await AVLiveQuery.Channel.OpenAsync(); - AVLiveQuery.ClientTs = (long) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; - var liveQueryLogInCmd = new AVIMCommand().Command("login") - .Argument("installationId", AVLiveQuery.InstallationId) - .Argument("clientTs", AVLiveQuery.ClientTs) - .Argument("service", 1).AppId(AVClient.CurrentConfiguration.ApplicationId); - // open the session for LiveQuery. - try { - await AVLiveQuery.Channel.AVIMCommandRunner.RunCommandAsync(liveQueryLogInCmd); - } catch (Exception e) { - AVRealtime.PrintLog(e.Message); - } - } - - static void OnChannelNoticeReceived(object sender, AVIMNotice e) { - if (e.CommandName == "data") { - var ids = AVDecoder.Instance.DecodeList(e.RawData["ids"]); - if (e.RawData["msg"] is IEnumerable msg) { - var receivedPayloads = from item in msg - select item as Dictionary; - if (receivedPayloads != null) { - foreach (var payload in receivedPayloads) { - var liveQueryId = payload["query_id"] as string; - if (liveQueryDict.TryGetValue(liveQueryId, out var liveQueryRef) && - liveQueryRef.TryGetTarget(out var liveQuery)) { - var scope = payload["op"] as string; - var objectPayload = payload["object"] as Dictionary; - string[] keys = null; - if (payload.TryGetValue("updatedKeys", out object updatedKeys)) { - // enter, leave, update - keys = (updatedKeys as List).Select(x => x.ToString()).ToArray(); - } - liveQuery.Emit(scope, objectPayload, keys); - } - } - } - } - } - } - - async Task InternalSubscribe() { - var queryMap = new Dictionary { - { "where", Query.Condition}, - { "className", Query.GetClassName()} - }; - - Dictionary data = new Dictionary { - { "query", queryMap }, - { "id", AVLiveQuery.InstallationId }, - { "clientTimestamp", AVLiveQuery.ClientTs } - }; - string sessionToken = AVUser.CurrentUser?.SessionToken; - if (!string.IsNullOrEmpty(sessionToken)) { - data.Add("sessionToken", sessionToken); - } - var command = new AVCommand("LiveQuery/subscribe", - "POST", - sessionToken, - data: data); - var res = await AVPlugins.Instance.CommandRunner.RunCommandAsync(command); - Id = res.Item2["query_id"] as string; - } - - /// - /// 取消对当前 LiveQuery 对象的订阅 - /// - /// - public async Task UnsubscribeAsync() { - Dictionary strs = new Dictionary { - { "id", AVLiveQuery.InstallationId }, - { "query_id", Id }, - }; - string sessionToken = AVUser.CurrentUser?.SessionToken; - var command = new AVCommand("LiveQuery/unsubscribe", - "POST", - sessionToken, - data: strs); - await AVPlugins.Instance.CommandRunner.RunCommandAsync(command); - lock (liveQueryDict) { - liveQueryDict.Remove(Id); - } - } - - void Emit(string scope, IDictionary payloadMap, string[] keys) { - var objectState = AVObjectCoder.Instance.Decode(payloadMap, AVDecoder.Instance); - var payloadObject = AVObject.FromState(objectState, Query.GetClassName()); - var args = new AVLiveQueryEventArgs { - Scope = scope, - Keys = keys, - Payload = payloadObject - }; - OnLiveQueryReceived?.Invoke(this, args); - } - } -} diff --git a/LiveQuery/Source/AVLiveQueryEventArgs.cs b/LiveQuery/Source/AVLiveQueryEventArgs.cs deleted file mode 100644 index 98e860f..0000000 --- a/LiveQuery/Source/AVLiveQueryEventArgs.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using LeanCloud; -using System.Collections; - -namespace LeanCloud.LiveQuery -{ - /// - /// AVLiveQuery 回调参数 - /// - public class AVLiveQueryEventArgs : EventArgs - where T : AVObject - { - internal AVLiveQueryEventArgs() - { - - } - - /// - /// 更新事件 - /// - public string Scope { get; set; } - - /// - /// 更新字段 - /// - public IEnumerable Keys { get; set; } - - /// - /// 更新数据 - /// - public T Payload { get; set; } - } -} diff --git a/LiveQuery/Source/AVLiveQueryExtensions.cs b/LiveQuery/Source/AVLiveQueryExtensions.cs deleted file mode 100644 index a15218c..0000000 --- a/LiveQuery/Source/AVLiveQueryExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.LiveQuery -{ - /// - /// AVLiveQuery 扩展类 - /// - public static class AVLiveQueryExtensions - { - /// - /// AVQuery 订阅 AVLiveQuery 的扩展方法 - /// - /// AVLiveQuery 对象 - /// Query. - /// Cancellation token. - /// The 1st type parameter. - public static async Task> SubscribeAsync(this AVQuery query, CancellationToken cancellationToken = default(CancellationToken)) where T : AVObject { - var liveQuery = new AVLiveQuery(query); - liveQuery = await liveQuery.SubscribeAsync(); - return liveQuery; - } - } -} diff --git a/RTM/RTM.PCL/RTM.PCL.csproj b/RTM/RTM.PCL/RTM.PCL.csproj index 72b7e89..a67701e 100644 --- a/RTM/RTM.PCL/RTM.PCL.csproj +++ b/RTM/RTM.PCL/RTM.PCL.csproj @@ -206,11 +206,5 @@ ..\..\packages\Websockets.Pcl.1.1.9\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10\WebSockets.PCL.dll - - - {659D19F0-9A40-42C0-886C-555E64F16848} - Storage.PCL - - \ No newline at end of file diff --git a/RTM/RTM.Unity/RTM.Unity.csproj b/RTM/RTM.Unity/RTM.Unity.csproj index 51ba4ed..2d18b38 100644 --- a/RTM/RTM.Unity/RTM.Unity.csproj +++ b/RTM/RTM.Unity/RTM.Unity.csproj @@ -215,11 +215,5 @@ Internal\WebSocket\Unity\websocket-sharp.dll - - - {A0D50BCB-E50E-4AAE-8E7D-24BF5AE33DAC} - Storage.Unity - - \ No newline at end of file diff --git a/RTM/RTM/Internal/AVIMCorePlugins.cs b/RTM/RTM/Internal/AVIMCorePlugins.cs deleted file mode 100644 index 859e0bc..0000000 --- a/RTM/RTM/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/RTM/Internal/Command/AVIMCommand.cs b/RTM/RTM/Internal/Command/AVIMCommand.cs deleted file mode 100644 index 18300e5..0000000 --- a/RTM/RTM/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/RTM/Internal/Command/AVIMCommandRunner.cs b/RTM/RTM/Internal/Command/AVIMCommandRunner.cs deleted file mode 100644 index 2052c46..0000000 --- a/RTM/RTM/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/RTM/Internal/Command/AckCommand.cs b/RTM/RTM/Internal/Command/AckCommand.cs deleted file mode 100644 index 938f078..0000000 --- a/RTM/RTM/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/RTM/Internal/Command/ConversationCommand.cs b/RTM/RTM/Internal/Command/ConversationCommand.cs deleted file mode 100644 index ab4de2d..0000000 --- a/RTM/RTM/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/RTM/Internal/Command/IAVIMCommandRunner.cs b/RTM/RTM/Internal/Command/IAVIMCommandRunner.cs deleted file mode 100644 index a904f97..0000000 --- a/RTM/RTM/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/RTM/Internal/Command/MessageCommand.cs b/RTM/RTM/Internal/Command/MessageCommand.cs deleted file mode 100644 index ec76d94..0000000 --- a/RTM/RTM/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/RTM/Internal/Command/PatchCommand.cs b/RTM/RTM/Internal/Command/PatchCommand.cs deleted file mode 100644 index 113a0db..0000000 --- a/RTM/RTM/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/RTM/Internal/Command/ReadCommand.cs b/RTM/RTM/Internal/Command/ReadCommand.cs deleted file mode 100644 index a627d00..0000000 --- a/RTM/RTM/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/RTM/Internal/Command/SessionCommand.cs b/RTM/RTM/Internal/Command/SessionCommand.cs deleted file mode 100644 index f18b7b9..0000000 --- a/RTM/RTM/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/RTM/Internal/DataEngine/Controller/DateTimeEngine.cs b/RTM/RTM/Internal/DataEngine/Controller/DateTimeEngine.cs deleted file mode 100644 index 25c920a..0000000 --- a/RTM/RTM/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/RTM/Internal/DataEngine/Controller/DictionaryEngine.cs b/RTM/RTM/Internal/DataEngine/Controller/DictionaryEngine.cs deleted file mode 100644 index a1da600..0000000 --- a/RTM/RTM/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/RTM/Internal/DataEngine/Controller/StringEngine.cs b/RTM/RTM/Internal/DataEngine/Controller/StringEngine.cs deleted file mode 100644 index 62e902a..0000000 --- a/RTM/RTM/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/RTM/Internal/IAVIMPlatformHooks.cs b/RTM/RTM/Internal/IAVIMPlatformHooks.cs deleted file mode 100644 index 53d737c..0000000 --- a/RTM/RTM/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/RTM/Internal/Message/Subclassing/FreeStyleMessageClassInfo.cs b/RTM/RTM/Internal/Message/Subclassing/FreeStyleMessageClassInfo.cs deleted file mode 100644 index e1a1e56..0000000 --- a/RTM/RTM/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/RTM/Internal/Message/Subclassing/FreeStyleMessageClassingController.cs b/RTM/RTM/Internal/Message/Subclassing/FreeStyleMessageClassingController.cs deleted file mode 100644 index 04bd105..0000000 --- a/RTM/RTM/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/RTM/Internal/Message/Subclassing/IFreeStyleMessageClassingController.cs b/RTM/RTM/Internal/Message/Subclassing/IFreeStyleMessageClassingController.cs deleted file mode 100644 index 0fc3912..0000000 --- a/RTM/RTM/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/RTM/Internal/Protocol/AVIMProtocol.cs b/RTM/RTM/Internal/Protocol/AVIMProtocol.cs deleted file mode 100644 index 6c71044..0000000 --- a/RTM/RTM/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/RTM/Internal/Router/AVRouterController.cs b/RTM/RTM/Internal/Router/AVRouterController.cs deleted file mode 100644 index dc47848..0000000 --- a/RTM/RTM/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/RTM/Internal/Router/IAVRouterController.cs b/RTM/RTM/Internal/Router/IAVRouterController.cs deleted file mode 100644 index b7eebfb..0000000 --- a/RTM/RTM/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/RTM/Internal/Router/State/RouterState.cs b/RTM/RTM/Internal/Router/State/RouterState.cs deleted file mode 100644 index 66eaea9..0000000 --- a/RTM/RTM/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/RTM/Internal/Timer/IAVTimer.cs b/RTM/RTM/Internal/Timer/IAVTimer.cs deleted file mode 100644 index 5573041..0000000 --- a/RTM/RTM/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/RTM/Internal/Timer/Portable/AVTimer.Portable.cs b/RTM/RTM/Internal/Timer/Portable/AVTimer.Portable.cs deleted file mode 100644 index e981dbd..0000000 --- a/RTM/RTM/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/RTM/Internal/WebSocket/IWebSocketClient.cs b/RTM/RTM/Internal/WebSocket/IWebSocketClient.cs deleted file mode 100644 index 6e670d2..0000000 --- a/RTM/RTM/Internal/WebSocket/IWebSocketClient.cs +++ /dev/null @@ -1,55 +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/RTM/Internal/WebSocket/Portable/DefaultWebSocketClient.Portable.cs b/RTM/RTM/Internal/WebSocket/Portable/DefaultWebSocketClient.Portable.cs deleted file mode 100644 index 54ee3f6..0000000 --- a/RTM/RTM/Internal/WebSocket/Portable/DefaultWebSocketClient.Portable.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Net.WebSockets; - -namespace LeanCloud.Realtime.Internal { - /// - /// LeanCloud Realtime SDK for .NET Portable 内置默认的 WebSocketClient - /// - public class DefaultWebSocketClient { - const int RECV_BUFFER_SIZE = 1024; - - ClientWebSocket client; - - public event Action OnClose; - public event Action OnOpened; - public event Action OnMessage; - - public bool IsOpen { - get { - return client != null && client.State == WebSocketState.Open; - } - } - - public async Task Close() { - if (IsOpen) { - await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "1", CancellationToken.None); - } - } - - public void Disconnect() { - OnClose?.Invoke(0, string.Empty); - _ = Close(); - } - - public async Task Send(string message) { - if (!IsOpen) { - throw new Exception("WebSocket is not open when send data."); - } - ArraySegment bytes = new ArraySegment(Encoding.UTF8.GetBytes(message)); - try { - await client.SendAsync(bytes, WebSocketMessageType.Text, true, default); - } catch (InvalidOperationException e) { - OnClose?.Invoke(-2, e.Message); - _ = Close(); - throw e; - } - } - - public async Task Connect(string url, string protocol = null) { - client = new ClientWebSocket(); - client.Options.AddSubProtocol(protocol); - client.Options.KeepAliveInterval = TimeSpan.FromSeconds(10); - try { - await client.ConnectAsync(new Uri(url), default); - // 开始接收 - _ = StartReceive(); - } catch (Exception e) { - - throw e; - } - } - - async Task StartReceive() { - byte[] buffer = new byte[RECV_BUFFER_SIZE]; - try { - while (client.State == WebSocketState.Open) { - byte[] data = new byte[0]; - WebSocketReceiveResult result; - do { - result = await client.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - if (result.MessageType == WebSocketMessageType.Close) { - // 断开事件 - AVRealtime.PrintLog($"-------------------- WebSocket Close: {result.CloseStatus}, {result.CloseStatusDescription}"); - OnClose?.Invoke((int)result.CloseStatus, result.CloseStatusDescription); - return; - } - data = await MergeData(data, buffer, result.Count); - } while (!result.EndOfMessage); - // 一个 WebSocket 消息体接收完成 - try { - string message = Encoding.UTF8.GetString(data); - OnMessage(message); - } catch (Exception e) { - AVRealtime.PrintLog($"************************* Parse command error: {e.Message}"); - } - } - } catch (Exception e) { - AVRealtime.PrintLog($"-------------------- WebSocket Receive Exception: {e.Message}"); - AVRealtime.PrintLog(e.StackTrace); - // 断线事件 - OnClose?.Invoke(-1, e.Message); - } - } - - static async Task MergeData(byte[] oldData, byte[] newData, int newDataLength) { - return await Task.Run(() => { - var data = new byte[oldData.Length + newDataLength]; - Array.Copy(oldData, data, oldData.Length); - Array.Copy(newData, 0, data, oldData.Length, newDataLength); - AVRealtime.PrintLog($"merge: {data.Length}"); - return data; - }); - } - } -} diff --git a/RTM/RTM/Public/AVIMAudioMessage.cs b/RTM/RTM/Public/AVIMAudioMessage.cs deleted file mode 100644 index 3c0907e..0000000 --- a/RTM/RTM/Public/AVIMAudioMessage.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Realtime -{ - /// - /// Audio message. - /// - [AVIMMessageClassName("_AVIMAudioMessage")] - [AVIMTypedMessageTypeInt(-3)] - public class AVIMAudioMessage : AVIMFileMessage - { - - } - - /// - /// Video message. - /// - [AVIMMessageClassName("_AVIMVideoMessage")] - [AVIMTypedMessageTypeInt(-4)] - public class AVIMVideoMessage: AVIMFileMessage - { - - } -} diff --git a/RTM/RTM/Public/AVIMBinaryMessage.cs b/RTM/RTM/Public/AVIMBinaryMessage.cs deleted file mode 100644 index 103d4b3..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMClient.cs b/RTM/RTM/Public/AVIMClient.cs deleted file mode 100644 index 4715c07..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMConversation.cs b/RTM/RTM/Public/AVIMConversation.cs deleted file mode 100644 index decc8e6..0000000 --- a/RTM/RTM/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 : AVObject - { - 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/RTM/Public/AVIMConversationQuery.cs b/RTM/RTM/Public/AVIMConversationQuery.cs deleted file mode 100644 index d5f7284..0000000 --- a/RTM/RTM/Public/AVIMConversationQuery.cs +++ /dev/null @@ -1,299 +0,0 @@ -using LeanCloud.Realtime.Internal; -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Text.RegularExpressions; - -namespace LeanCloud.Realtime { - /// - /// 对话查询类 - /// - public class AVIMConversationQuery { - internal AVIMClient CurrentClient { get; set; } - - QueryCombinedCondition condition; - - public AVIMConversationQuery() { - condition = new QueryCombinedCondition(); - } - - internal ConversationCommand GenerateQueryCommand() { - var cmd = new ConversationCommand(); - - var queryParameters = BuildParameters(); - if (queryParameters != null) { - if (queryParameters.TryGetValue("where", out object where)) { - cmd.Where(where); - } - if (queryParameters.TryGetValue("skip", out object skip)) { - cmd.Skip((int)skip); - } - if (queryParameters.TryGetValue("limit", out object limit)) { - cmd.Limit((int)limit); - } - if (queryParameters.TryGetValue("order", out object order)) { - cmd.Sort(order.ToString()); - } - } - - return cmd; - } - - #region Combined Query - - public static AVIMConversationQuery And(IEnumerable queries) { - AVIMConversationQuery composition = new AVIMConversationQuery(); - if (queries != null) { - foreach (AVIMConversationQuery query in queries) { - composition.condition.AddCondition(query.condition); - } - } - return composition; - } - - public static AVIMConversationQuery Or(IEnumerable queries) { - AVIMConversationQuery composition = new AVIMConversationQuery { - condition = new QueryCombinedCondition(QueryCombinedCondition.OR) - }; - if (queries != null) { - foreach (AVIMConversationQuery query in queries) { - composition.condition.AddCondition(query.condition); - } - } - return composition; - } - - #endregion - - public Task CountAsync() { - var convCmd = 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 Task> FindAsync() { - var convCmd = GenerateQueryCommand().Option("query"); - return CurrentClient.RunCommandAsync(convCmd).OnSuccess(t => { - var result = t.Result.Item2; - - IList rtn = new List(); - if (result["results"] is IList conList) { - foreach (var c in conList) { - if (c is IDictionary cData) { - var con = AVIMConversation.CreateWithData(cData, CurrentClient); - rtn.Add(con); - } - } - } - return rtn.AsEnumerable(); - }); - } - - public Task FirstAsync() { - return FirstOrDefaultAsync(); - } - - public Task FirstOrDefaultAsync() { - var firstQuery = Limit(1); - return firstQuery.FindAsync().OnSuccess(t => { - return t.Result.FirstOrDefault(); - }); - } - - public Task GetAsync(string objectId) { - var idQuery = WhereEqualTo("objectId", objectId); - return idQuery.FirstAsync(); - } - - public AVIMConversationQuery OrderBy(string key) { - condition.OrderBy(key); - return this; - } - - public AVIMConversationQuery OrderByDescending(string key) { - condition.OrderByDescending(key); - return this; - } - - public AVIMConversationQuery Include(string key) { - condition.Include(key); - return this; - } - - public AVIMConversationQuery Select(string key) { - condition.Select(key); - return this; - } - - public AVIMConversationQuery Skip(int count) { - condition.Skip(count); - return this; - } - - public AVIMConversationQuery Limit(int count) { - condition.Limit(count); - return this; - } - - #region Where - - public AVIMConversationQuery WhereContainedIn(string key, IEnumerable values) { - condition.WhereContainedIn(key, values); - return this; - } - - public AVIMConversationQuery WhereContainsAll(string key, IEnumerable values) { - condition.WhereContainsAll(key, values); - return this; - } - - public AVIMConversationQuery WhereContains(string key, string substring) { - condition.WhereContains(key, substring); - return this; - } - - public AVIMConversationQuery WhereDoesNotExist(string key) { - condition.WhereDoesNotExist(key); - return this; - } - - public AVIMConversationQuery WhereDoesNotMatchQuery(string key, AVQuery query) where TOther : AVObject { - condition.WhereDoesNotMatchQuery(key, query); - return this; - } - - public AVIMConversationQuery WhereEndsWith(string key, string suffix) { - condition.WhereEndsWith(key, suffix); - return this; - } - - public AVIMConversationQuery WhereEqualTo(string key, object value) { - condition.WhereEqualTo(key, value); - return this; - } - - public AVIMConversationQuery WhereSizeEqualTo(string key, uint size) { - condition.WhereSizeEqualTo(key, size); - return this; - } - - public AVIMConversationQuery WhereExists(string key) { - condition.WhereExists(key); - return this; - } - - public AVIMConversationQuery WhereGreaterThan(string key, object value) { - condition.WhereGreaterThan(key, value); - return this; - } - - public AVIMConversationQuery WhereGreaterThanOrEqualTo(string key, object value) { - condition.WhereGreaterThanOrEqualTo(key, value); - return this; - } - - public AVIMConversationQuery WhereLessThan(string key, object value) { - condition.WhereLessThan(key, value); - return this; - } - - public AVIMConversationQuery WhereLessThanOrEqualTo(string key, object value) { - condition.WhereLessThanOrEqualTo(key, value); - return this; - } - - public AVIMConversationQuery WhereMatches(string key, Regex regex, string modifiers) { - condition.WhereMatches(key, regex, modifiers); - return this; - } - - public AVIMConversationQuery WhereMatches(string key, Regex regex) { - return WhereMatches(key, regex, null); - } - - public AVIMConversationQuery WhereMatches(string key, string pattern, string modifiers) { - return WhereMatches(key, new Regex(pattern, RegexOptions.ECMAScript), modifiers); - } - - public AVIMConversationQuery WhereMatches(string key, string pattern) { - return WhereMatches(key, pattern, null); - } - - public AVIMConversationQuery WhereMatchesKeyInQuery(string key, string keyInQuery, AVQuery query) where TOther : AVObject { - condition.WhereMatchesKeyInQuery(key, keyInQuery, query); - return this; - } - - public AVIMConversationQuery WhereDoesNotMatchesKeyInQuery(string key, string keyInQuery, AVQuery query) where TOther : AVObject { - condition.WhereDoesNotMatchesKeyInQuery(key, keyInQuery, query); - return this; - } - - public AVIMConversationQuery WhereMatchesQuery(string key, AVQuery query) where TOther : AVObject { - condition.WhereMatchesQuery(key, query); - return this; - } - - public AVIMConversationQuery WhereNear(string key, AVGeoPoint point) { - condition.WhereNear(key, point); - return this; - } - - public AVIMConversationQuery WhereNotContainedIn(string key, IEnumerable values) { - condition.WhereNotContainedIn(key, values); - return this; - } - - public AVIMConversationQuery WhereNotEqualTo(string key, object value) { - condition.WhereNotEqualTo(key, value); - return this; - } - - public AVIMConversationQuery WhereStartsWith(string key, string suffix) { - condition.WhereStartsWith(key, suffix); - return this; - } - - public AVIMConversationQuery WhereWithinGeoBox(string key, AVGeoPoint southwest, AVGeoPoint northeast) { - condition.WhereWithinGeoBox(key, southwest, northeast); - return this; - } - - public AVIMConversationQuery WhereWithinDistance(string key, AVGeoPoint point, AVGeoDistance maxDistance) { - condition.WhereWithinDistance(key, point, maxDistance); - return this; - } - - public AVIMConversationQuery WhereRelatedTo(AVObject parent, string key) { - condition.WhereRelatedTo(parent, key); - return this; - } - - #endregion - - public IDictionary BuildParameters(string className = null) { - return condition.BuildParameters(className); - } - - public IDictionary BuildWhere() { - return condition.ToJSON(); - } - } -} diff --git a/RTM/RTM/Public/AVIMEnumerator.cs b/RTM/RTM/Public/AVIMEnumerator.cs deleted file mode 100644 index d9368bc..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMEventArgs.cs b/RTM/RTM/Public/AVIMEventArgs.cs deleted file mode 100644 index 2b91b23..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMException.cs b/RTM/RTM/Public/AVIMException.cs deleted file mode 100644 index f473746..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMImageMessage.cs b/RTM/RTM/Public/AVIMImageMessage.cs deleted file mode 100644 index 9e00b67..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMMessage.cs b/RTM/RTM/Public/AVIMMessage.cs deleted file mode 100644 index 21fb38d..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMMessageClassNameAttribute.cs b/RTM/RTM/Public/AVIMMessageClassNameAttribute.cs deleted file mode 100644 index e4d7228..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMMessageFieldNameAttribute.cs b/RTM/RTM/Public/AVIMMessageFieldNameAttribute.cs deleted file mode 100644 index 0e155b5..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMMessageListener.cs b/RTM/RTM/Public/AVIMMessageListener.cs deleted file mode 100644 index d0d91cf..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMNotice.cs b/RTM/RTM/Public/AVIMNotice.cs deleted file mode 100644 index 65f4053..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMRecalledMessage.cs b/RTM/RTM/Public/AVIMRecalledMessage.cs deleted file mode 100644 index 1e863fd..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMSignature.cs b/RTM/RTM/Public/AVIMSignature.cs deleted file mode 100644 index c5bd0d3..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMTemporaryConversation.cs b/RTM/RTM/Public/AVIMTemporaryConversation.cs deleted file mode 100644 index 1e06769..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMTextMessage.cs b/RTM/RTM/Public/AVIMTextMessage.cs deleted file mode 100644 index 7bc1d34..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMTypedMessage.cs b/RTM/RTM/Public/AVIMTypedMessage.cs deleted file mode 100644 index c756e1f..0000000 --- a/RTM/RTM/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/RTM/Public/AVIMTypedMessageTypeIntAttribute.cs b/RTM/RTM/Public/AVIMTypedMessageTypeIntAttribute.cs deleted file mode 100644 index 49c4b76..0000000 --- a/RTM/RTM/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/RTM/Public/AVRealtime.cs b/RTM/RTM/Public/AVRealtime.cs deleted file mode 100644 index 49bd9ec..0000000 --- a/RTM/RTM/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/RTM/Public/IAVIMListener.cs b/RTM/RTM/Public/IAVIMListener.cs deleted file mode 100644 index 366f4a2..0000000 --- a/RTM/RTM/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/RTM/Public/IAVIMMessage.cs b/RTM/RTM/Public/IAVIMMessage.cs deleted file mode 100644 index f797485..0000000 --- a/RTM/RTM/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/RTM/Public/ICacheEngine.cs b/RTM/RTM/Public/ICacheEngine.cs deleted file mode 100644 index 1c20ec7..0000000 --- a/RTM/RTM/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/RTM/Public/ISignatureFactory.cs b/RTM/RTM/Public/ISignatureFactory.cs deleted file mode 100644 index ffda11a..0000000 --- a/RTM/RTM/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/RTM/Public/Listener/AVIMConversationListener.cs b/RTM/RTM/Public/Listener/AVIMConversationListener.cs deleted file mode 100644 index 77f931a..0000000 --- a/RTM/RTM/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/RTM/Public/Listener/ConversationUnreadListener.cs b/RTM/RTM/Public/Listener/ConversationUnreadListener.cs deleted file mode 100644 index bd48b89..0000000 --- a/RTM/RTM/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/RTM/Public/Listener/GoAwayListener.cs b/RTM/RTM/Public/Listener/GoAwayListener.cs deleted file mode 100644 index 57ecb62..0000000 --- a/RTM/RTM/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/RTM/Public/Listener/MessagePatchListener.cs b/RTM/RTM/Public/Listener/MessagePatchListener.cs deleted file mode 100644 index b588190..0000000 --- a/RTM/RTM/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/RTM/Public/Listener/OfflineMessageListener.cs b/RTM/RTM/Public/Listener/OfflineMessageListener.cs deleted file mode 100644 index 983ad8a..0000000 --- a/RTM/RTM/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/RTM/Public/Listener/SessionListener.cs b/RTM/RTM/Public/Listener/SessionListener.cs deleted file mode 100644 index aad4c54..0000000 --- a/RTM/RTM/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/RTM/RTM.csproj b/RTM/RTM/RTM.csproj deleted file mode 100644 index 5f49628..0000000 --- a/RTM/RTM/RTM.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - netstandard2.0 - 0.1.0 - - - - - - - - - - - - diff --git a/Storage/Storage/Internal/Codec/LCDecoder.cs b/Storage/Internal/Codec/LCDecoder.cs similarity index 100% rename from Storage/Storage/Internal/Codec/LCDecoder.cs rename to Storage/Internal/Codec/LCDecoder.cs diff --git a/Storage/Storage/Internal/Codec/LCEncoder.cs b/Storage/Internal/Codec/LCEncoder.cs similarity index 100% rename from Storage/Storage/Internal/Codec/LCEncoder.cs rename to Storage/Internal/Codec/LCEncoder.cs diff --git a/Storage/Storage/Internal/File/LCAWSUploader.cs b/Storage/Internal/File/LCAWSUploader.cs similarity index 100% rename from Storage/Storage/Internal/File/LCAWSUploader.cs rename to Storage/Internal/File/LCAWSUploader.cs diff --git a/Storage/Storage/Internal/File/LCMimeTypeMap.cs b/Storage/Internal/File/LCMimeTypeMap.cs similarity index 100% rename from Storage/Storage/Internal/File/LCMimeTypeMap.cs rename to Storage/Internal/File/LCMimeTypeMap.cs diff --git a/Storage/Storage/Internal/File/LCProgressableStreamContent.cs b/Storage/Internal/File/LCProgressableStreamContent.cs similarity index 100% rename from Storage/Storage/Internal/File/LCProgressableStreamContent.cs rename to Storage/Internal/File/LCProgressableStreamContent.cs diff --git a/Storage/Storage/Internal/File/LCQiniuUploader.cs b/Storage/Internal/File/LCQiniuUploader.cs similarity index 100% rename from Storage/Storage/Internal/File/LCQiniuUploader.cs rename to Storage/Internal/File/LCQiniuUploader.cs diff --git a/Storage/Storage/Internal/Http/LCHttpClient.cs b/Storage/Internal/Http/LCHttpClient.cs similarity index 100% rename from Storage/Storage/Internal/Http/LCHttpClient.cs rename to Storage/Internal/Http/LCHttpClient.cs diff --git a/Storage/Storage/Internal/Http/LeanCloudJsonConverter.cs b/Storage/Internal/Http/LeanCloudJsonConverter.cs similarity index 100% rename from Storage/Storage/Internal/Http/LeanCloudJsonConverter.cs rename to Storage/Internal/Http/LeanCloudJsonConverter.cs diff --git a/Storage/Storage/Internal/Object/LCBatch.cs b/Storage/Internal/Object/LCBatch.cs similarity index 100% rename from Storage/Storage/Internal/Object/LCBatch.cs rename to Storage/Internal/Object/LCBatch.cs diff --git a/Storage/Storage/Internal/Object/LCObjectData.cs b/Storage/Internal/Object/LCObjectData.cs similarity index 100% rename from Storage/Storage/Internal/Object/LCObjectData.cs rename to Storage/Internal/Object/LCObjectData.cs diff --git a/Storage/Storage/Internal/Object/LCSubClassInfo.cs b/Storage/Internal/Object/LCSubClassInfo.cs similarity index 100% rename from Storage/Storage/Internal/Object/LCSubClassInfo.cs rename to Storage/Internal/Object/LCSubClassInfo.cs diff --git a/Storage/Storage/Internal/Operation/ILCOperation.cs b/Storage/Internal/Operation/ILCOperation.cs similarity index 100% rename from Storage/Storage/Internal/Operation/ILCOperation.cs rename to Storage/Internal/Operation/ILCOperation.cs diff --git a/Storage/Storage/Internal/Operation/LCAddOperation.cs b/Storage/Internal/Operation/LCAddOperation.cs similarity index 100% rename from Storage/Storage/Internal/Operation/LCAddOperation.cs rename to Storage/Internal/Operation/LCAddOperation.cs diff --git a/Storage/Storage/Internal/Operation/LCAddRelationOperation.cs b/Storage/Internal/Operation/LCAddRelationOperation.cs similarity index 100% rename from Storage/Storage/Internal/Operation/LCAddRelationOperation.cs rename to Storage/Internal/Operation/LCAddRelationOperation.cs diff --git a/Storage/Storage/Internal/Operation/LCAddUniqueOperation.cs b/Storage/Internal/Operation/LCAddUniqueOperation.cs similarity index 100% rename from Storage/Storage/Internal/Operation/LCAddUniqueOperation.cs rename to Storage/Internal/Operation/LCAddUniqueOperation.cs diff --git a/Storage/Storage/Internal/Operation/LCDeleteOperation.cs b/Storage/Internal/Operation/LCDeleteOperation.cs similarity index 100% rename from Storage/Storage/Internal/Operation/LCDeleteOperation.cs rename to Storage/Internal/Operation/LCDeleteOperation.cs diff --git a/Storage/Storage/Internal/Operation/LCNumberOperation.cs b/Storage/Internal/Operation/LCNumberOperation.cs similarity index 100% rename from Storage/Storage/Internal/Operation/LCNumberOperation.cs rename to Storage/Internal/Operation/LCNumberOperation.cs diff --git a/Storage/Storage/Internal/Operation/LCRemoveOperation.cs b/Storage/Internal/Operation/LCRemoveOperation.cs similarity index 100% rename from Storage/Storage/Internal/Operation/LCRemoveOperation.cs rename to Storage/Internal/Operation/LCRemoveOperation.cs diff --git a/Storage/Storage/Internal/Operation/LCRemoveRelationOperation.cs b/Storage/Internal/Operation/LCRemoveRelationOperation.cs similarity index 100% rename from Storage/Storage/Internal/Operation/LCRemoveRelationOperation.cs rename to Storage/Internal/Operation/LCRemoveRelationOperation.cs diff --git a/Storage/Storage/Internal/Operation/LCSetOperation.cs b/Storage/Internal/Operation/LCSetOperation.cs similarity index 100% rename from Storage/Storage/Internal/Operation/LCSetOperation.cs rename to Storage/Internal/Operation/LCSetOperation.cs diff --git a/Storage/Storage/Internal/Query/ILCQueryCondition.cs b/Storage/Internal/Query/ILCQueryCondition.cs similarity index 100% rename from Storage/Storage/Internal/Query/ILCQueryCondition.cs rename to Storage/Internal/Query/ILCQueryCondition.cs diff --git a/Storage/Storage/Internal/Query/LCCompositionalCondition.cs b/Storage/Internal/Query/LCCompositionalCondition.cs similarity index 100% rename from Storage/Storage/Internal/Query/LCCompositionalCondition.cs rename to Storage/Internal/Query/LCCompositionalCondition.cs diff --git a/Storage/Storage/Internal/Query/LCEqualCondition.cs b/Storage/Internal/Query/LCEqualCondition.cs similarity index 100% rename from Storage/Storage/Internal/Query/LCEqualCondition.cs rename to Storage/Internal/Query/LCEqualCondition.cs diff --git a/Storage/Storage/Internal/Query/LCOperationCondition.cs b/Storage/Internal/Query/LCOperationCondition.cs similarity index 100% rename from Storage/Storage/Internal/Query/LCOperationCondition.cs rename to Storage/Internal/Query/LCOperationCondition.cs diff --git a/Storage/Storage/Internal/Query/LCRelatedCondition.cs b/Storage/Internal/Query/LCRelatedCondition.cs similarity index 100% rename from Storage/Storage/Internal/Query/LCRelatedCondition.cs rename to Storage/Internal/Query/LCRelatedCondition.cs diff --git a/Storage/Storage/LCACL.cs b/Storage/LCACL.cs similarity index 100% rename from Storage/Storage/LCACL.cs rename to Storage/LCACL.cs diff --git a/Storage/Storage/LCCloud.cs b/Storage/LCCloud.cs similarity index 100% rename from Storage/Storage/LCCloud.cs rename to Storage/LCCloud.cs diff --git a/Storage/Storage/LCException.cs b/Storage/LCException.cs similarity index 89% rename from Storage/Storage/LCException.cs rename to Storage/LCException.cs index e58fb46..acf2249 100644 --- a/Storage/Storage/LCException.cs +++ b/Storage/LCException.cs @@ -6,7 +6,7 @@ namespace LeanCloud.Storage { get; set; } - public string Message { + public new string Message { get; set; } diff --git a/Storage/Storage/LCFile.cs b/Storage/LCFile.cs similarity index 100% rename from Storage/Storage/LCFile.cs rename to Storage/LCFile.cs diff --git a/Storage/Storage/LCGeoPoint.cs b/Storage/LCGeoPoint.cs similarity index 100% rename from Storage/Storage/LCGeoPoint.cs rename to Storage/LCGeoPoint.cs diff --git a/Storage/Storage/LCObject.cs b/Storage/LCObject.cs similarity index 100% rename from Storage/Storage/LCObject.cs rename to Storage/LCObject.cs diff --git a/Storage/Storage/LCQuery.cs b/Storage/LCQuery.cs similarity index 100% rename from Storage/Storage/LCQuery.cs rename to Storage/LCQuery.cs diff --git a/Storage/Storage/LCRelation.cs b/Storage/LCRelation.cs similarity index 100% rename from Storage/Storage/LCRelation.cs rename to Storage/LCRelation.cs diff --git a/Storage/Storage/LCRole.cs b/Storage/LCRole.cs similarity index 100% rename from Storage/Storage/LCRole.cs rename to Storage/LCRole.cs diff --git a/Storage/Storage/LCUser.cs b/Storage/LCUser.cs similarity index 100% rename from Storage/Storage/LCUser.cs rename to Storage/LCUser.cs diff --git a/Storage/Storage/LCUserAuthDataLoginOption.cs b/Storage/LCUserAuthDataLoginOption.cs similarity index 100% rename from Storage/Storage/LCUserAuthDataLoginOption.cs rename to Storage/LCUserAuthDataLoginOption.cs diff --git a/Storage/Storage/LeanCloud.cs b/Storage/LeanCloud.cs similarity index 100% rename from Storage/Storage/LeanCloud.cs rename to Storage/LeanCloud.cs diff --git a/Storage/Source/Internal/AVCorePlugins.cs b/Storage/Source/Internal/AVCorePlugins.cs deleted file mode 100644 index 3b2fd06..0000000 --- a/Storage/Source/Internal/AVCorePlugins.cs +++ /dev/null @@ -1,384 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal -{ - public class AVPlugins : IAVCorePlugins - { - private static readonly object instanceMutex = new object(); - private static IAVCorePlugins instance; - public static IAVCorePlugins Instance - { - get - { - lock (instanceMutex) - { - instance = instance ?? new AVPlugins(); - return instance; - } - } - set - { - lock (instanceMutex) - { - instance = value; - } - } - } - - private readonly object mutex = new object(); - - #region Server Controllers - - private IHttpClient httpClient; - private IAppRouterController appRouterController; - private IAVCommandRunner commandRunner; - private IStorageController storageController; - - private IAVCloudCodeController cloudCodeController; - private IAVConfigController configController; - private IAVFileController fileController; - private IAVObjectController objectController; - private IAVQueryController queryController; - private IAVSessionController sessionController; - private IAVUserController userController; - private IObjectSubclassingController subclassingController; - - #endregion - - #region Current Instance Controller - - private IAVCurrentUserController currentUserController; - private IInstallationIdController installationIdController; - - #endregion - - public void Reset() - { - lock (mutex) - { - HttpClient = null; - AppRouterController = null; - CommandRunner = null; - StorageController = null; - - CloudCodeController = null; - FileController = null; - ObjectController = null; - SessionController = null; - UserController = null; - SubclassingController = null; - - CurrentUserController = null; - InstallationIdController = null; - } - } - - public IHttpClient HttpClient - { - get - { - lock (mutex) - { - httpClient = httpClient ?? new HttpClient(); - return httpClient; - } - } - set - { - lock (mutex) - { - httpClient = value; - } - } - } - - public IAppRouterController AppRouterController - { - get - { - lock (mutex) - { - appRouterController = appRouterController ?? new AppRouterController(); - return appRouterController; - } - } - set - { - lock (mutex) - { - appRouterController = value; - } - } - } - - public IAVCommandRunner CommandRunner - { - get - { - lock (mutex) - { - commandRunner = commandRunner ?? new AVCommandRunner(HttpClient, InstallationIdController); - return commandRunner; - } - } - set - { - lock (mutex) - { - commandRunner = value; - } - } - } - -#if !UNITY - public IStorageController StorageController - { - get - { - lock (mutex) - { - storageController = storageController ?? new StorageController(AVClient.CurrentConfiguration.ApplicationId); - return storageController; - } - } - set - { - lock (mutex) - { - storageController = value; - } - } - } -#endif -#if UNITY - public IStorageController StorageController - { - get - { - lock (mutex) - { - storageController = storageController ?? new StorageController(AVInitializeBehaviour.IsWebPlayer, AVClient.CurrentConfiguration.ApplicationId); - return storageController; - } - } - set - { - lock (mutex) - { - storageController = value; - } - } - } -#endif - - public IAVCloudCodeController CloudCodeController - { - get - { - lock (mutex) - { - cloudCodeController = cloudCodeController ?? new AVCloudCodeController(CommandRunner); - return cloudCodeController; - } - } - set - { - lock (mutex) - { - cloudCodeController = value; - } - } - } - - public IAVFileController FileController - { - get - { - lock (mutex) - { - if (AVClient.CurrentConfiguration.RegionValue == 0) - fileController = fileController ?? new QiniuFileController(CommandRunner); - else if (AVClient.CurrentConfiguration.RegionValue == 2) - fileController = fileController ?? new QCloudCosFileController(CommandRunner); - else if (AVClient.CurrentConfiguration.RegionValue == 1) - fileController = fileController ?? new AWSS3FileController(CommandRunner); - - return fileController; - } - } - set - { - lock (mutex) - { - fileController = value; - } - } - } - - public IAVConfigController ConfigController - { - get - { - lock (mutex) - { - if (configController == null) - { - configController = new AVConfigController(CommandRunner, StorageController); - } - return configController; - } - } - set - { - lock (mutex) - { - configController = value; - } - } - } - - public IAVObjectController ObjectController - { - get - { - lock (mutex) - { - objectController = objectController ?? new AVObjectController(CommandRunner); - return objectController; - } - } - set - { - lock (mutex) - { - objectController = value; - } - } - } - - public IAVQueryController QueryController - { - get - { - lock (mutex) - { - if (queryController == null) - { - queryController = new AVQueryController(CommandRunner); - } - return queryController; - } - } - set - { - lock (mutex) - { - queryController = value; - } - } - } - - public IAVSessionController SessionController - { - get - { - lock (mutex) - { - sessionController = sessionController ?? new AVSessionController(CommandRunner); - return sessionController; - } - } - set - { - lock (mutex) - { - sessionController = value; - } - } - } - - public IAVUserController UserController - { - get - { - lock (mutex) - { - userController = userController ?? new AVUserController(CommandRunner); - return userController; - } - } - set - { - lock (mutex) - { - userController = value; - } - } - } - - public IAVCurrentUserController CurrentUserController - { - get - { - lock (mutex) - { - currentUserController = currentUserController ?? new AVCurrentUserController(StorageController); - return currentUserController; - } - } - set - { - lock (mutex) - { - currentUserController = value; - } - } - } - - public IObjectSubclassingController SubclassingController - { - get - { - lock (mutex) - { - if (subclassingController == null) - { - subclassingController = new ObjectSubclassingController(); - subclassingController.AddRegisterHook(typeof(AVUser), () => CurrentUserController.ClearFromMemory()); - } - return subclassingController; - } - } - set - { - lock (mutex) - { - subclassingController = value; - } - } - } - - public IInstallationIdController InstallationIdController - { - get - { - lock (mutex) - { - installationIdController = installationIdController ?? new InstallationIdController(StorageController); - return installationIdController; - } - } - set - { - lock (mutex) - { - installationIdController = value; - } - } - } - } -} diff --git a/Storage/Source/Internal/AppRouter/AppRouterController.cs b/Storage/Source/Internal/AppRouter/AppRouterController.cs deleted file mode 100644 index 1e0c73d..0000000 --- a/Storage/Source/Internal/AppRouter/AppRouterController.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Net; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal -{ - public class AppRouterController : IAppRouterController - { - private AppRouterState currentState; - private object mutex = new object(); - - /// - /// Get current app's router state - /// - /// - public AppRouterState Get() - { - if (string.IsNullOrEmpty(AVClient.CurrentConfiguration.ApplicationId)) - { - throw new AVException(AVException.ErrorCode.NotInitialized, "ApplicationId can not be null."); - } - AppRouterState state; - state = AppRouterState.GetInitial(AVClient.CurrentConfiguration.ApplicationId, AVClient.CurrentConfiguration.Region); - - lock (mutex) - { - if (currentState != null) - { - if (!currentState.isExpired()) - state = currentState; - } - } - - if (state.isExpired()) - { - lock (mutex) - { - state.FetchedAt = DateTime.Now + TimeSpan.FromMinutes(10); - } - Task.Factory.StartNew(RefreshAsync); - } - return state; - } - - public Task RefreshAsync() - { - return QueryAsync(CancellationToken.None).ContinueWith(t => - { - if (!t.IsFaulted && !t.IsCanceled) - { - lock (mutex) - { - currentState = t.Result; - } - } - }); - } - - public Task QueryAsync(CancellationToken cancellationToken) - { - string appId = AVClient.CurrentConfiguration.ApplicationId; - string url = string.Format("https://app-router.leancloud.cn/2/route?appId={0}", appId); - - return AVClient.HttpGetAsync(new Uri(url)).ContinueWith(t => - { - var tcs = new TaskCompletionSource(); - if (t.Result.Item1 != HttpStatusCode.OK) - { - tcs.SetException(new AVException(AVException.ErrorCode.ConnectionFailed, "can not reach router.", null)); - } - else - { - var result = Json.Parse(t.Result.Item2) as IDictionary; - tcs.SetResult(ParseAppRouterState(result)); - } - return tcs.Task; - }).Unwrap(); - } - - private static AppRouterState ParseAppRouterState(IDictionary jsonObj) - { - var state = new AppRouterState() - { - TTL = (int)jsonObj["ttl"], - StatsServer = jsonObj["stats_server"] as string, - RealtimeRouterServer = jsonObj["rtm_router_server"] as string, - PushServer = jsonObj["push_server"] as string, - EngineServer = jsonObj["engine_server"] as string, - ApiServer = jsonObj["api_server"] as string, - Source = "network", - }; - return state; - } - - public void Clear() - { - currentState = null; - } - } -} diff --git a/Storage/Source/Internal/AppRouter/AppRouterState.cs b/Storage/Source/Internal/AppRouter/AppRouterState.cs deleted file mode 100644 index 9b8f85d..0000000 --- a/Storage/Source/Internal/AppRouter/AppRouterState.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; - -namespace LeanCloud.Storage.Internal -{ - public class AppRouterState - { - public long TTL { get; internal set; } - public string ApiServer { get; internal set; } - public string EngineServer { get; internal set; } - public string PushServer { get; internal set; } - public string RealtimeRouterServer { get; internal set; } - public string StatsServer { get; internal set; } - public string Source { get; internal set; } - - public DateTime FetchedAt { get; internal set; } - - private static object mutex = new object(); - - public AppRouterState() - { - FetchedAt = DateTime.Now; - } - - /// - /// Is this app router state expired. - /// - public bool isExpired() - { - return DateTime.Now > FetchedAt + TimeSpan.FromSeconds(TTL); - } - - /// - /// Get the initial usable router state - /// - /// Current app's appId - /// Current app's region - /// Initial app router state - public static AppRouterState GetInitial(string appId, AVClient.Configuration.AVRegion region) - { - var regionValue = (int)region; - var prefix = appId.Substring(0, 8).ToLower(); - switch (regionValue) - { - case 0: - // 华北 - return new AppRouterState() - { - TTL = -1, - ApiServer = string.Format("{0}.api.lncld.net", prefix), - EngineServer = string.Format("{0}.engine.lncld.net", prefix), - PushServer = string.Format("{0}.push.lncld.net", prefix), - RealtimeRouterServer = string.Format("{0}.rtm.lncld.net", prefix), - StatsServer = string.Format("{0}.stats.lncld.net", prefix), - Source = "initial", - }; - case 1: - // 美国 - return new AppRouterState() - { - TTL = -1, - ApiServer = string.Format("{0}.api.lncldglobal.com", prefix), - EngineServer = string.Format("{0}.engine.lncldglobal.com", prefix), - PushServer = string.Format("{0}.push.lncldglobal.com", prefix), - RealtimeRouterServer = string.Format("{0}.rtm.lncldglobal.com", prefix), - StatsServer = string.Format("{0}.stats.lncldglobal.com", prefix), - Source = "initial", - }; - case 2: - // 华东 - return new AppRouterState() { - TTL = -1, - ApiServer = string.Format("{0}.api.lncldapi.com", prefix), - EngineServer = string.Format("{0}.engine.lncldapi.com", prefix), - PushServer = string.Format("{0}.push.lncldapi.com", prefix), - RealtimeRouterServer = string.Format("{0}.rtm.lncldapi.com", prefix), - StatsServer = string.Format("{0}.stats.lncldapi.com", prefix), - Source = "initial", - }; - default: - throw new AVException(AVException.ErrorCode.OtherCause, "invalid region"); - } - } - - } -} \ No newline at end of file diff --git a/Storage/Source/Internal/AppRouter/IAppRouterController.cs b/Storage/Source/Internal/AppRouter/IAppRouterController.cs deleted file mode 100644 index 771ee18..0000000 --- a/Storage/Source/Internal/AppRouter/IAppRouterController.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal -{ - public interface IAppRouterController - { - AppRouterState Get(); - /// - /// Start refresh the app router. - /// - /// - Task RefreshAsync(); - void Clear(); - /// - /// Query the app router. - /// - /// New AppRouterState - Task QueryAsync(CancellationToken cancellationToken); - } -} diff --git a/Storage/Source/Internal/Authentication/IAVAuthenticationProvider.cs b/Storage/Source/Internal/Authentication/IAVAuthenticationProvider.cs deleted file mode 100644 index 406e37c..0000000 --- a/Storage/Source/Internal/Authentication/IAVAuthenticationProvider.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - public interface IAVAuthenticationProvider { - /// - /// Authenticates with the service. - /// - /// The cancellation token. - Task> AuthenticateAsync(CancellationToken cancellationToken); - - /// - /// Deauthenticates (logs out) the user associated with this provider. This - /// call may block. - /// - void Deauthenticate(); - - /// - /// Restores authentication that has been serialized, such as session keys, - /// etc. - /// - /// The auth data for the provider. This value may be null - /// when unlinking an account. - /// true iff the authData was successfully synchronized. A false return - /// value indicates that the user should no longer be associated because of bad auth - /// data. - bool RestoreAuthentication(IDictionary authData); - - /// - /// Provides a unique name for the type of authentication the provider does. - /// For example, the FacebookAuthenticationProvider would return "facebook". - /// - string AuthType { get; } - } -} diff --git a/Storage/Source/Internal/Cloud/Controller/AVCloudCodeController.cs b/Storage/Source/Internal/Cloud/Controller/AVCloudCodeController.cs deleted file mode 100644 index c6fa216..0000000 --- a/Storage/Source/Internal/Cloud/Controller/AVCloudCodeController.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Utilities; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal -{ - public class AVCloudCodeController : IAVCloudCodeController - { - private readonly IAVCommandRunner commandRunner; - - public AVCloudCodeController(IAVCommandRunner commandRunner) - { - this.commandRunner = commandRunner; - } - - public Task CallFunctionAsync(String name, - IDictionary parameters, - string sessionToken, - CancellationToken cancellationToken) - { - var command = new AVCommand(string.Format("functions/{0}", Uri.EscapeUriString(name)), - method: "POST", - sessionToken: sessionToken, - data: NoObjectsEncoder.Instance.Encode(parameters) as IDictionary); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - var decoded = AVDecoder.Instance.Decode(t.Result.Item2) as IDictionary; - if (!decoded.ContainsKey("result")) - { - return default(T); - } - return Conversion.To(decoded["result"]); - }); - } - - public Task RPCFunction(string name, IDictionary parameters, string sessionToken, CancellationToken cancellationToken) - { - var command = new AVCommand(string.Format("call/{0}", Uri.EscapeUriString(name)), - method: "POST", - sessionToken: sessionToken, - data: PointerOrLocalIdEncoder.Instance.Encode(parameters) as IDictionary); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - var decoded = AVDecoder.Instance.Decode(t.Result.Item2) as IDictionary; - if (!decoded.ContainsKey("result")) - { - return default(T); - } - return Conversion.To(decoded["result"]); - }); - } - } -} diff --git a/Storage/Source/Internal/Cloud/Controller/IAVCloudCodeController.cs b/Storage/Source/Internal/Cloud/Controller/IAVCloudCodeController.cs deleted file mode 100644 index 2575aae..0000000 --- a/Storage/Source/Internal/Cloud/Controller/IAVCloudCodeController.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal -{ - public interface IAVCloudCodeController - { - Task CallFunctionAsync(string name, - IDictionary parameters, - string sessionToken, - CancellationToken cancellationToken); - - Task RPCFunction(string name, IDictionary parameters, - string sessionToken, - CancellationToken cancellationToken); - } -} diff --git a/Storage/Source/Internal/Command/AVCommand.cs b/Storage/Source/Internal/Command/AVCommand.cs deleted file mode 100644 index 0d038bc..0000000 --- a/Storage/Source/Internal/Command/AVCommand.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using LeanCloud.Storage.Internal; -using System.Linq; - -namespace LeanCloud.Storage.Internal -{ - /// - /// AVCommand is an with pre-populated - /// headers. - /// - public class AVCommand : HttpRequest - { - public IDictionary DataObject { get; private set; } - public override Stream Data - { - get - { - if (base.Data != null) - { - return base.Data; - } - - return base.Data = (DataObject != null - ? new MemoryStream(Encoding.UTF8.GetBytes(Json.Encode(DataObject))) - : null); - } - set { base.Data = value; } - } - - public AVCommand(string relativeUri, - string method, - string sessionToken = null, - IList> headers = null, - IDictionary data = null) : this(relativeUri: relativeUri, - method: method, - sessionToken: sessionToken, - headers: headers, - stream: null, - contentType: data != null ? "application/json" : null) - { - DataObject = data; - } - - public AVCommand(string relativeUri, - string method, - string sessionToken = null, - IList> headers = null, - Stream stream = null, - string contentType = null) - { - var state = AVPlugins.Instance.AppRouterController.Get(); - var urlTemplate = "https://{0}/{1}/{2}"; - AVClient.Configuration configuration = AVClient.CurrentConfiguration; - var apiVersion = "1.1"; - if (relativeUri.StartsWith("push") || relativeUri.StartsWith("installations")) - { - Uri = new Uri(string.Format(urlTemplate, state.PushServer, apiVersion, relativeUri)); - if (configuration.PushServer != null) - { - Uri = new Uri(string.Format("{0}{1}/{2}", configuration.PushServer, apiVersion, relativeUri)); - } - } - else if (relativeUri.StartsWith("stats") || relativeUri.StartsWith("always_collect") || relativeUri.StartsWith("statistics")) - { - Uri = new Uri(string.Format(urlTemplate, state.StatsServer, apiVersion, relativeUri)); - if (configuration.StatsServer != null) - { - Uri = new Uri(string.Format("{0}{1}/{2}", configuration.StatsServer, apiVersion, relativeUri)); - } - } - else if (relativeUri.StartsWith("functions") || relativeUri.StartsWith("call")) - { - Uri = new Uri(string.Format(urlTemplate, state.EngineServer, apiVersion, relativeUri)); - - if (configuration.EngineServer != null) - { - Uri = new Uri(string.Format("{0}{1}/{2}", configuration.EngineServer, apiVersion, relativeUri)); - } - } - else - { - Uri = new Uri(string.Format(urlTemplate, state.ApiServer, apiVersion, relativeUri)); - - if (configuration.ApiServer != null) - { - Uri = new Uri(string.Format("{0}{1}/{2}", configuration.ApiServer, apiVersion, relativeUri)); - } - } - Method = method; - Data = stream; - Headers = new List>(headers ?? Enumerable.Empty>()); - - string useProduction = AVClient.UseProduction ? "1" : "0"; - Headers.Add(new KeyValuePair("X-LC-Prod", useProduction)); - - if (!string.IsNullOrEmpty(sessionToken)) - { - Headers.Add(new KeyValuePair("X-LC-Session", sessionToken)); - } - if (!string.IsNullOrEmpty(contentType)) - { - Headers.Add(new KeyValuePair("Content-Type", contentType)); - } - } - - public AVCommand(AVCommand other) - { - this.Uri = other.Uri; - this.Method = other.Method; - this.DataObject = other.DataObject; - this.Headers = new List>(other.Headers); - this.Data = other.Data; - } - } -} diff --git a/Storage/Source/Internal/Command/AVCommandRunner.cs b/Storage/Source/Internal/Command/AVCommandRunner.cs deleted file mode 100644 index 5ed2f4d..0000000 --- a/Storage/Source/Internal/Command/AVCommandRunner.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal -{ - /// - /// Command Runner. - /// - public class AVCommandRunner : IAVCommandRunner - { - private readonly IHttpClient httpClient; - private readonly IInstallationIdController installationIdController; - - /// - /// - /// - /// - /// - public AVCommandRunner(IHttpClient httpClient, IInstallationIdController installationIdController) - { - this.httpClient = httpClient; - this.installationIdController = installationIdController; - } - - /// - /// - /// - /// - /// - /// - /// - /// - public Task>> RunCommandAsync(AVCommand command, - IProgress uploadProgress = null, - IProgress downloadProgress = null, - CancellationToken cancellationToken = default) - { - return PrepareCommand(command).ContinueWith(commandTask => - { - var requestLog = commandTask.Result.ToLog(); - AVClient.PrintLog("http=>" + requestLog); - - return httpClient.ExecuteAsync(commandTask.Result, uploadProgress, downloadProgress, cancellationToken).OnSuccess(t => - { - cancellationToken.ThrowIfCancellationRequested(); - - var response = t.Result; - var contentString = response.Item2; - int responseCode = (int)response.Item1; - - var responseLog = responseCode + ";" + contentString; - AVClient.PrintLog("http<=" + responseLog); - - if (responseCode >= 500) - { - // Server error, return InternalServerError. - throw new AVException(AVException.ErrorCode.InternalServerError, response.Item2); - } - else if (contentString != null) - { - IDictionary contentJson = null; - try - { - if (contentString.StartsWith("[")) - { - var arrayJson = Json.Parse(contentString); - contentJson = new Dictionary { { "results", arrayJson } }; - } - else - { - contentJson = Json.Parse(contentString) as IDictionary; - } - } - catch (Exception e) - { - throw new AVException(AVException.ErrorCode.OtherCause, - "Invalid response from server", e); - } - if (responseCode < 200 || responseCode > 299) - { - AVClient.PrintLog("error response code:" + responseCode); - int code = (int)(contentJson.ContainsKey("code") ? (int)contentJson["code"] : (int)AVException.ErrorCode.OtherCause); - string error = contentJson.ContainsKey("error") ? - contentJson["error"] as string : contentString; - AVException.ErrorCode ec = (AVException.ErrorCode)code; - throw new AVException(ec, error); - } - return new Tuple>(response.Item1, - contentJson); - } - return new Tuple>(response.Item1, null); - }); - }).Unwrap(); - } - - private const string revocableSessionTokenTrueValue = "1"; - private Task PrepareCommand(AVCommand command) - { - AVCommand newCommand = new AVCommand(command); - - Task installationIdTask = installationIdController.GetAsync().ContinueWith(t => - { - newCommand.Headers.Add(new KeyValuePair("X-LC-Installation-Id", t.Result.ToString())); - return newCommand; - }); - - AVClient.Configuration configuration = AVClient.CurrentConfiguration; - newCommand.Headers.Add(new KeyValuePair("X-LC-Id", configuration.ApplicationId)); - - long timestamp = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds; - if (!string.IsNullOrEmpty(configuration.MasterKey) && AVClient.UseMasterKey) - { - string sign = MD5.GetMd5String(timestamp + configuration.MasterKey); - newCommand.Headers.Add(new KeyValuePair("X-LC-Sign", string.Format("{0},{1},master", sign, timestamp))); - } - else - { - string sign = MD5.GetMd5String(timestamp + configuration.ApplicationKey); - newCommand.Headers.Add(new KeyValuePair("X-LC-Sign", string.Format("{0},{1}", sign, timestamp))); - } - - newCommand.Headers.Add(new KeyValuePair("X-LC-Client-Version", AVClient.VersionString)); - - if (!string.IsNullOrEmpty(configuration.VersionInfo.BuildVersion)) - { - newCommand.Headers.Add(new KeyValuePair("X-LC-App-Build-Version", configuration.VersionInfo.BuildVersion)); - } - if (!string.IsNullOrEmpty(configuration.VersionInfo.DisplayVersion)) - { - newCommand.Headers.Add(new KeyValuePair("X-LC-App-Display-Version", configuration.VersionInfo.DisplayVersion)); - } - if (!string.IsNullOrEmpty(configuration.VersionInfo.OSVersion)) - { - newCommand.Headers.Add(new KeyValuePair("X-LC-OS-Version", configuration.VersionInfo.OSVersion)); - } - - if (AVUser.IsRevocableSessionEnabled) - { - newCommand.Headers.Add(new KeyValuePair("X-LeanCloud-Revocable-Session", revocableSessionTokenTrueValue)); - } - - if (configuration.AdditionalHTTPHeaders != null) - { - var headersDictionary = newCommand.Headers.ToDictionary(kv => kv.Key, kv => kv.Value); - foreach (var header in configuration.AdditionalHTTPHeaders) - { - if (headersDictionary.ContainsKey(header.Key)) - { - headersDictionary[header.Key] = header.Value; - } - else - { - newCommand.Headers.Add(header); - } - } - newCommand.Headers = headersDictionary.ToList(); - } - - return installationIdTask; - } - } -} diff --git a/Storage/Source/Internal/Command/IAVCommandRunner.cs b/Storage/Source/Internal/Command/IAVCommandRunner.cs deleted file mode 100644 index 62d5ee1..0000000 --- a/Storage/Source/Internal/Command/IAVCommandRunner.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal -{ - public interface IAVCommandRunner - { - /// - /// Executes and convert the result into Dictionary. - /// - /// The command to be run. - /// Upload progress callback. - /// Download progress callback. - /// The cancellation token for the request. - /// - Task>> RunCommandAsync(AVCommand command, - IProgress uploadProgress = null, - IProgress downloadProgress = null, - CancellationToken cancellationToken = default); - } -} diff --git a/Storage/Source/Internal/Config/Controller/AVConfigController.cs b/Storage/Source/Internal/Config/Controller/AVConfigController.cs deleted file mode 100644 index 15d34ff..0000000 --- a/Storage/Source/Internal/Config/Controller/AVConfigController.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Threading; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal { - /// - /// Config controller. - /// - internal class AVConfigController : IAVConfigController { - private readonly IAVCommandRunner commandRunner; - - /// - /// Initializes a new instance of the class. - /// - public AVConfigController(IAVCommandRunner commandRunner, IStorageController storageController) { - this.commandRunner = commandRunner; - CurrentConfigController = new AVCurrentConfigController(storageController); - } - - public IAVCommandRunner CommandRunner { get; internal set; } - public IAVCurrentConfigController CurrentConfigController { get; internal set; } - - public Task FetchConfigAsync(String sessionToken, CancellationToken cancellationToken) { - var command = new AVCommand("config", - method: "GET", - sessionToken: sessionToken, - data: null); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(task => { - cancellationToken.ThrowIfCancellationRequested(); - return new AVConfig(task.Result.Item2); - }).OnSuccess(task => { - cancellationToken.ThrowIfCancellationRequested(); - CurrentConfigController.SetCurrentConfigAsync(task.Result); - return task; - }).Unwrap(); - } - } -} diff --git a/Storage/Source/Internal/Config/Controller/AVCurrentConfigController.cs b/Storage/Source/Internal/Config/Controller/AVCurrentConfigController.cs deleted file mode 100644 index 41ba8d8..0000000 --- a/Storage/Source/Internal/Config/Controller/AVCurrentConfigController.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Threading; -using System.Collections.Generic; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal { - /// - /// LeanCloud current config controller. - /// - internal class AVCurrentConfigController : IAVCurrentConfigController { - private const string CurrentConfigKey = "CurrentConfig"; - - private readonly TaskQueue taskQueue; - private AVConfig currentConfig; - - private IStorageController storageController; - - /// - /// Initializes a new instance of the class. - /// - public AVCurrentConfigController(IStorageController storageController) { - this.storageController = storageController; - - taskQueue = new TaskQueue(); - } - - public Task GetCurrentConfigAsync() { - return taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => { - if (currentConfig == null) { - return storageController.LoadAsync().OnSuccess(t => { - object tmp; - t.Result.TryGetValue(CurrentConfigKey, out tmp); - - string propertiesString = tmp as string; - if (propertiesString != null) { - var dictionary = AVClient.DeserializeJsonString(propertiesString); - currentConfig = new AVConfig(dictionary); - } else { - currentConfig = new AVConfig(); - } - - return currentConfig; - }); - } - - return Task.FromResult(currentConfig); - }), CancellationToken.None).Unwrap(); - } - - public Task SetCurrentConfigAsync(AVConfig config) { - return taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => { - currentConfig = config; - - var jsonObject = ((IJsonConvertible)config).ToJSON(); - var jsonString = AVClient.SerializeJsonString(jsonObject); - - return storageController.LoadAsync().OnSuccess(t => t.Result.AddAsync(CurrentConfigKey, jsonString)); - }).Unwrap().Unwrap(), CancellationToken.None); - } - - public Task ClearCurrentConfigAsync() { - return taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => { - currentConfig = null; - - return storageController.LoadAsync().OnSuccess(t => t.Result.RemoveAsync(CurrentConfigKey)); - }).Unwrap().Unwrap(), CancellationToken.None); - } - - public Task ClearCurrentConfigInMemoryAsync() { - return taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => { - currentConfig = null; - }), CancellationToken.None); - } - } -} diff --git a/Storage/Source/Internal/Config/Controller/IAVConfigController.cs b/Storage/Source/Internal/Config/Controller/IAVConfigController.cs deleted file mode 100644 index 47cf384..0000000 --- a/Storage/Source/Internal/Config/Controller/IAVConfigController.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Threading; - -namespace LeanCloud.Storage.Internal { - public interface IAVConfigController { - /// - /// Gets the current config controller. - /// - /// The current config controller. - IAVCurrentConfigController CurrentConfigController { get; } - - /// - /// Fetches the config from the server asynchronously. - /// - /// The config async. - /// Session token. - /// Cancellation token. - Task FetchConfigAsync(string sessionToken, CancellationToken cancellationToken); - } -} diff --git a/Storage/Source/Internal/Config/Controller/IAVCurrentConfigController.cs b/Storage/Source/Internal/Config/Controller/IAVCurrentConfigController.cs deleted file mode 100644 index 759329c..0000000 --- a/Storage/Source/Internal/Config/Controller/IAVCurrentConfigController.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - public interface IAVCurrentConfigController { - /// - /// Gets the current config async. - /// - /// The current config async. - Task GetCurrentConfigAsync(); - - /// - /// Sets the current config async. - /// - /// The current config async. - /// Config. - Task SetCurrentConfigAsync(AVConfig config); - - /// - /// Clears the current config async. - /// - /// The current config async. - Task ClearCurrentConfigAsync(); - - /// - /// Clears the current config in memory async. - /// - /// The current config in memory async. - Task ClearCurrentConfigInMemoryAsync(); - } -} diff --git a/Storage/Source/Internal/Dispatcher/Unity/UnityDispatcher.cs b/Storage/Source/Internal/Dispatcher/Unity/UnityDispatcher.cs deleted file mode 100644 index 3a061e2..0000000 --- a/Storage/Source/Internal/Dispatcher/Unity/UnityDispatcher.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Threading; -using UnityEngine; - -namespace LeanCloud.Storage.Internal -{ - /// - /// This class represents the internal Unity dispatcher used by the LeanCloud SDK. - /// - /// It should be initialized once in your game, usually via AVInitializeBehavior. - /// - /// In certain, advanced use-cases, you may wish to use - /// this to set up your dispatcher manually. - /// - // TODO: (richardross) Review this interface before going public. - public sealed class Dispatcher - { - static Dispatcher() - { - Instance = new Dispatcher(); - } - - private Dispatcher() - { - DispatcherCoroutine = CreateDispatcherCoroutine(); - } - - public static Dispatcher Instance { get; private set; } - - public GameObject GameObject { get; set; } - public IEnumerator DispatcherCoroutine { get; private set; } - - private readonly ReaderWriterLockSlim dispatchQueueLock = new ReaderWriterLockSlim(); - private readonly Queue dispatchQueue = new Queue(); - - public void Post(Action action) - { - if (dispatchQueueLock.IsWriteLockHeld) - { - dispatchQueue.Enqueue(action); - return; - } - - dispatchQueueLock.EnterWriteLock(); - try - { - dispatchQueue.Enqueue(action); - } - finally - { - dispatchQueueLock.ExitWriteLock(); - } - } - - private IEnumerator CreateDispatcherCoroutine() - { - // We must stop the first invocation here, so that we don't actually do anything until we begin looping. - yield return null; - while (true) - { - dispatchQueueLock.EnterUpgradeableReadLock(); - try - { - // We'll only empty what's already in the dispatch queue in this iteration (so that a - // nested dispatch behaves like nextTick()). - int count = dispatchQueue.Count; - if (count > 0) - { - dispatchQueueLock.EnterWriteLock(); - try - { - while (count > 0) - { - try - { - dispatchQueue.Dequeue()(); - } - catch (Exception e) - { - // If an exception occurs, catch it and log it so that dispatches aren't broken. - Debug.LogException(e); - } - count--; - } - } - finally - { - dispatchQueueLock.ExitWriteLock(); - } - } - } - finally - { - dispatchQueueLock.ExitUpgradeableReadLock(); - } - yield return null; - } - } - } -} diff --git a/Storage/Source/Internal/Encoding/AVDecoder.cs b/Storage/Source/Internal/Encoding/AVDecoder.cs deleted file mode 100644 index fdb4303..0000000 --- a/Storage/Source/Internal/Encoding/AVDecoder.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.Globalization; -using LeanCloud.Utilities; - -namespace LeanCloud.Storage.Internal -{ - public class AVDecoder - { - // This class isn't really a Singleton, but since it has no state, it's more efficient to get - // the default instance. - private static readonly AVDecoder instance = new AVDecoder(); - public static AVDecoder Instance - { - get - { - return instance; - } - } - - // Prevent default constructor. - private AVDecoder() { } - - public object Decode(object data) - { - if (data == null) - { - return null; - } - - var dict = data as IDictionary; - if (dict != null) - { - if (dict.ContainsKey("__op")) - { - return AVFieldOperations.Decode(dict); - } - - object type; - dict.TryGetValue("__type", out type); - var typeString = type as string; - - if (typeString == null) - { - var newDict = new Dictionary(); - foreach (var pair in dict) - { - newDict[pair.Key] = Decode(pair.Value); - } - return newDict; - } - - if (typeString == "Date") - { - return ParseDate(dict["iso"] as string); - } - - if (typeString == "Bytes") - { - return Convert.FromBase64String(dict["base64"] as string); - } - - if (typeString == "Pointer") - { - //set a include key to fetch or query. - if (dict.Keys.Count > 3) - { - return DecodeAVObject(dict); - } - return DecodePointer(dict["className"] as string, dict["objectId"] as string); - } - - if (typeString == "File") - { - return DecodeAVFile(dict); - } - - if (typeString == "GeoPoint") - { - return new AVGeoPoint(Conversion.To(dict["latitude"]), - Conversion.To(dict["longitude"])); - } - - if (typeString == "Object") - { - return DecodeAVObject(dict); - } - - if (typeString == "Relation") - { - return AVRelationBase.CreateRelation(null, null, dict["className"] as string); - } - - var converted = new Dictionary(); - foreach (var pair in dict) - { - converted[pair.Key] = Decode(pair.Value); - } - return converted; - } - - var list = data as IList; - if (list != null) - { - return (from item in list - select Decode(item)).ToList(); - } - - return data; - } - - protected virtual object DecodePointer(string className, string objectId) - { - if (className == "_File") - { - return AVFile.CreateWithoutData(objectId); - } - return AVObject.CreateWithoutData(className, objectId); - } - protected virtual object DecodeAVObject(IDictionary dict) - { - var className = dict["className"] as string; - if (className == "_File") - { - return DecodeAVFile(dict); - } - var state = AVObjectCoder.Instance.Decode(dict, this); - return AVObject.FromState(state, dict["className"] as string); - } - protected virtual object DecodeAVFile(IDictionary dict) - { - var objectId = dict["objectId"] as string; - var file = AVFile.CreateWithoutData(objectId); - file.MergeFromJSON(dict); - return file; - } - - - public virtual IList DecodeList(object data) - { - IList rtn = null; - var list = (IList)data; - if (list != null) - { - rtn = new List(); - foreach (var item in list) - { - rtn.Add((T)item); - } - } - return rtn; - } - - public static DateTime ParseDate(string input) - { - var rtn = DateTime.ParseExact(input, - AVClient.DateFormatStrings, - CultureInfo.InvariantCulture, - DateTimeStyles.AssumeUniversal); - return rtn; - } - } -} diff --git a/Storage/Source/Internal/Encoding/AVEncoder.cs b/Storage/Source/Internal/Encoding/AVEncoder.cs deleted file mode 100644 index 401edc1..0000000 --- a/Storage/Source/Internal/Encoding/AVEncoder.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using LeanCloud.Utilities; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal -{ - /// - /// A AVEncoder can be used to transform objects such as into JSON - /// data structures. - /// - /// - public abstract class AVEncoder - { -#if UNITY - private static readonly bool isCompiledByIL2CPP = AppDomain.CurrentDomain.FriendlyName.Equals("IL2CPP Root Domain"); -#else - private static readonly bool isCompiledByIL2CPP = false; -#endif - - public static bool IsValidType(object value) - { - return value == null || - ReflectionHelpers.IsPrimitive(value.GetType()) || - value is string || - value is AVObject || - value is AVACL || - value is AVFile || - value is AVGeoPoint || - value is AVRelationBase || - value is DateTime || - value is byte[] || - Conversion.As>(value) != null || - Conversion.As>(value) != null; - } - - public object Encode(object value) - { - // If this object has a special encoding, encode it and return the - // encoded object. Otherwise, just return the original object. - if (value is DateTime) - { - return new Dictionary - { - { - "iso", ((DateTime)value).ToUniversalTime().ToString(AVClient.DateFormatStrings.First(), CultureInfo.InvariantCulture) - }, - { - "__type", "Date" - } - }; - } - - if (value is AVFile) - { - var file = value as AVFile; - return new Dictionary - { - {"__type", "Pointer"}, - { "className", "_File"}, - { "objectId", file.ObjectId} - }; - } - - var bytes = value as byte[]; - if (bytes != null) - { - return new Dictionary - { - { "__type", "Bytes"}, - { "base64", Convert.ToBase64String(bytes)} - }; - } - - var obj = value as AVObject; - if (obj != null) - { - return EncodeAVObject(obj); - } - - var jsonConvertible = value as IJsonConvertible; - if (jsonConvertible != null) - { - return jsonConvertible.ToJSON(); - } - - var dict = Conversion.As>(value); - if (dict != null) - { - var json = new Dictionary(); - foreach (var pair in dict) - { - json[pair.Key] = Encode(pair.Value); - } - return json; - } - - var list = Conversion.As>(value); - if (list != null) - { - return EncodeList(list); - } - - // TODO (hallucinogen): convert IAVFieldOperation to IJsonConvertible - var operation = value as IAVFieldOperation; - if (operation != null) - { - return operation.Encode(); - } - - return value; - } - - protected abstract IDictionary EncodeAVObject(AVObject value); - - private object EncodeList(IList list) - { - var newArray = new List(); - // We need to explicitly cast `list` to `List` rather than - // `IList` because IL2CPP is stricter than the usual Unity AOT compiler pipeline. - if (isCompiledByIL2CPP && list.GetType().IsArray) - { - list = new List(list); - } - foreach (var item in list) - { - if (!IsValidType(item)) - { - throw new ArgumentException("Invalid type for value in an array"); - } - newArray.Add(Encode(item)); - } - return newArray; - } - } -} diff --git a/Storage/Source/Internal/Encoding/AVObjectCoder.cs b/Storage/Source/Internal/Encoding/AVObjectCoder.cs deleted file mode 100644 index 05a6509..0000000 --- a/Storage/Source/Internal/Encoding/AVObjectCoder.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal -{ - // TODO: (richardross) refactor entire LeanCloud coder interfaces. - public class AVObjectCoder - { - private static readonly AVObjectCoder instance = new AVObjectCoder(); - public static AVObjectCoder Instance - { - get - { - return instance; - } - } - - // Prevent default constructor. - private AVObjectCoder() { } - - public IDictionary Encode(T state, - IDictionary operations, - AVEncoder encoder) where T : IObjectState - { - var result = new Dictionary(); - foreach (var pair in operations) - { - // AVRPCSerialize the data - var operation = pair.Value; - - result[pair.Key] = encoder.Encode(operation); - } - - return result; - } - - public IObjectState Decode(IDictionary data, - AVDecoder decoder) - { - IDictionary serverData = new Dictionary(); - var mutableData = new Dictionary(data); - string objectId = extractFromDictionary(mutableData, "objectId", (obj) => - { - return obj as string; - }); - DateTime? createdAt = extractFromDictionary(mutableData, "createdAt", (obj) => - { - return AVDecoder.ParseDate(obj as string); - }); - DateTime? updatedAt = extractFromDictionary(mutableData, "updatedAt", (obj) => - { - return AVDecoder.ParseDate(obj as string); - }); - - if (mutableData.ContainsKey("ACL")) - { - serverData["ACL"] = extractFromDictionary(mutableData, "ACL", (obj) => - { - return new AVACL(obj as IDictionary); - }); - } - string className = extractFromDictionary(mutableData, "className", obj => - { - return obj as string; - }); - if (createdAt != null && updatedAt == null) - { - updatedAt = createdAt; - } - - // Bring in the new server data. - foreach (var pair in mutableData) - { - if (pair.Key == "__type" || pair.Key == "className") - { - continue; - } - - var value = pair.Value; - serverData[pair.Key] = decoder.Decode(value); - } - - return new MutableObjectState - { - ObjectId = objectId, - CreatedAt = createdAt, - UpdatedAt = updatedAt, - ServerData = serverData, - ClassName = className - }; - } - - private T extractFromDictionary(IDictionary data, string key, Func action) - { - T result = default(T); - if (data.ContainsKey(key)) - { - result = action(data[key]); - data.Remove(key); - } - - return result; - } - } -} diff --git a/Storage/Source/Internal/Encoding/NoObjectsEncoder.cs b/Storage/Source/Internal/Encoding/NoObjectsEncoder.cs deleted file mode 100644 index 6a1e7e1..0000000 --- a/Storage/Source/Internal/Encoding/NoObjectsEncoder.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - /// - /// A that throws an exception if it attempts to encode - /// a - /// - public class NoObjectsEncoder : AVEncoder { - // This class isn't really a Singleton, but since it has no state, it's more efficient to get - // the default instance. - private static readonly NoObjectsEncoder instance = new NoObjectsEncoder(); - public static NoObjectsEncoder Instance { - get { - return instance; - } - } - - protected override IDictionary EncodeAVObject(AVObject value) { - throw new ArgumentException("AVObjects not allowed here."); - } - } -} diff --git a/Storage/Source/Internal/Encoding/PointerOrLocalIdEncoder.cs b/Storage/Source/Internal/Encoding/PointerOrLocalIdEncoder.cs deleted file mode 100644 index 2394cf7..0000000 --- a/Storage/Source/Internal/Encoding/PointerOrLocalIdEncoder.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; - -namespace LeanCloud.Storage.Internal -{ - /// - /// A that encode as pointers. If the object - /// does not have an , uses a local id. - /// - public class PointerOrLocalIdEncoder : AVEncoder - { - // This class isn't really a Singleton, but since it has no state, it's more efficient to get - // the default instance. - private static readonly PointerOrLocalIdEncoder instance = new PointerOrLocalIdEncoder(); - public static PointerOrLocalIdEncoder Instance - { - get - { - return instance; - } - } - - protected override IDictionary EncodeAVObject(AVObject value) - { - if (value.ObjectId == null) - { - // TODO (hallucinogen): handle local id. For now we throw. - throw new ArgumentException("Cannot create a pointer to an object without an objectId"); - } - - return new Dictionary { - {"__type", "Pointer"}, - { "className", value.ClassName}, - { "objectId", value.ObjectId} - }; - } - - public IDictionary EncodeAVObject(AVObject value, bool isPointer) - { - if (isPointer) - { - return EncodeAVObject(value); - } - var operations = value.GetCurrentOperations(); - var operationJSON = AVObject.ToJSONObjectForSaving(operations); - var objectJSON = value.ToDictionary(kvp => kvp.Key, kvp => PointerOrLocalIdEncoder.Instance.Encode(kvp.Value)); - foreach (var kvp in operationJSON) - { - objectJSON[kvp.Key] = kvp.Value; - } - if (value.CreatedAt.HasValue) - { - objectJSON["createdAt"] = value.CreatedAt.Value.ToString(AVClient.DateFormatStrings.First(), - CultureInfo.InvariantCulture); - } - if (value.UpdatedAt.HasValue) - { - objectJSON["updatedAt"] = value.UpdatedAt.Value.ToString(AVClient.DateFormatStrings.First(), - CultureInfo.InvariantCulture); - } - if(!string.IsNullOrEmpty(value.ObjectId)) - { - objectJSON["objectId"] = value.ObjectId; - } - objectJSON["className"] = value.ClassName; - objectJSON["__type"] = "Object"; - return objectJSON; - } - } -} diff --git a/Storage/Source/Internal/File/Controller/AVFileController.cs b/Storage/Source/Internal/File/Controller/AVFileController.cs deleted file mode 100644 index 192d64c..0000000 --- a/Storage/Source/Internal/File/Controller/AVFileController.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Storage.Internal; -using System.Net; -using System.Collections.Generic; -using System.Linq; - -namespace LeanCloud.Storage.Internal { - /// - /// AVF ile controller. - /// - public class AVFileController : IAVFileController { - private readonly IAVCommandRunner commandRunner; - /// - /// Initializes a new instance of the class. - /// - /// Command runner. - public AVFileController(IAVCommandRunner commandRunner) { - this.commandRunner = commandRunner; - } - /// - /// Saves the async. - /// - /// The async. - /// State. - /// Data stream. - /// Session token. - /// Progress. - /// Cancellation token. - public virtual Task SaveAsync(FileState state, - Stream dataStream, - string sessionToken, - IProgress progress, - CancellationToken cancellationToken = default) { - if (state.Url != null) { - // !isDirty - return Task.FromResult(state); - } - - if (cancellationToken.IsCancellationRequested) { - var tcs = new TaskCompletionSource(); - tcs.TrySetCanceled(); - return tcs.Task; - } - - var oldPosition = dataStream.Position; - var command = new AVCommand("files/" + state.Name, - method: "POST", - sessionToken: sessionToken, - contentType: state.MimeType, - stream: dataStream); - - return commandRunner.RunCommandAsync(command, - uploadProgress: progress, - cancellationToken: cancellationToken).OnSuccess(uploadTask => { - var result = uploadTask.Result; - var jsonData = result.Item2; - cancellationToken.ThrowIfCancellationRequested(); - - return new FileState { - Name = jsonData["name"] as string, - Url = new Uri(jsonData["url"] as string, UriKind.Absolute), - MimeType = state.MimeType - }; - }).ContinueWith(t => { - // Rewind the stream on failure or cancellation (if possible) - if ((t.IsFaulted || t.IsCanceled) && dataStream.CanSeek) { - dataStream.Seek(oldPosition, SeekOrigin.Begin); - } - return t; - }).Unwrap(); - } - public Task DeleteAsync(FileState state, string sessionToken, CancellationToken cancellationToken) { - var command = new AVCommand("files/" + state.ObjectId, - method: "DELETE", - sessionToken: sessionToken, - data: null); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken); - } - internal static Task>> GetFileToken(FileState fileState, CancellationToken cancellationToken) { - Task>> rtn; - string currentSessionToken = AVUser.CurrentSessionToken; - string str = fileState.Name; - IDictionary parameters = new Dictionary(); - parameters.Add("name", str); - parameters.Add("key", GetUniqueName(fileState)); - parameters.Add("__type", "File"); - parameters.Add("mime_type", AVFile.GetMIMEType(str)); - parameters.Add("metaData", fileState.MetaData); - - rtn = AVClient.RequestAsync("POST", new Uri("fileTokens", UriKind.Relative), currentSessionToken, parameters, cancellationToken); - - return rtn; - } - public Task GetAsync(string objectId, string sessionToken, CancellationToken cancellationToken) { - var command = new AVCommand("files/" + objectId, - method: "GET", - sessionToken: sessionToken, - data: null); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(_ => { - var result = _.Result; - var jsonData = result.Item2; - cancellationToken.ThrowIfCancellationRequested(); - return new FileState { - ObjectId = jsonData["objectId"] as string, - Name = jsonData["name"] as string, - Url = new Uri(jsonData["url"] as string, UriKind.Absolute), - }; - }); - } - internal static string GetUniqueName(FileState fileState) { - string key = Random(12); - string extension = Path.GetExtension(fileState.Name); - key += extension; - fileState.CloudName = key; - return key; - } - internal static string Random(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 double CalcProgress(double already, double total) { - var pv = (1.0 * already / total); - return Math.Round(pv, 3); - } - } -} diff --git a/Storage/Source/Internal/File/Controller/AWSS3FileController.cs b/Storage/Source/Internal/File/Controller/AWSS3FileController.cs deleted file mode 100644 index 313b82c..0000000 --- a/Storage/Source/Internal/File/Controller/AWSS3FileController.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Threading; -using System.IO; -using LeanCloud.Storage.Internal; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal -{ - internal class AWSS3FileController : AVFileController - { - - private object mutex = new object(); - - - public AWSS3FileController(IAVCommandRunner commandRunner) : base(commandRunner) - { - - } - - public override Task SaveAsync(FileState state, Stream dataStream, string sessionToken, IProgress progress, CancellationToken cancellationToken = default) - { - if (state.Url != null) - { - return Task.FromResult(state); - } - - return GetFileToken(state, cancellationToken).OnSuccess(t => - { - var fileToken = t.Result.Item2; - var uploadUrl = fileToken["upload_url"].ToString(); - state.ObjectId = fileToken["objectId"].ToString(); - string url = fileToken["url"] as string; - state.Url = new Uri(url, UriKind.Absolute); - return PutFile(state, uploadUrl, dataStream); - - }).Unwrap().OnSuccess(s => - { - return s.Result; - }); - } - - internal Task PutFile(FileState state, string uploadUrl, Stream dataStream) - { - IList> makeBlockHeaders = new List>(); - makeBlockHeaders.Add(new KeyValuePair("Content-Type", state.MimeType)); - - return AVClient.RequestAsync(new Uri(uploadUrl), "PUT", makeBlockHeaders, dataStream, state.MimeType, CancellationToken.None).OnSuccess(t => - { - return state; - }); - } - } -} diff --git a/Storage/Source/Internal/File/Controller/IAVFileController.cs b/Storage/Source/Internal/File/Controller/IAVFileController.cs deleted file mode 100644 index 305f51e..0000000 --- a/Storage/Source/Internal/File/Controller/IAVFileController.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal -{ - public interface IAVFileController - { - Task SaveAsync(FileState state, - Stream dataStream, - string sessionToken, - IProgress progress, - CancellationToken cancellationToken); - - Task DeleteAsync(FileState state, - string sessionToken, - CancellationToken cancellationToken); - - Task GetAsync(string objectId, - string sessionToken, - CancellationToken cancellationToken); - } -} diff --git a/Storage/Source/Internal/File/Controller/QCloudCosFileController.cs b/Storage/Source/Internal/File/Controller/QCloudCosFileController.cs deleted file mode 100644 index 9eee34a..0000000 --- a/Storage/Source/Internal/File/Controller/QCloudCosFileController.cs +++ /dev/null @@ -1,250 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Linq; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal -{ - internal class QCloudCosFileController : AVFileController - { - private object mutex = new object(); - - FileState fileState; - Stream data; - string bucket; - string token; - string uploadUrl; - bool done; - private long sliceSize = (long)CommonSize.KB512; - - public QCloudCosFileController(IAVCommandRunner commandRunner) : base(commandRunner) - { - } - - public Task SaveAsync(FileState state, - Stream dataStream, - string sessionToken, - IProgress progress, - CancellationToken cancellationToken) - { - if (state.Url != null) - { - return Task.FromResult(state); - } - fileState = state; - data = dataStream; - return GetFileToken(fileState, cancellationToken).OnSuccess(_ => - { - var fileToken = _.Result.Item2; - uploadUrl = fileToken["upload_url"].ToString(); - token = fileToken["token"].ToString(); - fileState.ObjectId = fileToken["objectId"].ToString(); - bucket = fileToken["bucket"].ToString(); - - return FileSlice(cancellationToken).OnSuccess(t => - { - if (done) return Task.FromResult(state); - var response = t.Result.Item2; - var resumeData = response["data"] as IDictionary; - if (resumeData.ContainsKey("access_url")) return Task.FromResult(state); - var sliceSession = resumeData["session"].ToString(); - var sliceOffset = long.Parse(resumeData["offset"].ToString()); - return UploadSlice(sliceSession, sliceOffset, dataStream, progress, cancellationToken); - }).Unwrap(); - - }).Unwrap(); - } - - Task UploadSlice( - string sessionId, - long offset, - Stream dataStream, - IProgress progress, - CancellationToken cancellationToken) - { - - long dataLength = dataStream.Length; - if (progress != null) - { - lock (mutex) - { - progress.Report(new AVUploadProgressEventArgs() - { - Progress = AVFileController.CalcProgress(offset, dataLength) - }); - } - } - - if (offset == dataLength) - { - return Task.FromResult(fileState); - } - - var sliceFile = GetNextBinary(offset, dataStream); - return ExcuteUpload(sessionId, offset, sliceFile, cancellationToken).OnSuccess(_ => - { - offset += sliceFile.Length; - if (offset == dataLength) - { - done = true; - return Task.FromResult(fileState); - } - var response = _.Result.Item2; - var resumeData = response["data"] as IDictionary; - var sliceSession = resumeData["session"].ToString(); - return UploadSlice(sliceSession, offset, dataStream, progress, cancellationToken); - }).Unwrap(); - } - - Task>> ExcuteUpload(string sessionId, long offset, byte[] sliceFile, CancellationToken cancellationToken) - { - var body = new Dictionary(); - body.Add("op", "upload_slice"); - body.Add("session", sessionId); - body.Add("offset", offset.ToString()); - - return PostToQCloud(body, sliceFile, cancellationToken); - } - - Task>> FileSlice(CancellationToken cancellationToken) - { - SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider(); - var body = new Dictionary(); - if (data.Length <= (long)CommonSize.KB512) - { - body.Add("op", "upload"); - body.Add("sha", HexStringFromBytes(sha1.ComputeHash(data))); - var wholeFile = GetNextBinary(0, data); - return PostToQCloud(body, wholeFile, cancellationToken).OnSuccess(_ => - { - if (_.Result.Item1 == HttpStatusCode.OK) - { - done = true; - } - return _.Result; - }); - } - else - { - body.Add("op", "upload_slice"); - body.Add("filesize", data.Length); - body.Add("sha", HexStringFromBytes(sha1.ComputeHash(data))); - body.Add("slice_size", (long)CommonSize.KB512); - } - - return PostToQCloud(body, null, cancellationToken); - } - public static string HexStringFromBytes(byte[] bytes) - { - var sb = new StringBuilder(); - foreach (byte b in bytes) - { - var hex = b.ToString("x2"); - sb.Append(hex); - } - return sb.ToString(); - } - - public static string SHA1HashStringForUTF8String(string s) - { - byte[] bytes = Encoding.UTF8.GetBytes(s); - - SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider(); - byte[] hashBytes = sha1.ComputeHash(bytes); - - return HexStringFromBytes(hashBytes); - } - Task>> PostToQCloud( - Dictionary body, - byte[] sliceFile, - CancellationToken cancellationToken) - { - IList> sliceHeaders = new List>(); - sliceHeaders.Add(new KeyValuePair("Authorization", this.token)); - - string contentType; - long contentLength; - - var tempStream = HttpUploadFile(sliceFile, fileState.CloudName, out contentType, out contentLength, body); - - sliceHeaders.Add(new KeyValuePair("Content-Type", contentType)); - - var rtn = AVClient.RequestAsync(new Uri(this.uploadUrl), "POST", sliceHeaders, tempStream, null, cancellationToken).OnSuccess(_ => - { - var dic = AVClient.ReponseResolve(_.Result, CancellationToken.None); - - return dic; - }); - - return rtn; - } - public static Stream HttpUploadFile(byte[] file, string fileName, out string contentType, out long contentLength, IDictionary nvc) - { - string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); - byte[] boundarybytes = StringToAscii("\r\n--" + boundary + "\r\n"); - contentType = "multipart/form-data; boundary=" + boundary; - - MemoryStream rs = new MemoryStream(); - - string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}"; - foreach (string key in nvc.Keys) - { - rs.Write(boundarybytes, 0, boundarybytes.Length); - string formitem = string.Format(formdataTemplate, key, nvc[key]); - byte[] formitembytes = System.Text.Encoding.UTF8.GetBytes(formitem); - rs.Write(formitembytes, 0, formitembytes.Length); - } - rs.Write(boundarybytes, 0, boundarybytes.Length); - - if (file != null) - { - string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n"; - string header = string.Format(headerTemplate, "fileContent", fileName, "application/octet-stream"); - byte[] headerbytes = System.Text.Encoding.UTF8.GetBytes(header); - rs.Write(headerbytes, 0, headerbytes.Length); - - rs.Write(file, 0, file.Length); - } - - byte[] trailer = StringToAscii("\r\n--" + boundary + "--\r\n"); - rs.Write(trailer, 0, trailer.Length); - contentLength = rs.Length; - - rs.Position = 0; - var tempBuffer = new byte[rs.Length]; - rs.Read(tempBuffer, 0, tempBuffer.Length); - - return new MemoryStream(tempBuffer); - } - - public static byte[] StringToAscii(string s) - { - byte[] retval = new byte[s.Length]; - for (int ix = 0; ix < s.Length; ++ix) - { - char ch = s[ix]; - if (ch <= 0x7f) retval[ix] = (byte)ch; - else retval[ix] = (byte)'?'; - } - return retval; - } - - byte[] GetNextBinary(long completed, Stream dataStream) - { - if (completed + sliceSize > dataStream.Length) - { - sliceSize = dataStream.Length - completed; - } - - byte[] chunkBinary = new byte[sliceSize]; - dataStream.Seek(completed, SeekOrigin.Begin); - dataStream.Read(chunkBinary, 0, (int)sliceSize); - return chunkBinary; - } - } -} diff --git a/Storage/Source/Internal/File/Controller/QiniuFileController.cs b/Storage/Source/Internal/File/Controller/QiniuFileController.cs deleted file mode 100644 index 72a8c5b..0000000 --- a/Storage/Source/Internal/File/Controller/QiniuFileController.cs +++ /dev/null @@ -1,332 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal -{ - internal enum CommonSize : long - { - MB4 = 1024 * 1024 * 4, - MB1 = 1024 * 1024, - KB512 = 1024 * 1024 / 2, - KB256 = 1024 * 1024 / 4 - } - - internal class QiniuFileController : AVFileController - { - private static int BLOCKSIZE = 1024 * 1024 * 4; - private const int blockMashk = (1 << blockBits) - 1; - private const int blockBits = 22; - private int CalcBlockCount(long fsize) - { - return (int)((fsize + blockMashk) >> blockBits); - } - internal static string UP_HOST = "https://up.qbox.me"; - private object mutex = new object(); - - public QiniuFileController(IAVCommandRunner commandRunner) : base(commandRunner) - { - - } - - public override Task SaveAsync(FileState state, - Stream dataStream, - String sessionToken, - IProgress progress, - CancellationToken cancellationToken) - { - if (state.Url != null) - { - return Task.FromResult(state); - } - state.frozenData = dataStream; - state.CloudName = GetUniqueName(state); - return GetQiniuToken(state, CancellationToken.None).ContinueWith(t => - { - MergeFromJSON(state, t.Result.Item2); - return UploadNextChunk(state, dataStream, string.Empty, 0, progress); - }).Unwrap().OnSuccess(s => - { - return state; - }); - } - Task UploadNextChunk(FileState state, Stream dataStream, string context, long offset, IProgress progress) - { - var totalSize = dataStream.Length; - var remainingSize = totalSize - state.completed; - - if (progress != null) - { - lock (mutex) - { - progress.Report(new AVUploadProgressEventArgs() - { - Progress = AVFileController.CalcProgress(state.completed, totalSize) - }); - } - } - if (state.completed == totalSize) - { - return QiniuMakeFile(state, state.frozenData, state.token, state.CloudName, totalSize, state.block_ctxes.ToArray(), CancellationToken.None); - - } - else if (state.completed % BLOCKSIZE == 0) - { - var firstChunkBinary = GetChunkBinary(state.completed, dataStream); - - var blockSize = remainingSize > BLOCKSIZE ? BLOCKSIZE : remainingSize; - return MakeBlock(state, firstChunkBinary, blockSize).ContinueWith(t => - { - - var dic = AVClient.ReponseResolve(t.Result, CancellationToken.None); - var ctx = dic.Item2["ctx"].ToString(); - - offset = long.Parse(dic.Item2["offset"].ToString()); - var host = dic.Item2["host"].ToString(); - - state.completed += firstChunkBinary.Length; - if (state.completed % BLOCKSIZE == 0 || state.completed == totalSize) - { - state.block_ctxes.Add(ctx); - } - - return UploadNextChunk(state, dataStream, ctx, offset, progress); - }).Unwrap(); - - } - else - { - var chunkBinary = GetChunkBinary(state.completed, dataStream); - return PutChunk(state, chunkBinary, context, offset).ContinueWith(t => - { - var dic = AVClient.ReponseResolve(t.Result, CancellationToken.None); - var ctx = dic.Item2["ctx"].ToString(); - - offset = long.Parse(dic.Item2["offset"].ToString()); - var host = dic.Item2["host"].ToString(); - state.completed += chunkBinary.Length; - if (state.completed % BLOCKSIZE == 0 || state.completed == totalSize) - { - state.block_ctxes.Add(ctx); - } - //if (AVClient.fileUploaderDebugLog) - //{ - // AVClient.LogTracker(state.counter + "|completed=" + state.completed + "stream:position=" + dataStream.Position + "|"); - //} - - return UploadNextChunk(state, dataStream, ctx, offset, progress); - }).Unwrap(); - } - } - - byte[] GetChunkBinary(long completed, Stream dataStream) - { - long chunkSize = (long)CommonSize.MB1; - if (completed + chunkSize > dataStream.Length) - { - chunkSize = dataStream.Length - completed; - } - byte[] chunkBinary = new byte[chunkSize]; - dataStream.Seek(completed, SeekOrigin.Begin); - dataStream.Read(chunkBinary, 0, (int)chunkSize); - return chunkBinary; - } - - internal string GetUniqueName(FileState state) - { - string key = Guid.NewGuid().ToString();//file Key in Qiniu. - string extension = Path.GetExtension(state.Name); - key += extension; - return key; - } - internal Task>> GetQiniuToken(FileState state, CancellationToken cancellationToken) - { - Task>> rtn; - string currentSessionToken = AVUser.CurrentSessionToken; - string str = state.Name; - - IDictionary parameters = new Dictionary(); - parameters.Add("name", str); - parameters.Add("key", state.CloudName); - parameters.Add("__type", "File"); - parameters.Add("mime_type", AVFile.GetMIMEType(str)); - - state.MetaData = GetMetaData(state, state.frozenData); - - parameters.Add("metaData", state.MetaData); - - rtn = AVClient.RequestAsync("POST", new Uri("qiniu", UriKind.Relative), currentSessionToken, parameters, cancellationToken); - - return rtn; - } - IList> GetQiniuRequestHeaders(FileState state) - { - IList> makeBlockHeaders = new List>(); - - string authHead = "UpToken " + state.token; - makeBlockHeaders.Add(new KeyValuePair("Authorization", authHead)); - return makeBlockHeaders; - } - - Task> MakeBlock(FileState state, byte[] firstChunkBinary, long blcokSize = 4194304) - { - MemoryStream firstChunkData = new MemoryStream(firstChunkBinary, 0, firstChunkBinary.Length); - return AVClient.RequestAsync(new Uri(new Uri(UP_HOST) + string.Format("mkblk/{0}", blcokSize)), "POST", GetQiniuRequestHeaders(state), firstChunkData, "application/octet-stream", CancellationToken.None); - } - Task> PutChunk(FileState state, byte[] chunkBinary, string LastChunkctx, long currentChunkOffsetInBlock) - { - MemoryStream chunkData = new MemoryStream(chunkBinary, 0, chunkBinary.Length); - return AVClient.RequestAsync(new Uri(new Uri(UP_HOST) + string.Format("bput/{0}/{1}", LastChunkctx, - currentChunkOffsetInBlock)), "POST", - GetQiniuRequestHeaders(state), chunkData, - "application/octet-stream", CancellationToken.None); - } - internal Task> QiniuMakeFile(FileState state, Stream dataStream, string upToken, string key, long fsize, string[] ctxes, CancellationToken cancellationToken) - { - StringBuilder urlBuilder = new StringBuilder(); - urlBuilder.AppendFormat("{0}/mkfile/{1}", UP_HOST, fsize); - if (key != null) - { - urlBuilder.AppendFormat("/key/{0}", ToBase64URLSafe(key)); - } - var metaData = GetMetaData(state, dataStream); - - StringBuilder sb = new StringBuilder(); - foreach (string _key in metaData.Keys) - { - sb.AppendFormat("/{0}/{1}", _key, ToBase64URLSafe(metaData[_key].ToString())); - } - urlBuilder.Append(sb.ToString()); - - IList> headers = new List>(); - //makeBlockDic.Add("Content-Type", "application/octet-stream"); - - string authHead = "UpToken " + upToken; - headers.Add(new KeyValuePair("Authorization", authHead)); - int proCount = ctxes.Length; - Stream body = new MemoryStream(); - - for (int i = 0; i < proCount; i++) - { - byte[] bctx = StringToAscii(ctxes[i]); - body.Write(bctx, 0, bctx.Length); - if (i != proCount - 1) - { - body.WriteByte((byte)','); - } - } - body.Seek(0, SeekOrigin.Begin); - - var rtn = AVClient.RequestAsync(new Uri(urlBuilder.ToString()), "POST", headers, body, "text/plain", cancellationToken).OnSuccess(_ => - { - var dic = AVClient.ReponseResolve(_.Result, CancellationToken.None); - return _.Result; - }); - return rtn; - } - internal void MergeFromJSON(FileState state, IDictionary jsonData) - { - lock (this.mutex) - { - string url = jsonData["url"] as string; - state.Url = new Uri(url, UriKind.Absolute); - state.bucketId = FetchBucketId(url); - state.token = jsonData["token"] as string; - state.bucket = jsonData["bucket"] as string; - state.ObjectId = jsonData["objectId"] as string; - } - } - - string FetchBucketId(string url) - { - var elements = url.Split('/'); - - return elements[elements.Length - 1]; - } - public static byte[] StringToAscii(string s) - { - byte[] retval = new byte[s.Length]; - for (int ix = 0; ix < s.Length; ++ix) - { - char ch = s[ix]; - if (ch <= 0x7f) - retval[ix] = (byte)ch; - else - retval[ix] = (byte)'?'; - } - return retval; - } - public static string ToBase64URLSafe(string str) - { - return Encode(str); - } - public static string Encode(byte[] bs) - { - if (bs == null || bs.Length == 0) - return ""; - string encodedStr = Convert.ToBase64String(bs); - encodedStr = encodedStr.Replace('+', '-').Replace('/', '_'); - return encodedStr; - } - public static string Encode(string text) - { - if (String.IsNullOrEmpty(text)) - return ""; - byte[] bs = Encoding.UTF8.GetBytes(text); - string encodedStr = Convert.ToBase64String(bs); - encodedStr = encodedStr.Replace('+', '-').Replace('/', '_'); - return encodedStr; - } - - internal static string GetMD5Code(Stream data) - { - MD5 md5 = new MD5CryptoServiceProvider(); - byte[] retVal = md5.ComputeHash(data); - - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < retVal.Length; i++) - { - sb.Append(retVal[i].ToString("x2")); - } - return sb.ToString(); - - } - - internal IDictionary GetMetaData(FileState state, Stream data) - { - IDictionary rtn = new Dictionary(); - - if (state.MetaData != null) - { - foreach (var meta in state.MetaData) - { - rtn.Add(meta.Key, meta.Value); - } - } - MergeDic(rtn, "mime_type", AVFile.GetMIMEType(state.Name)); - MergeDic(rtn, "size", data.Length); - MergeDic(rtn, "_checksum", GetMD5Code(data)); - if (AVUser.CurrentUser != null) - if (AVUser.CurrentUser.ObjectId != null) - MergeDic(rtn, "owner", AVUser.CurrentUser.ObjectId); - - return rtn; - } - internal void MergeDic(IDictionary dic, string key, object value) - { - if (dic.ContainsKey(key)) - { - dic[key] = value; - } - else - { - dic.Add(key, value); - } - } - } -} diff --git a/Storage/Source/Internal/File/Cryptography/MD5/MD5.cs b/Storage/Source/Internal/File/Cryptography/MD5/MD5.cs deleted file mode 100644 index aa3eb2b..0000000 --- a/Storage/Source/Internal/File/Cryptography/MD5/MD5.cs +++ /dev/null @@ -1,566 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.IO; - -namespace LeanCloud.Storage.Internal -{ - /// - /// 弥补Windows Phone 8 API没有自带MD5加密的拓展方法。 - /// - internal class MD5CryptoServiceProvider : MD5 - { - public MD5CryptoServiceProvider() - : base() - { - } - } - /// - /// Summary description for MD5. - /// - internal class MD5 : IDisposable - { - static public MD5 Create(string hashName) - { - if (hashName == "MD5") - return new MD5(); - else - throw new NotSupportedException(); - } - - static public string GetMd5String(String source) - { - MD5 md = MD5CryptoServiceProvider.Create(); - byte[] hash; - - //Create a new instance of ASCIIEncoding to - //convert the string into an array of Unicode bytes. - UTF8Encoding enc = new UTF8Encoding(); - // ASCIIEncoding enc = new ASCIIEncoding(); - - //Convert the string into an array of bytes. - byte[] buffer = enc.GetBytes(source); - - //Create the hash value from the array of bytes. - hash = md.ComputeHash(buffer); - - StringBuilder sb = new StringBuilder(); - foreach (byte b in hash) - sb.Append(b.ToString("x2")); - return sb.ToString(); - } - - static public MD5 Create() - { - return new MD5(); - } - - #region base implementation of the MD5 - #region constants - private const byte S11 = 7; - private const byte S12 = 12; - private const byte S13 = 17; - private const byte S14 = 22; - private const byte S21 = 5; - private const byte S22 = 9; - private const byte S23 = 14; - private const byte S24 = 20; - private const byte S31 = 4; - private const byte S32 = 11; - private const byte S33 = 16; - private const byte S34 = 23; - private const byte S41 = 6; - private const byte S42 = 10; - private const byte S43 = 15; - private const byte S44 = 21; - static private byte[] PADDING = new byte[] { - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - #endregion - - #region F, G, H and I are basic MD5 functions. - static private uint F(uint x, uint y, uint z) - { - return (((x) & (y)) | ((~x) & (z))); - } - static private uint G(uint x, uint y, uint z) - { - return (((x) & (z)) | ((y) & (~z))); - } - static private uint H(uint x, uint y, uint z) - { - return ((x) ^ (y) ^ (z)); - } - static private uint I(uint x, uint y, uint z) - { - return ((y) ^ ((x) | (~z))); - } - #endregion - - #region rotates x left n bits. - /// - /// rotates x left n bits. - /// - /// - /// - /// - static private uint ROTATE_LEFT(uint x, byte n) - { - return (((x) << (n)) | ((x) >> (32 - (n)))); - } - #endregion - - #region FF, GG, HH, and II transformations - /// FF, GG, HH, and II transformations - /// for rounds 1, 2, 3, and 4. - /// Rotation is separate from addition to prevent recomputation. - static private void FF(ref uint a, uint b, uint c, uint d, uint x, byte s, uint ac) - { - (a) += F((b), (c), (d)) + (x) + (uint)(ac); - (a) = ROTATE_LEFT((a), (s)); - (a) += (b); - } - static private void GG(ref uint a, uint b, uint c, uint d, uint x, byte s, uint ac) - { - (a) += G((b), (c), (d)) + (x) + (uint)(ac); - (a) = ROTATE_LEFT((a), (s)); - (a) += (b); - } - static private void HH(ref uint a, uint b, uint c, uint d, uint x, byte s, uint ac) - { - (a) += H((b), (c), (d)) + (x) + (uint)(ac); - (a) = ROTATE_LEFT((a), (s)); - (a) += (b); - } - static private void II(ref uint a, uint b, uint c, uint d, uint x, byte s, uint ac) - { - (a) += I((b), (c), (d)) + (x) + (uint)(ac); - (a) = ROTATE_LEFT((a), (s)); - (a) += (b); - } - #endregion - - #region context info - /// - /// state (ABCD) - /// - uint[] state = new uint[4]; - - /// - /// number of bits, modulo 2^64 (lsb first) - /// - uint[] count = new uint[2]; - - /// - /// input buffer - /// - byte[] buffer = new byte[64]; - #endregion - - internal MD5() - { - Initialize(); - } - - /// - /// MD5 initialization. Begins an MD5 operation, writing a new context. - /// - /// - /// The RFC named it "MD5Init" - /// - public virtual void Initialize() - { - count[0] = count[1] = 0; - - // Load magic initialization constants. - state[0] = 0x67452301; - state[1] = 0xefcdab89; - state[2] = 0x98badcfe; - state[3] = 0x10325476; - } - - /// - /// MD5 block update operation. Continues an MD5 message-digest - /// operation, processing another message block, and updating the - /// context. - /// - /// - /// - /// - /// The RFC Named it MD5Update - protected virtual void HashCore(byte[] input, int offset, int count) - { - int i; - int index; - int partLen; - - // Compute number of bytes mod 64 - index = (int)((this.count[0] >> 3) & 0x3F); - - // Update number of bits - if ((this.count[0] += (uint)((uint)count << 3)) < ((uint)count << 3)) - this.count[1]++; - this.count[1] += ((uint)count >> 29); - - partLen = 64 - index; - - // Transform as many times as possible. - if (count >= partLen) - { - Buffer.BlockCopy(input, offset, this.buffer, index, partLen); - Transform(this.buffer, 0); - - for (i = partLen; i + 63 < count; i += 64) - Transform(input, offset + i); - - index = 0; - } - else - i = 0; - - // Buffer remaining input - Buffer.BlockCopy(input, offset + i, this.buffer, index, count - i); - } - - /// - /// MD5 finalization. Ends an MD5 message-digest operation, writing the - /// the message digest and zeroizing the context. - /// - /// message digest - /// The RFC named it MD5Final - protected virtual byte[] HashFinal() - { - byte[] digest = new byte[16]; - byte[] bits = new byte[8]; - int index, padLen; - - // Save number of bits - Encode(bits, 0, this.count, 0, 8); - - // Pad out to 56 mod 64. - index = (int)((uint)(this.count[0] >> 3) & 0x3f); - padLen = (index < 56) ? (56 - index) : (120 - index); - HashCore(PADDING, 0, padLen); - - // Append length (before padding) - HashCore(bits, 0, 8); - - // Store state in digest - Encode(digest, 0, state, 0, 16); - - // Zeroize sensitive information. - count[0] = count[1] = 0; - state[0] = 0; - state[1] = 0; - state[2] = 0; - state[3] = 0; - - // initialize again, to be ready to use - Initialize(); - - return digest; - } - - /// - /// MD5 basic transformation. Transforms state based on 64 bytes block. - /// - /// - /// - private void Transform(byte[] block, int offset) - { - uint a = state[0], b = state[1], c = state[2], d = state[3]; - uint[] x = new uint[16]; - Decode(x, 0, block, offset, 64); - - // Round 1 - FF(ref a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */ - FF(ref d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */ - FF(ref c, d, a, b, x[2], S13, 0x242070db); /* 3 */ - FF(ref b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */ - FF(ref a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */ - FF(ref d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */ - FF(ref c, d, a, b, x[6], S13, 0xa8304613); /* 7 */ - FF(ref b, c, d, a, x[7], S14, 0xfd469501); /* 8 */ - FF(ref a, b, c, d, x[8], S11, 0x698098d8); /* 9 */ - FF(ref d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */ - FF(ref c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ - FF(ref b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ - FF(ref a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ - FF(ref d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ - FF(ref c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ - FF(ref b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ - - // Round 2 - GG(ref a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */ - GG(ref d, a, b, c, x[6], S22, 0xc040b340); /* 18 */ - GG(ref c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ - GG(ref b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */ - GG(ref a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */ - GG(ref d, a, b, c, x[10], S22, 0x2441453); /* 22 */ - GG(ref c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ - GG(ref b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */ - GG(ref a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */ - GG(ref d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ - GG(ref c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */ - GG(ref b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */ - GG(ref a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ - GG(ref d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */ - GG(ref c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */ - GG(ref b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ - - // Round 3 - HH(ref a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */ - HH(ref d, a, b, c, x[8], S32, 0x8771f681); /* 34 */ - HH(ref c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ - HH(ref b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ - HH(ref a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */ - HH(ref d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */ - HH(ref c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */ - HH(ref b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ - HH(ref a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ - HH(ref d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */ - HH(ref c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */ - HH(ref b, c, d, a, x[6], S34, 0x4881d05); /* 44 */ - HH(ref a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */ - HH(ref d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ - HH(ref c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ - HH(ref b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */ - - // Round 4 - II(ref a, b, c, d, x[0], S41, 0xf4292244); /* 49 */ - II(ref d, a, b, c, x[7], S42, 0x432aff97); /* 50 */ - II(ref c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ - II(ref b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */ - II(ref a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ - II(ref d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */ - II(ref c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ - II(ref b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */ - II(ref a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */ - II(ref d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ - II(ref c, d, a, b, x[6], S43, 0xa3014314); /* 59 */ - II(ref b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ - II(ref a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */ - II(ref d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ - II(ref c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */ - II(ref b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */ - - state[0] += a; - state[1] += b; - state[2] += c; - state[3] += d; - - // Zeroize sensitive information. - for (int i = 0; i < x.Length; i++) - x[i] = 0; - } - - /// - /// Encodes input (uint) into output (byte). Assumes len is - /// multiple of 4. - /// - /// - /// - /// - /// - /// - private static void Encode(byte[] output, int outputOffset, uint[] input, int inputOffset, int count) - { - int i, j; - int end = outputOffset + count; - for (i = inputOffset, j = outputOffset; j < end; i++, j += 4) - { - output[j] = (byte)(input[i] & 0xff); - output[j + 1] = (byte)((input[i] >> 8) & 0xff); - output[j + 2] = (byte)((input[i] >> 16) & 0xff); - output[j + 3] = (byte)((input[i] >> 24) & 0xff); - } - } - - /// - /// Decodes input (byte) into output (uint). Assumes len is - /// a multiple of 4. - /// - /// - /// - /// - /// - /// - static private void Decode(uint[] output, int outputOffset, byte[] input, int inputOffset, int count) - { - int i, j; - int end = inputOffset + count; - for (i = outputOffset, j = inputOffset; j < end; i++, j += 4) - output[i] = ((uint)input[j]) | (((uint)input[j + 1]) << 8) | (((uint)input[j + 2]) << 16) | (((uint)input[j + 3]) << 24); - } - #endregion - - #region expose the same interface as the regular MD5 object - - protected byte[] HashValue; - protected int State; - public virtual bool CanReuseTransform - { - get - { - return true; - } - } - - public virtual bool CanTransformMultipleBlocks - { - get - { - return true; - } - } - public virtual byte[] Hash - { - get - { - if (this.State != 0) - throw new InvalidOperationException(); - return (byte[])HashValue.Clone(); - } - } - public virtual int HashSize - { - get - { - return HashSizeValue; - } - } - protected int HashSizeValue = 128; - - public virtual int InputBlockSize - { - get - { - return 1; - } - } - public virtual int OutputBlockSize - { - get - { - return 1; - } - } - - public void Clear() - { - Dispose(true); - } - - public byte[] ComputeHash(byte[] buffer) - { - return ComputeHash(buffer, 0, buffer.Length); - } - public byte[] ComputeHash(byte[] buffer, int offset, int count) - { - Initialize(); - HashCore(buffer, offset, count); - HashValue = HashFinal(); - return (byte[])HashValue.Clone(); - } - - public byte[] ComputeHash(Stream inputStream) - { - Initialize(); - int count; - byte[] buffer = new byte[4096]; - while (0 < (count = inputStream.Read(buffer, 0, 4096))) - { - HashCore(buffer, 0, count); - } - HashValue = HashFinal(); - return (byte[])HashValue.Clone(); - } - - public int TransformBlock( - byte[] inputBuffer, - int inputOffset, - int inputCount, - byte[] outputBuffer, - int outputOffset - ) - { - if (inputBuffer == null) - { - throw new ArgumentNullException("inputBuffer"); - } - if (inputOffset < 0) - { - throw new ArgumentOutOfRangeException("inputOffset"); - } - if ((inputCount < 0) || (inputCount > inputBuffer.Length)) - { - throw new ArgumentException("inputCount"); - } - if ((inputBuffer.Length - inputCount) < inputOffset) - { - throw new ArgumentOutOfRangeException("inputOffset"); - } - if (this.State == 0) - { - Initialize(); - this.State = 1; - } - - HashCore(inputBuffer, inputOffset, inputCount); - if ((inputBuffer != outputBuffer) || (inputOffset != outputOffset)) - { - Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount); - } - return inputCount; - } - public byte[] TransformFinalBlock( - byte[] inputBuffer, - int inputOffset, - int inputCount - ) - { - if (inputBuffer == null) - { - throw new ArgumentNullException("inputBuffer"); - } - if (inputOffset < 0) - { - throw new ArgumentOutOfRangeException("inputOffset"); - } - if ((inputCount < 0) || (inputCount > inputBuffer.Length)) - { - throw new ArgumentException("inputCount"); - } - if ((inputBuffer.Length - inputCount) < inputOffset) - { - throw new ArgumentOutOfRangeException("inputOffset"); - } - if (this.State == 0) - { - Initialize(); - } - HashCore(inputBuffer, inputOffset, inputCount); - HashValue = HashFinal(); - byte[] buffer = new byte[inputCount]; - Buffer.BlockCopy(inputBuffer, inputOffset, buffer, 0, inputCount); - this.State = 0; - return buffer; - } - #endregion - - protected virtual void Dispose(bool disposing) - { - if (!disposing) - Initialize(); - } - public void Dispose() - { - Dispose(true); - } - } -} - diff --git a/Storage/Source/Internal/File/Cryptography/SHA1/SHA1CryptoServiceProvider.cs b/Storage/Source/Internal/File/Cryptography/SHA1/SHA1CryptoServiceProvider.cs deleted file mode 100644 index 74f6e09..0000000 --- a/Storage/Source/Internal/File/Cryptography/SHA1/SHA1CryptoServiceProvider.cs +++ /dev/null @@ -1,495 +0,0 @@ -// -// System.Security.Cryptography.SHA1CryptoServiceProvider.cs -// -// Authors: -// Matthew S. Ford (Matthew.S.Ford@Rose-Hulman.Edu) -// Sebastien Pouliot (sebastien@ximian.com) -// -// Copyright 2001 by Matthew S. Ford. -// Copyright (C) 2004, 2005, 2008 Novell, Inc (http://www.novell.com) -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -// Note: -// The MS Framework includes two (almost) identical class for SHA1. -// SHA1Managed is a 100% managed implementation. -// SHA1CryptoServiceProvider (this file) is a wrapper on CryptoAPI. -// Mono must provide those two class for binary compatibility. -// In our case both class are wrappers around a managed internal class SHA1Internal. - -using System.IO; -using System; -using System.Runtime.InteropServices; - -namespace LeanCloud.Storage.Internal -{ - - internal class SHA1Internal - { - - private const int BLOCK_SIZE_BYTES = 64; - private uint[] _H; // these are my chaining variables - private ulong count; - private byte[] _ProcessingBuffer; // Used to start data when passed less than a block worth. - private int _ProcessingBufferCount; // Counts how much data we have stored that still needs processed. - private uint[] buff; - - public SHA1Internal() - { - _H = new uint[5]; - _ProcessingBuffer = new byte[BLOCK_SIZE_BYTES]; - buff = new uint[80]; - - Initialize(); - } - - public void HashCore(byte[] rgb, int ibStart, int cbSize) - { - int i; - - if (_ProcessingBufferCount != 0) - { - if (cbSize < (BLOCK_SIZE_BYTES - _ProcessingBufferCount)) - { - System.Buffer.BlockCopy(rgb, ibStart, _ProcessingBuffer, _ProcessingBufferCount, cbSize); - _ProcessingBufferCount += cbSize; - return; - } - else - { - i = (BLOCK_SIZE_BYTES - _ProcessingBufferCount); - System.Buffer.BlockCopy(rgb, ibStart, _ProcessingBuffer, _ProcessingBufferCount, i); - ProcessBlock(_ProcessingBuffer, 0); - _ProcessingBufferCount = 0; - ibStart += i; - cbSize -= i; - } - } - - for (i = 0; i < cbSize - cbSize % BLOCK_SIZE_BYTES; i += BLOCK_SIZE_BYTES) - { - ProcessBlock(rgb, (uint)(ibStart + i)); - } - - if (cbSize % BLOCK_SIZE_BYTES != 0) - { - System.Buffer.BlockCopy(rgb, cbSize - cbSize % BLOCK_SIZE_BYTES + ibStart, _ProcessingBuffer, 0, cbSize % BLOCK_SIZE_BYTES); - _ProcessingBufferCount = cbSize % BLOCK_SIZE_BYTES; - } - } - - public byte[] HashFinal() - { - byte[] hash = new byte[20]; - - ProcessFinalBlock(_ProcessingBuffer, 0, _ProcessingBufferCount); - - for (int i = 0; i < 5; i++) - { - for (int j = 0; j < 4; j++) - { - hash[i * 4 + j] = (byte)(_H[i] >> (8 * (3 - j))); - } - } - - return hash; - } - - public void Initialize() - { - count = 0; - _ProcessingBufferCount = 0; - - _H[0] = 0x67452301; - _H[1] = 0xefcdab89; - _H[2] = 0x98badcfe; - _H[3] = 0x10325476; - _H[4] = 0xC3D2E1F0; - } - - private void ProcessBlock(byte[] inputBuffer, uint inputOffset) - { - uint a, b, c, d, e; - - count += BLOCK_SIZE_BYTES; - - // abc removal would not work on the fields - uint[] _H = this._H; - uint[] buff = this.buff; - InitialiseBuff(buff, inputBuffer, inputOffset); - FillBuff(buff); - - a = _H[0]; - b = _H[1]; - c = _H[2]; - d = _H[3]; - e = _H[4]; - - // This function was unrolled because it seems to be doubling our performance with current compiler/VM. - // Possibly roll up if this changes. - - // ---- Round 1 -------- - int i = 0; - while (i < 20) - { - e += ((a << 5) | (a >> 27)) + (((c ^ d) & b) ^ d) + 0x5A827999 + buff[i]; - b = (b << 30) | (b >> 2); - - d += ((e << 5) | (e >> 27)) + (((b ^ c) & a) ^ c) + 0x5A827999 + buff[i + 1]; - a = (a << 30) | (a >> 2); - - c += ((d << 5) | (d >> 27)) + (((a ^ b) & e) ^ b) + 0x5A827999 + buff[i + 2]; - e = (e << 30) | (e >> 2); - - b += ((c << 5) | (c >> 27)) + (((e ^ a) & d) ^ a) + 0x5A827999 + buff[i + 3]; - d = (d << 30) | (d >> 2); - - a += ((b << 5) | (b >> 27)) + (((d ^ e) & c) ^ e) + 0x5A827999 + buff[i + 4]; - c = (c << 30) | (c >> 2); - i += 5; - } - - // ---- Round 2 -------- - while (i < 40) - { - e += ((a << 5) | (a >> 27)) + (b ^ c ^ d) + 0x6ED9EBA1 + buff[i]; - b = (b << 30) | (b >> 2); - - d += ((e << 5) | (e >> 27)) + (a ^ b ^ c) + 0x6ED9EBA1 + buff[i + 1]; - a = (a << 30) | (a >> 2); - - c += ((d << 5) | (d >> 27)) + (e ^ a ^ b) + 0x6ED9EBA1 + buff[i + 2]; - e = (e << 30) | (e >> 2); - - b += ((c << 5) | (c >> 27)) + (d ^ e ^ a) + 0x6ED9EBA1 + buff[i + 3]; - d = (d << 30) | (d >> 2); - - a += ((b << 5) | (b >> 27)) + (c ^ d ^ e) + 0x6ED9EBA1 + buff[i + 4]; - c = (c << 30) | (c >> 2); - i += 5; - } - - // ---- Round 3 -------- - while (i < 60) - { - e += ((a << 5) | (a >> 27)) + ((b & c) | (b & d) | (c & d)) + 0x8F1BBCDC + buff[i]; - b = (b << 30) | (b >> 2); - - d += ((e << 5) | (e >> 27)) + ((a & b) | (a & c) | (b & c)) + 0x8F1BBCDC + buff[i + 1]; - a = (a << 30) | (a >> 2); - - c += ((d << 5) | (d >> 27)) + ((e & a) | (e & b) | (a & b)) + 0x8F1BBCDC + buff[i + 2]; - e = (e << 30) | (e >> 2); - - b += ((c << 5) | (c >> 27)) + ((d & e) | (d & a) | (e & a)) + 0x8F1BBCDC + buff[i + 3]; - d = (d << 30) | (d >> 2); - - a += ((b << 5) | (b >> 27)) + ((c & d) | (c & e) | (d & e)) + 0x8F1BBCDC + buff[i + 4]; - c = (c << 30) | (c >> 2); - i += 5; - } - - // ---- Round 4 -------- - while (i < 80) - { - e += ((a << 5) | (a >> 27)) + (b ^ c ^ d) + 0xCA62C1D6 + buff[i]; - b = (b << 30) | (b >> 2); - - d += ((e << 5) | (e >> 27)) + (a ^ b ^ c) + 0xCA62C1D6 + buff[i + 1]; - a = (a << 30) | (a >> 2); - - c += ((d << 5) | (d >> 27)) + (e ^ a ^ b) + 0xCA62C1D6 + buff[i + 2]; - e = (e << 30) | (e >> 2); - - b += ((c << 5) | (c >> 27)) + (d ^ e ^ a) + 0xCA62C1D6 + buff[i + 3]; - d = (d << 30) | (d >> 2); - - a += ((b << 5) | (b >> 27)) + (c ^ d ^ e) + 0xCA62C1D6 + buff[i + 4]; - c = (c << 30) | (c >> 2); - i += 5; - } - - _H[0] += a; - _H[1] += b; - _H[2] += c; - _H[3] += d; - _H[4] += e; - } - - private static void InitialiseBuff(uint[] buff, byte[] input, uint inputOffset) - { - buff[0] = (uint)((input[inputOffset + 0] << 24) | (input[inputOffset + 1] << 16) | (input[inputOffset + 2] << 8) | (input[inputOffset + 3])); - buff[1] = (uint)((input[inputOffset + 4] << 24) | (input[inputOffset + 5] << 16) | (input[inputOffset + 6] << 8) | (input[inputOffset + 7])); - buff[2] = (uint)((input[inputOffset + 8] << 24) | (input[inputOffset + 9] << 16) | (input[inputOffset + 10] << 8) | (input[inputOffset + 11])); - buff[3] = (uint)((input[inputOffset + 12] << 24) | (input[inputOffset + 13] << 16) | (input[inputOffset + 14] << 8) | (input[inputOffset + 15])); - buff[4] = (uint)((input[inputOffset + 16] << 24) | (input[inputOffset + 17] << 16) | (input[inputOffset + 18] << 8) | (input[inputOffset + 19])); - buff[5] = (uint)((input[inputOffset + 20] << 24) | (input[inputOffset + 21] << 16) | (input[inputOffset + 22] << 8) | (input[inputOffset + 23])); - buff[6] = (uint)((input[inputOffset + 24] << 24) | (input[inputOffset + 25] << 16) | (input[inputOffset + 26] << 8) | (input[inputOffset + 27])); - buff[7] = (uint)((input[inputOffset + 28] << 24) | (input[inputOffset + 29] << 16) | (input[inputOffset + 30] << 8) | (input[inputOffset + 31])); - buff[8] = (uint)((input[inputOffset + 32] << 24) | (input[inputOffset + 33] << 16) | (input[inputOffset + 34] << 8) | (input[inputOffset + 35])); - buff[9] = (uint)((input[inputOffset + 36] << 24) | (input[inputOffset + 37] << 16) | (input[inputOffset + 38] << 8) | (input[inputOffset + 39])); - buff[10] = (uint)((input[inputOffset + 40] << 24) | (input[inputOffset + 41] << 16) | (input[inputOffset + 42] << 8) | (input[inputOffset + 43])); - buff[11] = (uint)((input[inputOffset + 44] << 24) | (input[inputOffset + 45] << 16) | (input[inputOffset + 46] << 8) | (input[inputOffset + 47])); - buff[12] = (uint)((input[inputOffset + 48] << 24) | (input[inputOffset + 49] << 16) | (input[inputOffset + 50] << 8) | (input[inputOffset + 51])); - buff[13] = (uint)((input[inputOffset + 52] << 24) | (input[inputOffset + 53] << 16) | (input[inputOffset + 54] << 8) | (input[inputOffset + 55])); - buff[14] = (uint)((input[inputOffset + 56] << 24) | (input[inputOffset + 57] << 16) | (input[inputOffset + 58] << 8) | (input[inputOffset + 59])); - buff[15] = (uint)((input[inputOffset + 60] << 24) | (input[inputOffset + 61] << 16) | (input[inputOffset + 62] << 8) | (input[inputOffset + 63])); - } - - private static void FillBuff(uint[] buff) - { - uint val; - for (int i = 16; i < 80; i += 8) - { - val = buff[i - 3] ^ buff[i - 8] ^ buff[i - 14] ^ buff[i - 16]; - buff[i] = (val << 1) | (val >> 31); - - val = buff[i - 2] ^ buff[i - 7] ^ buff[i - 13] ^ buff[i - 15]; - buff[i + 1] = (val << 1) | (val >> 31); - - val = buff[i - 1] ^ buff[i - 6] ^ buff[i - 12] ^ buff[i - 14]; - buff[i + 2] = (val << 1) | (val >> 31); - - val = buff[i + 0] ^ buff[i - 5] ^ buff[i - 11] ^ buff[i - 13]; - buff[i + 3] = (val << 1) | (val >> 31); - - val = buff[i + 1] ^ buff[i - 4] ^ buff[i - 10] ^ buff[i - 12]; - buff[i + 4] = (val << 1) | (val >> 31); - - val = buff[i + 2] ^ buff[i - 3] ^ buff[i - 9] ^ buff[i - 11]; - buff[i + 5] = (val << 1) | (val >> 31); - - val = buff[i + 3] ^ buff[i - 2] ^ buff[i - 8] ^ buff[i - 10]; - buff[i + 6] = (val << 1) | (val >> 31); - - val = buff[i + 4] ^ buff[i - 1] ^ buff[i - 7] ^ buff[i - 9]; - buff[i + 7] = (val << 1) | (val >> 31); - } - } - - private void ProcessFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) - { - ulong total = count + (ulong)inputCount; - int paddingSize = (56 - (int)(total % BLOCK_SIZE_BYTES)); - - if (paddingSize < 1) - paddingSize += BLOCK_SIZE_BYTES; - - int length = inputCount + paddingSize + 8; - byte[] fooBuffer = (length == 64) ? _ProcessingBuffer : new byte[length]; - - for (int i = 0; i < inputCount; i++) - { - fooBuffer[i] = inputBuffer[i + inputOffset]; - } - - fooBuffer[inputCount] = 0x80; - for (int i = inputCount + 1; i < inputCount + paddingSize; i++) - { - fooBuffer[i] = 0x00; - } - - // I deal in bytes. The algorithm deals in bits. - ulong size = total << 3; - AddLength(size, fooBuffer, inputCount + paddingSize); - ProcessBlock(fooBuffer, 0); - - if (length == 128) - ProcessBlock(fooBuffer, 64); - } - - internal void AddLength(ulong length, byte[] buffer, int position) - { - buffer[position++] = (byte)(length >> 56); - buffer[position++] = (byte)(length >> 48); - buffer[position++] = (byte)(length >> 40); - buffer[position++] = (byte)(length >> 32); - buffer[position++] = (byte)(length >> 24); - buffer[position++] = (byte)(length >> 16); - buffer[position++] = (byte)(length >> 8); - buffer[position] = (byte)(length); - } - } - - public sealed class SHA1CryptoServiceProvider : SHA1 - { - - private SHA1Internal sha; - - public SHA1CryptoServiceProvider() - { - sha = new SHA1Internal(); - } - - ~SHA1CryptoServiceProvider() - { - Dispose(false); - } - - protected override void Dispose(bool disposing) - { - // nothing new to do (managed implementation) - base.Dispose(disposing); - } - - protected override void HashCore(byte[] rgb, int ibStart, int cbSize) - { - State = 1; - sha.HashCore(rgb, ibStart, cbSize); - } - - protected override byte[] HashFinal() - { - State = 0; - return sha.HashFinal(); - } - - public override void Initialize() - { - sha.Initialize(); - } - } - - public abstract class SHA1 : HashAlgorithm - { - protected SHA1() - { - HashSizeValue = 160; - } - } - - public abstract class HashAlgorithm : IDisposable - { - protected int HashSizeValue; - protected internal byte[] HashValue; - protected int State = 0; - - private bool m_bDisposed = false; - - protected HashAlgorithm() { } - - // - // public properties - // - - public virtual int HashSize - { - get { return HashSizeValue; } - } - - // - // public methods - // - - public byte[] ComputeHash(Stream inputStream) - { - if (m_bDisposed) - throw new ObjectDisposedException(null); - - // Default the buffer size to 4K. - byte[] buffer = new byte[4096]; - int bytesRead; - do - { - bytesRead = inputStream.Read(buffer, 0, 4096); - if (bytesRead > 0) - { - HashCore(buffer, 0, bytesRead); - } - } while (bytesRead > 0); - - HashValue = HashFinal(); - byte[] Tmp = (byte[])HashValue.Clone(); - Initialize(); - return (Tmp); - } - - public byte[] ComputeHash(byte[] buffer) - { - if (m_bDisposed) - throw new ObjectDisposedException(null); - - // Do some validation - if (buffer == null) throw new ArgumentNullException("buffer"); - - HashCore(buffer, 0, buffer.Length); - HashValue = HashFinal(); - byte[] Tmp = (byte[])HashValue.Clone(); - Initialize(); - return (Tmp); - } - - // ICryptoTransform methods - - // we assume any HashAlgorithm can take input a byte at a time - public virtual int InputBlockSize - { - get { return (1); } - } - - public virtual int OutputBlockSize - { - get { return (1); } - } - - public virtual bool CanTransformMultipleBlocks - { - get { return (true); } - } - - public virtual bool CanReuseTransform - { - get { return (true); } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public void Clear() - { - (this as IDisposable).Dispose(); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - if (HashValue != null) - Array.Clear(HashValue, 0, HashValue.Length); - HashValue = null; - m_bDisposed = true; - } - } - - // - // abstract public methods - // - - public abstract void Initialize(); - - protected abstract void HashCore(byte[] array, int ibStart, int cbSize); - - protected abstract byte[] HashFinal(); - } -} diff --git a/Storage/Source/Internal/File/State/FileState.cs b/Storage/Source/Internal/File/State/FileState.cs deleted file mode 100644 index 667646a..0000000 --- a/Storage/Source/Internal/File/State/FileState.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace LeanCloud.Storage.Internal -{ - public class FileState - { - public string ObjectId { get; internal set; } - public string Name { get; internal set; } - public string CloudName { get; set; } - public string MimeType { get; internal set; } - public Uri Url { get; internal set; } - public IDictionary MetaData { get; internal set; } - public long Size { get; internal set; } - public long FixedChunkSize { get; internal set; } - - public int counter; - public Stream frozenData; - public string bucketId; - public string bucket; - public string token; - public long completed; - public List block_ctxes = new List(); - - } -} diff --git a/Storage/Source/Internal/HttpClient/HttpRequest.cs b/Storage/Source/Internal/HttpClient/HttpRequest.cs deleted file mode 100644 index f3e1444..0000000 --- a/Storage/Source/Internal/HttpClient/HttpRequest.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace LeanCloud.Storage.Internal -{ - /// - /// IHttpRequest is an interface that provides an API to execute HTTP request data. - /// - public class HttpRequest - { - public Uri Uri { get; set; } - public IList> Headers { get; set; } - - /// - /// Data stream to be uploaded. - /// - public virtual Stream Data { get; set; } - - /// - /// HTTP method. One of DELETE, GET, HEAD, POST or PUT - /// - public string Method { get; set; } - } -} diff --git a/Storage/Source/Internal/HttpClient/IHttpClient.cs b/Storage/Source/Internal/HttpClient/IHttpClient.cs deleted file mode 100644 index 6bbe9d2..0000000 --- a/Storage/Source/Internal/HttpClient/IHttpClient.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal -{ - public interface IHttpClient - { - /// - /// Executes HTTP request to a with HTTP verb - /// and . - /// - /// The HTTP request to be executed. - /// Upload progress callback. - /// Download progress callback. - /// The cancellation token. - /// A task that resolves to Htt - Task> ExecuteAsync(HttpRequest httpRequest, - IProgress uploadProgress, - IProgress downloadProgress, - CancellationToken cancellationToken); - } -} diff --git a/Storage/Source/Internal/HttpClient/Portable/HttpClient.Portable.cs b/Storage/Source/Internal/HttpClient/Portable/HttpClient.Portable.cs deleted file mode 100644 index 210b66f..0000000 --- a/Storage/Source/Internal/HttpClient/Portable/HttpClient.Portable.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Net.Http; -using System.Linq; - -using NetHttpClient = System.Net.Http.HttpClient; -using System.Net.Http.Headers; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal -{ - public class HttpClient : IHttpClient - { - private static HashSet HttpContentHeaders = new HashSet { - { "Allow" }, - { "Content-Disposition" }, - { "Content-Encoding" }, - { "Content-Language" }, - { "Content-Length" }, - { "Content-Location" }, - { "Content-MD5" }, - { "Content-Range" }, - { "Content-Type" }, - { "Expires" }, - { "Last-Modified" } - }; - - public HttpClient() - { - client = new NetHttpClient(); - client.DefaultRequestHeaders.Add("User-Agent", "LeanCloud-dotNet-SDK/" + "2.0.0"); - } - - public HttpClient(NetHttpClient client) - { - this.client = client; - } - - private NetHttpClient client; - - public Task> ExecuteAsync(HttpRequest httpRequest, - IProgress uploadProgress, - IProgress downloadProgress, - CancellationToken cancellationToken) - { - uploadProgress = uploadProgress ?? new Progress(); - downloadProgress = downloadProgress ?? new Progress(); - - HttpMethod httpMethod = new HttpMethod(httpRequest.Method); - HttpRequestMessage message = new HttpRequestMessage(httpMethod, httpRequest.Uri); - - // Fill in zero-length data if method is post. - Stream data = httpRequest.Data; - if (httpRequest.Data == null && httpRequest.Method.ToLower().Equals("post")) - { - data = new MemoryStream(new byte[0]); - } - - if (data != null) - { - message.Content = new StreamContent(data); - } - - if (httpRequest.Headers != null) - { - foreach (var header in httpRequest.Headers) - { - if (!string.IsNullOrEmpty(header.Value)) - { - if (HttpContentHeaders.Contains(header.Key)) - { - message.Content.Headers.Add(header.Key, header.Value); - } - else - { - message.Headers.Add(header.Key, header.Value); - } - } - } - } - - // Avoid aggressive caching on Windows Phone 8.1. - message.Headers.Add("Cache-Control", "no-cache"); - message.Headers.IfModifiedSince = DateTimeOffset.UtcNow; - - // TODO: (richardross) investigate progress here, maybe there's something we're missing in order to support this. - uploadProgress.Report(new AVUploadProgressEventArgs { Progress = 0 }); - - return client.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken) - .ContinueWith(httpMessageTask => - { - var response = httpMessageTask.Result; - - uploadProgress.Report(new AVUploadProgressEventArgs { Progress = 1 }); - - return response.Content.ReadAsStreamAsync().ContinueWith(streamTask => - { - var resultStream = new MemoryStream(); - var responseStream = streamTask.Result; - - int bufferSize = 4096; - byte[] buffer = new byte[bufferSize]; - int bytesRead = 0; - long totalLength = -1; - long readSoFar = 0; - - try - { - totalLength = responseStream.Length; - } - catch (NotSupportedException) - { - - } - - return InternalExtensions.WhileAsync(() => - { - return responseStream.ReadAsync(buffer, 0, bufferSize, cancellationToken).OnSuccess(readTask => - { - bytesRead = readTask.Result; - return bytesRead > 0; - }); - }, () => - { - cancellationToken.ThrowIfCancellationRequested(); - - return resultStream.WriteAsync(buffer, 0, bytesRead, cancellationToken).OnSuccess(_ => - { - cancellationToken.ThrowIfCancellationRequested(); - readSoFar += bytesRead; - - if (totalLength > -1) - { - downloadProgress.Report(new AVDownloadProgressEventArgs { Progress = 1.0 * readSoFar / totalLength }); - } - }); - }).ContinueWith(_ => - { - responseStream.Dispose(); - return _; - }).Unwrap().OnSuccess(_ => - { - // If getting stream size is not supported, then report download only once. - if (totalLength == -1) - { - downloadProgress.Report(new AVDownloadProgressEventArgs { Progress = 1.0 }); - } - - // Assume UTF-8 encoding. - var resultAsArray = resultStream.ToArray(); - var resultString = Encoding.UTF8.GetString(resultAsArray, 0, resultAsArray.Length); - resultStream.Dispose(); - return new Tuple(response.StatusCode, resultString); - }); - }); - }).Unwrap().Unwrap(); - } - } -} diff --git a/Storage/Source/Internal/HttpClient/Unity/HttpClient.Unity.cs b/Storage/Source/Internal/HttpClient/Unity/HttpClient.Unity.cs deleted file mode 100644 index 39b2671..0000000 --- a/Storage/Source/Internal/HttpClient/Unity/HttpClient.Unity.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using UnityEngine; -using UnityEngine.Networking; - -namespace LeanCloud.Storage.Internal -{ - public class HttpClient : IHttpClient - { - private static bool isCompiledByIL2CPP = System.AppDomain.CurrentDomain.FriendlyName.Equals("IL2CPP Root Domain"); - - public Task> ExecuteAsync(HttpRequest httpRequest, - IProgress uploadProgress, - IProgress downloadProgress, - CancellationToken cancellationToken) - { - var tcs = new TaskCompletionSource>(); - cancellationToken.Register(() => tcs.TrySetCanceled()); - uploadProgress = uploadProgress ?? new Progress(); - downloadProgress = downloadProgress ?? new Progress(); - - Task readBytesTask = null; - IDisposable toDisposeAfterReading = null; - byte[] bytes = null; - if (httpRequest.Data != null) - { - var ms = new MemoryStream(); - toDisposeAfterReading = ms; - readBytesTask = httpRequest.Data.CopyToAsync(ms).OnSuccess(_ => - { - bytes = ms.ToArray(); - }); - } - - readBytesTask.Safe().ContinueWith(t => - { - if (toDisposeAfterReading != null) - { - toDisposeAfterReading.Dispose(); - } - return t; - }).Unwrap().OnSuccess(_ => - { - float oldDownloadProgress = 0; - float oldUploadProgress = 0; - - Dispatcher.Instance.Post(() => - { - WaitForWebRequest(GenerateRequest(httpRequest, bytes), request => - { - if (cancellationToken.IsCancellationRequested) - { - tcs.TrySetCanceled(); - return; - } - if (request.isDone) - { - uploadProgress.Report(new AVUploadProgressEventArgs { Progress = 1 }); - downloadProgress.Report(new AVDownloadProgressEventArgs { Progress = 1 }); - - var statusCode = GetResponseStatusCode(request); - // Returns HTTP error if that's the only info we have. - // if (!String.IsNullOrEmpty(www.error) && String.IsNullOrEmpty(www.text)) - if (!string.IsNullOrEmpty(request.error) && string.IsNullOrEmpty(request.downloadHandler.text)) - { - var errorString = string.Format("{{\"error\":\"{0}\"}}", request.error); - tcs.TrySetResult(new Tuple(statusCode, errorString)); - } - else - { - tcs.TrySetResult(new Tuple(statusCode, request.downloadHandler.text)); - } - } - else - { - // Update upload progress - var newUploadProgress = request.uploadProgress; - if (oldUploadProgress < newUploadProgress) - { - uploadProgress.Report(new AVUploadProgressEventArgs { Progress = newUploadProgress }); - } - oldUploadProgress = newUploadProgress; - - // Update download progress - var newDownloadProgress = request.downloadProgress; - if (oldDownloadProgress < newDownloadProgress) - { - downloadProgress.Report(new AVDownloadProgressEventArgs { Progress = newDownloadProgress }); - } - oldDownloadProgress = newDownloadProgress; - } - }); - }); - }); - - // Get off of the main thread for further processing. - return tcs.Task.ContinueWith(t => - { - var dispatchTcs = new TaskCompletionSource(); - // ThreadPool doesn't work well in IL2CPP environment, but Thread does! - if (isCompiledByIL2CPP) - { - var thread = new Thread(_ => - { - dispatchTcs.TrySetResult(null); - }); - thread.Start(); - } - else - { - ThreadPool.QueueUserWorkItem(_ => dispatchTcs.TrySetResult(null)); - } - return dispatchTcs.Task; - }).Unwrap() - .ContinueWith(_ => tcs.Task).Unwrap(); - } - - private static HttpStatusCode GetResponseStatusCode(UnityWebRequest request) - { - if (Enum.IsDefined(typeof(HttpStatusCode), (int)request.responseCode)) - { - return (HttpStatusCode)request.responseCode; - } - return (HttpStatusCode)400; - } - - private static UnityWebRequest GenerateRequest(HttpRequest request, byte[] bytes) - { - var webRequest = new UnityWebRequest(); - webRequest.method = request.Method; - webRequest.url = request.Uri.AbsoluteUri; - // Explicitly assume a JSON content. - webRequest.SetRequestHeader("Content-Type", "application/json"); - //webRequest.SetRequestHeader("User-Agent", "net-unity-" + AVVersionInfo.Version); - if (request.Headers != null) - { - foreach (var header in request.Headers) - { - webRequest.SetRequestHeader(header.Key as string, header.Value as string); - } - } - - if (bytes != null) - { - webRequest.uploadHandler = new UploadHandlerRaw(bytes); - } - webRequest.downloadHandler = new DownloadHandlerBuffer(); - webRequest.Send(); - return webRequest; - } - - private static void WaitForWebRequest(UnityWebRequest request, Action action) - { - Dispatcher.Instance.Post(() => - { - var isDone = request.isDone; - action(request); - if (!isDone) - { - WaitForWebRequest(request, action); - } - }); - } - } -} diff --git a/Storage/Source/Internal/IAVCorePlugins.cs b/Storage/Source/Internal/IAVCorePlugins.cs deleted file mode 100644 index 51060bd..0000000 --- a/Storage/Source/Internal/IAVCorePlugins.cs +++ /dev/null @@ -1,26 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; - -namespace LeanCloud.Storage.Internal -{ - public interface IAVCorePlugins - { - void Reset(); - - IHttpClient HttpClient { get; } - IAppRouterController AppRouterController { get; } - IAVCommandRunner CommandRunner { get; } - IStorageController StorageController { get; } - - IAVCloudCodeController CloudCodeController { get; } - IAVConfigController ConfigController { get; } - IAVFileController FileController { get; } - IAVObjectController ObjectController { get; } - IAVQueryController QueryController { get; } - IAVSessionController SessionController { get; } - IAVUserController UserController { get; } - IObjectSubclassingController SubclassingController { get; } - IAVCurrentUserController CurrentUserController { get; } - IInstallationIdController InstallationIdController { get; } - } -} \ No newline at end of file diff --git a/Storage/Source/Internal/InstallationId/Controller/IInstallationIdController.cs b/Storage/Source/Internal/InstallationId/Controller/IInstallationIdController.cs deleted file mode 100644 index 1fb9af4..0000000 --- a/Storage/Source/Internal/InstallationId/Controller/IInstallationIdController.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - public interface IInstallationIdController { - /// - /// Sets current installationId and saves it to local storage. - /// - /// The installationId to be saved. - Task SetAsync(Guid? installationId); - - /// - /// Gets current installationId from local storage. Generates a none exists. - /// - /// Current installationId. - Task GetAsync(); - - /// - /// Clears current installationId from memory and local storage. - /// - Task ClearAsync(); - } -} diff --git a/Storage/Source/Internal/InstallationId/Controller/InstallationIdController.cs b/Storage/Source/Internal/InstallationId/Controller/InstallationIdController.cs deleted file mode 100644 index d28b015..0000000 --- a/Storage/Source/Internal/InstallationId/Controller/InstallationIdController.cs +++ /dev/null @@ -1,66 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - public class InstallationIdController : IInstallationIdController { - private const string InstallationIdKey = "InstallationId"; - private readonly object mutex = new object(); - private Guid? installationId; - - private readonly IStorageController storageController; - public InstallationIdController(IStorageController storageController) { - this.storageController = storageController; - } - - public Task SetAsync(Guid? installationId) { - lock (mutex) { - Task saveTask; - - if (installationId == null) { - saveTask = storageController - .LoadAsync() - .OnSuccess(storage => storage.Result.RemoveAsync(InstallationIdKey)) - .Unwrap(); - } else { - saveTask = storageController - .LoadAsync() - .OnSuccess(storage => storage.Result.AddAsync(InstallationIdKey, installationId.ToString())) - .Unwrap(); - } - this.installationId = installationId; - return saveTask; - } - } - - public Task GetAsync() { - lock (mutex) { - if (installationId != null) { - return Task.FromResult(installationId); - } - } - - return storageController - .LoadAsync() - .OnSuccess, Task>(s => { - object id; - s.Result.TryGetValue(InstallationIdKey, out id); - try { - lock (mutex) { - installationId = new Guid((string)id); - return Task.FromResult(installationId); - } - } catch (Exception) { - var newInstallationId = Guid.NewGuid(); - return SetAsync(newInstallationId).OnSuccess(_ => newInstallationId); - } - }) - .Unwrap(); - } - - public Task ClearAsync() { - return SetAsync(null); - } - } -} diff --git a/Storage/Source/Internal/Object/Controller/AVObjectController.cs b/Storage/Source/Internal/Object/Controller/AVObjectController.cs deleted file mode 100644 index 63e5a30..0000000 --- a/Storage/Source/Internal/Object/Controller/AVObjectController.cs +++ /dev/null @@ -1,248 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Utilities; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal -{ - public class AVObjectController : IAVObjectController - { - private readonly IAVCommandRunner commandRunner; - - public AVObjectController(IAVCommandRunner commandRunner) - { - this.commandRunner = commandRunner; - } - - public Task FetchAsync(IObjectState state, - string sessionToken, - CancellationToken cancellationToken) - { - var command = new AVCommand(string.Format("classes/{0}/{1}", - Uri.EscapeDataString(state.ClassName), - Uri.EscapeDataString(state.ObjectId)), - method: "GET", - sessionToken: sessionToken, - data: null); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); - }); - } - - public Task FetchAsync(IObjectState state, - IDictionary queryString, - string sessionToken, - CancellationToken cancellationToken) - { - var command = new AVCommand(string.Format("classes/{0}/{1}?{2}", - Uri.EscapeDataString(state.ClassName), - Uri.EscapeDataString(state.ObjectId), - AVClient.BuildQueryString(queryString)), - method: "GET", - sessionToken: sessionToken, - data: null); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); - }); - } - - public Task SaveAsync(IObjectState state, - IDictionary operations, - string sessionToken, - CancellationToken cancellationToken) - { - var objectJSON = AVObject.ToJSONObjectForSaving(operations); - - var command = new AVCommand((state.ObjectId == null ? - string.Format("classes/{0}", Uri.EscapeDataString(state.ClassName)) : - string.Format("classes/{0}/{1}", Uri.EscapeDataString(state.ClassName), state.ObjectId)), - method: (state.ObjectId == null ? "POST" : "PUT"), - sessionToken: sessionToken, - data: objectJSON); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - var serverState = AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); - serverState = serverState.MutatedClone(mutableClone => - { - mutableClone.IsNew = t.Result.Item1 == System.Net.HttpStatusCode.Created; - }); - return serverState; - }); - } - - public IList> SaveAllAsync(IList states, - IList> operationsList, - string sessionToken, - CancellationToken cancellationToken) - { - - var requests = states - .Zip(operationsList, (item, ops) => new AVCommand( - item.ObjectId == null - ? string.Format("classes/{0}", Uri.EscapeDataString(item.ClassName)) - : string.Format("classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), Uri.EscapeDataString(item.ObjectId)), - method: item.ObjectId == null ? "POST" : "PUT", - data: AVObject.ToJSONObjectForSaving(ops))) - .ToList(); - - var batchTasks = ExecuteBatchRequests(requests, sessionToken, cancellationToken); - var stateTasks = new List>(); - foreach (var task in batchTasks) - { - stateTasks.Add(task.OnSuccess(t => - { - return AVObjectCoder.Instance.Decode(t.Result, AVDecoder.Instance); - })); - } - - return stateTasks; - } - - public Task DeleteAsync(IObjectState state, - string sessionToken, - CancellationToken cancellationToken) - { - var command = new AVCommand(string.Format("classes/{0}/{1}", - state.ClassName, state.ObjectId), - method: "DELETE", - sessionToken: sessionToken, - data: null); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken); - } - - public IList DeleteAllAsync(IList states, - string sessionToken, - CancellationToken cancellationToken) - { - var requests = states - .Where(item => item.ObjectId != null) - .Select(item => new AVCommand( - string.Format("classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), Uri.EscapeDataString(item.ObjectId)), - method: "DELETE", - data: null)) - .ToList(); - return ExecuteBatchRequests(requests, sessionToken, cancellationToken).Cast().ToList(); - } - - // TODO (hallucinogen): move this out to a class to be used by Analytics - private const int MaximumBatchSize = 50; - internal IList>> ExecuteBatchRequests(IList requests, - string sessionToken, - CancellationToken cancellationToken) - { - var tasks = new List>>(); - int batchSize = requests.Count; - - IEnumerable remaining = requests; - while (batchSize > MaximumBatchSize) - { - var process = remaining.Take(MaximumBatchSize).ToList(); - remaining = remaining.Skip(MaximumBatchSize); - - tasks.AddRange(ExecuteBatchRequest(process, sessionToken, cancellationToken)); - - batchSize = remaining.Count(); - } - tasks.AddRange(ExecuteBatchRequest(remaining.ToList(), sessionToken, cancellationToken)); - - return tasks; - } - - private IList>> ExecuteBatchRequest(IList requests, - string sessionToken, - CancellationToken cancellationToken) - { - var tasks = new List>>(); - int batchSize = requests.Count; - var tcss = new List>>(); - for (int i = 0; i < batchSize; ++i) - { - var tcs = new TaskCompletionSource>(); - tcss.Add(tcs); - tasks.Add(tcs.Task); - } - - var encodedRequests = requests.Select(r => - { - var results = new Dictionary { - { "method", r.Method }, - { "path", r.Uri.AbsolutePath }, - }; - - if (r.DataObject != null) - { - results["body"] = r.DataObject; - } - return results; - }).Cast().ToList(); - var command = new AVCommand("batch", - method: "POST", - sessionToken: sessionToken, - data: new Dictionary { { "requests", encodedRequests } }); - - commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(t => - { - if (t.IsFaulted || t.IsCanceled) - { - foreach (var tcs in tcss) - { - if (t.IsFaulted) - { - tcs.TrySetException(t.Exception); - } - else if (t.IsCanceled) - { - tcs.TrySetCanceled(); - } - } - return; - } - - var resultsArray = Conversion.As>(t.Result.Item2["results"]); - int resultLength = resultsArray.Count; - if (resultLength != batchSize) - { - foreach (var tcs in tcss) - { - tcs.TrySetException(new InvalidOperationException( - "Batch command result count expected: " + batchSize + " but was: " + resultLength + ".")); - } - return; - } - - for (int i = 0; i < batchSize; ++i) - { - var result = resultsArray[i] as Dictionary; - var tcs = tcss[i]; - - if (result.ContainsKey("success")) - { - tcs.TrySetResult(result["success"] as IDictionary); - } - else if (result.ContainsKey("error")) - { - var error = result["error"] as IDictionary; - long errorCode = long.Parse(error["code"].ToString()); - tcs.TrySetException(new AVException((AVException.ErrorCode)errorCode, error["error"] as string)); - } - else - { - tcs.TrySetException(new InvalidOperationException( - "Invalid batch command response.")); - } - } - }); - - return tasks; - } - } -} diff --git a/Storage/Source/Internal/Object/Controller/IAVObjectController.cs b/Storage/Source/Internal/Object/Controller/IAVObjectController.cs deleted file mode 100644 index 9230eb5..0000000 --- a/Storage/Source/Internal/Object/Controller/IAVObjectController.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal -{ - public interface IAVObjectController - { - //Task FetchAsync(IObjectState state, - // string sessionToken, - // CancellationToken cancellationToken); - - Task FetchAsync(IObjectState state, - IDictionary queryString, - string sessionToken, - CancellationToken cancellationToken); - - Task SaveAsync(IObjectState state, - IDictionary operations, - string sessionToken, - CancellationToken cancellationToken); - - IList> SaveAllAsync(IList states, - IList> operationsList, - string sessionToken, - CancellationToken cancellationToken); - - Task DeleteAsync(IObjectState state, - string sessionToken, - CancellationToken cancellationToken); - - IList DeleteAllAsync(IList states, - string sessionToken, - CancellationToken cancellationToken); - } -} diff --git a/Storage/Source/Internal/Object/Controller/IAVObjectCurrentController.cs b/Storage/Source/Internal/Object/Controller/IAVObjectCurrentController.cs deleted file mode 100644 index e9b6f40..0000000 --- a/Storage/Source/Internal/Object/Controller/IAVObjectCurrentController.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - /// - /// IAVObjectCurrentController controls the single-instance - /// persistence used throughout the code-base. Sample usages are and - /// . - /// - /// Type of object being persisted. - public interface IAVObjectCurrentController where T : AVObject { - /// - /// Persists current . - /// - /// to be persisted. - /// The cancellation token. - Task SetAsync(T obj, CancellationToken cancellationToken); - - /// - /// Gets the persisted current . - /// - /// The cancellation token. - Task GetAsync(CancellationToken cancellationToken); - - /// - /// Returns a that resolves to true if current - /// exists. - /// - /// The cancellation token. - Task ExistsAsync(CancellationToken cancellationToken); - - /// - /// Returns true if the given is the persisted current - /// . - /// - /// The object to check. - /// True if obj is the current persisted . - bool IsCurrent(T obj); - - /// - /// Nullifies the current from memory. - /// - void ClearFromMemory(); - - /// - /// Clears current from disk. - /// - void ClearFromDisk(); - } -} diff --git a/Storage/Source/Internal/Object/State/IObjectState.cs b/Storage/Source/Internal/Object/State/IObjectState.cs deleted file mode 100644 index ab7b074..0000000 --- a/Storage/Source/Internal/Object/State/IObjectState.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal -{ - public interface IObjectState : IEnumerable> - { - bool IsNew { get; } - string ClassName { get; } - string ObjectId { get; } - DateTime? UpdatedAt { get; } - DateTime? CreatedAt { get; } - object this[string key] { get; } - - bool ContainsKey(string key); - - IObjectState MutatedClone(Action func); - } -} diff --git a/Storage/Source/Internal/Object/State/MutableObjectState.cs b/Storage/Source/Internal/Object/State/MutableObjectState.cs deleted file mode 100644 index ff9be3a..0000000 --- a/Storage/Source/Internal/Object/State/MutableObjectState.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal -{ - public class MutableObjectState : IObjectState - { - public bool IsNew { get; set; } - public string ClassName { get; set; } - public string ObjectId { get; set; } - public DateTime? UpdatedAt { get; set; } - public DateTime? CreatedAt { get; set; } - - // Initialize serverData to avoid further null checking. - private IDictionary serverData = new Dictionary(); - public IDictionary ServerData - { - get - { - return serverData; - } - - set - { - serverData = value; - } - } - - public object this[string key] - { - get - { - return ServerData[key]; - } - } - - public bool ContainsKey(string key) - { - return ServerData.ContainsKey(key); - } - - public void Apply(IDictionary operationSet) - { - // Apply operationSet - foreach (var pair in operationSet) - { - object oldValue; - ServerData.TryGetValue(pair.Key, out oldValue); - var newValue = pair.Value.Apply(oldValue, pair.Key); - if (newValue != AVDeleteOperation.DeleteToken) - { - ServerData[pair.Key] = newValue; - } - else - { - ServerData.Remove(pair.Key); - } - } - } - - public void Apply(IObjectState other) - { - IsNew = other.IsNew; - if (other.ObjectId != null) - { - ObjectId = other.ObjectId; - } - if (other.UpdatedAt != null) - { - UpdatedAt = other.UpdatedAt; - } - if (other.CreatedAt != null) - { - CreatedAt = other.CreatedAt; - } - - foreach (var pair in other) - { - ServerData[pair.Key] = pair.Value; - } - } - - public IObjectState MutatedClone(Action func) - { - var clone = MutableClone(); - func(clone); - return clone; - } - - protected virtual MutableObjectState MutableClone() - { - return new MutableObjectState - { - IsNew = IsNew, - ClassName = ClassName, - ObjectId = ObjectId, - CreatedAt = CreatedAt, - UpdatedAt = UpdatedAt, - ServerData = this.ToDictionary(t => t.Key, t => t.Value) - }; - } - - IEnumerator> IEnumerable>.GetEnumerator() - { - return ServerData.GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return ((IEnumerable>)this).GetEnumerator(); - } - } -} diff --git a/Storage/Source/Internal/Object/Subclassing/IObjectSubclassingController.cs b/Storage/Source/Internal/Object/Subclassing/IObjectSubclassingController.cs deleted file mode 100644 index 38322fc..0000000 --- a/Storage/Source/Internal/Object/Subclassing/IObjectSubclassingController.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal -{ - public interface IObjectSubclassingController - { - String GetClassName(Type type); - Type GetType(String className); - - bool IsTypeValid(String className, Type type); - - void RegisterSubclass(Type t); - void UnregisterSubclass(Type t); - - void AddRegisterHook(Type t, Action action); - - AVObject Instantiate(String className); - IDictionary GetPropertyMappings(String className); - } -} diff --git a/Storage/Source/Internal/Object/Subclassing/ObjectSubclassInfo.cs b/Storage/Source/Internal/Object/Subclassing/ObjectSubclassInfo.cs deleted file mode 100644 index 080fe6c..0000000 --- a/Storage/Source/Internal/Object/Subclassing/ObjectSubclassInfo.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Reflection; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal -{ - internal class ObjectSubclassInfo - { - public ObjectSubclassInfo(Type type, ConstructorInfo constructor) - { - TypeInfo = type.GetTypeInfo(); - ClassName = GetClassName(TypeInfo); - 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 TypeInfo TypeInfo { get; private set; } - public String ClassName { get; private set; } - public IDictionary PropertyMappings { get; private set; } - private ConstructorInfo Constructor { get; set; } - - public AVObject Instantiate() - { - return (AVObject)Constructor.Invoke(null); - } - - internal static String GetClassName(TypeInfo type) - { - var attribute = type.GetCustomAttribute(); - return attribute != null ? attribute.ClassName : null; - } - } -} diff --git a/Storage/Source/Internal/Object/Subclassing/ObjectSubclassingController.cs b/Storage/Source/Internal/Object/Subclassing/ObjectSubclassingController.cs deleted file mode 100644 index b1f913e..0000000 --- a/Storage/Source/Internal/Object/Subclassing/ObjectSubclassingController.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal -{ - internal class ObjectSubclassingController : IObjectSubclassingController - { - // Class names starting with _ are documented to be reserved. Use this one - // here to allow us to 'inherit' certain properties. - private static readonly string avObjectClassName = "_AVObject"; - - private readonly ReaderWriterLockSlim mutex; - private readonly IDictionary registeredSubclasses; - private Dictionary registerActions; - - public ObjectSubclassingController() - { - mutex = new ReaderWriterLockSlim(); - registeredSubclasses = new Dictionary(); - registerActions = new Dictionary(); - - // Register the AVObject subclass, so we get access to the ACL, - // objectId, and other AVFieldName properties. - RegisterSubclass(typeof(AVObject)); - } - - public String GetClassName(Type type) - { - return type == typeof(AVObject) - ? avObjectClassName - : ObjectSubclassInfo.GetClassName(type.GetTypeInfo()); - } - - public Type GetType(String className) - { - ObjectSubclassInfo info = null; - mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out info); - mutex.ExitReadLock(); - - return info != null - ? info.TypeInfo.AsType() - : null; - } - - public bool IsTypeValid(String className, Type type) - { - ObjectSubclassInfo subclassInfo = null; - - mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out subclassInfo); - mutex.ExitReadLock(); - - return subclassInfo == null - ? type == typeof(AVObject) - : subclassInfo.TypeInfo == type.GetTypeInfo(); - } - - public void RegisterSubclass(Type type) - { - TypeInfo typeInfo = type.GetTypeInfo(); - if (!typeof(AVObject).GetTypeInfo().IsAssignableFrom(typeInfo)) - { - throw new ArgumentException("Cannot register a type that is not a subclass of AVObject"); - } - - String className = GetClassName(type); - - try - { - // Perform this as a single independent transaction, so we can never get into an - // intermediate state where we *theoretically* register the wrong class due to a - // TOCTTOU bug. - mutex.EnterWriteLock(); - - ObjectSubclassInfo previousInfo = null; - if (registeredSubclasses.TryGetValue(className, out previousInfo)) - { - if (typeInfo.IsAssignableFrom(previousInfo.TypeInfo)) - { - // Previous subclass is more specific or equal to the current type, do nothing. - return; - } - else if (previousInfo.TypeInfo.IsAssignableFrom(typeInfo)) - { - // Previous subclass is parent of new child, fallthrough and actually register - // this class. - /* Do nothing */ - } - else - { - throw new ArgumentException( - "Tried to register both " + previousInfo.TypeInfo.FullName + " and " + typeInfo.FullName + - " as the AVObject subclass of " + className + ". Cannot determine the right class " + - "to use because neither inherits from the other." - ); - } - } - - ConstructorInfo constructor = type.FindConstructor(); - if (constructor == null) - { - throw new ArgumentException("Cannot register a type that does not implement the default constructor!"); - } - - registeredSubclasses[className] = new ObjectSubclassInfo(type, constructor); - } - finally - { - mutex.ExitWriteLock(); - } - - Action toPerform; - - mutex.EnterReadLock(); - registerActions.TryGetValue(className, out toPerform); - mutex.ExitReadLock(); - - if (toPerform != null) - { - toPerform(); - } - } - - public void UnregisterSubclass(Type type) - { - mutex.EnterWriteLock(); - registeredSubclasses.Remove(GetClassName(type)); - mutex.ExitWriteLock(); - } - - public void AddRegisterHook(Type t, Action action) - { - mutex.EnterWriteLock(); - registerActions.Add(GetClassName(t), action); - mutex.ExitWriteLock(); - } - - public AVObject Instantiate(String className) - { - ObjectSubclassInfo info = null; - - mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out info); - mutex.ExitReadLock(); - - return info != null - ? info.Instantiate() - : new AVObject(className); - } - - public IDictionary GetPropertyMappings(String className) - { - ObjectSubclassInfo info = null; - mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out info); - if (info == null) - { - registeredSubclasses.TryGetValue(avObjectClassName, out info); - } - mutex.ExitReadLock(); - - return info.PropertyMappings; - } - - } -} diff --git a/Storage/Source/Internal/Operation/AVAddOperation.cs b/Storage/Source/Internal/Operation/AVAddOperation.cs deleted file mode 100644 index ed0a83e..0000000 --- a/Storage/Source/Internal/Operation/AVAddOperation.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using LeanCloud.Utilities; - -namespace LeanCloud.Storage.Internal { - public class AVAddOperation : IAVFieldOperation { - private ReadOnlyCollection objects; - public AVAddOperation(IEnumerable objects) { - this.objects = new ReadOnlyCollection(objects.ToList()); - } - - public object Encode() { - return new Dictionary { - {"__op", "Add"}, - {"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)} - }; - } - - public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) { - if (previous == null) { - return this; - } - if (previous is AVDeleteOperation) { - return new AVSetOperation(objects.ToList()); - } - if (previous is AVSetOperation) { - var setOp = (AVSetOperation)previous; - var oldList = Conversion.To>(setOp.Value); - return new AVSetOperation(oldList.Concat(objects).ToList()); - } - if (previous is AVAddOperation) { - return new AVAddOperation(((AVAddOperation)previous).Objects.Concat(objects)); - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } - - public object Apply(object oldValue, string key) { - if (oldValue == null) { - return objects.ToList(); - } - var oldList = Conversion.To>(oldValue); - return oldList.Concat(objects).ToList(); - } - - public IEnumerable Objects { - get { - return objects; - } - } - } -} diff --git a/Storage/Source/Internal/Operation/AVAddUniqueOperation.cs b/Storage/Source/Internal/Operation/AVAddUniqueOperation.cs deleted file mode 100644 index 8e0a957..0000000 --- a/Storage/Source/Internal/Operation/AVAddUniqueOperation.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using LeanCloud.Utilities; - -namespace LeanCloud.Storage.Internal { - public class AVAddUniqueOperation : IAVFieldOperation { - private ReadOnlyCollection objects; - public AVAddUniqueOperation(IEnumerable objects) { - this.objects = new ReadOnlyCollection(objects.Distinct().ToList()); - } - - public object Encode() { - return new Dictionary { - {"__op", "AddUnique"}, - {"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)} - }; - } - - public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) { - if (previous == null) { - return this; - } - if (previous is AVDeleteOperation) { - return new AVSetOperation(objects.ToList()); - } - if (previous is AVSetOperation) { - var setOp = (AVSetOperation)previous; - var oldList = Conversion.To>(setOp.Value); - var result = this.Apply(oldList, null); - return new AVSetOperation(result); - } - if (previous is AVAddUniqueOperation) { - var oldList = ((AVAddUniqueOperation)previous).Objects; - return new AVAddUniqueOperation((IList)this.Apply(oldList, null)); - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } - - public object Apply(object oldValue, string key) { - if (oldValue == null) { - return objects.ToList(); - } - var newList = Conversion.To>(oldValue).ToList(); - var comparer = AVFieldOperations.AVObjectComparer; - foreach (var objToAdd in objects) { - if (objToAdd is AVObject) { - var matchedObj = newList.FirstOrDefault(listObj => comparer.Equals(objToAdd, listObj)); - if (matchedObj == null) { - newList.Add(objToAdd); - } else { - var index = newList.IndexOf(matchedObj); - newList[index] = objToAdd; - } - } else if (!newList.Contains(objToAdd, comparer)) { - newList.Add(objToAdd); - } - } - return newList; - } - - public IEnumerable Objects { - get { - return objects; - } - } - } -} diff --git a/Storage/Source/Internal/Operation/AVDeleteOperation.cs b/Storage/Source/Internal/Operation/AVDeleteOperation.cs deleted file mode 100644 index 7b77b94..0000000 --- a/Storage/Source/Internal/Operation/AVDeleteOperation.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal -{ - /// - /// An operation where a field is deleted from the object. - /// - public class AVDeleteOperation : IAVFieldOperation - { - internal static readonly object DeleteToken = new object(); - private static AVDeleteOperation _Instance = new AVDeleteOperation(); - public static AVDeleteOperation Instance - { - get - { - return _Instance; - } - } - - private AVDeleteOperation() { } - public object Encode() - { - return new Dictionary { - {"__op", "Delete"} - }; - } - - public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) - { - return this; - } - - public object Apply(object oldValue, string key) - { - return DeleteToken; - } - } -} diff --git a/Storage/Source/Internal/Operation/AVFieldOperations.cs b/Storage/Source/Internal/Operation/AVFieldOperations.cs deleted file mode 100644 index 9c07ffa..0000000 --- a/Storage/Source/Internal/Operation/AVFieldOperations.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - public class AVObjectIdComparer : IEqualityComparer { - bool IEqualityComparer.Equals(object p1, object p2) { - var avObj1 = p1 as AVObject; - var avObj2 = p2 as AVObject; - if (avObj1 != null && avObj2 != null) { - return object.Equals(avObj1.ObjectId, avObj2.ObjectId); - } - return object.Equals(p1, p2); - } - - public int GetHashCode(object p) { - var avObject = p as AVObject; - if (avObject != null) { - return avObject.ObjectId.GetHashCode(); - } - return p.GetHashCode(); - } - } - - static class AVFieldOperations { - private static AVObjectIdComparer comparer; - - public static IAVFieldOperation Decode(IDictionary json) { - throw new NotImplementedException(); - } - - public static IEqualityComparer AVObjectComparer { - get { - if (comparer == null) { - comparer = new AVObjectIdComparer(); - } - return comparer; - } - } - } -} diff --git a/Storage/Source/Internal/Operation/AVIncrementOperation.cs b/Storage/Source/Internal/Operation/AVIncrementOperation.cs deleted file mode 100644 index 1606e5f..0000000 --- a/Storage/Source/Internal/Operation/AVIncrementOperation.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace LeanCloud.Storage.Internal -{ - public class AVIncrementOperation : IAVFieldOperation - { - private static readonly IDictionary, Func> adders; - - static AVIncrementOperation() - { - // Defines adders for all of the implicit conversions: http://msdn.microsoft.com/en-US/library/y5b434w4(v=vs.80).aspx - adders = new Dictionary, Func> { - {new Tuple(typeof(sbyte), typeof(sbyte)), (left, right) => (sbyte)left + (sbyte)right}, - {new Tuple(typeof(sbyte), typeof(short)), (left, right) => (sbyte)left + (short)right}, - {new Tuple(typeof(sbyte), typeof(int)), (left, right) => (sbyte)left + (int)right}, - {new Tuple(typeof(sbyte), typeof(long)), (left, right) => (sbyte)left + (long)right}, - {new Tuple(typeof(sbyte), typeof(float)), (left, right) => (sbyte)left + (float)right}, - {new Tuple(typeof(sbyte), typeof(double)), (left, right) => (sbyte)left + (double)right}, - {new Tuple(typeof(sbyte), typeof(decimal)), (left, right) => (sbyte)left + (decimal)right}, - {new Tuple(typeof(byte), typeof(byte)), (left, right) => (byte)left + (byte)right}, - {new Tuple(typeof(byte), typeof(short)), (left, right) => (byte)left + (short)right}, - {new Tuple(typeof(byte), typeof(ushort)), (left, right) => (byte)left + (ushort)right}, - {new Tuple(typeof(byte), typeof(int)), (left, right) => (byte)left + (int)right}, - {new Tuple(typeof(byte), typeof(uint)), (left, right) => (byte)left + (uint)right}, - {new Tuple(typeof(byte), typeof(long)), (left, right) => (byte)left + (long)right}, - {new Tuple(typeof(byte), typeof(ulong)), (left, right) => (byte)left + (ulong)right}, - {new Tuple(typeof(byte), typeof(float)), (left, right) => (byte)left + (float)right}, - {new Tuple(typeof(byte), typeof(double)), (left, right) => (byte)left + (double)right}, - {new Tuple(typeof(byte), typeof(decimal)), (left, right) => (byte)left + (decimal)right}, - {new Tuple(typeof(short), typeof(short)), (left, right) => (short)left + (short)right}, - {new Tuple(typeof(short), typeof(int)), (left, right) => (short)left + (int)right}, - {new Tuple(typeof(short), typeof(long)), (left, right) => (short)left + (long)right}, - {new Tuple(typeof(short), typeof(float)), (left, right) => (short)left + (float)right}, - {new Tuple(typeof(short), typeof(double)), (left, right) => (short)left + (double)right}, - {new Tuple(typeof(short), typeof(decimal)), (left, right) => (short)left + (decimal)right}, - {new Tuple(typeof(ushort), typeof(ushort)), (left, right) => (ushort)left + (ushort)right}, - {new Tuple(typeof(ushort), typeof(int)), (left, right) => (ushort)left + (int)right}, - {new Tuple(typeof(ushort), typeof(uint)), (left, right) => (ushort)left + (uint)right}, - {new Tuple(typeof(ushort), typeof(long)), (left, right) => (ushort)left + (long)right}, - {new Tuple(typeof(ushort), typeof(ulong)), (left, right) => (ushort)left + (ulong)right}, - {new Tuple(typeof(ushort), typeof(float)), (left, right) => (ushort)left + (float)right}, - {new Tuple(typeof(ushort), typeof(double)), (left, right) => (ushort)left + (double)right}, - {new Tuple(typeof(ushort), typeof(decimal)), (left, right) => (ushort)left + (decimal)right}, - {new Tuple(typeof(int), typeof(int)), (left, right) => (int)left + (int)right}, - {new Tuple(typeof(int), typeof(long)), (left, right) => (int)left + (long)right}, - {new Tuple(typeof(int), typeof(float)), (left, right) => (int)left + (float)right}, - {new Tuple(typeof(int), typeof(double)), (left, right) => (int)left + (double)right}, - {new Tuple(typeof(int), typeof(decimal)), (left, right) => (int)left + (decimal)right}, - {new Tuple(typeof(uint), typeof(uint)), (left, right) => (uint)left + (uint)right}, - {new Tuple(typeof(uint), typeof(long)), (left, right) => (uint)left + (long)right}, - {new Tuple(typeof(uint), typeof(ulong)), (left, right) => (uint)left + (ulong)right}, - {new Tuple(typeof(uint), typeof(float)), (left, right) => (uint)left + (float)right}, - {new Tuple(typeof(uint), typeof(double)), (left, right) => (uint)left + (double)right}, - {new Tuple(typeof(uint), typeof(decimal)), (left, right) => (uint)left + (decimal)right}, - {new Tuple(typeof(long), typeof(long)), (left, right) => (long)left + (long)right}, - {new Tuple(typeof(long), typeof(float)), (left, right) => (long)left + (float)right}, - {new Tuple(typeof(long), typeof(double)), (left, right) => (long)left + (double)right}, - {new Tuple(typeof(long), typeof(decimal)), (left, right) => (long)left + (decimal)right}, - {new Tuple(typeof(char), typeof(char)), (left, right) => (char)left + (char)right}, - {new Tuple(typeof(char), typeof(ushort)), (left, right) => (char)left + (ushort)right}, - {new Tuple(typeof(char), typeof(int)), (left, right) => (char)left + (int)right}, - {new Tuple(typeof(char), typeof(uint)), (left, right) => (char)left + (uint)right}, - {new Tuple(typeof(char), typeof(long)), (left, right) => (char)left + (long)right}, - {new Tuple(typeof(char), typeof(ulong)), (left, right) => (char)left + (ulong)right}, - {new Tuple(typeof(char), typeof(float)), (left, right) => (char)left + (float)right}, - {new Tuple(typeof(char), typeof(double)), (left, right) => (char)left + (double)right}, - {new Tuple(typeof(char), typeof(decimal)), (left, right) => (char)left + (decimal)right}, - {new Tuple(typeof(float), typeof(float)), (left, right) => (float)left + (float)right}, - {new Tuple(typeof(float), typeof(double)), (left, right) => (float)left + (double)right}, - {new Tuple(typeof(ulong), typeof(ulong)), (left, right) => (ulong)left + (ulong)right}, - {new Tuple(typeof(ulong), typeof(float)), (left, right) => (ulong)left + (float)right}, - {new Tuple(typeof(ulong), typeof(double)), (left, right) => (ulong)left + (double)right}, - {new Tuple(typeof(ulong), typeof(decimal)), (left, right) => (ulong)left + (decimal)right}, - {new Tuple(typeof(double), typeof(double)), (left, right) => (double)left + (double)right}, - {new Tuple(typeof(decimal), typeof(decimal)), (left, right) => (decimal)left + (decimal)right} - }; - // Generate the adders in the other direction - foreach (var pair in adders.Keys.ToList()) - { - if (pair.Item1.Equals(pair.Item2)) - { - continue; - } - var reversePair = new Tuple(pair.Item2, pair.Item1); - var func = adders[pair]; - adders[reversePair] = (left, right) => func(right, left); - } - } - - private object amount; - - public AVIncrementOperation(object amount) - { - this.amount = amount; - } - - public object Encode() - { - return new Dictionary - { - {"__op", "Increment"}, - {"amount", amount} - }; - } - - private static object Add(object obj1, object obj2) - { - Func adder; - if (adders.TryGetValue(new Tuple(obj1.GetType(), obj2.GetType()), out adder)) - { - return adder(obj1, obj2); - } - throw new InvalidCastException("Cannot add " + obj1.GetType() + " to " + obj2.GetType()); - } - - public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) - { - if (previous == null) - { - return this; - } - if (previous is AVDeleteOperation) - { - return new AVSetOperation(amount); - } - if (previous is AVSetOperation) - { - var otherAmount = ((AVSetOperation)previous).Value; - if (otherAmount is string) - { - throw new InvalidOperationException("Cannot increment a non-number type."); - } - var myAmount = amount; - return new AVSetOperation(Add(otherAmount, myAmount)); - } - if (previous is AVIncrementOperation) - { - object otherAmount = ((AVIncrementOperation)previous).Amount; - object myAmount = amount; - return new AVIncrementOperation(Add(otherAmount, myAmount)); - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } - - public object Apply(object oldValue, string key) - { - if (oldValue is string) - { - throw new InvalidOperationException("Cannot increment a non-number type."); - } - object otherAmount = oldValue ?? 0; - object myAmount = amount; - return Add(otherAmount, myAmount); - } - - public object Amount - { - get - { - return amount; - } - } - } -} diff --git a/Storage/Source/Internal/Operation/AVRelationOperation.cs b/Storage/Source/Internal/Operation/AVRelationOperation.cs deleted file mode 100644 index 512b5f5..0000000 --- a/Storage/Source/Internal/Operation/AVRelationOperation.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - public class AVRelationOperation : IAVFieldOperation { - private readonly IList adds; - private readonly IList removes; - private readonly string targetClassName; - - private AVRelationOperation(IEnumerable adds, - IEnumerable removes, - string targetClassName) { - this.targetClassName = targetClassName; - this.adds = new ReadOnlyCollection(adds.ToList()); - this.removes = new ReadOnlyCollection(removes.ToList()); - } - - public AVRelationOperation(IEnumerable adds, - IEnumerable removes) { - adds = adds ?? new AVObject[0]; - removes = removes ?? new AVObject[0]; - this.targetClassName = adds.Concat(removes).Select(o => o.ClassName).FirstOrDefault(); - this.adds = new ReadOnlyCollection(IdsFromObjects(adds).ToList()); - this.removes = new ReadOnlyCollection(IdsFromObjects(removes).ToList()); - } - - public object Encode() { - var adds = this.adds - .Select(id => PointerOrLocalIdEncoder.Instance.Encode( - AVObject.CreateWithoutData(targetClassName, id))) - .ToList(); - var removes = this.removes - .Select(id => PointerOrLocalIdEncoder.Instance.Encode( - AVObject.CreateWithoutData(targetClassName, id))) - .ToList(); - var addDict = adds.Count == 0 ? null : new Dictionary { - {"__op", "AddRelation"}, - {"objects", adds} - }; - var removeDict = removes.Count == 0 ? null : new Dictionary { - {"__op", "RemoveRelation"}, - {"objects", removes} - }; - - if (addDict != null && removeDict != null) { - return new Dictionary { - {"__op", "Batch"}, - {"ops", new[] {addDict, removeDict}} - }; - } - return addDict ?? removeDict; - } - - public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) { - if (previous == null) { - return this; - } - if (previous is AVDeleteOperation) { - throw new InvalidOperationException("You can't modify a relation after deleting it."); - } - var other = previous as AVRelationOperation; - if (other != null) { - if (other.TargetClassName != TargetClassName) { - throw new InvalidOperationException( - string.Format("Related object must be of class {0}, but {1} was passed in.", - other.TargetClassName, - TargetClassName)); - } - var newAdd = adds.Union(other.adds.Except(removes)).ToList(); - var newRemove = removes.Union(other.removes.Except(adds)).ToList(); - return new AVRelationOperation(newAdd, newRemove, TargetClassName); - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } - - public object Apply(object oldValue, string key) { - if (adds.Count == 0 && removes.Count == 0) { - return null; - } - if (oldValue == null) { - return AVRelationBase.CreateRelation(null, key, targetClassName); - } - if (oldValue is AVRelationBase) { - var oldRelation = (AVRelationBase)oldValue; - var oldClassName = oldRelation.TargetClassName; - if (oldClassName != null && oldClassName != targetClassName) { - throw new InvalidOperationException("Related object must be a " + oldClassName - + ", but a " + targetClassName + " was passed in."); - } - oldRelation.TargetClassName = targetClassName; - return oldRelation; - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } - - public string TargetClassName { get { return targetClassName; } } - - private IEnumerable IdsFromObjects(IEnumerable objects) { - foreach (var obj in objects) { - if (obj.ObjectId == null) { - throw new ArgumentException( - "You can't add an unsaved AVObject to a relation."); - } - if (obj.ClassName != targetClassName) { - throw new ArgumentException(string.Format( - "Tried to create a AVRelation with 2 different types: {0} and {1}", - targetClassName, - obj.ClassName)); - } - } - return objects.Select(o => o.ObjectId).Distinct(); - } - } -} diff --git a/Storage/Source/Internal/Operation/AVRemoveOperation.cs b/Storage/Source/Internal/Operation/AVRemoveOperation.cs deleted file mode 100644 index c9d14a4..0000000 --- a/Storage/Source/Internal/Operation/AVRemoveOperation.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; - -using LeanCloud.Utilities; - -namespace LeanCloud.Storage.Internal -{ - public class AVRemoveOperation : IAVFieldOperation - { - private ReadOnlyCollection objects; - public AVRemoveOperation(IEnumerable objects) - { - this.objects = new ReadOnlyCollection(objects.Distinct().ToList()); - } - - public object Encode() - { - return new Dictionary { - {"__op", "Remove"}, - {"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)} - }; - } - - public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) - { - if (previous == null) - { - return this; - } - if (previous is AVDeleteOperation) - { - return previous; - } - if (previous is AVSetOperation) - { - var setOp = (AVSetOperation)previous; - var oldList = Conversion.As>(setOp.Value); - return new AVSetOperation(this.Apply(oldList, null)); - } - if (previous is AVRemoveOperation) - { - var oldOp = (AVRemoveOperation)previous; - return new AVRemoveOperation(oldOp.Objects.Concat(objects)); - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } - - public object Apply(object oldValue, string key) - { - if (oldValue == null) - { - return new List(); - } - var oldList = Conversion.As>(oldValue); - return oldList.Except(objects, AVFieldOperations.AVObjectComparer).ToList(); - } - - public IEnumerable Objects - { - get - { - return objects; - } - } - } -} diff --git a/Storage/Source/Internal/Operation/AVSetOperation.cs b/Storage/Source/Internal/Operation/AVSetOperation.cs deleted file mode 100644 index 1c3a412..0000000 --- a/Storage/Source/Internal/Operation/AVSetOperation.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace LeanCloud.Storage.Internal { - public class AVSetOperation : IAVFieldOperation { - public AVSetOperation(object value) { - Value = value; - } - - public object Encode() { - return PointerOrLocalIdEncoder.Instance.Encode(Value); - } - - public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) { - return this; - } - - public object Apply(object oldValue, string key) { - return Value; - } - - public object Value { get; private set; } - } -} diff --git a/Storage/Source/Internal/Operation/IAVFieldOperation.cs b/Storage/Source/Internal/Operation/IAVFieldOperation.cs deleted file mode 100644 index 70b2930..0000000 --- a/Storage/Source/Internal/Operation/IAVFieldOperation.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace LeanCloud.Storage.Internal { - /// - /// A AVFieldOperation represents a modification to a value in a AVObject. - /// For example, setting, deleting, or incrementing a value are all different kinds of - /// AVFieldOperations. AVFieldOperations themselves can be considered to be - /// immutable. - /// - public interface IAVFieldOperation { - /// - /// Converts the AVFieldOperation to a data structure that can be converted to JSON and sent to - /// LeanCloud as part of a save operation. - /// - /// An object to be JSONified. - object Encode(); - - /// - /// Returns a field operation that is composed of a previous operation followed by - /// this operation. This will not mutate either operation. However, it may return - /// this if the current operation is not affected by previous changes. - /// For example: - /// {increment by 2}.MergeWithPrevious({set to 5}) -> {set to 7} - /// {set to 5}.MergeWithPrevious({increment by 2}) -> {set to 5} - /// {add "foo"}.MergeWithPrevious({delete}) -> {set to ["foo"]} - /// {delete}.MergeWithPrevious({add "foo"}) -> {delete} /// - /// The most recent operation on the field, or null if none. - /// A new AVFieldOperation or this. - IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous); - - /// - /// Returns a new estimated value based on a previous value and this operation. This - /// value is not intended to be sent to LeanCloud, but it is used locally on the client to - /// inspect the most likely current value for a field. - /// - /// The key and object are used solely for AVRelation to be able to construct objects - /// that refer back to their parents. - /// - /// The previous value for the field. - /// The key that this value is for. - /// The new value for the field. - object Apply(object oldValue, string key); - } -} diff --git a/Storage/Source/Internal/Query/Controller/AVQueryController.cs b/Storage/Source/Internal/Query/Controller/AVQueryController.cs deleted file mode 100644 index 42c1869..0000000 --- a/Storage/Source/Internal/Query/Controller/AVQueryController.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal -{ - internal class AVQueryController : IAVQueryController - { - private readonly IAVCommandRunner commandRunner; - - public AVQueryController(IAVCommandRunner commandRunner) - { - this.commandRunner = commandRunner; - } - - public Task> FindAsync(AVQuery query, - AVUser user, - CancellationToken cancellationToken) where T : AVObject - { - string sessionToken = user != null ? user.SessionToken : null; - - return FindAsync(query.RelativeUri, query.BuildParameters(), sessionToken, cancellationToken).OnSuccess(t => - { - var items = t.Result["results"] as IList; - - return (from item in items - select AVObjectCoder.Instance.Decode(item as IDictionary, AVDecoder.Instance)); - }); - } - - public Task CountAsync(AVQuery query, - AVUser user, - CancellationToken cancellationToken) where T : AVObject - { - string sessionToken = user != null ? user.SessionToken : null; - var parameters = query.BuildParameters(); - parameters["limit"] = 0; - parameters["count"] = 1; - - return FindAsync(query.RelativeUri, parameters, sessionToken, cancellationToken).OnSuccess(t => - { - return Convert.ToInt32(t.Result["count"]); - }); - } - - public Task FirstAsync(AVQuery query, - AVUser user, - CancellationToken cancellationToken) where T : AVObject - { - string sessionToken = user != null ? user.SessionToken : null; - var parameters = query.BuildParameters(); - parameters["limit"] = 1; - - return FindAsync(query.RelativeUri, parameters, sessionToken, cancellationToken).OnSuccess(t => - { - var items = t.Result["results"] as IList; - var item = items.FirstOrDefault() as IDictionary; - - // Not found. Return empty state. - if (item == null) - { - return (IObjectState)null; - } - - return AVObjectCoder.Instance.Decode(item, AVDecoder.Instance); - }); - } - - private Task> FindAsync(string relativeUri, - IDictionary parameters, - string sessionToken, - CancellationToken cancellationToken) - { - - var command = new AVCommand(string.Format("{0}?{1}", - relativeUri, - AVClient.BuildQueryString(parameters)), - method: "GET", - sessionToken: sessionToken, - data: null); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - return t.Result.Item2; - }); - } - - //private Task> FindAsync(string className, - // IDictionary parameters, - // string sessionToken, - // CancellationToken cancellationToken) - //{ - // var command = new AVCommand(string.Format("classes/{0}?{1}", - // Uri.EscapeDataString(className), - // AVClient.BuildQueryString(parameters)), - // method: "GET", - // sessionToken: sessionToken, - // data: null); - - // return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - // { - // return t.Result.Item2; - // }); - //} - } -} diff --git a/Storage/Source/Internal/Query/Controller/IAVQueryController.cs b/Storage/Source/Internal/Query/Controller/IAVQueryController.cs deleted file mode 100644 index f5d1494..0000000 --- a/Storage/Source/Internal/Query/Controller/IAVQueryController.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - public interface IAVQueryController { - Task> FindAsync(AVQuery query, - AVUser user, - CancellationToken cancellationToken) where T : AVObject; - - Task CountAsync(AVQuery query, - AVUser user, - CancellationToken cancellationToken) where T : AVObject; - - Task FirstAsync(AVQuery query, - AVUser user, - CancellationToken cancellationToken) where T : AVObject; - } -} diff --git a/Storage/Source/Internal/Session/Controller/AVSessionController.cs b/Storage/Source/Internal/Session/Controller/AVSessionController.cs deleted file mode 100644 index fd40711..0000000 --- a/Storage/Source/Internal/Session/Controller/AVSessionController.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal { - public class AVSessionController : IAVSessionController { - private readonly IAVCommandRunner commandRunner; - - public AVSessionController(IAVCommandRunner commandRunner) { - this.commandRunner = commandRunner; - } - - public Task GetSessionAsync(string sessionToken, CancellationToken cancellationToken) { - var command = new AVCommand("sessions/me", - method: "GET", - sessionToken: sessionToken, - data: null); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => { - return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); - }); - } - - public Task RevokeAsync(string sessionToken, CancellationToken cancellationToken) { - var command = new AVCommand("logout", - method: "POST", - sessionToken: sessionToken, - data: new Dictionary()); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken); - } - - public Task UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) { - var command = new AVCommand("upgradeToRevocableSession", - method: "POST", - sessionToken: sessionToken, - data: new Dictionary()); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => { - return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); - }); - } - - public bool IsRevocableSessionToken(string sessionToken) { - return sessionToken.Contains("r:"); - } - } -} diff --git a/Storage/Source/Internal/Session/Controller/IAVSessionController.cs b/Storage/Source/Internal/Session/Controller/IAVSessionController.cs deleted file mode 100644 index 72e7e95..0000000 --- a/Storage/Source/Internal/Session/Controller/IAVSessionController.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - public interface IAVSessionController { - Task GetSessionAsync(string sessionToken, CancellationToken cancellationToken); - - Task RevokeAsync(string sessionToken, CancellationToken cancellationToken); - - Task UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken); - - bool IsRevocableSessionToken(string sessionToken); - } -} diff --git a/Storage/Source/Internal/Storage/IStorageController.cs b/Storage/Source/Internal/Storage/IStorageController.cs deleted file mode 100644 index b334c92..0000000 --- a/Storage/Source/Internal/Storage/IStorageController.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - /// - /// An abstraction for accessing persistent storage in the LeanCloud SDK. - /// - public interface IStorageController { - /// - /// Load the contents of this storage controller asynchronously. - /// - /// - Task> LoadAsync(); - - /// - /// Overwrites the contents of this storage controller asynchronously. - /// - /// - /// - Task> SaveAsync(IDictionary contents); - } - - /// - /// An interface for a dictionary that is persisted to disk asynchronously. - /// - /// They key type of the dictionary. - /// The value type of the dictionary. - public interface IStorageDictionary : IEnumerable> { - int Count { get; } - TValue this[TKey key] { get; } - - IEnumerable Keys { get; } - IEnumerable Values { get; } - - bool ContainsKey(TKey key); - bool TryGetValue(TKey key, out TValue value); - - /// - /// Adds a key to this dictionary, and saves it asynchronously. - /// - /// The key to insert. - /// The value to insert. - /// - Task AddAsync(TKey key, TValue value); - - /// - /// Removes a key from this dictionary, and saves it asynchronously. - /// - /// - /// - Task RemoveAsync(TKey key); - } -} \ No newline at end of file diff --git a/Storage/Source/Internal/Storage/NetCore/StorageController.cs b/Storage/Source/Internal/Storage/NetCore/StorageController.cs deleted file mode 100644 index 87d537c..0000000 --- a/Storage/Source/Internal/Storage/NetCore/StorageController.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Linq; -using System.Collections.Generic; -using System.Threading; - -namespace LeanCloud.Storage.Internal -{ - /// - /// Implements `IStorageController` for PCL targets, based off of PCLStorage. - /// - public class StorageController : IStorageController - { - private class StorageDictionary : IStorageDictionary - { - private object mutex; - private Dictionary dictionary; - - public StorageDictionary() - { - this.mutex = new Object(); - dictionary = new Dictionary(); - } - - internal Task SaveAsync() - { - string json; - lock (mutex) - { - json = Json.Encode(dictionary); - } - return Task.FromResult(json); - } - - internal Task LoadAsync() - { - return Task.FromResult("{}").ContinueWith(t => - { - string text = t.Result; - Dictionary result = null; - try - { - result = Json.Parse(text) as Dictionary; - } - catch (Exception) - { - // Do nothing, JSON error. Probaby was empty string. - } - - lock (mutex) - { - dictionary = result ?? new Dictionary(); - } - }); - - } - - internal void Update(IDictionary contents) - { - lock (mutex) - { - dictionary = contents.ToDictionary(p => p.Key, p => p.Value); - } - } - - public Task AddAsync(string key, object value) - { - lock (mutex) - { - dictionary[key] = value; - } - return SaveAsync(); - } - - public Task RemoveAsync(string key) - { - lock (mutex) - { - dictionary.Remove(key); - } - return SaveAsync(); - } - - public bool ContainsKey(string key) - { - lock (mutex) - { - return dictionary.ContainsKey(key); - } - } - - public IEnumerable Keys - { - get { lock (mutex) { return dictionary.Keys; } } - } - - public bool TryGetValue(string key, out object value) - { - lock (mutex) - { - return dictionary.TryGetValue(key, out value); - } - } - - public IEnumerable Values - { - get { lock (mutex) { return dictionary.Values; } } - } - - public object this[string key] - { - get { lock (mutex) { return dictionary[key]; } } - } - - public int Count - { - get { lock (mutex) { return dictionary.Count; } } - } - - public IEnumerator> GetEnumerator() - { - lock (mutex) - { - return dictionary.GetEnumerator(); - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - lock (mutex) - { - return dictionary.GetEnumerator(); - } - } - } - - private const string AVStorageFileName = "ApplicationSettings"; - private readonly TaskQueue taskQueue = new TaskQueue(); - - private StorageDictionary storageDictionary; - - public StorageController() - { - - } - public StorageController(string appId) - { - - } - public Task> LoadAsync() - { - return taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - if (storageDictionary != null) - { - return Task.FromResult>(storageDictionary); - } - - storageDictionary = new StorageDictionary(); - return storageDictionary.LoadAsync().OnSuccess(__ => storageDictionary as IStorageDictionary); - }).Unwrap(); - }, CancellationToken.None); - } - - public Task> SaveAsync(IDictionary contents) - { - return taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - if (storageDictionary == null) - { - storageDictionary = new StorageDictionary(); - } - - storageDictionary.Update(contents); - return storageDictionary.SaveAsync().OnSuccess(__ => storageDictionary as IStorageDictionary); - }).Unwrap(); - }, CancellationToken.None); - } - } -} diff --git a/Storage/Source/Internal/Storage/Portable/StorageController.cs b/Storage/Source/Internal/Storage/Portable/StorageController.cs deleted file mode 100644 index 0390ed7..0000000 --- a/Storage/Source/Internal/Storage/Portable/StorageController.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Linq; -using PCLStorage; -using System.Collections.Generic; -using System.Threading; - -namespace LeanCloud.Storage.Internal -{ - /// - /// Implements `IStorageController` for PCL targets, based off of PCLStorage. - /// - public class StorageController : IStorageController - { - private class StorageDictionary : IStorageDictionary - { - private object mutex; - private Dictionary dictionary; - private IFile file; - - public StorageDictionary(IFile file) - { - this.file = file; - - mutex = new Object(); - dictionary = new Dictionary(); - } - - internal Task SaveAsync() - { - string json; - lock (mutex) - { - json = Json.Encode(dictionary); - } - return file.WriteAllTextAsync(json); - } - - internal Task LoadAsync() - { - return file.ReadAllTextAsync().ContinueWith(t => - { - string text = t.Result; - Dictionary result = null; - try - { - result = Json.Parse(text) as Dictionary; - } - catch (Exception) - { - // Do nothing, JSON error. Probaby was empty string. - } - - lock (mutex) - { - dictionary = result ?? new Dictionary(); - } - }); - } - - internal void Update(IDictionary contents) - { - lock (mutex) - { - dictionary = contents.ToDictionary(p => p.Key, p => p.Value); - } - } - - public Task AddAsync(string key, object value) - { - lock (mutex) - { - dictionary[key] = value; - } - return SaveAsync(); - } - - public Task RemoveAsync(string key) - { - lock (mutex) - { - dictionary.Remove(key); - } - return SaveAsync(); - } - - public bool ContainsKey(string key) - { - lock (mutex) - { - return dictionary.ContainsKey(key); - } - } - - public IEnumerable Keys - { - get { lock (mutex) { return dictionary.Keys; } } - } - - public bool TryGetValue(string key, out object value) - { - lock (mutex) - { - return dictionary.TryGetValue(key, out value); - } - } - - public IEnumerable Values - { - get { lock (mutex) { return dictionary.Values; } } - } - - public object this[string key] - { - get { lock (mutex) { return dictionary[key]; } } - } - - public int Count - { - get { lock (mutex) { return dictionary.Count; } } - } - - public IEnumerator> GetEnumerator() - { - lock (mutex) - { - return dictionary.GetEnumerator(); - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - lock (mutex) - { - return dictionary.GetEnumerator(); - } - } - } - - private const string LeanCloudStorageFileName = "ApplicationSettings"; - private readonly TaskQueue taskQueue = new TaskQueue(); - private readonly Task fileTask; - private StorageDictionary storageDictionary; - - public StorageController(string fileNamePrefix) - { - fileTask = taskQueue.Enqueue(t => t.ContinueWith(_ => - { - return FileSystem.Current.LocalStorage.CreateFileAsync(string.Format("{0}_{1}", fileNamePrefix, LeanCloudStorageFileName), CreationCollisionOption.OpenIfExists); - }).Unwrap(), CancellationToken.None); - } - - internal StorageController(IFile file) - { - this.fileTask = Task.FromResult(file); - } - - public Task> LoadAsync() - { - return taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - if (storageDictionary != null) - { - return Task.FromResult>(storageDictionary); - } - - storageDictionary = new StorageDictionary(fileTask.Result); - return storageDictionary.LoadAsync().OnSuccess(__ => storageDictionary as IStorageDictionary); - }).Unwrap(); - }, CancellationToken.None); - } - - public Task> SaveAsync(IDictionary contents) - { - return taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - if (storageDictionary == null) - { - storageDictionary = new StorageDictionary(fileTask.Result); - } - - storageDictionary.Update(contents); - return storageDictionary.SaveAsync().OnSuccess(__ => storageDictionary as IStorageDictionary); - }).Unwrap(); - }, CancellationToken.None); - } - } -} diff --git a/Storage/Source/Internal/Storage/Unity/StorageController.cs b/Storage/Source/Internal/Storage/Unity/StorageController.cs deleted file mode 100644 index 432a760..0000000 --- a/Storage/Source/Internal/Storage/Unity/StorageController.cs +++ /dev/null @@ -1,250 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using UnityEngine; -using System.Threading; - -namespace LeanCloud.Storage.Internal -{ - /// - /// Implements `IStorageController` for PCL targets, based off of PCLStorage. - /// - public class StorageController : IStorageController - { - private const string LeanCloudStorageFileName = "LeanCloud.settings"; - - private TaskQueue taskQueue = new TaskQueue(); - private string settingsPath; - private StorageDictionary storageDictionary; - private bool isWebPlayer; - - private class StorageDictionary : IStorageDictionary - { - private object mutex; - private Dictionary dictionary; - private string settingsPath; - private bool isWebPlayer; - - public StorageDictionary(string settingsPath, bool isWebPlayer) - { - this.settingsPath = settingsPath; - this.isWebPlayer = isWebPlayer; - - mutex = new object(); - dictionary = new Dictionary(); - } - - internal Task SaveAsync() - { - string jsonEncoded; - lock (mutex) - { - jsonEncoded = Json.Encode(dictionary); - } - - if (this.isWebPlayer) - { - PlayerPrefs.SetString(LeanCloudStorageFileName, jsonEncoded); - PlayerPrefs.Save(); - } - else if (Application.platform == RuntimePlatform.tvOS) - { - Debug.Log("Running on TvOS, prefs cannot be saved."); - } - else - { - using (var fs = new FileStream(settingsPath, FileMode.Create, FileAccess.Write)) - { - using (var writer = new StreamWriter(fs)) - { - writer.Write(jsonEncoded); - } - } - } - - return Task.FromResult(null); - } - - internal Task LoadAsync() - { - string jsonString = null; - - try - { - if (this.isWebPlayer) - { - jsonString = PlayerPrefs.GetString(LeanCloudStorageFileName, null); - } - else if (Application.platform == RuntimePlatform.tvOS) - { - Debug.Log("Running on TvOS, prefs cannot be loaded."); - } - else - { - using (var fs = new FileStream(settingsPath, FileMode.Open, FileAccess.Read)) - { - var reader = new StreamReader(fs); - jsonString = reader.ReadToEnd(); - } - } - } - catch (Exception) - { - // Do nothing - } - - if (jsonString == null) - { - lock (mutex) - { - dictionary = new Dictionary(); - return Task.FromResult(null); - } - } - - Dictionary decoded = Json.Parse(jsonString) as Dictionary; - lock (mutex) - { - dictionary = decoded ?? new Dictionary(); - return Task.FromResult(null); - } - } - - internal void Update(IDictionary contents) - { - lock (mutex) - { - dictionary = contents.ToDictionary(p => p.Key, p => p.Value); - } - } - - public Task AddAsync(string key, object value) - { - lock (mutex) - { - dictionary[key] = value; - } - return SaveAsync(); - } - - public Task RemoveAsync(string key) - { - lock (mutex) - { - dictionary.Remove(key); - } - return SaveAsync(); - } - - public bool ContainsKey(string key) - { - lock (mutex) - { - return dictionary.ContainsKey(key); - } - } - - public IEnumerable Keys - { - get { lock (mutex) { return dictionary.Keys; } } - } - - public bool TryGetValue(string key, out object value) - { - lock (mutex) - { - return dictionary.TryGetValue(key, out value); - } - } - - public IEnumerable Values - { - get { lock (mutex) { return dictionary.Values; } } - } - - public object this[string key] - { - get { lock (mutex) { return dictionary[key]; } } - } - - public int Count - { - get { lock (mutex) { return dictionary.Count; } } - } - - public IEnumerator> GetEnumerator() - { - lock (mutex) - { - return dictionary.GetEnumerator(); - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - lock (mutex) - { - return dictionary.GetEnumerator(); - } - } - } - - public StorageController() - : this(Path.Combine(Application.persistentDataPath, LeanCloudStorageFileName), false) - { - - } - - public StorageController(String settingsPath) - : this(settingsPath, false) - { - - } - public StorageController(bool isWebPlayer, string fileNamePrefix) - : this(Path.Combine(Application.persistentDataPath, string.Format("{0}_{1}", fileNamePrefix, LeanCloudStorageFileName)), isWebPlayer) - { - - } - - public StorageController(string settingsPath, bool isWebPlayer) - { - this.settingsPath = settingsPath; - this.isWebPlayer = isWebPlayer; - } - - public Task> LoadAsync() - { - return taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - if (storageDictionary != null) - { - return Task.FromResult>(storageDictionary); - } - storageDictionary = new StorageDictionary(settingsPath, this.isWebPlayer); - return storageDictionary.LoadAsync().OnSuccess(__ => storageDictionary as IStorageDictionary); - }).Unwrap(); - }, CancellationToken.None); - } - - public Task> SaveAsync(IDictionary contents) - { - return taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - if (storageDictionary == null) - { - storageDictionary = new StorageDictionary(settingsPath, this.isWebPlayer); - } - - storageDictionary.Update(contents); - return storageDictionary.SaveAsync().OnSuccess(__ => storageDictionary as IStorageDictionary); - }).Unwrap(); - }, CancellationToken.None); - } - } -} diff --git a/Storage/Source/Internal/User/Controller/AVCurrentUserController.cs b/Storage/Source/Internal/User/Controller/AVCurrentUserController.cs deleted file mode 100644 index eb78961..0000000 --- a/Storage/Source/Internal/User/Controller/AVCurrentUserController.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Globalization; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal -{ - public class AVCurrentUserController : IAVCurrentUserController - { - private readonly object mutex = new object(); - private readonly TaskQueue taskQueue = new TaskQueue(); - - private IStorageController storageController; - - public AVCurrentUserController(IStorageController storageController) - { - this.storageController = storageController; - } - - private AVUser currentUser; - public AVUser CurrentUser - { - get - { - lock (mutex) - { - return currentUser; - } - } - set - { - lock (mutex) - { - currentUser = value; - } - } - } - - public Task SetAsync(AVUser user, CancellationToken cancellationToken) - { - Task saveTask = null; - if (user == null) - { - saveTask = storageController - .LoadAsync() - .OnSuccess(t => t.Result.RemoveAsync("CurrentUser")) - .Unwrap(); - } - else - { - var data = user.ServerDataToJSONObjectForSerialization(); - data["objectId"] = user.ObjectId; - if (user.CreatedAt != null) - { - data["createdAt"] = user.CreatedAt.Value.ToString(AVClient.DateFormatStrings.First(), - CultureInfo.InvariantCulture); - } - if (user.UpdatedAt != null) - { - data["updatedAt"] = user.UpdatedAt.Value.ToString(AVClient.DateFormatStrings.First(), - CultureInfo.InvariantCulture); - } - - saveTask = storageController - .LoadAsync() - .OnSuccess(t => t.Result.AddAsync("CurrentUser", Json.Encode(data))) - .Unwrap(); - } - CurrentUser = user; - - return saveTask; - } - - public Task GetAsync(CancellationToken cancellationToken) - { - AVUser cachedCurrent; - - lock (mutex) - { - cachedCurrent = CurrentUser; - } - - if (cachedCurrent != null) - { - return Task.FromResult(cachedCurrent); - } - - return storageController.LoadAsync().OnSuccess(t => - { - object temp; - t.Result.TryGetValue("CurrentUser", out temp); - var userDataString = temp as string; - AVUser user = null; - if (userDataString != null) - { - var userData = Json.Parse(userDataString) as IDictionary; - var state = AVObjectCoder.Instance.Decode(userData, AVDecoder.Instance); - user = AVObject.FromState(state, "_User"); - } - - CurrentUser = user; - return user; - }); - } - - public Task ExistsAsync(CancellationToken cancellationToken) - { - if (CurrentUser != null) - { - return Task.FromResult(true); - } - - return storageController.LoadAsync().OnSuccess(t => t.Result.ContainsKey("CurrentUser")); - } - - public bool IsCurrent(AVUser user) - { - lock (mutex) - { - return CurrentUser == user; - } - } - - public void ClearFromMemory() - { - CurrentUser = null; - } - - public void ClearFromDisk() - { - lock (mutex) - { - ClearFromMemory(); - - storageController.LoadAsync().OnSuccess(t => t.Result.RemoveAsync("CurrentUser")); - } - } - - public Task GetCurrentSessionTokenAsync(CancellationToken cancellationToken) - { - return GetAsync(cancellationToken).OnSuccess(t => - { - var user = t.Result; - return user == null ? null : user.SessionToken; - }); - } - - public Task LogOutAsync(CancellationToken cancellationToken) - { - return GetAsync(cancellationToken).OnSuccess(t => - { - ClearFromDisk(); - }); - } - } -} diff --git a/Storage/Source/Internal/User/Controller/AVUserController.cs b/Storage/Source/Internal/User/Controller/AVUserController.cs deleted file mode 100644 index c74f602..0000000 --- a/Storage/Source/Internal/User/Controller/AVUserController.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal -{ - public class AVUserController : IAVUserController - { - private readonly IAVCommandRunner commandRunner; - - public AVUserController(IAVCommandRunner commandRunner) - { - this.commandRunner = commandRunner; - } - - public Task SignUpAsync(IObjectState state, - IDictionary operations, - CancellationToken cancellationToken) - { - var objectJSON = AVObject.ToJSONObjectForSaving(operations); - - var command = new AVCommand("classes/_User", - method: "POST", - data: objectJSON); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - var serverState = AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); - serverState = serverState.MutatedClone(mutableClone => - { - mutableClone.IsNew = true; - }); - return serverState; - }); - } - - public Task LogInAsync(string username, string email, - string password, - CancellationToken cancellationToken) - { - var data = new Dictionary{ - { "password", password} - }; - if (username != null) { - data.Add("username", username); - } - if (email != null) { - data.Add("email", email); - } - - var command = new AVCommand("login", - method: "POST", - data: data); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - var serverState = AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); - serverState = serverState.MutatedClone(mutableClone => - { - mutableClone.IsNew = t.Result.Item1 == System.Net.HttpStatusCode.Created; - }); - return serverState; - }); - } - - public Task LogInAsync(string authType, - IDictionary data, - bool failOnNotExist, - CancellationToken cancellationToken) - { - var authData = new Dictionary(); - authData[authType] = data; - var path = failOnNotExist ? "users?failOnNotExist=true" : "users"; - var command = new AVCommand(path, - method: "POST", - data: new Dictionary { - { "authData", authData} - }); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - var serverState = AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); - serverState = serverState.MutatedClone(mutableClone => - { - mutableClone.IsNew = t.Result.Item1 == System.Net.HttpStatusCode.Created; - }); - return serverState; - }); - } - - public Task GetUserAsync(string sessionToken, CancellationToken cancellationToken) - { - var command = new AVCommand("users/me", - method: "GET", - sessionToken: sessionToken, - data: null); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); - }); - } - - public Task RequestPasswordResetAsync(string email, CancellationToken cancellationToken) - { - var command = new AVCommand("requestPasswordReset", - method: "POST", - data: new Dictionary { - { "email", email} - }); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken); - } - - public Task LogInWithParametersAsync(string relativeUrl, IDictionary data, - CancellationToken cancellationToken) - { - var command = new AVCommand(string.Format("{0}", relativeUrl), - method: "POST", - data: data); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - var serverState = AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); - serverState = serverState.MutatedClone(mutableClone => - { - mutableClone.IsNew = t.Result.Item1 == System.Net.HttpStatusCode.Created; - }); - return serverState; - }); - } - - public Task UpdatePasswordAsync(string userId, string sessionToken, string oldPassword, string newPassword, CancellationToken cancellationToken) - { - var command = new AVCommand(String.Format("users/{0}/updatePassword", userId), - method: "PUT", - sessionToken: sessionToken, - data: new Dictionary { - {"old_password", oldPassword}, - {"new_password", newPassword}, - }); - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken); - } - - public Task RefreshSessionTokenAsync(string userId, string sessionToken, - CancellationToken cancellationToken) - { - var command = new AVCommand(String.Format("users/{0}/refreshSessionToken", userId), - method: "PUT", - sessionToken: sessionToken, - data: null); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).OnSuccess(t => - { - var serverState = AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); - return serverState; - }); - } - } -} diff --git a/Storage/Source/Internal/User/Controller/IAVCurrentUserController.cs b/Storage/Source/Internal/User/Controller/IAVCurrentUserController.cs deleted file mode 100644 index c7664aa..0000000 --- a/Storage/Source/Internal/User/Controller/IAVCurrentUserController.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - public interface IAVCurrentUserController : IAVObjectCurrentController { - Task GetCurrentSessionTokenAsync(CancellationToken cancellationToken); - - Task LogOutAsync(CancellationToken cancellationToken); - } -} diff --git a/Storage/Source/Internal/User/Controller/IAVUserController.cs b/Storage/Source/Internal/User/Controller/IAVUserController.cs deleted file mode 100644 index 58966a7..0000000 --- a/Storage/Source/Internal/User/Controller/IAVUserController.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal -{ - public interface IAVUserController - { - Task SignUpAsync(IObjectState state, - IDictionary operations, - CancellationToken cancellationToken); - - Task LogInAsync(string username, - string email, - string password, - CancellationToken cancellationToken); - - Task LogInWithParametersAsync(string relativeUrl, - IDictionary data, - CancellationToken cancellationToken); - - Task LogInAsync(string authType, - IDictionary data, - bool failOnNotExist, - CancellationToken cancellationToken); - - Task GetUserAsync(string sessionToken, - CancellationToken cancellationToken); - - Task RequestPasswordResetAsync(string email, - CancellationToken cancellationToken); - - Task UpdatePasswordAsync(string usedId, string sessionToken, - string oldPassword, string newPassword, - CancellationToken cancellationToken); - - Task RefreshSessionTokenAsync(string userId, - string sessionToken, - CancellationToken cancellationToken); - } -} diff --git a/Storage/Source/Internal/Utilities/AVConfigExtensions.cs b/Storage/Source/Internal/Utilities/AVConfigExtensions.cs deleted file mode 100644 index 0df396d..0000000 --- a/Storage/Source/Internal/Utilities/AVConfigExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - /// - /// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc. - /// - /// These cannot be 'internal' anymore if we are fully modularizing things out, because - /// they are no longer a part of the same library, especially as we create things like - /// Installation inside push library. - /// - /// So this class contains a bunch of extension methods that can live inside another - /// namespace, which 'wrap' the intenral APIs that already exist. - /// - public static class AVConfigExtensions { - public static AVConfig Create(IDictionary fetchedConfig) { - return new AVConfig(fetchedConfig); - } - } -} diff --git a/Storage/Source/Internal/Utilities/AVFileExtensions.cs b/Storage/Source/Internal/Utilities/AVFileExtensions.cs deleted file mode 100644 index ae502d5..0000000 --- a/Storage/Source/Internal/Utilities/AVFileExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - /// - /// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc. - /// - /// These cannot be 'internal' anymore if we are fully modularizing things out, because - /// they are no longer a part of the same library, especially as we create things like - /// Installation inside push library. - /// - /// So this class contains a bunch of extension methods that can live inside another - /// namespace, which 'wrap' the intenral APIs that already exist. - /// - public static class AVFileExtensions { - public static AVFile Create(string name, Uri uri, string mimeType = null) { - return new AVFile(name, uri, mimeType); - } - } -} diff --git a/Storage/Source/Internal/Utilities/AVObjectExtensions.cs b/Storage/Source/Internal/Utilities/AVObjectExtensions.cs deleted file mode 100644 index 2147049..0000000 --- a/Storage/Source/Internal/Utilities/AVObjectExtensions.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using System.Linq; -using System.Globalization; -using System.ComponentModel; -using System.Collections; - -namespace LeanCloud.Storage.Internal -{ - /// - /// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc. - /// - /// These cannot be 'internal' anymore if we are fully modularizing things out, because - /// they are no longer a part of the same library, especially as we create things like - /// Installation inside push library. - /// - /// So this class contains a bunch of extension methods that can live inside another - /// namespace, which 'wrap' the intenral APIs that already exist. - /// - public static class AVObjectExtensions - { - public static T FromState(IObjectState state, string defaultClassName) where T : AVObject - { - return AVObject.FromState(state, defaultClassName); - } - - public static IObjectState GetState(this AVObject obj) - { - return obj.State; - } - - public static void HandleFetchResult(this AVObject obj, IObjectState serverState) - { - obj.HandleFetchResult(serverState); - } - - public static IDictionary GetCurrentOperations(this AVObject obj) - { - return obj.CurrentOperations; - } - - public static IDictionary Encode(this AVObject obj) - { - return PointerOrLocalIdEncoder.Instance.EncodeAVObject(obj, false); - } - - public static IEnumerable DeepTraversal(object root, bool traverseAVObjects = false, bool yieldRoot = false) - { - return AVObject.DeepTraversal(root, traverseAVObjects, yieldRoot); - } - - public static void SetIfDifferent(this AVObject obj, string key, T value) - { - obj.SetIfDifferent(key, value); - } - - public static IDictionary ServerDataToJSONObjectForSerialization(this AVObject obj) - { - return obj.ServerDataToJSONObjectForSerialization(); - } - - public static void Set(this AVObject obj, string key, object value) - { - obj.Set(key, value); - } - - public static void DisableHooks(this AVObject obj, IEnumerable hookKeys) - { - obj.Set("__ignore_hooks", hookKeys); - } - public static void DisableHook(this AVObject obj, string hookKey) - { - var newList = new List(); - if (obj.ContainsKey("__ignore_hooks")) - { - var hookKeys = obj.Get>("__ignore_hooks"); - newList = hookKeys.ToList(); - } - newList.Add(hookKey); - obj.DisableHooks(newList); - } - - public static void DisableAfterHook(this AVObject obj) - { - obj.DisableAfterSave(); - obj.DisableAfterUpdate(); - obj.DisableAfterDelete(); - } - - public static void DisableBeforeHook(this AVObject obj) - { - obj.DisableBeforeSave(); - obj.DisableBeforeDelete(); - obj.DisableBeforeUpdate(); - } - - public static void DisableBeforeSave(this AVObject obj) - { - obj.DisableHook("beforeSave"); - } - public static void DisableAfterSave(this AVObject obj) - { - obj.DisableHook("afterSave"); - } - public static void DisableBeforeUpdate(this AVObject obj) - { - obj.DisableHook("beforeUpdate"); - } - public static void DisableAfterUpdate(this AVObject obj) - { - obj.DisableHook("afterUpdate"); - } - public static void DisableBeforeDelete(this AVObject obj) - { - obj.DisableHook("beforeDelete"); - } - public static void DisableAfterDelete(this AVObject obj) - { - obj.DisableHook("afterDelete"); - } - - #region on property updated or changed or collection updated - - /// - /// On the property changed. - /// - /// Av object. - /// Property name. - /// Handler. - public static void OnPropertyChanged(this AVObject avObj, string propertyName, PropertyChangedEventHandler handler) - { - avObj.PropertyChanged += (sender, e) => - { - if (e.PropertyName == propertyName) - { - handler(sender, e); - } - }; - } - - /// - /// On the property updated. - /// - /// Av object. - /// Property name. - /// Handler. - public static void OnPropertyUpdated(this AVObject avObj, string propertyName, PropertyUpdatedEventHandler handler) - { - avObj.PropertyUpdated += (sender, e) => - { - if (e.PropertyName == propertyName) - { - handler(sender, e); - } - }; - } - - /// - /// On the property updated. - /// - /// Av object. - /// Property name. - /// Handler. - public static void OnPropertyUpdated(this AVObject avObj, string propertyName, Action handler) - { - avObj.OnPropertyUpdated(propertyName,(object sender, PropertyUpdatedEventArgs e) => - { - handler(e.OldValue, e.NewValue); - }); - } - - /// - /// On the collection property updated. - /// - /// Av object. - /// Property name. - /// Handler. - public static void OnCollectionPropertyUpdated(this AVObject avObj, string propertyName, CollectionPropertyUpdatedEventHandler handler) - { - avObj.CollectionPropertyUpdated += (sender, e) => - { - if (e.PropertyName == propertyName) - { - handler(sender, e); - } - }; - } - - /// - /// On the collection property added. - /// - /// Av object. - /// Property name. - /// Handler. - public static void OnCollectionPropertyAdded(this AVObject avObj, string propertyName, Action handler) - { - avObj.OnCollectionPropertyUpdated(propertyName, (sender, e) => - { - if (e.CollectionAction == NotifyCollectionUpdatedAction.Add) - { - handler(e.NewValues); - } - }); - } - - /// - /// On the collection property removed. - /// - /// Av object. - /// Property name. - /// Handler. - public static void OnCollectionPropertyRemoved(this AVObject avObj, string propertyName, Action handler) - { - avObj.OnCollectionPropertyUpdated(propertyName, (sender, e) => - { - if (e.CollectionAction == NotifyCollectionUpdatedAction.Remove) - { - handler(e.NewValues); - } - }); - } - #endregion - } -} diff --git a/Storage/Source/Internal/Utilities/AVQueryExtensions.cs b/Storage/Source/Internal/Utilities/AVQueryExtensions.cs deleted file mode 100644 index 56cdb2c..0000000 --- a/Storage/Source/Internal/Utilities/AVQueryExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - /// - /// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc. - /// - /// These cannot be 'internal' anymore if we are fully modularizing things out, because - /// they are no longer a part of the same library, especially as we create things like - /// Installation inside push library. - /// - /// So this class contains a bunch of extension methods that can live inside another - /// namespace, which 'wrap' the intenral APIs that already exist. - /// - public static class AVQueryExtensions { - public static string GetClassName(this AVQuery query) where T: AVObject { - return query.ClassName; - } - - public static IDictionary BuildParameters(this AVQuery query) where T: AVObject { - return query.BuildParameters(false); - } - - public static object GetConstraint(this AVQuery query, string key) where T : AVObject { - return query.GetConstraint(key); - } - } -} diff --git a/Storage/Source/Internal/Utilities/AVRelationExtensions.cs b/Storage/Source/Internal/Utilities/AVRelationExtensions.cs deleted file mode 100644 index c2d2c85..0000000 --- a/Storage/Source/Internal/Utilities/AVRelationExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - /// - /// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc. - /// - /// These cannot be 'internal' anymore if we are fully modularizing things out, because - /// they are no longer a part of the same library, especially as we create things like - /// Installation inside push library. - /// - /// So this class contains a bunch of extension methods that can live inside another - /// namespace, which 'wrap' the intenral APIs that already exist. - /// - public static class AVRelationExtensions { - public static AVRelation Create(AVObject parent, string childKey) where T : AVObject { - return new AVRelation(parent, childKey); - } - - public static AVRelation Create(AVObject parent, string childKey, string targetClassName) where T: AVObject { - return new AVRelation(parent, childKey, targetClassName); - } - - public static string GetTargetClassName(this AVRelation relation) where T : AVObject { - return relation.TargetClassName; - } - } -} diff --git a/Storage/Source/Internal/Utilities/AVSessionExtensions.cs b/Storage/Source/Internal/Utilities/AVSessionExtensions.cs deleted file mode 100644 index 40a3c3d..0000000 --- a/Storage/Source/Internal/Utilities/AVSessionExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - /// - /// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc. - /// - /// These cannot be 'internal' anymore if we are fully modularizing things out, because - /// they are no longer a part of the same library, especially as we create things like - /// Installation inside push library. - /// - /// So this class contains a bunch of extension methods that can live inside another - /// namespace, which 'wrap' the intenral APIs that already exist. - /// - public static class AVSessionExtensions { - public static Task UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) { - return AVSession.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken); - } - - public static Task RevokeAsync(string sessionToken, CancellationToken cancellationToken) { - return AVSession.RevokeAsync(sessionToken, cancellationToken); - } - } -} diff --git a/Storage/Source/Internal/Utilities/AVUserExtensions.cs b/Storage/Source/Internal/Utilities/AVUserExtensions.cs deleted file mode 100644 index 936dc37..0000000 --- a/Storage/Source/Internal/Utilities/AVUserExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal -{ - /// - /// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc. - /// - /// These cannot be 'internal' anymore if we are fully modularizing things out, because - /// they are no longer a part of the same library, especially as we create things like - /// Installation inside push library. - /// - /// So this class contains a bunch of extension methods that can live inside another - /// namespace, which 'wrap' the intenral APIs that already exist. - /// - public static class AVUserExtensions - { - - } -} diff --git a/Storage/Source/Internal/Utilities/FlexibleDictionaryWrapper.cs b/Storage/Source/Internal/Utilities/FlexibleDictionaryWrapper.cs deleted file mode 100644 index 5ee7d2c..0000000 --- a/Storage/Source/Internal/Utilities/FlexibleDictionaryWrapper.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using LeanCloud.Utilities; - -namespace LeanCloud.Storage.Internal { - /// - /// Provides a Dictionary implementation that can delegate to any other - /// dictionary, regardless of its value type. Used for coercion of - /// dictionaries when returning them to users. - /// - /// The resulting type of value in the dictionary. - /// The original type of value in the dictionary. - [Preserve(AllMembers = true, Conditional = false)] - public class FlexibleDictionaryWrapper : IDictionary { - private readonly IDictionary toWrap; - public FlexibleDictionaryWrapper(IDictionary toWrap) { - this.toWrap = toWrap; - } - - public void Add(string key, TOut value) { - toWrap.Add(key, (TIn)Conversion.ConvertTo(value)); - } - - public bool ContainsKey(string key) { - return toWrap.ContainsKey(key); - } - - public ICollection Keys { - get { return toWrap.Keys; } - } - - public bool Remove(string key) { - return toWrap.Remove(key); - } - - public bool TryGetValue(string key, out TOut value) { - TIn outValue; - bool result = toWrap.TryGetValue(key, out outValue); - value = (TOut)Conversion.ConvertTo(outValue); - return result; - } - - public ICollection Values { - get { - return toWrap.Values - .Select(item => (TOut)Conversion.ConvertTo(item)).ToList(); - } - } - - public TOut this[string key] { - get { - return (TOut)Conversion.ConvertTo(toWrap[key]); - } - set { - toWrap[key] = (TIn)Conversion.ConvertTo(value); - } - } - - public void Add(KeyValuePair item) { - toWrap.Add(new KeyValuePair(item.Key, - (TIn)Conversion.ConvertTo(item.Value))); - } - - public void Clear() { - toWrap.Clear(); - } - - public bool Contains(KeyValuePair item) { - return toWrap.Contains(new KeyValuePair(item.Key, - (TIn)Conversion.ConvertTo(item.Value))); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) { - var converted = from pair in toWrap - select new KeyValuePair(pair.Key, - (TOut)Conversion.ConvertTo(pair.Value)); - converted.ToList().CopyTo(array, arrayIndex); - } - - public int Count { - get { return toWrap.Count; } - } - - public bool IsReadOnly { - get { return toWrap.IsReadOnly; } - } - - public bool Remove(KeyValuePair item) { - return toWrap.Remove(new KeyValuePair(item.Key, - (TIn)Conversion.ConvertTo(item.Value))); - } - - public IEnumerator> GetEnumerator() { - foreach (var pair in toWrap) { - yield return new KeyValuePair(pair.Key, - (TOut)Conversion.ConvertTo(pair.Value)); - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - } -} diff --git a/Storage/Source/Internal/Utilities/FlexibleListWrapper.cs b/Storage/Source/Internal/Utilities/FlexibleListWrapper.cs deleted file mode 100644 index 3db78d6..0000000 --- a/Storage/Source/Internal/Utilities/FlexibleListWrapper.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using LeanCloud.Utilities; - -namespace LeanCloud.Storage.Internal { - /// - /// Provides a List implementation that can delegate to any other - /// list, regardless of its value type. Used for coercion of - /// lists when returning them to users. - /// - /// The resulting type of value in the list. - /// The original type of value in the list. - [Preserve(AllMembers = true, Conditional = false)] - public class FlexibleListWrapper : IList { - private IList toWrap; - public FlexibleListWrapper(IList toWrap) { - this.toWrap = toWrap; - } - - public int IndexOf(TOut item) { - return toWrap.IndexOf((TIn)Conversion.ConvertTo(item)); - } - - public void Insert(int index, TOut item) { - toWrap.Insert(index, (TIn)Conversion.ConvertTo(item)); - } - - public void RemoveAt(int index) { - toWrap.RemoveAt(index); - } - - public TOut this[int index] { - get { - return (TOut)Conversion.ConvertTo(toWrap[index]); - } - set { - toWrap[index] = (TIn)Conversion.ConvertTo(value); - } - } - - public void Add(TOut item) { - toWrap.Add((TIn)Conversion.ConvertTo(item)); - } - - public void Clear() { - toWrap.Clear(); - } - - public bool Contains(TOut item) { - return toWrap.Contains((TIn)Conversion.ConvertTo(item)); - } - - public void CopyTo(TOut[] array, int arrayIndex) { - toWrap.Select(item => (TOut)Conversion.ConvertTo(item)) - .ToList().CopyTo(array, arrayIndex); - } - - public int Count { - get { return toWrap.Count; } - } - - public bool IsReadOnly { - get { return toWrap.IsReadOnly; } - } - - public bool Remove(TOut item) { - return toWrap.Remove((TIn)Conversion.ConvertTo(item)); - } - - public IEnumerator GetEnumerator() { - foreach (var item in (IEnumerable)toWrap) { - yield return (TOut)Conversion.ConvertTo(item); - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - } -} diff --git a/Storage/Source/Internal/Utilities/IJsonConvertible.cs b/Storage/Source/Internal/Utilities/IJsonConvertible.cs deleted file mode 100644 index af29ef1..0000000 --- a/Storage/Source/Internal/Utilities/IJsonConvertible.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - /// - /// Represents an object that can be converted into JSON. - /// - public interface IJsonConvertible { - /// - /// Converts the object to a data structure that can be converted to JSON. - /// - /// An object to be JSONified. - IDictionary ToJSON(); - } -} diff --git a/Storage/Source/Internal/Utilities/IdentityEqualityComparer.cs b/Storage/Source/Internal/Utilities/IdentityEqualityComparer.cs deleted file mode 100644 index e25f818..0000000 --- a/Storage/Source/Internal/Utilities/IdentityEqualityComparer.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace LeanCloud.Storage.Internal -{ - /// - /// An equality comparer that uses the object identity (i.e. ReferenceEquals) - /// rather than .Equals, allowing identity to be used for checking equality in - /// ISets and IDictionaries. - /// - public class IdentityEqualityComparer : IEqualityComparer - { - public bool Equals(T x, T y) - { - return object.ReferenceEquals(x, y); - } - - public int GetHashCode(T obj) - { - return RuntimeHelpers.GetHashCode(obj); - } - } -} diff --git a/Storage/Source/Internal/Utilities/InternalExtensions.cs b/Storage/Source/Internal/Utilities/InternalExtensions.cs deleted file mode 100644 index e0228a5..0000000 --- a/Storage/Source/Internal/Utilities/InternalExtensions.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.ExceptionServices; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - /// - /// Provides helper methods that allow us to use terser code elsewhere. - /// - public static class InternalExtensions { - /// - /// Ensures a task (even null) is awaitable. - /// - /// - /// - /// - public static Task Safe(this Task task) { - return task ?? Task.FromResult(default(T)); - } - - /// - /// Ensures a task (even null) is awaitable. - /// - /// - /// - public static Task Safe(this Task task) { - return task ?? Task.FromResult(null); - } - - public delegate void PartialAccessor(ref T arg); - - public static TValue GetOrDefault(this IDictionary self, - TKey key, - TValue defaultValue) { - TValue value; - if (self.TryGetValue(key, out value)) { - return value; - } - return defaultValue; - } - - public static bool CollectionsEqual(this IEnumerable a, IEnumerable b) { - return Object.Equals(a, b) || - (a != null && b != null && - a.SequenceEqual(b)); - } - - public static Task OnSuccess(this Task task, - Func, TResult> continuation) { - return ((Task)task).OnSuccess(t => continuation((Task)t)); - } - - public static Task OnSuccess(this Task task, Action> continuation) { - return task.OnSuccess((Func, object>)(t => { - continuation(t); - return null; - })); - } - - public static Task OnSuccess(this Task task, - Func continuation) { - return task.ContinueWith(t => { - if (t.IsFaulted) { - var ex = t.Exception.Flatten(); - if (ex.InnerExceptions.Count == 1) { - ExceptionDispatchInfo.Capture(ex.InnerExceptions[0]).Throw(); - } else { - ExceptionDispatchInfo.Capture(ex).Throw(); - } - // Unreachable - return Task.FromResult(default(TResult)); - } else if (t.IsCanceled) { - var tcs = new TaskCompletionSource(); - tcs.SetCanceled(); - return tcs.Task; - } else { - return Task.FromResult(continuation(t)); - } - }).Unwrap(); - } - - public static Task OnSuccess(this Task task, Action continuation) { - return task.OnSuccess((Func)(t => { - continuation(t); - return null; - })); - } - - public static Task WhileAsync(Func> predicate, Func body) { - Func iterate = null; - iterate = () => { - return predicate().OnSuccess(t => { - if (!t.Result) { - return Task.FromResult(0); - } - return body().OnSuccess(_ => iterate()).Unwrap(); - }).Unwrap(); - }; - return iterate(); - } - } -} diff --git a/Storage/Source/Internal/Utilities/Json.cs b/Storage/Source/Internal/Utilities/Json.cs deleted file mode 100644 index 8babc7f..0000000 --- a/Storage/Source/Internal/Utilities/Json.cs +++ /dev/null @@ -1,554 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal -{ - /// - /// A simple recursive-descent JSON Parser based on the grammar defined at http://www.json.org - /// and http://tools.ietf.org/html/rfc4627 - /// - public class Json - { - /// - /// Place at the start of a regex to force the match to begin wherever the search starts (i.e. - /// anchored at the index of the first character of the search, even when that search starts - /// in the middle of the string). - /// - private static readonly string startOfString = "\\G"; - private static readonly char startObject = '{'; - private static readonly char endObject = '}'; - private static readonly char startArray = '['; - private static readonly char endArray = ']'; - private static readonly char valueSeparator = ','; - private static readonly char nameSeparator = ':'; - private static readonly char[] falseValue = "false".ToCharArray(); - private static readonly char[] trueValue = "true".ToCharArray(); - private static readonly char[] nullValue = "null".ToCharArray(); - private static readonly Regex numberValue = new Regex(startOfString + - @"-?(?:0|[1-9]\d*)(?\.\d+)?(?(?:e|E)(?:-|\+)?\d+)?"); - private static readonly Regex stringValue = new Regex(startOfString + - "\"(?(?:[^\\\\\"]|(?\\\\(?:[\\\\\"/bfnrt]|u[0-9a-fA-F]{4})))*)\"", - RegexOptions.Multiline); - - private static readonly Regex escapePattern = new Regex("\\\\|\"|[\u0000-\u001F]"); - - private class JsonStringParser - { - public string Input { get; private set; } - - public char[] InputAsArray { get; private set; } - - private int currentIndex; - public int CurrentIndex - { - get - { - return currentIndex; - } - } - - public void Skip(int skip) - { - currentIndex += skip; - } - - public JsonStringParser(string input) - { - Input = input; - InputAsArray = input.ToCharArray(); - } - - /// - /// Parses JSON object syntax (e.g. '{}') - /// - internal bool AVObject(out object output) - { - output = null; - int initialCurrentIndex = CurrentIndex; - if (!Accept(startObject)) - { - return false; - } - var dict = new Dictionary(); - while (true) - { - object pairValue; - if (!ParseMember(out pairValue)) - { - break; - } - var pair = pairValue as Tuple; - dict[pair.Item1] = pair.Item2; - if (!Accept(valueSeparator)) - { - break; - } - } - if (!Accept(endObject)) - { - return false; - } - output = dict; - return true; - } - - /// - /// Parses JSON member syntax (e.g. '"keyname" : null') - /// - private bool ParseMember(out object output) - { - output = null; - object key; - if (!ParseString(out key)) - { - return false; - } - if (!Accept(nameSeparator)) - { - return false; - } - object value; - if (!ParseValue(out value)) - { - return false; - } - output = new Tuple((string)key, value); - return true; - } - - /// - /// Parses JSON array syntax (e.g. '[]') - /// - internal bool ParseArray(out object output) - { - output = null; - if (!Accept(startArray)) - { - return false; - } - var list = new List(); - while (true) - { - object value; - if (!ParseValue(out value)) - { - break; - } - list.Add(value); - if (!Accept(valueSeparator)) - { - break; - } - } - if (!Accept(endArray)) - { - return false; - } - output = list; - return true; - } - - /// - /// Parses a value (i.e. the right-hand side of an object member assignment or - /// an element in an array) - /// - private bool ParseValue(out object output) - { - if (Accept(falseValue)) - { - output = false; - return true; - } - else if (Accept(nullValue)) - { - output = null; - return true; - } - else if (Accept(trueValue)) - { - output = true; - return true; - } - return AVObject(out output) || - ParseArray(out output) || - ParseNumber(out output) || - ParseString(out output); - } - - /// - /// Parses a JSON string (e.g. '"foo\u1234bar\n"') - /// - private bool ParseString(out object output) - { - output = null; - Match m; - if (!Accept(stringValue, out m)) - { - return false; - } - // handle escapes: - int offset = 0; - var contentCapture = m.Groups["content"]; - var builder = new StringBuilder(contentCapture.Value); - foreach (Capture escape in m.Groups["escape"].Captures) - { - int index = (escape.Index - contentCapture.Index) - offset; - offset += escape.Length - 1; - builder.Remove(index + 1, escape.Length - 1); - switch (escape.Value[1]) - { - case '\"': - builder[index] = '\"'; - break; - case '\\': - builder[index] = '\\'; - break; - case '/': - builder[index] = '/'; - break; - case 'b': - builder[index] = '\b'; - break; - case 'f': - builder[index] = '\f'; - break; - case 'n': - builder[index] = '\n'; - break; - case 'r': - builder[index] = '\r'; - break; - case 't': - builder[index] = '\t'; - break; - case 'u': - builder[index] = (char)ushort.Parse(escape.Value.Substring(2), NumberStyles.AllowHexSpecifier); - break; - default: - throw new ArgumentException("Unexpected escape character in string: " + escape.Value); - } - } - output = builder.ToString(); - return true; - } - - /// - /// Parses a number. Returns a long if the number is an integer or has an exponent, - /// otherwise returns a double. - /// - private bool ParseNumber(out object output) - { - output = null; - Match m; - if (!Accept(numberValue, out m)) - { - return false; - } - if (m.Groups["frac"].Length > 0 || m.Groups["exp"].Length > 0) - { - // It's a double. - output = double.Parse(m.Value, CultureInfo.InvariantCulture); - return true; - } - else - { - int temp = 0; - if (int.TryParse(m.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out temp)) - { - output = temp; - return true; - } - output = long.Parse(m.Value, CultureInfo.InvariantCulture); - return true; - } - } - - /// - /// Matches the string to a regex, consuming part of the string and returning the match. - /// - private bool Accept(Regex matcher, out Match match) - { - match = matcher.Match(Input, CurrentIndex); - if (match.Success) - { - Skip(match.Length); - } - return match.Success; - } - - /// - /// Find the first occurrences of a character, consuming part of the string. - /// - private bool Accept(char condition) - { - int step = 0; - int strLen = InputAsArray.Length; - int currentStep = currentIndex; - char currentChar; - - // Remove whitespace - while (currentStep < strLen && - ((currentChar = InputAsArray[currentStep]) == ' ' || - currentChar == '\r' || - currentChar == '\t' || - currentChar == '\n')) - { - ++step; - ++currentStep; - } - - bool match = (currentStep < strLen) && (InputAsArray[currentStep] == condition); - if (match) - { - ++step; - ++currentStep; - - // Remove whitespace - while (currentStep < strLen && - ((currentChar = InputAsArray[currentStep]) == ' ' || - currentChar == '\r' || - currentChar == '\t' || - currentChar == '\n')) - { - ++step; - ++currentStep; - } - - Skip(step); - } - return match; - } - - /// - /// Find the first occurrences of a string, consuming part of the string. - /// - private bool Accept(char[] condition) - { - int step = 0; - int strLen = InputAsArray.Length; - int currentStep = currentIndex; - char currentChar; - - // Remove whitespace - while (currentStep < strLen && - ((currentChar = InputAsArray[currentStep]) == ' ' || - currentChar == '\r' || - currentChar == '\t' || - currentChar == '\n')) - { - ++step; - ++currentStep; - } - - bool strMatch = true; - for (int i = 0; currentStep < strLen && i < condition.Length; ++i, ++currentStep) - { - if (InputAsArray[currentStep] != condition[i]) - { - strMatch = false; - break; - } - } - - bool match = (currentStep < strLen) && strMatch; - if (match) - { - Skip(step + condition.Length); - } - return match; - } - } - - /// - /// Parses a JSON-text as defined in http://tools.ietf.org/html/rfc4627, returning an - /// IDictionary<string, object> or an IList<object> depending on whether - /// the value was an array or dictionary. Nested objects also match these types. - /// - public static object Parse(string input) - { - object output; - input = input.Trim(); - JsonStringParser parser = new JsonStringParser(input); - - if ((parser.AVObject(out output) || - parser.ParseArray(out output)) && - parser.CurrentIndex == input.Length) - { - return output; - } - throw new ArgumentException("Input JSON was invalid."); - } - - /// - /// Encodes a dictionary into a JSON string. Supports values that are - /// IDictionary<string, object>, IList<object>, strings, - /// nulls, and any of the primitive types. - /// - public static string Encode(IDictionary dict) - { - if (dict == null) - { - throw new ArgumentNullException(); - } - if (dict.Count == 0) - { - return "{}"; - } - var builder = new StringBuilder("{"); - foreach (var pair in dict) - { - builder.Append(Encode(pair.Key)); - builder.Append(":"); - builder.Append(Encode(pair.Value)); - builder.Append(","); - } - builder[builder.Length - 1] = '}'; - return builder.ToString(); - } - - /// - /// Encodes a list into a JSON string. Supports values that are - /// IDictionary<string, object>, IList<object>, strings, - /// nulls, and any of the primitive types. - /// - public static string Encode(IList list) - { - if (list == null) - { - throw new ArgumentNullException(); - } - if (list.Count == 0) - { - return "[]"; - } - var builder = new StringBuilder("["); - foreach (var item in list) - { - builder.Append(Encode(item)); - builder.Append(","); - } - builder[builder.Length - 1] = ']'; - return builder.ToString(); - } - - public static string Encode(IList strList) - { - if (strList == null) - { - throw new ArgumentNullException(); - } - if (strList.Count == 0) - { - return "[]"; - } - StringBuilder stringBuilder = new StringBuilder("["); - foreach (object obj in strList) - { - stringBuilder.Append(Json.Encode(obj)); - stringBuilder.Append(","); - } - stringBuilder[stringBuilder.Length - 1] = ']'; - return stringBuilder.ToString(); - } - - public static string Encode(IList> dicList) - { - if (dicList == null) - { - throw new ArgumentNullException(); - } - if (dicList.Count == 0) - { - return "[]"; - } - StringBuilder stringBuilder = new StringBuilder("["); - foreach (object obj in dicList) - { - stringBuilder.Append(Json.Encode(obj)); - stringBuilder.Append(","); - } - stringBuilder[stringBuilder.Length - 1] = ']'; - return stringBuilder.ToString(); - } - - /// - /// Encodes an object into a JSON string. - /// - public static string Encode(object obj) - { - var dict = obj as IDictionary; - if (dict != null) - { - return Encode(dict); - } - var list = obj as IList; - if (list != null) - { - return Encode(list); - } - var dicList = obj as IList>; - if (dicList != null) - { - return Encode(dicList); - } - var strLists = obj as IList; - if (strLists != null) - { - return Encode(strLists); - } - var str = obj as string; - if (str != null) - { - str = escapePattern.Replace(str, m => - { - switch (m.Value[0]) - { - case '\\': - return "\\\\"; - case '\"': - return "\\\""; - case '\b': - return "\\b"; - case '\f': - return "\\f"; - case '\n': - return "\\n"; - case '\r': - return "\\r"; - case '\t': - return "\\t"; - default: - return "\\u" + ((ushort)m.Value[0]).ToString("x4"); - } - }); - return "\"" + str + "\""; - } - if (obj == null) - { - return "null"; - } - if (obj is bool) - { - if ((bool)obj) - { - return "true"; - } - else - { - return "false"; - } - } - if (!obj.GetType().GetTypeInfo().IsPrimitive) - { - throw new ArgumentException("Unable to encode objects of type " + obj.GetType()); - } - return Convert.ToString(obj, CultureInfo.InvariantCulture); - } - } -} diff --git a/Storage/Source/Internal/Utilities/LockSet.cs b/Storage/Source/Internal/Utilities/LockSet.cs deleted file mode 100644 index 65e188c..0000000 --- a/Storage/Source/Internal/Utilities/LockSet.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - public class LockSet { - private static readonly ConditionalWeakTable stableIds = - new ConditionalWeakTable(); - private static long nextStableId = 0; - - private readonly IEnumerable mutexes; - - public LockSet(IEnumerable mutexes) { - this.mutexes = (from mutex in mutexes - orderby GetStableId(mutex) - select mutex).ToList(); - } - - public void Enter() { - foreach (var mutex in mutexes) { - Monitor.Enter(mutex); - } - } - - public void Exit() { - foreach (var mutex in mutexes) { - Monitor.Exit(mutex); - } - } - - private static IComparable GetStableId(object mutex) { - lock (stableIds) { - return stableIds.GetValue(mutex, k => nextStableId++); - } - } - } -} diff --git a/Storage/Source/Internal/Utilities/ReflectionHelpers.cs b/Storage/Source/Internal/Utilities/ReflectionHelpers.cs deleted file mode 100644 index 55c4833..0000000 --- a/Storage/Source/Internal/Utilities/ReflectionHelpers.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using System.Reflection; -using System.Collections.Generic; -using System.Linq; - -namespace LeanCloud.Storage.Internal -{ - public static class ReflectionHelpers - { - public static IEnumerable GetProperties(Type type) - { -#if MONO || UNITY - return type.GetProperties(); -#else - return type.GetRuntimeProperties(); -#endif - } - - public static MethodInfo GetMethod(Type type, string name, Type[] parameters) - { -#if MONO || UNITY - return type.GetMethod(name, parameters); -#else - return type.GetRuntimeMethod(name, parameters); -#endif - } - - public static bool IsPrimitive(Type type) - { -#if MONO || UNITY - return type.IsPrimitive; -#else - return type.GetTypeInfo().IsPrimitive; -#endif - } - - public static IEnumerable GetInterfaces(Type type) - { -#if MONO || UNITY - return type.GetInterfaces(); -#else - return type.GetTypeInfo().ImplementedInterfaces; -#endif - } - - public static bool IsConstructedGenericType(Type type) - { -#if UNITY - return type.IsGenericType && !type.IsGenericTypeDefinition; -#else - return type.IsConstructedGenericType; -#endif - } - - public static IEnumerable GetConstructors(Type type) - { -#if UNITY - BindingFlags searchFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - return type.GetConstructors(searchFlags); -#else - return type.GetTypeInfo().DeclaredConstructors - .Where(c => (c.Attributes & MethodAttributes.Static) == 0); -#endif - } - - public static Type[] GetGenericTypeArguments(Type type) - { -#if UNITY - return type.GetGenericArguments(); -#else - return type.GenericTypeArguments; -#endif - } - - public static PropertyInfo GetProperty(Type type, string name) - { -#if MONO || UNITY - return type.GetProperty(name); -#else - return type.GetRuntimeProperty(name); -#endif - } - - /// - /// This method helps simplify the process of getting a constructor for a type. - /// A method like this exists in .NET but is not allowed in a Portable Class Library, - /// so we've built our own. - /// - /// - /// - /// - public static ConstructorInfo FindConstructor(this Type self, params Type[] parameterTypes) - { - var constructors = - from constructor in GetConstructors(self) - let parameters = constructor.GetParameters() - let types = from p in parameters select p.ParameterType - where types.SequenceEqual(parameterTypes) - select constructor; - return constructors.SingleOrDefault(); - } - - public static bool IsNullable(Type t) - { - bool isGeneric; -#if UNITY - isGeneric = t.IsGenericType && !t.IsGenericTypeDefinition; -#else - isGeneric = t.IsConstructedGenericType; -#endif - return isGeneric && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)); - } - - public static IEnumerable GetCustomAttributes(this Assembly assembly) where T : Attribute - { -#if UNITY - return assembly.GetCustomAttributes(typeof(T), false).Select(attr => attr as T); -#else - return CustomAttributeExtensions.GetCustomAttributes(assembly); -#endif - } - } -} diff --git a/Storage/Source/Internal/Utilities/SynchronizedEventHandler.cs b/Storage/Source/Internal/Utilities/SynchronizedEventHandler.cs deleted file mode 100644 index d94f12a..0000000 --- a/Storage/Source/Internal/Utilities/SynchronizedEventHandler.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - /// - /// Represents an event handler that calls back from the synchronization context - /// that subscribed. - /// Should look like an EventArgs, but may not inherit EventArgs if T is implemented by the Windows team. - /// - public class SynchronizedEventHandler { - private LinkedList> delegates = - new LinkedList>(); - public void Add(Delegate del) { - lock (delegates) { - TaskFactory factory; - if (SynchronizationContext.Current != null) { - factory = - new TaskFactory(CancellationToken.None, - TaskCreationOptions.None, - TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.FromCurrentSynchronizationContext()); - } else { - factory = Task.Factory; - } - foreach (var d in del.GetInvocationList()) { - delegates.AddLast(new Tuple(d, factory)); - } - } - } - - public void Remove(Delegate del) { - lock (delegates) { - if (delegates.Count == 0) { - return; - } - foreach (var d in del.GetInvocationList()) { - var node = delegates.First; - while (node != null) { - if (node.Value.Item1 == d) { - delegates.Remove(node); - break; - } - node = node.Next; - } - } - } - } - - public Task Invoke(object sender, T args) { - IEnumerable> toInvoke; - var toContinue = new[] { Task.FromResult(0) }; - lock (delegates) { - toInvoke = delegates.ToList(); - } - var invocations = toInvoke - .Select(p => p.Item2.ContinueWhenAll(toContinue, - _ => p.Item1.DynamicInvoke(sender, args))) - .ToList(); - return Task.WhenAll(invocations); - } - } -} diff --git a/Storage/Source/Internal/Utilities/TaskQueue.cs b/Storage/Source/Internal/Utilities/TaskQueue.cs deleted file mode 100644 index 4f1ee25..0000000 --- a/Storage/Source/Internal/Utilities/TaskQueue.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - /// - /// A helper class for enqueuing tasks - /// - public class TaskQueue { - /// - /// We only need to keep the tail of the queue. Cancelled tasks will - /// just complete normally/immediately when their turn arrives. - /// - private Task tail; - private readonly object mutex = new object(); - - /// - /// Gets a cancellable task that can be safely awaited and is dependent - /// on the current tail of the queue. This essentially gives us a proxy - /// for the tail end of the queue whose awaiting can be cancelled. - /// - /// A cancellation token that cancels - /// the task even if the task is still in the queue. This allows the - /// running task to return immediately without breaking the dependency - /// chain. It also ensures that errors do not propagate. - /// A new task that should be awaited by enqueued tasks. - private Task GetTaskToAwait(CancellationToken cancellationToken) { - lock (mutex) { - Task toAwait = tail ?? Task.FromResult(true); - return toAwait.ContinueWith(task => { }, cancellationToken); - } - } - - /// - /// Enqueues a task created by . If the task is - /// cancellable (or should be able to be cancelled while it is waiting in the - /// queue), pass a cancellationToken. - /// - /// The type of task. - /// A function given a task to await once state is - /// snapshotted (e.g. after capturing session tokens at the time of the save call). - /// Awaiting this task will wait for the created task's turn in the queue. - /// A cancellation token that can be used to - /// cancel waiting in the queue. - /// The task created by the taskStart function. - public T Enqueue(Func taskStart, CancellationToken cancellationToken) - where T : Task { - Task oldTail; - T task; - lock (mutex) { - oldTail = this.tail ?? Task.FromResult(true); - // The task created by taskStart is responsible for waiting the - // task passed to it before doing its work (this gives it an opportunity - // to do startup work or save state before waiting for its turn in the queue - task = taskStart(GetTaskToAwait(cancellationToken)); - - // The tail task should be dependent on the old tail as well as the newly-created - // task. This prevents cancellation of the new task from causing the queue to run - // out of order. - this.tail = Task.WhenAll(oldTail, task); - } - return task; - } - - public object Mutex { get { return mutex; } } - } -} diff --git a/Storage/Source/Internal/Utilities/XamarinAttributes.cs b/Storage/Source/Internal/Utilities/XamarinAttributes.cs deleted file mode 100644 index 219cb1a..0000000 --- a/Storage/Source/Internal/Utilities/XamarinAttributes.cs +++ /dev/null @@ -1,426 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; - -namespace LeanCloud.Storage.Internal { - /// - /// A reimplementation of Xamarin's PreserveAttribute. - /// This allows us to support AOT and linking for Xamarin platforms. - /// - [AttributeUsage(AttributeTargets.All)] - internal class PreserveAttribute : Attribute { - public bool AllMembers; - public bool Conditional; - } - - [AttributeUsage(AttributeTargets.All)] - internal class LinkerSafeAttribute : Attribute { - public LinkerSafeAttribute() { } - } - - [Preserve(AllMembers = true)] - internal class PreserveWrapperTypes { - /// - /// Exists to ensure that generic types are AOT-compiled for the conversions we support. - /// Any new value types that we add support for will need to be registered here. - /// The method itself is never called, but by virtue of the Preserve attribute being set - /// on the class, these types will be AOT-compiled. - /// - /// This also applies to Unity. - /// - private static List CreateWrapperTypes() { - return new List { - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - (Action)(() => AVCloud.CallFunctionAsync(null, null, null,CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null,null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null,null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null,null ,CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null,null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null,null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null,null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null,null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null, null,CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null,null ,CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null,null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null,null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null,null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync>(null, null,null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync>(null, null,null, CancellationToken.None)), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - }; - } - } -} diff --git a/Storage/Source/Public/AVACL.cs b/Storage/Source/Public/AVACL.cs deleted file mode 100644 index 22544c4..0000000 --- a/Storage/Source/Public/AVACL.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using LeanCloud.Storage.Internal; - -namespace LeanCloud { - /// - /// A AVACL is used to control which users and roles can access or modify a particular object. Each - /// can have its own AVACL. You can grant read and write permissions - /// separately to specific users, to groups of users that belong to roles, or you can grant permissions - /// to "the public" so that, for example, any user could read a particular object but only a particular - /// set of users could write to that object. - /// - public class AVACL : IJsonConvertible { - private enum AccessKind { - Read, - Write - } - private const string publicName = "*"; - private readonly ICollection readers = new HashSet(); - private readonly ICollection writers = new HashSet(); - - internal AVACL(IDictionary jsonObject) { - readers = new HashSet(from pair in jsonObject - where ((IDictionary)pair.Value).ContainsKey("read") - select pair.Key); - writers = new HashSet(from pair in jsonObject - where ((IDictionary)pair.Value).ContainsKey("write") - select pair.Key); - } - - /// - /// Creates an ACL with no permissions granted. - /// - public AVACL() { - } - - /// - /// Creates an ACL where only the provided user has access. - /// - /// The only user that can read or write objects governed by this ACL. - public AVACL(AVUser owner) { - SetReadAccess(owner, true); - SetWriteAccess(owner, true); - } - - IDictionary IJsonConvertible.ToJSON() { - var result = new Dictionary(); - foreach (var user in readers.Union(writers)) { - var userPermissions = new Dictionary(); - if (readers.Contains(user)) { - userPermissions["read"] = true; - } - if (writers.Contains(user)) { - userPermissions["write"] = true; - } - result[user] = userPermissions; - } - return result; - } - - private void SetAccess(AccessKind kind, string userId, bool allowed) { - if (userId == null) { - throw new ArgumentException("Cannot set access for an unsaved user or role."); - } - ICollection target = null; - switch (kind) { - case AccessKind.Read: - target = readers; - break; - case AccessKind.Write: - target = writers; - break; - default: - throw new NotImplementedException("Unknown AccessKind"); - } - if (allowed) { - target.Add(userId); - } else { - target.Remove(userId); - } - } - - private bool GetAccess(AccessKind kind, string userId) { - if (userId == null) { - throw new ArgumentException("Cannot get access for an unsaved user or role."); - } - switch (kind) { - case AccessKind.Read: - return readers.Contains(userId); - case AccessKind.Write: - return writers.Contains(userId); - default: - throw new NotImplementedException("Unknown AccessKind"); - } - } - - /// - /// Gets or sets whether the public is allowed to read this object. - /// - public bool PublicReadAccess { - get { - return GetAccess(AccessKind.Read, publicName); - } - set { - SetAccess(AccessKind.Read, publicName, value); - } - } - - /// - /// Gets or sets whether the public is allowed to write this object. - /// - public bool PublicWriteAccess { - get { - return GetAccess(AccessKind.Write, publicName); - } - set { - SetAccess(AccessKind.Write, publicName, value); - } - } - - /// - /// Sets whether the given user id is allowed to read this object. - /// - /// The objectId of the user. - /// Whether the user has permission. - public void SetReadAccess(string userId, bool allowed) { - SetAccess(AccessKind.Read, userId, allowed); - } - - /// - /// Sets whether the given user is allowed to read this object. - /// - /// The user. - /// Whether the user has permission. - public void SetReadAccess(AVUser user, bool allowed) { - SetReadAccess(user.ObjectId, allowed); - } - - /// - /// Sets whether the given user id is allowed to write this object. - /// - /// The objectId of the user. - /// Whether the user has permission. - public void SetWriteAccess(string userId, bool allowed) { - SetAccess(AccessKind.Write, userId, allowed); - } - - /// - /// Sets whether the given user is allowed to write this object. - /// - /// The user. - /// Whether the user has permission. - public void SetWriteAccess(AVUser user, bool allowed) { - SetWriteAccess(user.ObjectId, allowed); - } - - /// - /// Gets whether the given user id is *explicitly* allowed to read this object. - /// Even if this returns false, the user may still be able to read it if - /// PublicReadAccess is true or a role that the user belongs to has read access. - /// - /// The user objectId to check. - /// Whether the user has access. - public bool GetReadAccess(string userId) { - return GetAccess(AccessKind.Read, userId); - } - - /// - /// Gets whether the given user is *explicitly* allowed to read this object. - /// Even if this returns false, the user may still be able to read it if - /// PublicReadAccess is true or a role that the user belongs to has read access. - /// - /// The user to check. - /// Whether the user has access. - public bool GetReadAccess(AVUser user) { - return GetReadAccess(user.ObjectId); - } - - /// - /// Gets whether the given user id is *explicitly* allowed to write this object. - /// Even if this returns false, the user may still be able to write it if - /// PublicReadAccess is true or a role that the user belongs to has write access. - /// - /// The user objectId to check. - /// Whether the user has access. - public bool GetWriteAccess(string userId) { - return GetAccess(AccessKind.Write, userId); - } - - /// - /// Gets whether the given user is *explicitly* allowed to write this object. - /// Even if this returns false, the user may still be able to write it if - /// PublicReadAccess is true or a role that the user belongs to has write access. - /// - /// The user to check. - /// Whether the user has access. - public bool GetWriteAccess(AVUser user) { - return GetWriteAccess(user.ObjectId); - } - - /// - /// Sets whether users belonging to the role with the given - /// are allowed to read this object. - /// - /// The name of the role. - /// Whether the role has access. - public void SetRoleReadAccess(string roleName, bool allowed) { - SetAccess(AccessKind.Read, "role:" + roleName, allowed); - } - - /// - /// Sets whether users belonging to the given role are allowed to read this object. - /// - /// The role. - /// Whether the role has access. - public void SetRoleReadAccess(AVRole role, bool allowed) { - SetRoleReadAccess(role.Name, allowed); - } - - /// - /// Gets whether users belonging to the role with the given - /// are allowed to read this object. Even if this returns false, the role may still be - /// able to read it if a parent role has read access. - /// - /// The name of the role. - /// Whether the role has access. - public bool GetRoleReadAccess(string roleName) { - return GetAccess(AccessKind.Read, "role:" + roleName); - } - - /// - /// Gets whether users belonging to the role are allowed to read this object. - /// Even if this returns false, the role may still be able to read it if a - /// parent role has read access. - /// - /// The name of the role. - /// Whether the role has access. - public bool GetRoleReadAccess(AVRole role) { - return GetRoleReadAccess(role.Name); - } - - /// - /// Sets whether users belonging to the role with the given - /// are allowed to write this object. - /// - /// The name of the role. - /// Whether the role has access. - public void SetRoleWriteAccess(string roleName, bool allowed) { - SetAccess(AccessKind.Write, "role:" + roleName, allowed); - } - - /// - /// Sets whether users belonging to the given role are allowed to write this object. - /// - /// The role. - /// Whether the role has access. - public void SetRoleWriteAccess(AVRole role, bool allowed) { - SetRoleWriteAccess(role.Name, allowed); - } - - /// - /// Gets whether users belonging to the role with the given - /// are allowed to write this object. Even if this returns false, the role may still be - /// able to write it if a parent role has write access. - /// - /// The name of the role. - /// Whether the role has access. - public bool GetRoleWriteAccess(string roleName) { - return GetAccess(AccessKind.Write, "role:" + roleName); - } - - /// - /// Gets whether users belonging to the role are allowed to write this object. - /// Even if this returns false, the role may still be able to write it if a - /// parent role has write access. - /// - /// The name of the role. - /// Whether the role has access. - public bool GetRoleWriteAccess(AVRole role) { - return GetRoleWriteAccess(role.Name); - } - } -} diff --git a/Storage/Source/Public/AVClassNameAttribute.cs b/Storage/Source/Public/AVClassNameAttribute.cs deleted file mode 100644 index aa82d0b..0000000 --- a/Storage/Source/Public/AVClassNameAttribute.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud -{ - /// - /// Defines the class name for a subclass of AVObject. - /// - [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] - public sealed class AVClassNameAttribute : Attribute - { - /// - /// Constructs a new AVClassName attribute. - /// - /// The class name to associate with the AVObject subclass. - public AVClassNameAttribute(string className) - { - this.ClassName = className; - } - - /// - /// Gets the class name to associate with the AVObject subclass. - /// - public string ClassName { get; private set; } - } -} diff --git a/Storage/Source/Public/AVClient.cs b/Storage/Source/Public/AVClient.cs deleted file mode 100644 index d49f76a..0000000 --- a/Storage/Source/Public/AVClient.cs +++ /dev/null @@ -1,506 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud -{ - /// - /// AVClient contains static functions that handle global - /// configuration for the LeanCloud library. - /// - public static partial class AVClient - { - public static readonly string[] DateFormatStrings = { - // Official ISO format - "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'", - // It's possible that the string converter server-side may trim trailing zeroes, - // so these two formats cover ourselves from that. - "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'ff'Z'", - "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'f'Z'", - }; - - /// - /// Represents the configuration of the LeanCloud SDK. - /// - public struct Configuration - { - /// - /// 与 SDK 通讯的云端节点 - /// - public enum AVRegion - { - /// - /// 默认值,LeanCloud 华北节点,同 Public_North_China - /// - [Obsolete("please use Configuration.AVRegion.Public_North_China")] - Public_CN = 0, - - /// - /// 默认值,华北公有云节点,同 Public_CN - /// - Public_North_China = 0, - - /// - /// LeanCloud 北美区公有云节点,同 Public_North_America - /// - [Obsolete("please use Configuration.AVRegion.Public_North_America")] - Public_US = 1, - /// - /// LeanCloud 北美区公有云节点,同 Public_US - /// - Public_North_America = 1, - - /// - /// 华东公有云节点,同 Public_East_China - /// - [Obsolete("please use Configuration.AVRegion.Public_East_China")] - Vendor_Tencent = 2, - - /// - /// 华东公有云节点,同 Vendor_Tencent - /// - Public_East_China = 2, - } - - /// - /// In the event that you would like to use the LeanCloud SDK - /// from a completely portable project, with no platform-specific library required, - /// to get full access to all of our features available on LeanCloud.com - /// (A/B testing, slow queries, etc.), you must set the values of this struct - /// to be appropriate for your platform. - /// - /// Any values set here will overwrite those that are automatically configured by - /// any platform-specific migration library your app includes. - /// - public struct VersionInformation - { - /// - /// The build number of your app. - /// - public string BuildVersion { get; set; } - - /// - /// The human friendly version number of your happ. - /// - public string DisplayVersion { get; set; } - - /// - /// The operating system version of the platform the SDK is operating in.. - /// - public string OSVersion { get; set; } - - } - - /// - /// The LeanCloud application ID of your app. - /// - public string ApplicationId { get; set; } - - /// - /// LeanCloud C# SDK 支持的服务节点,目前支持华北,华东和北美公有云节点和私有节点,以及专属节点 - /// - public AVRegion Region { get; set; } - - internal int RegionValue - { - get - { - return (int)Region; - } - } - - /// - /// The LeanCloud application key for your app. - /// - public string ApplicationKey { get; set; } - - /// - /// The LeanCloud master key for your app. - /// - /// The master key. - public string MasterKey { get; set; } - - /// - /// Gets or sets additional HTTP headers to be sent with network requests from the SDK. - /// - public IDictionary AdditionalHTTPHeaders { get; set; } - - /// - /// The version information of your application environment. - /// - public VersionInformation VersionInfo { get; set; } - - /// - /// 存储服务器地址 - /// - /// The API server. - public string ApiServer { get; set; } - - /// - /// 云引擎服务器地址 - /// - /// The engine server uri. - public string EngineServer { get; set; } - - /// - /// 即时通信服务器地址 - /// - /// The RTMR outer. - public string RTMServer { get; set; } - - /// - /// 直连即时通信服务器地址 - /// - /// The realtime server. - public string RealtimeServer { get; set; } - - public Uri PushServer { get; set; } - - public Uri StatsServer { get; set; } - } - - private static readonly object mutex = new object(); - - static AVClient() - { - versionString = "net-portable-" + Version; - - //AVModuleController.Instance.ScanForModules(); - } - - /// - /// The current configuration that LeanCloud has been initialized with. - /// - public static Configuration CurrentConfiguration { get; internal set; } - - internal static Version Version - { - get - { - var assemblyName = new AssemblyName(typeof(AVClient).GetTypeInfo().Assembly.FullName); - return assemblyName.Version; - } - } - - private static readonly string versionString; - /// - /// 当前 SDK 版本号 - /// - public static string VersionString - { - get - { - return versionString; - } - } - - /// - /// Authenticates this client as belonging to your application. This must be - /// called before your application can use the LeanCloud library. The recommended - /// way is to put a call to AVClient.Initialize in your - /// Application startup. - /// - /// The Application ID provided in the LeanCloud dashboard. - /// - /// The .NET API Key provided in the LeanCloud dashboard. - /// - public static void Initialize(string applicationId, string applicationKey) - { - Initialize(new Configuration - { - ApplicationId = applicationId, - ApplicationKey = applicationKey - }); - } - - internal static Action LogTracker { get; private set; } - - /// - /// 启动日志打印 - /// - /// - public static void HttpLog(Action trace) - { - LogTracker = trace; - } - /// - /// 打印 HTTP 访问日志 - /// - /// - public static void PrintLog(string log) - { - if (AVClient.LogTracker != null) - { - AVClient.LogTracker(log); - } - } - - static bool useProduction = true; - /// - /// Gets or sets a value indicating whether send the request to production server or staging server. - /// - /// true if use production; otherwise, false. - public static bool UseProduction - { - get - { - return useProduction; - } - set - { - useProduction = value; - } - } - - static bool useMasterKey = false; - public static bool UseMasterKey - { - get - { - return useMasterKey; - } - set - { - useMasterKey = value; - } - } - - /// - /// Authenticates this client as belonging to your application. This must be - /// called before your application can use the LeanCloud library. The recommended - /// way is to put a call to AVClient.Initialize in your - /// Application startup. - /// - /// The configuration to initialize LeanCloud with. - /// - public static void Initialize(Configuration configuration) - { - Config(configuration); - - AVObject.RegisterSubclass(); - AVObject.RegisterSubclass(); - AVObject.RegisterSubclass(); - } - - internal static void Config(Configuration configuration) - { - lock (mutex) - { - var nodeHash = configuration.ApplicationId.Split('-'); - if (nodeHash.Length > 1) - { - if (nodeHash[1].Trim() == "9Nh9j0Va") - { - configuration.Region = Configuration.AVRegion.Public_East_China; - } - } - - CurrentConfiguration = configuration; - } - } - - internal static void Clear() - { - AVPlugins.Instance.AppRouterController.Clear(); - AVPlugins.Instance.Reset(); - AVUser.ClearInMemoryUser(); - } - - /// - /// Switch app. - /// - /// Configuration. - public static void Switch(Configuration configuration) - { - Clear(); - Initialize(configuration); - } - - public static void Switch(string applicationId, string applicationKey, Configuration.AVRegion region = Configuration.AVRegion.Public_North_China) - { - var configuration = new Configuration - { - ApplicationId = applicationId, - ApplicationKey = applicationKey, - Region = region - }; - Switch(configuration); - } - - public static string BuildQueryString(IDictionary parameters) - { - return string.Join("&", (from pair in parameters - let valueString = pair.Value as string - select string.Format("{0}={1}", - Uri.EscapeDataString(pair.Key), - Uri.EscapeDataString(string.IsNullOrEmpty(valueString) ? - Json.Encode(pair.Value) : valueString))) - .ToArray()); - } - - internal static IDictionary DecodeQueryString(string queryString) - { - var dict = new Dictionary(); - foreach (var pair in queryString.Split('&')) - { - var parts = pair.Split(new char[] { '=' }, 2); - dict[parts[0]] = parts.Length == 2 ? Uri.UnescapeDataString(parts[1].Replace("+", " ")) : null; - } - return dict; - } - - internal static IDictionary DeserializeJsonString(string jsonData) - { - return Json.Parse(jsonData) as IDictionary; - } - - internal static string SerializeJsonString(IDictionary jsonData) - { - return Json.Encode(jsonData); - } - - public static Task> HttpGetAsync(Uri uri) - { - return RequestAsync(uri, "GET", null, body: null, contentType: null, cancellationToken: CancellationToken.None); - } - - public static Task> RequestAsync(Uri uri, string method, IList> headers, IDictionary body, string contentType, CancellationToken cancellationToken) - { - var dataStream = body != null ? new MemoryStream(Encoding.UTF8.GetBytes(Json.Encode(body))) : null; - return AVClient.RequestAsync(uri, method, headers, dataStream, contentType, cancellationToken); - //return AVPlugins.Instance.HttpClient.ExecuteAsync(request, null, null, cancellationToken); - } - - public static Task> RequestAsync(Uri uri, string method, IList> headers, Stream data, string contentType, CancellationToken cancellationToken) - { - HttpRequest request = new HttpRequest() - { - Data = data != null ? data : null, - Headers = headers, - Method = method, - Uri = uri - }; - return AVPlugins.Instance.HttpClient.ExecuteAsync(request, null, null, cancellationToken).OnSuccess(t => - { - var response = t.Result; - var contentString = response.Item2; - int responseCode = (int)response.Item1; - var responseLog = responseCode + ";" + contentString; - PrintLog(responseLog); - return response; - }); - } - - internal static Tuple> ReponseResolve(Tuple response, CancellationToken cancellationToken) - { - Tuple result = response; - HttpStatusCode code = result.Item1; - string item2 = result.Item2; - - if (item2 == null) - { - cancellationToken.ThrowIfCancellationRequested(); - return new Tuple>(code, null); - } - IDictionary strs = null; - try - { - strs = (!item2.StartsWith("[", StringComparison.Ordinal) ? AVClient.DeserializeJsonString(item2) : new Dictionary() - { - { "results", Json.Parse(item2) } - }); - } - catch (Exception exception) - { - throw new AVException(AVException.ErrorCode.OtherCause, "Invalid response from server", exception); - } - var codeValue = (int)code; - if (codeValue > 203 || codeValue < 200) - { - throw new AVException((AVException.ErrorCode)((int)((strs.ContainsKey("code") ? (long)strs["code"] : (long)-1))), (strs.ContainsKey("error") ? strs["error"] as string : item2), null); - } - - cancellationToken.ThrowIfCancellationRequested(); - return new Tuple>(code, strs); - } - internal static Task>> RequestAsync(string method, Uri relativeUri, string sessionToken, IDictionary data, CancellationToken cancellationToken) - { - - var command = new AVCommand(relativeUri.ToString(), - method: method, - sessionToken: sessionToken, - data: data); - - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken); - } - - internal static Task>> RunCommandAsync(AVCommand command) - { - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command); - } - - internal static bool IsSuccessStatusCode(HttpStatusCode responseStatus) - { - var codeValue = (int)responseStatus; - return (codeValue > 199) && (codeValue < 204); - } - - public static string ToLog(this HttpRequest request) - { - StringBuilder sb = new StringBuilder(); - var start = "===HTTP Request Start==="; - sb.AppendLine(start); - var urlLog = "Url: " + request.Uri; - sb.AppendLine(urlLog); - - var methodLog = "Method: " + request.Method; - sb.AppendLine(methodLog); - - try - { - var headers = request.Headers.ToDictionary(x => x.Key, x => x.Value as object); - var headersLog = "Headers: " + Json.Encode(headers); - sb.AppendLine(headersLog); - } - catch (Exception) - { - - } - - try - { - if (request is AVCommand) - { - var command = (AVCommand)request; - if (command.DataObject != null) - { - var bodyLog = "Body:" + Json.Encode(command.DataObject); - sb.AppendLine(bodyLog); - } - } - else - { - StreamReader reader = new StreamReader(request.Data); - string bodyLog = reader.ReadToEnd(); - sb.AppendLine(bodyLog); - } - } - catch (Exception) - { - - } - - var end = "===HTTP Request End==="; - sb.AppendLine(end); - return sb.ToString(); - } - } -} diff --git a/Storage/Source/Public/AVCloud.cs b/Storage/Source/Public/AVCloud.cs deleted file mode 100644 index 6954bed..0000000 --- a/Storage/Source/Public/AVCloud.cs +++ /dev/null @@ -1,583 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Storage.Internal; - -namespace LeanCloud { - /// - /// The AVCloud class provides methods for interacting with LeanCloud Cloud Functions. - /// - /// - /// For example, this sample code calls the - /// "validateGame" Cloud Function and calls processResponse if the call succeeded - /// and handleError if it failed. - /// - /// - /// var result = - /// await AVCloud.CallFunctionAsync<IDictionary<string, object>>("validateGame", parameters); - /// - /// - public static class AVCloud - { - internal static IAVCloudCodeController CloudCodeController - { - get - { - return AVPlugins.Instance.CloudCodeController; - } - } - - /// - /// Calls a cloud function. - /// - /// The type of data you will receive from the cloud function. This - /// can be an IDictionary, string, IList, AVObject, or any other type supported by - /// AVObject. - /// The cloud function to call. - /// The parameters to send to the cloud function. This - /// dictionary can contain anything that could be passed into a AVObject except for - /// AVObjects themselves. - /// - /// The cancellation token. - /// The result of the cloud call. - public static Task CallFunctionAsync(string name, IDictionary parameters = null, string sesstionToken = null, CancellationToken cancellationToken = default) - { - var sessionTokenTask = AVUser.TakeSessionToken(sesstionToken); - - return sessionTokenTask.OnSuccess(s => - { - return CloudCodeController.CallFunctionAsync(name, - parameters, s.Result, - cancellationToken); - - }).Unwrap(); - } - - /// - /// 远程调用云函数,返回结果会反序列化为 . - /// - /// - /// - /// - /// - /// - /// - public static Task RPCFunctionAsync(string name, IDictionary parameters = null, string sesstionToken = null, CancellationToken cancellationToken = default) - { - var sessionTokenTask = AVUser.TakeSessionToken(sesstionToken); - - return sessionTokenTask.OnSuccess(s => - { - return CloudCodeController.RPCFunction(name, - parameters, - s.Result, - cancellationToken); - }).Unwrap(); - } - - /// - /// 获取 LeanCloud 服务器的时间 - /// - /// 如果获取失败,将返回 DateTime.MinValue - /// - /// - /// 服务器的时间 - public static Task GetServerDateTimeAsync() - { - var command = new AVCommand(relativeUri: "date", - method: "GET", - sessionToken: null, - data: null); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t => - { - DateTime rtn = DateTime.MinValue; - if (AVClient.IsSuccessStatusCode(t.Result.Item1)) - { - var date = AVDecoder.Instance.Decode(t.Result.Item2); - if (date != null) - { - if (date is DateTime) - { - rtn = (DateTime)date; - } - } - } - return rtn; - }); - } - - /// - /// 请求发送验证码。 - /// - /// 是否发送成功。 - /// 手机号。 - /// 应用名称。 - /// 进行的操作名称。 - /// 验证码失效时间。 - /// Cancellation token。 - public static Task RequestSMSCodeAsync(string mobilePhoneNumber, string name, string op, int ttl = 10, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(mobilePhoneNumber)) - { - throw new AVException(AVException.ErrorCode.MobilePhoneInvalid, "Moblie Phone number is invalid.", null); - } - - Dictionary strs = new Dictionary() - { - { "mobilePhoneNumber", mobilePhoneNumber }, - }; - if (!string.IsNullOrEmpty(name)) - { - strs.Add("name", name); - } - if (!string.IsNullOrEmpty(op)) - { - strs.Add("op", op); - } - if (ttl > 0) - { - strs.Add("TTL", ttl); - } - var command = new AVCommand("requestSmsCode", - method: "POST", - sessionToken: null, - data: strs); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(t => - { - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - - /// - /// 请求发送验证码。 - /// - /// 是否发送成功。 - /// 手机号。 - public static Task RequestSMSCodeAsync(string mobilePhoneNumber) - { - return AVCloud.RequestSMSCodeAsync(mobilePhoneNumber, CancellationToken.None); - } - - - /// - /// 请求发送验证码。 - /// - /// 是否发送成功。 - /// 手机号。 - /// Cancellation Token. - public static Task RequestSMSCodeAsync(string mobilePhoneNumber, CancellationToken cancellationToken) - { - return AVCloud.RequestSMSCodeAsync(mobilePhoneNumber, null, null, 0, cancellationToken); - } - - /// - /// 发送手机短信,并指定模板以及传入模板所需的参数。 - /// Exceptions: - /// AVOSCloud.AVException: - /// 手机号为空。 - /// - /// Sms's template - /// Template variables env. - /// Sms's sign. - /// - public static Task RequestSMSCodeAsync( - string mobilePhoneNumber, - string template, - IDictionary env, - string sign = "", - string validateToken = "") - { - - if (string.IsNullOrEmpty(mobilePhoneNumber)) - { - throw new AVException(AVException.ErrorCode.MobilePhoneInvalid, "Moblie Phone number is invalid.", null); - } - Dictionary strs = new Dictionary() - { - { "mobilePhoneNumber", mobilePhoneNumber }, - }; - strs.Add("template", template); - if (string.IsNullOrEmpty(sign)) - { - strs.Add("sign", sign); - } - if (string.IsNullOrEmpty(validateToken)) - { - strs.Add("validate_token", validateToken); - } - foreach (var key in env.Keys) - { - strs.Add(key, env[key]); - } - var command = new AVCommand("requestSmsCode", - method: "POST", - sessionToken: null, - data: strs); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t => - { - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - - /// - /// - /// - /// - /// - public static Task RequestVoiceCodeAsync(string mobilePhoneNumber) - { - if (string.IsNullOrEmpty(mobilePhoneNumber)) - { - throw new AVException(AVException.ErrorCode.MobilePhoneInvalid, "Moblie Phone number is invalid.", null); - } - Dictionary strs = new Dictionary() - { - { "mobilePhoneNumber", mobilePhoneNumber }, - { "smsType", "voice" }, - { "IDD","+86" } - }; - - var command = new AVCommand("requestSmsCode", - method: "POST", - sessionToken: null, - data: strs); - - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t => - { - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - - /// - /// 验证是否是有效短信验证码。 - /// - /// 是否验证通过。 - /// 手机号 - /// 验证码。 - public static Task VerifySmsCodeAsync(string code, string mobilePhoneNumber) - { - return AVCloud.VerifySmsCodeAsync(code, mobilePhoneNumber, CancellationToken.None); - } - - /// - /// 验证是否是有效短信验证码。 - /// - /// 是否验证通过。 - /// 验证码。 - /// 手机号 - /// Cancellation token. - public static Task VerifySmsCodeAsync(string code, string mobilePhoneNumber, CancellationToken cancellationToken) - { - var command = new AVCommand("verifySmsCode/" + code.Trim() + "?mobilePhoneNumber=" + mobilePhoneNumber.Trim(), - method: "POST", - sessionToken: null, - data: null); - - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(t => - { - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - - /// - /// Stands for a captcha result. - /// - public class Captcha - { - /// - /// Used for captcha verify. - /// - public string Token { internal set; get; } - - /// - /// Captcha image URL. - /// - public string Url { internal set; get; } - - /// - /// Verify the user's input of catpcha. - /// - /// User's input of this captcha. - /// CancellationToken. - /// - public Task VerifyAsync(string code) - { - return AVCloud.VerifyCaptchaAsync(code, Token); - } - } - - /// - /// Get a captcha image. - /// - /// captcha image width. - /// captcha image height. - /// CancellationToken. - /// an instance of Captcha. - public static Task RequestCaptchaAsync(int width = 85, int height = 30, CancellationToken cancellationToken = default) - { - var path = string.Format("requestCaptcha?width={0}&height={1}", width, height); - var command = new AVCommand(path, "GET", null, data: null); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - var decoded = AVDecoder.Instance.Decode(t.Result.Item2) as IDictionary; - return new Captcha() - { - Token = decoded["captcha_token"] as string, - Url = decoded["captcha_url"] as string, - }; - }); - } - - /// - /// Verify the user's input of catpcha. - /// - /// The captcha's token, from server. - /// User's input of this captcha. - /// CancellationToken. - /// - public static Task VerifyCaptchaAsync(string code, string token, CancellationToken cancellationToken = default) - { - var data = new Dictionary - { - { "captcha_token", token }, - { "captcha_code", code }, - }; - var command = new AVCommand("verifyCaptcha", "POST", null, data: data); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(t => - { - if (!t.Result.Item2.ContainsKey("validate_token")) - throw new KeyNotFoundException("validate_token"); - return t.Result.Item2["validate_token"] as string; - }); - } - - /// - /// Get the custom cloud parameters, you can set them at console https://leancloud.cn/dashboard/devcomponent.html?appid={your_app_Id}#/component/custom_param - /// - /// - /// - public static Task> GetCustomParametersAsync(CancellationToken cancellationToken = default) - { - var command = new AVCommand(string.Format("statistics/apps/{0}/sendPolicy", AVClient.CurrentConfiguration.ApplicationId), - method: "GET", - sessionToken: null, - data: null); - - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - var settings = t.Result.Item2; - var CloudParameters = settings["parameters"] as IDictionary; - return CloudParameters; - }); - } - - public class RealtimeSignature - { - public string Nonce { internal set; get; } - public long Timestamp { internal set; get; } - public string ClientId { internal set; get; } - public string Signature { internal set; get; } - } - - public static Task RequestRealtimeSignatureAsync(CancellationToken cancellationToken = default) - { - return AVUser.GetCurrentUserAsync(cancellationToken).OnSuccess(t => - { - return RequestRealtimeSignatureAsync(t.Result, cancellationToken); - }).Unwrap(); - } - - public static Task RequestRealtimeSignatureAsync(AVUser user, CancellationToken cancellationToken = default) - { - var command = new AVCommand(string.Format("rtm/sign"), - method: "POST", - sessionToken: null, - data: new Dictionary - { - { "session_token", user.SessionToken }, - } - ); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(t => - { - var body = t.Result.Item2; - return new RealtimeSignature() - { - Nonce = body["nonce"] as string, - Timestamp = (long)body["timestamp"], - ClientId = body["client_id"] as string, - Signature = body["signature"] as string, - }; - }); - } - - /// - /// Gets the LeanEngine hosting URL for current app async. - /// - /// The lean engine hosting URL async. - public static Task GetLeanEngineHostingUrlAsync() - { - return CallFunctionAsync("_internal_extensions_get_domain"); - } - - } - - - /// - /// AVRPCC loud function base. - /// - public class AVRPCCloudFunctionBase - { - /// - /// AVRPCD eserialize. - /// - public delegate R AVRPCDeserialize(IDictionary result); - /// - /// AVRPCS erialize. - /// - public delegate IDictionary AVRPCSerialize

(P parameters); - - public AVRPCCloudFunctionBase() - : this(true) - { - - } - - public AVRPCCloudFunctionBase(bool noneParameters) - { - if (noneParameters) - { - this.Encode = n => - { - return null; - }; - } - } - - - - private AVRPCDeserialize _decode; - public AVRPCDeserialize Decode - { - get - { - return _decode; - } - set - { - _decode = value; - } - } - - - private AVRPCSerialize

_encode; - public AVRPCSerialize

Encode - { - get - { - if (_encode == null) - { - _encode = n => - { - if (n != null) - return Json.Parse(n.ToString()) as IDictionary; - return null; - }; - } - return _encode; - } - set - { - _encode = value; - } - - } - public string FunctionName { get; set; } - - public Task ExecuteAsync(P parameters) - { - return AVUser.GetCurrentAsync().OnSuccess(t => - { - var user = t.Result; - var encodedParameters = Encode(parameters); - var command = new AVCommand( - string.Format("call/{0}", Uri.EscapeUriString(FunctionName)), - method: "POST", - sessionToken: user != null ? user.SessionToken : null, - data: encodedParameters); - - return AVClient.RunCommandAsync(command); - - }).Unwrap().OnSuccess(s => - { - var responseBody = s.Result.Item2; - if (!responseBody.ContainsKey("result")) - { - return default(R); - } - - return Decode(responseBody); - }); - - } - } - - - public class AVObjectRPCCloudFunction : AVObjectRPCCloudFunction - { - - } - public class AVObjectListRPCCloudFunction : AVObjectListRPCCloudFunction - { - - } - - public class AVObjectListRPCCloudFunction : AVRPCCloudFunctionBase> where R : AVObject - { - public AVObjectListRPCCloudFunction() - : base(true) - { - this.Decode = this.AVObjectListDeserializer(); - } - - public AVRPCDeserialize> AVObjectListDeserializer() - { - AVRPCDeserialize> del = data => - { - var items = data["result"] as IList; - - return items.Select(item => - { - var state = AVObjectCoder.Instance.Decode(item as IDictionary, AVDecoder.Instance); - return AVObject.FromState(state, state.ClassName); - }).ToList() as IList; - - }; - return del; - } - } - - public class AVObjectRPCCloudFunction : AVRPCCloudFunctionBase where R : AVObject - { - public AVObjectRPCCloudFunction() - : base(true) - { - this.Decode = this.AVObjectDeserializer(); - } - - - public AVRPCDeserialize AVObjectDeserializer() - { - AVRPCDeserialize del = data => - { - var item = data["result"] as object; - var state = AVObjectCoder.Instance.Decode(item as IDictionary, AVDecoder.Instance); - - return AVObject.FromState(state, state.ClassName); - }; - return del; - - } - } -} diff --git a/Storage/Source/Public/AVConfig.cs b/Storage/Source/Public/AVConfig.cs deleted file mode 100644 index 8e3b041..0000000 --- a/Storage/Source/Public/AVConfig.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Storage.Internal; -using LeanCloud.Utilities; - -namespace LeanCloud -{ - /// - /// The AVConfig is a representation of the remote configuration object, - /// that enables you to add things like feature gating, a/b testing or simple "Message of the day". - /// - public class AVConfig : IJsonConvertible - { - private IDictionary properties = new Dictionary(); - - /// - /// Gets the latest fetched AVConfig. - /// - /// AVConfig object - public static AVConfig CurrentConfig - { - get - { - Task task = ConfigController.CurrentConfigController.GetCurrentConfigAsync(); - task.Wait(); - return task.Result; - } - } - - internal static void ClearCurrentConfig() - { - ConfigController.CurrentConfigController.ClearCurrentConfigAsync().Wait(); - } - - internal static void ClearCurrentConfigInMemory() - { - ConfigController.CurrentConfigController.ClearCurrentConfigInMemoryAsync().Wait(); - } - - private static IAVConfigController ConfigController - { - get { return AVPlugins.Instance.ConfigController; } - } - - internal AVConfig() - : base() - { - } - - internal AVConfig(IDictionary fetchedConfig) - { - var props = AVDecoder.Instance.Decode(fetchedConfig["params"]) as IDictionary; - properties = props; - } - - /// - /// Retrieves the AVConfig asynchronously from the server. - /// - /// AVConfig object that was fetched - public static Task GetAsync() - { - return GetAsync(CancellationToken.None); - } - - /// - /// Retrieves the AVConfig asynchronously from the server. - /// - /// The cancellation token. - /// AVConfig object that was fetched - public static Task GetAsync(CancellationToken cancellationToken) - { - return ConfigController.FetchConfigAsync(AVUser.CurrentSessionToken, cancellationToken); - } - - /// - /// Gets a value for the key of a particular type. - /// - /// The type to convert the value to. Supported types are - /// AVObject and its descendents, LeanCloud types such as AVRelation and AVGeopoint, - /// primitive types,IList<T>, IDictionary<string, T> and strings. - /// The key of the element to get. - /// The property is retrieved - /// and is not found. - /// The property under this - /// key was found, but of a different type. - public T Get(string key) - { - return Conversion.To(this.properties[key]); - } - - /// - /// Populates result with the value for the key, if possible. - /// - /// The desired type for the value. - /// The key to retrieve a value for. - /// The value for the given key, converted to the - /// requested type, or null if unsuccessful. - /// true if the lookup and conversion succeeded, otherwise false. - public bool TryGetValue(string key, out T result) - { - if (this.properties.ContainsKey(key)) - { - try - { - var temp = Conversion.To(this.properties[key]); - result = temp; - return true; - } - catch (Exception) - { - // Could not convert, do nothing - } - } - result = default(T); - return false; - } - - /// - /// Gets a value on the config. - /// - /// The key for the parameter. - /// The property is - /// retrieved and is not found. - /// The value for the key. - virtual public object this[string key] - { - get - { - return this.properties[key]; - } - } - - IDictionary IJsonConvertible.ToJSON() - { - return new Dictionary { - { "params", NoObjectsEncoder.Instance.Encode(properties) } - }; - } - } -} diff --git a/Storage/Source/Public/AVDownloadProgressEventArgs.cs b/Storage/Source/Public/AVDownloadProgressEventArgs.cs deleted file mode 100644 index 8858eaa..0000000 --- a/Storage/Source/Public/AVDownloadProgressEventArgs.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace LeanCloud { - /// - /// Represents download progress. - /// - public class AVDownloadProgressEventArgs : EventArgs { - public AVDownloadProgressEventArgs() { } - - /// - /// Gets the progress (a number between 0.0 and 1.0) of a download. - /// - public double Progress { get; set; } - } -} diff --git a/Storage/Source/Public/AVException.cs b/Storage/Source/Public/AVException.cs deleted file mode 100644 index 4fedef0..0000000 --- a/Storage/Source/Public/AVException.cs +++ /dev/null @@ -1,275 +0,0 @@ -using System; - -namespace LeanCloud -{ - /// - /// Exceptions that may occur when sending requests to LeanCloud. - /// - public class AVException : Exception - { - /// - /// Error codes that may be delivered in response to requests to LeanCloud. - /// - public enum ErrorCode - { - /// - /// Error code indicating that an unknown error or an error unrelated to LeanCloud - /// occurred. - /// - OtherCause = -1, - - /// - /// Error code indicating that something has gone wrong with the server. - /// If you get this error code, it is LeanCloud's fault. - /// - InternalServerError = 1, - - /// - /// Error code indicating the connection to the LeanCloud servers failed. - /// - ConnectionFailed = 100, - - /// - /// Error code indicating the specified object doesn't exist. - /// - ObjectNotFound = 101, - - /// - /// Error code indicating you tried to query with a datatype that doesn't - /// support it, like exact matching an array or object. - /// - InvalidQuery = 102, - - /// - /// Error code indicating a missing or invalid classname. Classnames are - /// case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the - /// only valid characters. - /// - InvalidClassName = 103, - - /// - /// Error code indicating an unspecified object id. - /// - MissingObjectId = 104, - - /// - /// Error code indicating an invalid key name. Keys are case-sensitive. They - /// must start with a letter, and a-zA-Z0-9_ are the only valid characters. - /// - InvalidKeyName = 105, - - /// - /// Error code indicating a malformed pointer. You should not see this unless - /// you have been mucking about changing internal LeanCloud code. - /// - InvalidPointer = 106, - - /// - /// Error code indicating that badly formed JSON was received upstream. This - /// either indicates you have done something unusual with modifying how - /// things encode to JSON, or the network is failing badly. - /// - InvalidJSON = 107, - - /// - /// Error code indicating that the feature you tried to access is only - /// available internally for testing purposes. - /// - CommandUnavailable = 108, - - /// - /// You must call LeanCloud.initialize before using the LeanCloud library. - /// - NotInitialized = 109, - - /// - /// Error code indicating that a field was set to an inconsistent type. - /// - IncorrectType = 111, - - /// - /// Error code indicating an invalid channel name. A channel name is either - /// an empty string (the broadcast channel) or contains only a-zA-Z0-9_ - /// characters and starts with a letter. - /// - InvalidChannelName = 112, - - /// - /// Error code indicating that push is misconfigured. - /// - PushMisconfigured = 115, - - /// - /// Error code indicating that the object is too large. - /// - ObjectTooLarge = 116, - - /// - /// Error code indicating that the operation isn't allowed for clients. - /// - OperationForbidden = 119, - - /// - /// Error code indicating the result was not found in the cache. - /// - CacheMiss = 120, - - /// - /// Error code indicating that an invalid key was used in a nested - /// JSONObject. - /// - InvalidNestedKey = 121, - - /// - /// Error code indicating that an invalid filename was used for AVFile. - /// A valid file name contains only a-zA-Z0-9_. characters and is between 1 - /// and 128 characters. - /// - InvalidFileName = 122, - - /// - /// Error code indicating an invalid ACL was provided. - /// - InvalidACL = 123, - - /// - /// Error code indicating that the request timed out on the server. Typically - /// this indicates that the request is too expensive to run. - /// - Timeout = 124, - - /// - /// Error code indicating that the email address was invalid. - /// - InvalidEmailAddress = 125, - - /// - /// Error code indicating that a unique field was given a value that is - /// already taken. - /// - DuplicateValue = 137, - - /// - /// Error code indicating that a role's name is invalid. - /// - InvalidRoleName = 139, - - /// - /// Error code indicating that an application quota was exceeded. Upgrade to - /// resolve. - /// - ExceededQuota = 140, - - /// - /// Error code indicating that a Cloud Code script failed. - /// - ScriptFailed = 141, - - /// - /// Error code indicating that a Cloud Code validation failed. - /// - ValidationFailed = 142, - - /// - /// Error code indicating that deleting a file failed. - /// - FileDeleteFailed = 153, - - /// - /// Error code indicating that the application has exceeded its request limit. - /// - RequestLimitExceeded = 155, - - /// - /// Error code indicating that the provided event name is invalid. - /// - InvalidEventName = 160, - - /// - /// Error code indicating that the username is missing or empty. - /// - UsernameMissing = 200, - - /// - /// Error code indicating that the password is missing or empty. - /// - PasswordMissing = 201, - - /// - /// Error code indicating that the username has already been taken. - /// - UsernameTaken = 202, - - /// - /// Error code indicating that the email has already been taken. - /// - EmailTaken = 203, - - /// - /// Error code indicating that the email is missing, but must be specified. - /// - EmailMissing = 204, - - /// - /// Error code indicating that a user with the specified email was not found. - /// - EmailNotFound = 205, - - /// - /// Error code indicating that a user object without a valid session could - /// not be altered. - /// - SessionMissing = 206, - - /// - /// Error code indicating that a user can only be created through signup. - /// - MustCreateUserThroughSignup = 207, - - /// - /// Error code indicating that an an account being linked is already linked - /// to another user. - /// - AccountAlreadyLinked = 208, - - /// - /// Error code indicating that the current session token is invalid. - /// - InvalidSessionToken = 209, - - /// - /// Error code indicating that a user cannot be linked to an account because - /// that account's id could not be found. - /// - LinkedIdMissing = 250, - - /// - /// Error code indicating that a user with a linked (e.g. Facebook) account - /// has an invalid session. - /// - InvalidLinkedSession = 251, - - /// - /// Error code indicating that a service being linked (e.g. Facebook or - /// Twitter) is unsupported. - /// - UnsupportedService = 252, - - /// - /// 手机号不合法 - /// - MobilePhoneInvalid = 253 - } - - internal AVException(ErrorCode code, string message, Exception cause = null) - : base(message, cause) - { - this.Code = code; - } - - /// - /// The LeanCloud error code associated with the exception. - /// - public ErrorCode Code { get; private set; } - } -} diff --git a/Storage/Source/Public/AVExtensions.cs b/Storage/Source/Public/AVExtensions.cs deleted file mode 100644 index 973ff5c..0000000 --- a/Storage/Source/Public/AVExtensions.cs +++ /dev/null @@ -1,160 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using System.Linq; -using LeanCloud.Storage.Internal; - -namespace LeanCloud -{ - /// - /// Provides convenience extension methods for working with collections - /// of AVObjects so that you can easily save and fetch them in batches. - /// - public static class AVExtensions - { - /// - /// Saves all of the AVObjects in the enumeration. Equivalent to - /// calling . - /// - /// The objects to save. - public static Task SaveAllAsync(this IEnumerable objects) where T : AVObject - { - return AVObject.SaveAllAsync(objects); - } - - /// - /// Saves all of the AVObjects in the enumeration. Equivalent to - /// calling - /// . - /// - /// The objects to save. - /// The cancellation token. - public static Task SaveAllAsync( - this IEnumerable objects, CancellationToken cancellationToken) where T : AVObject - { - return AVObject.SaveAllAsync(objects, cancellationToken); - } - - /// - /// Fetches all of the objects in the enumeration. Equivalent to - /// calling . - /// - /// The objects to save. - public static Task> FetchAllAsync(this IEnumerable objects) - where T : AVObject - { - return AVObject.FetchAllAsync(objects); - } - - /// - /// Fetches all of the objects in the enumeration. Equivalent to - /// calling - /// . - /// - /// The objects to fetch. - /// The cancellation token. - public static Task> FetchAllAsync( - this IEnumerable objects, CancellationToken cancellationToken) - where T : AVObject - { - return AVObject.FetchAllAsync(objects, cancellationToken); - } - - /// - /// Fetches all of the objects in the enumeration that don't already have - /// data. Equivalent to calling - /// . - /// - /// The objects to fetch. - public static Task> FetchAllIfNeededAsync( - this IEnumerable objects) - where T : AVObject - { - return AVObject.FetchAllIfNeededAsync(objects); - } - - /// - /// Fetches all of the objects in the enumeration that don't already have - /// data. Equivalent to calling - /// . - /// - /// The objects to fetch. - /// The cancellation token. - public static Task> FetchAllIfNeededAsync( - this IEnumerable objects, CancellationToken cancellationToken) - where T : AVObject - { - return AVObject.FetchAllIfNeededAsync(objects, cancellationToken); - } - - /// - /// Constructs a query that is the or of the given queries. - /// - /// The type of AVObject being queried. - /// An initial query to 'or' with additional queries. - /// The list of AVQueries to 'or' together. - /// A query that is the or of the given queries. - public static AVQuery Or(this AVQuery source, params AVQuery[] queries) - where T : AVObject - { - return AVQuery.Or(queries.Concat(new[] { source })); - } - - /// - /// Fetches this object with the data from the server. - /// - public static Task FetchAsync(this T obj) where T : AVObject - { - return obj.FetchAsyncInternal(CancellationToken.None).OnSuccess(t => (T)t.Result); - } - - /// - /// Fetches this object with the data from the server. - /// - /// The AVObject to fetch. - /// The cancellation token. - public static Task FetchAsync(this T obj, CancellationToken cancellationToken) - where T : AVObject - { - return FetchAsync(obj, null, cancellationToken); - } - public static Task FetchAsync(this T obj, IEnumerable includeKeys) where T : AVObject - { - return FetchAsync(obj, includeKeys, CancellationToken.None).OnSuccess(t => (T)t.Result); - } - public static Task FetchAsync(this T obj, IEnumerable includeKeys, CancellationToken cancellationToken) - where T : AVObject - { - var queryString = new Dictionary(); - if (includeKeys != null) - { - - var encode = string.Join(",", includeKeys.ToArray()); - queryString.Add("include", encode); - } - return obj.FetchAsyncInternal(queryString, cancellationToken).OnSuccess(t => (T)t.Result); - } - - /// - /// If this AVObject has not been fetched (i.e. returns - /// false), fetches this object with the data from the server. - /// - /// The AVObject to fetch. - public static Task FetchIfNeededAsync(this T obj) where T : AVObject - { - return obj.FetchIfNeededAsyncInternal(CancellationToken.None).OnSuccess(t => (T)t.Result); - } - - /// - /// If this AVObject has not been fetched (i.e. returns - /// false), fetches this object with the data from the server. - /// - /// The AVObject to fetch. - /// The cancellation token. - public static Task FetchIfNeededAsync(this T obj, CancellationToken cancellationToken) - where T : AVObject - { - return obj.FetchIfNeededAsyncInternal(cancellationToken).OnSuccess(t => (T)t.Result); - } - } -} diff --git a/Storage/Source/Public/AVFieldNameAttribute.cs b/Storage/Source/Public/AVFieldNameAttribute.cs deleted file mode 100644 index 8ee91a7..0000000 --- a/Storage/Source/Public/AVFieldNameAttribute.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 -{ - /// - /// Specifies a field name for a property on a AVObject subclass. - /// - [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] - public sealed class AVFieldNameAttribute : Attribute - { - /// - /// Constructs a new AVFieldName attribute. - /// - /// The name of the field on the AVObject that the - /// property represents. - public AVFieldNameAttribute(string fieldName) - { - FieldName = fieldName; - } - - /// - /// Gets the name of the field represented by this property. - /// - public string FieldName { get; private set; } - } -} diff --git a/Storage/Source/Public/AVFile.cs b/Storage/Source/Public/AVFile.cs deleted file mode 100644 index 5e9edb7..0000000 --- a/Storage/Source/Public/AVFile.cs +++ /dev/null @@ -1,728 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud -{ - /// - /// AVFile is a local representation of a file that is saved to the LeanCloud. - /// - /// - /// The workflow is to construct a with data and a filename, - /// then save it and set it as a field on a AVObject: - /// - /// - /// var file = new AVFile("hello.txt", - /// new MemoryStream(Encoding.UTF8.GetBytes("hello"))); - /// await file.SaveAsync(); - /// var obj = new AVObject("TestObject"); - /// obj["file"] = file; - /// await obj.SaveAsync(); - /// - /// - public partial class AVFile : IJsonConvertible - { - internal static int objectCounter = 0; - internal static readonly object Mutex = new object(); - private FileState state; - private readonly Stream dataStream; - private readonly TaskQueue taskQueue = new TaskQueue(); - - #region Constructor - /// - /// 通过文件名,数据流,文件类型,文件源信息构建一个 AVFile - /// - /// 文件名 - /// 数据流 - /// 文件类型 - /// 文件源信息 - public AVFile(string name, Stream data, string mimeType = null, IDictionary metaData = null) - { - mimeType = mimeType == null ? GetMIMEType(name) : mimeType; - state = new FileState - { - Name = name, - MimeType = mimeType, - MetaData = metaData - }; - this.dataStream = data; - lock (Mutex) - { - objectCounter++; - state.counter = objectCounter; - } - } - - /// - /// 根据文件名,文件 Byte 数组,以及文件类型构建 AVFile - /// - /// 文件名 - /// 文件 Byte 数组 - /// 文件类型 - public AVFile(string name, byte[] data, string mimeType = null) - : this(name, new MemoryStream(data), mimeType) { } - - /// - /// 根据文件名,文件流数据,文件类型构建 AVFile - /// - /// 文件名 - /// 文件流数据 - /// 文件类型 - public AVFile(string name, Stream data, string mimeType = null) - : this(name, data, mimeType, new Dictionary()) - { - } - - /// - /// 根据 byte 数组以及文件名创建文件 - /// - /// 文件名 - /// 文件的 byte[] 数据 - public AVFile(string name, byte[] data) - : this(name, new MemoryStream(data), new Dictionary()) - { - - } - - /// - /// 根据文件名,数据 byte[] 数组以及元数据创建文件 - /// - /// 文件名 - /// 文件的 byte[] 数据 - /// 元数据 - public AVFile(string name, byte[] data, IDictionary metaData) - : this(name, new MemoryStream(data), metaData) - { - - } - - /// - /// 根据文件名,数据流以及元数据创建文件 - /// - /// 文件名 - /// 文件的数据流 - /// 元数据 - public AVFile(string name, Stream data, IDictionary metaData) - : this(name, data, GetMIMEType(name), metaData) - { - } - - /// - /// 根据文件名,数据流以及元数据创建文件 - /// - /// 文件名 - /// 文件的数据流 - public AVFile(string name, Stream data) - : this(name, data, new Dictionary()) - { - - } - - #region created by url or uri - /// - /// 根据文件名,Uri,文件类型以及文件源信息 - /// - /// 文件名 - /// 文件Uri - /// 文件类型 - /// 文件源信息 - public AVFile(string name, Uri uri, string mimeType = null, IDictionary metaData = null) - { - mimeType = mimeType == null ? GetMIMEType(name) : mimeType; - state = new FileState - { - Name = name, - Url = uri, - MetaData = metaData, - MimeType = mimeType - }; - lock (Mutex) - { - objectCounter++; - state.counter = objectCounter; - } - this.isExternal = true; - } - - /// - /// 根据文件名,文件 Url,文件类型,文件源信息构建 AVFile - /// - /// 文件名 - /// 文件 Url - /// 文件类型 - /// 文件源信息 - public AVFile(string name, string url, string mimeType = null, IDictionary metaData = null) - : this(name, new Uri(url), mimeType, metaData) - { - - } - - /// - /// 根据文件名,文件 Url以及文件的源信息构建 AVFile - /// - /// 文件名 - /// 文件 Url - /// 文件源信息 - public AVFile(string name, string url, IDictionary metaData) - : this(name, url, null, metaData) - { - } - - /// - /// 根据文件名,文件 Uri,以及文件类型构建 AVFile - /// - /// 文件名 - /// 文件 Uri - /// 文件类型 - public AVFile(string name, Uri uri, string mimeType = null) - : this(name, uri, mimeType, new Dictionary()) - { - - } - - /// - /// 根据文件名以及文件 Uri 构建 AVFile - /// - /// 文件名 - /// 文件 Uri - public AVFile(string name, Uri uri) - : this(name, uri, null, new Dictionary()) - { - - } - /// - /// 根据文件名和 Url 创建文件 - /// - /// 文件名 - /// 文件的 Url - public AVFile(string name, string url) - : this(name, new Uri(url)) - { - } - - internal AVFile(FileState filestate) - { - this.state = filestate; - } - internal AVFile(string objectId) - : this(new FileState() - { - ObjectId = objectId - }) - { - - } - #endregion - - #endregion - - #region Properties - - /// - /// Gets whether the file still needs to be saved. - /// - public bool IsDirty - { - get - { - return state.Url == null; - } - } - - /// - /// Gets the name of the file. Before save is called, this is the filename given by - /// the user. After save is called, that name gets prefixed with a unique identifier. - /// - [AVFieldName("name")] - public string Name - { - get - { - return state.Name; - } - } - - /// - /// Gets the MIME type of the file. This is either passed in to the constructor or - /// inferred from the file extension. "unknown/unknown" will be used if neither is - /// available. - /// - public string MimeType - { - get - { - return state.MimeType; - } - } - - /// - /// Gets the url of the file. It is only available after you save the file or after - /// you get the file from a . - /// - [AVFieldName("url")] - public Uri Url - { - get - { - return state.Url; - } - } - - internal static IAVFileController FileController - { - get - { - return AVPlugins.Instance.FileController; - } - } - - #endregion - - IDictionary IJsonConvertible.ToJSON() - { - if (this.IsDirty) - { - throw new InvalidOperationException( - "AVFile must be saved before it can be serialized."); - } - return new Dictionary { - {"__type", "File"}, - { "id", ObjectId }, - {"name", Name}, - {"url", Url.AbsoluteUri} - }; - } - - #region Save - - /// - /// Saves the file to the LeanCloud cloud. - /// - public Task SaveAsync() - { - return SaveAsync(null, CancellationToken.None); - } - - /// - /// Saves the file to the LeanCloud cloud. - /// - /// The cancellation token. - public Task SaveAsync(CancellationToken cancellationToken) - { - return SaveAsync(null, cancellationToken); - } - - /// - /// Saves the file to the LeanCloud cloud. - /// - /// The progress callback. - public Task SaveAsync(IProgress progress) - { - return SaveAsync(progress, CancellationToken.None); - } - - /// - /// Saves the file to the LeanCloud cloud. - /// - /// The progress callback. - /// The cancellation token. - public Task SaveAsync(IProgress progress, - CancellationToken cancellationToken) - { - if (this.isExternal) - return this.SaveExternal(); - - return taskQueue.Enqueue( - toAwait => FileController.SaveAsync(state, dataStream, AVUser.CurrentSessionToken, progress, cancellationToken), cancellationToken) - .OnSuccess(t => - { - state = t.Result; - }); - } - - internal Task SaveExternal() - { - Dictionary strs = new Dictionary() - { - { "url", this.Url.ToString() }, - { "name",this.Name }, - { "mime_type",this.MimeType}, - { "metaData",this.MetaData} - }; - AVCommand cmd = null; - - if (!string.IsNullOrEmpty(this.ObjectId)) - { - cmd = new AVCommand("files/" + this.ObjectId, - method: "PUT", sessionToken: AVUser.CurrentSessionToken, data: strs); - } - else - { - cmd = new AVCommand("files", method: "POST", sessionToken: AVUser.CurrentSessionToken, data: strs); - } - - return AVPlugins.Instance.CommandRunner.RunCommandAsync(cmd).ContinueWith(t => - { - var result = t.Result.Item2; - this.state.ObjectId = result["objectId"].ToString(); - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - - #endregion - - #region Compatible - private readonly static Dictionary MIMETypesDictionary; - private object mutex = new object(); - private bool isExternal; - /// - /// 文件在 LeanCloud 的唯一Id 标识 - /// - public string ObjectId - { - get - { - string str; - lock (this.mutex) - { - str = state.ObjectId; - } - return str; - } - } - - /// - /// 文件的元数据。 - /// - public IDictionary MetaData - { - get - { - return state.MetaData; - } - } - - /// - /// 文件是否为外链文件。 - /// - /// - /// - public bool IsExternal - { - get - { - return isExternal; - } - } - - static AVFile() - { - Dictionary strs = new Dictionary() - { - { "ai", "application/postscript" }, - { "aif", "audio/x-aiff" }, - { "aifc", "audio/x-aiff" }, - { "aiff", "audio/x-aiff" }, - { "asc", "text/plain" }, - { "atom", "application/atom+xml" }, - { "au", "audio/basic" }, - { "avi", "video/x-msvideo" }, - { "bcpio", "application/x-bcpio" }, - { "bin", "application/octet-stream" }, - { "bmp", "image/bmp" }, - { "cdf", "application/x-netcdf" }, - { "cgm", "image/cgm" }, - { "class", "application/octet-stream" }, - { "cpio", "application/x-cpio" }, - { "cpt", "application/mac-compactpro" }, - { "csh", "application/x-csh" }, - { "css", "text/css" }, - { "dcr", "application/x-director" }, - { "dif", "video/x-dv" }, - { "dir", "application/x-director" }, - { "djv", "image/vnd.djvu" }, - { "djvu", "image/vnd.djvu" }, - { "dll", "application/octet-stream" }, - { "dmg", "application/octet-stream" }, - { "dms", "application/octet-stream" }, - { "doc", "application/msword" }, - { "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }, - { "dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template" }, - { "docm", "application/vnd.ms-word.document.macroEnabled.12" }, - { "dotm", "application/vnd.ms-word.template.macroEnabled.12" }, - { "dtd", "application/xml-dtd" }, - { "dv", "video/x-dv" }, - { "dvi", "application/x-dvi" }, - { "dxr", "application/x-director" }, - { "eps", "application/postscript" }, - { "etx", "text/x-setext" }, - { "exe", "application/octet-stream" }, - { "ez", "application/andrew-inset" }, - { "gif", "image/gif" }, - { "gram", "application/srgs" }, - { "grxml", "application/srgs+xml" }, - { "gtar", "application/x-gtar" }, - { "hdf", "application/x-hdf" }, - { "hqx", "application/mac-binhex40" }, - { "htm", "text/html" }, - { "html", "text/html" }, - { "ice", "x-conference/x-cooltalk" }, - { "ico", "image/x-icon" }, - { "ics", "text/calendar" }, - { "ief", "image/ief" }, - { "ifb", "text/calendar" }, - { "iges", "model/iges" }, - { "igs", "model/iges" }, - { "jnlp", "application/x-java-jnlp-file" }, - { "jp2", "image/jp2" }, - { "jpe", "image/jpeg" }, - { "jpeg", "image/jpeg" }, - { "jpg", "image/jpeg" }, - { "js", "application/x-javascript" }, - { "kar", "audio/midi" }, - { "latex", "application/x-latex" }, - { "lha", "application/octet-stream" }, - { "lzh", "application/octet-stream" }, - { "m3u", "audio/x-mpegurl" }, - { "m4a", "audio/mp4a-latm" }, - { "m4b", "audio/mp4a-latm" }, - { "m4p", "audio/mp4a-latm" }, - { "m4u", "video/vnd.mpegurl" }, - { "m4v", "video/x-m4v" }, - { "mac", "image/x-macpaint" }, - { "man", "application/x-troff-man" }, - { "mathml", "application/mathml+xml" }, - { "me", "application/x-troff-me" }, - { "mesh", "model/mesh" }, - { "mid", "audio/midi" }, - { "midi", "audio/midi" }, - { "mif", "application/vnd.mif" }, - { "mov", "video/quicktime" }, - { "movie", "video/x-sgi-movie" }, - { "mp2", "audio/mpeg" }, - { "mp3", "audio/mpeg" }, - { "mp4", "video/mp4" }, - { "mpe", "video/mpeg" }, - { "mpeg", "video/mpeg" }, - { "mpg", "video/mpeg" }, - { "mpga", "audio/mpeg" }, - { "ms", "application/x-troff-ms" }, - { "msh", "model/mesh" }, - { "mxu", "video/vnd.mpegurl" }, - { "nc", "application/x-netcdf" }, - { "oda", "application/oda" }, - { "ogg", "application/ogg" }, - { "pbm", "image/x-portable-bitmap" }, - { "pct", "image/pict" }, - { "pdb", "chemical/x-pdb" }, - { "pdf", "application/pdf" }, - { "pgm", "image/x-portable-graymap" }, - { "pgn", "application/x-chess-pgn" }, - { "pic", "image/pict" }, - { "pict", "image/pict" }, - { "png", "image/png" }, - { "pnm", "image/x-portable-anymap" }, - { "pnt", "image/x-macpaint" }, - { "pntg", "image/x-macpaint" }, - { "ppm", "image/x-portable-pixmap" }, - { "ppt", "application/vnd.ms-powerpoint" }, - { "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" }, - { "potx", "application/vnd.openxmlformats-officedocument.presentationml.template" }, - { "ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow" }, - { "ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12" }, - { "pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12" }, - { "potm", "application/vnd.ms-powerpoint.template.macroEnabled.12" }, - { "ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12" }, - { "ps", "application/postscript" }, - { "qt", "video/quicktime" }, - { "qti", "image/x-quicktime" }, - { "qtif", "image/x-quicktime" }, - { "ra", "audio/x-pn-realaudio" }, - { "ram", "audio/x-pn-realaudio" }, - { "ras", "image/x-cmu-raster" }, - { "rdf", "application/rdf+xml" }, - { "rgb", "image/x-rgb" }, - { "rm", "application/vnd.rn-realmedia" }, - { "roff", "application/x-troff" }, - { "rtf", "text/rtf" }, - { "rtx", "text/richtext" }, - { "sgm", "text/sgml" }, - { "sgml", "text/sgml" }, - { "sh", "application/x-sh" }, - { "shar", "application/x-shar" }, - { "silo", "model/mesh" }, - { "sit", "application/x-stuffit" }, - { "skd", "application/x-koan" }, - { "skm", "application/x-koan" }, - { "skp", "application/x-koan" }, - { "skt", "application/x-koan" }, - { "smi", "application/smil" }, - { "smil", "application/smil" }, - { "snd", "audio/basic" }, - { "so", "application/octet-stream" }, - { "spl", "application/x-futuresplash" }, - { "src", "application/x-wais-Source" }, - { "sv4cpio", "application/x-sv4cpio" }, - { "sv4crc", "application/x-sv4crc" }, - { "svg", "image/svg+xml" }, - { "swf", "application/x-shockwave-flash" }, - { "t", "application/x-troff" }, - { "tar", "application/x-tar" }, - { "tcl", "application/x-tcl" }, - { "tex", "application/x-tex" }, - { "texi", "application/x-texinfo" }, - { "texinfo", "application/x-texinfo" }, - { "tif", "image/tiff" }, - { "tiff", "image/tiff" }, - { "tr", "application/x-troff" }, - { "tsv", "text/tab-separated-values" }, - { "txt", "text/plain" }, - { "ustar", "application/x-ustar" }, - { "vcd", "application/x-cdlink" }, - { "vrml", "model/vrml" }, - { "vxml", "application/voicexml+xml" }, - { "wav", "audio/x-wav" }, - { "wbmp", "image/vnd.wap.wbmp" }, - { "wbmxl", "application/vnd.wap.wbxml" }, - { "wml", "text/vnd.wap.wml" }, - { "wmlc", "application/vnd.wap.wmlc" }, - { "wmls", "text/vnd.wap.wmlscript" }, - { "wmlsc", "application/vnd.wap.wmlscriptc" }, - { "wrl", "model/vrml" }, - { "xbm", "image/x-xbitmap" }, - { "xht", "application/xhtml+xml" }, - { "xhtml", "application/xhtml+xml" }, - { "xls", "application/vnd.ms-excel" }, - { "xml", "application/xml" }, - { "xpm", "image/x-xpixmap" }, - { "xsl", "application/xml" }, - { "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }, - { "xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template" }, - { "xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12" }, - { "xltm", "application/vnd.ms-excel.template.macroEnabled.12" }, - { "xlam", "application/vnd.ms-excel.addin.macroEnabled.12" }, - { "xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12" }, - { "xslt", "application/xslt+xml" }, - { "xul", "application/vnd.mozilla.xul+xml" }, - { "xwd", "image/x-xwindowdump" }, - { "xyz", "chemical/x-xyz" }, - { "zip", "application/zip" }, - }; - AVFile.MIMETypesDictionary = strs; - } - internal static string GetMIMEType(string fileName) - { - try - { - string str = Path.GetExtension(fileName).Remove(0, 1); - if (!AVFile.MIMETypesDictionary.ContainsKey(str)) - { - return "unknown/unknown"; - } - return AVFile.MIMETypesDictionary[str]; - } - catch - { - return "unknown/unknown"; - } - } - - /// - /// 根据 ObjectId 获取文件 - /// - /// 获取之后并没有实际执行下载,只是加载了文件的元信息以及物理地址(Url) - /// - public static Task GetFileWithObjectIdAsync(string objectId, CancellationToken cancellationToken) - { - string currentSessionToken = AVUser.CurrentSessionToken; - return FileController.GetAsync(objectId, currentSessionToken, cancellationToken).OnSuccess(_ => - { - var filestate = _.Result; - return new AVFile(filestate); - }); - } - - public static AVFile CreateWithoutData(string objectId) - { - return new AVFile(objectId); - } - - public static AVFile CreateWithState(FileState state) - { - return new AVFile(state); - } - - public static AVFile CreateWithData(string objectId,string name, string url,IDictionary metaData) - { - var fileState = new FileState(); - fileState.Name = name; - fileState.ObjectId = objectId; - fileState.Url = new Uri(url); - fileState.MetaData = metaData; - return CreateWithState(fileState); - } - /// - /// 根据 ObjectId 获取文件 - /// - /// 获取之后并没有实际执行下载,只是加载了文件的元信息以及物理地址(Url) - /// - public static Task GetFileWithObjectIdAsync(string objectId) - { - return GetFileWithObjectIdAsync(objectId, CancellationToken.None); - } - - internal void MergeFromJSON(IDictionary jsonData) - { - lock (this.mutex) - { - state.ObjectId = jsonData["objectId"] as string; - state.Url = new Uri(jsonData["url"] as string, UriKind.Absolute); - if (jsonData.ContainsKey("name")) - { - state.Name = jsonData["name"] as string; - } - if (jsonData.ContainsKey("metaData")) - { - state.MetaData = jsonData["metaData"] as Dictionary; - } - - } - } - - /// - /// 删除文件 - /// - /// Task - public Task DeleteAsync() - { - return DeleteAsync(CancellationToken.None); - } - internal Task DeleteAsync(CancellationToken cancellationToken) - { - return taskQueue.Enqueue(toAwait => DeleteAsync(toAwait, cancellationToken), - cancellationToken); - - } - internal Task DeleteAsync(Task toAwait, CancellationToken cancellationToken) - { - if (ObjectId == null) - { - return Task.FromResult(0); - } - - string sessionToken = AVUser.CurrentSessionToken; - - return toAwait.OnSuccess(_ => - { - return FileController.DeleteAsync(state, sessionToken, cancellationToken); - }).Unwrap().OnSuccess(_ => { }); - } - #endregion - } - - -} diff --git a/Storage/Source/Public/AVGeoDistance.cs b/Storage/Source/Public/AVGeoDistance.cs deleted file mode 100644 index e761658..0000000 --- a/Storage/Source/Public/AVGeoDistance.cs +++ /dev/null @@ -1,78 +0,0 @@ -namespace LeanCloud -{ - /// - /// Represents a distance between two AVGeoPoints. - /// - public struct AVGeoDistance - { - private const double EarthMeanRadiusKilometers = 6371.0; - private const double EarthMeanRadiusMiles = 3958.8; - - /// - /// Creates a AVGeoDistance. - /// - /// The distance in radians. - public AVGeoDistance(double radians) - : this() - { - Radians = radians; - } - - /// - /// Gets the distance in radians. - /// - public double Radians { get; private set; } - - /// - /// Gets the distance in miles. - /// - public double Miles - { - get - { - return Radians * EarthMeanRadiusMiles; - } - } - - /// - /// Gets the distance in kilometers. - /// - public double Kilometers - { - get - { - return Radians * EarthMeanRadiusKilometers; - } - } - - /// - /// Gets a AVGeoDistance from a number of miles. - /// - /// The number of miles. - /// A AVGeoDistance for the given number of miles. - public static AVGeoDistance FromMiles(double miles) - { - return new AVGeoDistance(miles / EarthMeanRadiusMiles); - } - - /// - /// Gets a AVGeoDistance from a number of kilometers. - /// - /// The number of kilometers. - /// A AVGeoDistance for the given number of kilometers. - public static AVGeoDistance FromKilometers(double kilometers) - { - return new AVGeoDistance(kilometers / EarthMeanRadiusKilometers); - } - - /// - /// Gets a AVGeoDistance from a number of radians. - /// - /// The number of radians. - /// A AVGeoDistance for the given number of radians. - public static AVGeoDistance FromRadians(double radians) - { - return new AVGeoDistance(radians); - } - } -} diff --git a/Storage/Source/Public/AVGeoPoint.cs b/Storage/Source/Public/AVGeoPoint.cs deleted file mode 100644 index 7ca0f57..0000000 --- a/Storage/Source/Public/AVGeoPoint.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using LeanCloud.Storage.Internal; - -namespace LeanCloud -{ - /// - /// AVGeoPoint represents a latitude / longitude point that may be associated - /// with a key in a AVObject or used as a reference point for geo queries. - /// This allows proximity-based queries on the key. - /// - /// Only one key in a class may contain a GeoPoint. - /// - public struct AVGeoPoint : IJsonConvertible - { - /// - /// Constructs a AVGeoPoint with the specified latitude and longitude. - /// - /// The point's latitude. - /// The point's longitude. - public AVGeoPoint(double latitude, double longitude) - : this() - { - Latitude = latitude; - Longitude = longitude; - } - - private double latitude; - /// - /// Gets or sets the latitude of the GeoPoint. Valid range is [-90, 90]. - /// Extremes should not be used. - /// - public double Latitude - { - get - { - return latitude; - } - set - { - if (value > 90 || value < -90) - { - throw new ArgumentOutOfRangeException("value", - "Latitude must be within the range [-90, 90]"); - } - latitude = value; - } - } - - private double longitude; - /// - /// Gets or sets the longitude. Valid range is [-180, 180]. - /// Extremes should not be used. - /// - public double Longitude - { - get - { - return longitude; - } - set - { - if (value > 180 || value < -180) - { - throw new ArgumentOutOfRangeException("value", - "Longitude must be within the range [-180, 180]"); - } - longitude = value; - } - } - - /// - /// Get the distance in radians between this point and another GeoPoint. This is the smallest angular - /// distance between the two points. - /// - /// GeoPoint describing the other point being measured against. - /// The distance in between the two points. - public AVGeoDistance DistanceTo(AVGeoPoint point) - { - double d2r = Math.PI / 180; // radian conversion factor - double lat1rad = Latitude * d2r; - double long1rad = longitude * d2r; - double lat2rad = point.Latitude * d2r; - double long2rad = point.Longitude * d2r; - double deltaLat = lat1rad - lat2rad; - double deltaLong = long1rad - long2rad; - double sinDeltaLatDiv2 = Math.Sin(deltaLat / 2); - double sinDeltaLongDiv2 = Math.Sin(deltaLong / 2); - // Square of half the straight line chord distance between both points. - // [0.0, 1.0] - double a = sinDeltaLatDiv2 * sinDeltaLatDiv2 + - Math.Cos(lat1rad) * Math.Cos(lat2rad) * sinDeltaLongDiv2 * sinDeltaLongDiv2; - a = Math.Min(1.0, a); - return new AVGeoDistance(2 * Math.Asin(Math.Sqrt(a))); - } - - IDictionary IJsonConvertible.ToJSON() - { - return new Dictionary - { - { "__type", "GeoPoint" }, - { "latitude", Latitude }, - { "longitude", Longitude } - }; - } - } -} diff --git a/Storage/Source/Public/AVObject.cs b/Storage/Source/Public/AVObject.cs deleted file mode 100644 index 0f9cde7..0000000 --- a/Storage/Source/Public/AVObject.cs +++ /dev/null @@ -1,2099 +0,0 @@ -using LeanCloud.Storage.Internal; -using LeanCloud.Utilities; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using System.Collections; - -namespace LeanCloud -{ - /// - /// The AVObject is a local representation of data that can be saved and - /// retrieved from the LeanCloud cloud. - /// - /// - /// The basic workflow for creating new data is to construct a new AVObject, - /// use the indexer to fill it with data, and then use SaveAsync() to persist to the - /// database. - /// - /// - /// The basic workflow for accessing existing data is to use a AVQuery - /// to specify which existing data to retrieve. - /// - /// - public class AVObject : IEnumerable>, INotifyPropertyChanged, INotifyPropertyUpdated, INotifyCollectionPropertyUpdated, IAVObject - { - private static readonly string AutoClassName = "_Automatic"; - -#if UNITY - private static readonly bool isCompiledByIL2CPP = AppDomain.CurrentDomain.FriendlyName.Equals("IL2CPP Root Domain"); -#else - private static readonly bool isCompiledByIL2CPP = false; -#endif - - - internal readonly object mutex = new object(); - - private readonly LinkedList> operationSetQueue = - new LinkedList>(); - private readonly IDictionary estimatedData = new Dictionary(); - - private static readonly ThreadLocal isCreatingPointer = new ThreadLocal(() => false); - - private bool hasBeenFetched; - private bool dirty; - internal TaskQueue taskQueue = new TaskQueue(); - - private IObjectState state; - internal void MutateState(Action func) - { - lock (mutex) - { - state = state.MutatedClone(func); - - // Refresh the estimated data. - RebuildEstimatedData(); - } - } - - public IObjectState State - { - get - { - return state; - } - } - - internal static IAVObjectController ObjectController - { - get - { - return AVPlugins.Instance.ObjectController; - } - } - - internal static IObjectSubclassingController SubclassingController - { - get - { - return AVPlugins.Instance.SubclassingController; - } - } - - public static string GetSubClassName() - { - return SubclassingController.GetClassName(typeof(TAVObject)); - } - - #region AVObject Creation - - /// - /// Constructor for use in AVObject subclasses. Subclasses must specify a AVClassName attribute. - /// - protected AVObject() - : this(AutoClassName) - { - } - - /// - /// Constructs a new AVObject with no data in it. A AVObject constructed in this way will - /// not have an ObjectId and will not persist to the database until - /// is called. - /// - /// - /// Class names must be alphanumerical plus underscore, and start with a letter. It is recommended - /// to name classes in CamelCaseLikeThis. - /// - /// The className for this AVObject. - public AVObject(string className) - { - // We use a ThreadLocal rather than passing a parameter so that createWithoutData can do the - // right thing with subclasses. It's ugly and terrible, but it does provide the development - // experience we generally want, so... yeah. Sorry to whomever has to deal with this in the - // future. I pinky-swear we won't make a habit of this -- you believe me, don't you? - var isPointer = isCreatingPointer.Value; - isCreatingPointer.Value = false; - - if (className == null) - { - throw new ArgumentException("You must specify a LeanCloud class name when creating a new AVObject."); - } - if (AutoClassName.Equals(className)) - { - className = SubclassingController.GetClassName(GetType()); - } - // If this is supposed to be created by a factory but wasn't, throw an exception - if (!SubclassingController.IsTypeValid(className, GetType())) - { - throw new ArgumentException( - "You must create this type of AVObject using AVObject.Create() or the proper subclass."); - } - state = new MutableObjectState - { - ClassName = className - }; - OnPropertyChanged("ClassName"); - - operationSetQueue.AddLast(new Dictionary()); - if (!isPointer) - { - hasBeenFetched = true; - IsDirty = true; - SetDefaultValues(); - } - else - { - IsDirty = false; - hasBeenFetched = false; - } - } - - /// - /// Creates a new AVObject based upon a class name. If the class name is a special type (e.g. - /// for ), then the appropriate type of AVObject is returned. - /// - /// The class of object to create. - /// A new AVObject for the given class name. - public static AVObject Create(string className) - { - return SubclassingController.Instantiate(className); - } - - /// - /// Creates a reference to an existing AVObject for use in creating associations between - /// AVObjects. Calling on this object will return - /// false until has been called. - /// No network request will be made. - /// - /// The object's class. - /// The object id for the referenced object. - /// A AVObject without data. - public static AVObject CreateWithoutData(string className, string objectId) - { - isCreatingPointer.Value = true; - try - { - var result = SubclassingController.Instantiate(className); - result.ObjectId = objectId; - result.IsDirty = false; - if (result.IsDirty) - { - throw new InvalidOperationException( - "A AVObject subclass default constructor must not make changes to the object that cause it to be dirty."); - } - return result; - } - finally - { - isCreatingPointer.Value = false; - } - } - - /// - /// Creates a new AVObject based upon a given subclass type. - /// - /// A new AVObject for the given class name. - public static T Create() where T : AVObject - { - return (T)SubclassingController.Instantiate(SubclassingController.GetClassName(typeof(T))); - } - - /// - /// Creates a reference to an existing AVObject for use in creating associations between - /// AVObjects. Calling on this object will return - /// false until has been called. - /// No network request will be made. - /// - /// The object id for the referenced object. - /// A AVObject without data. - public static T CreateWithoutData(string objectId) where T : AVObject - { - return (T)CreateWithoutData(SubclassingController.GetClassName(typeof(T)), objectId); - } - - /// - /// restore a AVObject of subclass instance from IObjectState. - /// - /// IObjectState after encode from Dictionary. - /// The name of the subclass. - public static T FromState(IObjectState state, string defaultClassName) where T : AVObject - { - string className = state.ClassName ?? defaultClassName; - - T obj = (T)CreateWithoutData(className, state.ObjectId); - obj.HandleFetchResult(state); - - return obj; - } - - #endregion - - public static IDictionary GetPropertyMappings(string className) - { - return SubclassingController.GetPropertyMappings(className); - } - - private static string GetFieldForPropertyName(string className, string propertyName) - { - SubclassingController.GetPropertyMappings(className).TryGetValue(propertyName, out string fieldName); - return fieldName; - } - - /// - /// Sets the value of a property based upon its associated AVFieldName attribute. - /// - /// The new value. - /// The name of the property. - /// The type for the property. - protected virtual void SetProperty(T value, -#if !UNITY - [CallerMemberName] string propertyName = null -#else - string propertyName -#endif -) - { - this[GetFieldForPropertyName(ClassName, propertyName)] = value; - } - - /// - /// Gets a relation for a property based upon its associated AVFieldName attribute. - /// - /// The AVRelation for the property. - /// The name of the property. - /// The AVObject subclass type of the AVRelation. - protected AVRelation GetRelationProperty( -#if !UNITY -[CallerMemberName] string propertyName = null -#else -string propertyName -#endif -) where T : AVObject - { - return GetRelation(GetFieldForPropertyName(ClassName, propertyName)); - } - - /// - /// Gets the value of a property based upon its associated AVFieldName attribute. - /// - /// The value of the property. - /// The name of the property. - /// The return type of the property. - protected virtual T GetProperty( -#if !UNITY -[CallerMemberName] string propertyName = null -#else -string propertyName -#endif -) - { - return GetProperty(default(T), propertyName); - } - - /// - /// Gets the value of a property based upon its associated AVFieldName attribute. - /// - /// The value of the property. - /// The value to return if the property is not present on the AVObject. - /// The name of the property. - /// The return type of the property. - protected virtual T GetProperty(T defaultValue, -#if !UNITY - [CallerMemberName] string propertyName = null -#else - string propertyName -#endif -) - { - T result; - if (TryGetValue(GetFieldForPropertyName(ClassName, propertyName), out result)) - { - return result; - } - return defaultValue; - } - - /// - /// Allows subclasses to set values for non-pointer construction. - /// - internal virtual void SetDefaultValues() - { - } - - /// - /// Registers a custom subclass type with the LeanCloud SDK, enabling strong-typing of those AVObjects whenever - /// they appear. Subclasses must specify the AVClassName attribute, have a default constructor, and properties - /// backed by AVObject fields should have AVFieldName attributes supplied. - /// - /// The AVObject subclass type to register. - public static void RegisterSubclass() where T : AVObject, new() - { - SubclassingController.RegisterSubclass(typeof(T)); - } - - internal static void UnregisterSubclass() where T : AVObject, new() - { - SubclassingController.UnregisterSubclass(typeof(T)); - } - - /// - /// Clears any changes to this object made since the last call to . - /// - public void Revert() - { - lock (mutex) - { - bool wasDirty = CurrentOperations.Count > 0; - if (wasDirty) - { - CurrentOperations.Clear(); - RebuildEstimatedData(); - OnPropertyChanged("IsDirty"); - } - } - } - - internal virtual void HandleFetchResult(IObjectState serverState) - { - lock (mutex) - { - MergeFromServer(serverState); - } - } - - internal void HandleFailedSave( - IDictionary operationsBeforeSave) - { - lock (mutex) - { - var opNode = operationSetQueue.Find(operationsBeforeSave); - var nextOperations = opNode.Next.Value; - bool wasDirty = nextOperations.Count > 0; - operationSetQueue.Remove(opNode); - // Merge the data from the failed save into the next save. - foreach (var pair in operationsBeforeSave) - { - var operation1 = pair.Value; - IAVFieldOperation operation2 = null; - nextOperations.TryGetValue(pair.Key, out operation2); - if (operation2 != null) - { - operation2 = operation2.MergeWithPrevious(operation1); - } - else - { - operation2 = operation1; - } - nextOperations[pair.Key] = operation2; - } - if (!wasDirty && nextOperations == CurrentOperations && operationsBeforeSave.Count > 0) - { - OnPropertyChanged("IsDirty"); - } - } - } - - internal virtual void HandleSave(IObjectState serverState) - { - lock (mutex) - { - var operationsBeforeSave = operationSetQueue.First.Value; - operationSetQueue.RemoveFirst(); - - // Merge the data from the save and the data from the server into serverData. - //MutateState(mutableClone => - //{ - // mutableClone.Apply(operationsBeforeSave); - //}); - state = state.MutatedClone((objectState) => objectState.Apply(operationsBeforeSave)); - MergeFromServer(serverState); - } - } - - public virtual void MergeFromServer(IObjectState serverState) - { - // Make a new serverData with fetched values. - var newServerData = serverState.ToDictionary(t => t.Key, t => t.Value); - - lock (mutex) - { - // Trigger handler based on serverState - if (serverState.ObjectId != null) - { - // If the objectId is being merged in, consider this object to be fetched. - hasBeenFetched = true; - OnPropertyChanged("IsDataAvailable"); - } - - if (serverState.UpdatedAt != null) - { - OnPropertyChanged("UpdatedAt"); - } - - if (serverState.CreatedAt != null) - { - OnPropertyChanged("CreatedAt"); - } - - // We cache the fetched object because subsequent Save operation might flush - // the fetched objects into Pointers. - IDictionary fetchedObject = CollectFetchedObjects(); - - foreach (var pair in serverState) - { - var value = pair.Value; - if (value is AVObject) - { - // Resolve fetched object. - var avObject = value as AVObject; - if (fetchedObject.ContainsKey(avObject.ObjectId)) - { - value = fetchedObject[avObject.ObjectId]; - } - } - newServerData[pair.Key] = value; - } - - IsDirty = false; - serverState = serverState.MutatedClone(mutableClone => - { - mutableClone.ServerData = newServerData; - }); - MutateState(mutableClone => - { - mutableClone.Apply(serverState); - }); - } - } - - internal void MergeFromObject(AVObject other) - { - lock (mutex) - { - // If they point to the same instance, we don't need to merge - if (this == other) - { - return; - } - } - - // Clear out any changes on this object. - if (operationSetQueue.Count != 1) - { - throw new InvalidOperationException("Attempt to MergeFromObject during save."); - } - operationSetQueue.Clear(); - foreach (var operationSet in other.operationSetQueue) - { - operationSetQueue.AddLast(operationSet.ToDictionary(entry => entry.Key, - entry => entry.Value)); - } - - lock (mutex) - { - state = other.State; - } - RebuildEstimatedData(); - } - - private bool HasDirtyChildren - { - get - { - lock (mutex) - { - return FindUnsavedChildren().FirstOrDefault() != null; - } - } - } - - /// - /// Flattens dictionaries and lists into a single enumerable of all contained objects - /// that can then be queried over. - /// - /// The root of the traversal - /// Whether to traverse into AVObjects' children - /// Whether to include the root in the result - /// - internal static IEnumerable DeepTraversal( - object root, bool traverseAVObjects = false, bool yieldRoot = false) - { - var items = DeepTraversalInternal(root, - traverseAVObjects, - new HashSet(new IdentityEqualityComparer())); - if (yieldRoot) - { - return new[] { root }.Concat(items); - } - else - { - return items; - } - } - - private static IEnumerable DeepTraversalInternal( - object root, bool traverseAVObjects, ICollection seen) - { - seen.Add(root); - var itemsToVisit = isCompiledByIL2CPP ? (System.Collections.IEnumerable)null : (IEnumerable)null; - var dict = Conversion.As>(root); - if (dict != null) - { - itemsToVisit = dict.Values; - } - else - { - var list = Conversion.As>(root); - if (list != null) - { - itemsToVisit = list; - } - else if (traverseAVObjects) - { - var obj = root as AVObject; - if (obj != null) - { - itemsToVisit = obj.Keys.ToList().Select(k => obj[k]); - } - } - } - if (itemsToVisit != null) - { - foreach (var i in itemsToVisit) - { - if (!seen.Contains(i)) - { - yield return i; - var children = DeepTraversalInternal(i, traverseAVObjects, seen); - foreach (var child in children) - { - yield return child; - } - } - } - } - } - - private IEnumerable FindUnsavedChildren() - { - return DeepTraversal(estimatedData) - .OfType() - .Where(o => o.IsDirty); - } - - /// - /// Deep traversal of this object to grab a copy of any object referenced by this object. - /// These instances may have already been fetched, and we don't want to lose their data when - /// refreshing or saving. - /// - /// Map of objectId to AVObject which have been fetched. - private IDictionary CollectFetchedObjects() - { - return DeepTraversal(estimatedData) - .OfType() - .Where(o => o.ObjectId != null && o.IsDataAvailable) - .GroupBy(o => o.ObjectId) - .ToDictionary(group => group.Key, group => group.Last()); - } - - public static IDictionary ToJSONObjectForSaving( - IDictionary operations) - { - var result = new Dictionary(); - foreach (var pair in operations) - { - // AVRPCSerialize the data - var operation = pair.Value; - - result[pair.Key] = PointerOrLocalIdEncoder.Instance.Encode(operation); - } - return result; - } - - internal IDictionary EncodeForSaving(IDictionary data) - { - var result = new Dictionary(); - lock (this.mutex) - { - foreach (var key in data.Keys) - { - var value = data[key]; - result.Add(key, PointerOrLocalIdEncoder.Instance.Encode(value)); - } - } - - return result; - } - - - internal IDictionary ServerDataToJSONObjectForSerialization() - { - return PointerOrLocalIdEncoder.Instance.Encode(state.ToDictionary(t => t.Key, t => t.Value)) - as IDictionary; - } - - #region Save Object(s) - - /// - /// Pushes new operations onto the queue and returns the current set of operations. - /// - public IDictionary StartSave() - { - lock (mutex) - { - var currentOperations = CurrentOperations; - operationSetQueue.AddLast(new Dictionary()); - OnPropertyChanged("IsDirty"); - return currentOperations; - } - } - - protected virtual Task SaveAsync(Task toAwait, - CancellationToken cancellationToken) - { - IDictionary currentOperations = null; - if (!IsDirty) - { - return Task.FromResult(0); - } - - Task deepSaveTask; - string sessionToken; - lock (mutex) - { - // Get the JSON representation of the object. - currentOperations = StartSave(); - - sessionToken = AVUser.CurrentSessionToken; - - deepSaveTask = DeepSaveAsync(estimatedData, sessionToken, cancellationToken); - } - - return deepSaveTask.OnSuccess(_ => - { - return toAwait; - }).Unwrap().OnSuccess(_ => - { - return ObjectController.SaveAsync(state, - currentOperations, - sessionToken, - cancellationToken); - }).Unwrap().ContinueWith(t => - { - if (t.IsFaulted || t.IsCanceled) - { - HandleFailedSave(currentOperations); - } - else - { - var serverState = t.Result; - HandleSave(serverState); - } - return t; - }).Unwrap(); - } - - /// - /// Saves this object to the server. - /// - public virtual Task SaveAsync() - { - return SaveAsync(CancellationToken.None); - } - - /// - /// Saves this object to the server. - /// - /// The cancellation token. - public virtual Task SaveAsync(CancellationToken cancellationToken) - { - return taskQueue.Enqueue(toAwait => SaveAsync(toAwait, cancellationToken), - cancellationToken); - } - - internal virtual Task FetchAsyncInternal( - Task toAwait, - IDictionary queryString, - CancellationToken cancellationToken) - { - return toAwait.OnSuccess(_ => - { - if (ObjectId == null) - { - throw new InvalidOperationException("Cannot refresh an object that hasn't been saved to the server."); - } - if (queryString == null) - { - queryString = new Dictionary(); - } - - return ObjectController.FetchAsync(state, queryString, AVUser.CurrentSessionToken, cancellationToken); - }).Unwrap().OnSuccess(t => - { - HandleFetchResult(t.Result); - return this; - }); - } - - private static Task DeepSaveAsync(object obj, string sessionToken, CancellationToken cancellationToken) - { - var objects = new List(); - CollectDirtyChildren(obj, objects); - - var uniqueObjects = new HashSet(objects, - new IdentityEqualityComparer()); - - var saveDirtyFileTasks = DeepTraversal(obj, true) - .OfType() - .Where(f => f.IsDirty) - .Select(f => f.SaveAsync(cancellationToken)).ToList(); - - return Task.WhenAll(saveDirtyFileTasks).OnSuccess(_ => - { - IEnumerable remaining = new List(uniqueObjects); - return InternalExtensions.WhileAsync(() => Task.FromResult(remaining.Any()), () => - { - // Partition the objects into two sets: those that can be saved immediately, - // and those that rely on other objects to be created first. - var current = (from item in remaining - where item.CanBeSerialized - select item).ToList(); - var nextBatch = (from item in remaining - where !item.CanBeSerialized - select item).ToList(); - remaining = nextBatch; - - if (current.Count == 0) - { - // We do cycle-detection when building the list of objects passed to this - // function, so this should never get called. But we should check for it - // anyway, so that we get an exception instead of an infinite loop. - throw new InvalidOperationException( - "Unable to save a AVObject with a relation to a cycle."); - } - - // Save all of the objects in current. - return AVObject.EnqueueForAll(current, toAwait => - { - return toAwait.OnSuccess(__ => - { - var states = (from item in current - select item.state).ToList(); - var operationsList = (from item in current - select item.StartSave()).ToList(); - - var saveTasks = ObjectController.SaveAllAsync(states, - operationsList, - sessionToken, - cancellationToken); - - return Task.WhenAll(saveTasks).ContinueWith(t => - { - if (t.IsFaulted || t.IsCanceled) - { - foreach (var pair in current.Zip(operationsList, (item, ops) => new { item, ops })) - { - pair.item.HandleFailedSave(pair.ops); - } - } - else - { - var serverStates = t.Result; - foreach (var pair in current.Zip(serverStates, (item, state) => new { item, state })) - { - pair.item.HandleSave(pair.state); - } - } - cancellationToken.ThrowIfCancellationRequested(); - return t; - }).Unwrap(); - }).Unwrap().OnSuccess(t => (object)null); - }, cancellationToken); - }); - }).Unwrap(); - } - - /// - /// Saves each object in the provided list. - /// - /// The objects to save. - public static Task SaveAllAsync(IEnumerable objects) where T : AVObject - { - return SaveAllAsync(objects, CancellationToken.None); - } - - /// - /// Saves each object in the provided list. - /// - /// The objects to save. - /// The cancellation token. - public static Task SaveAllAsync( - IEnumerable objects, CancellationToken cancellationToken) where T : AVObject - { - return DeepSaveAsync(objects.ToList(), AVUser.CurrentSessionToken, cancellationToken); - } - - #endregion - - #region Fetch Object(s) - - /// - /// Fetches this object with the data from the server. - /// - /// The cancellation token. - internal Task FetchAsyncInternal(CancellationToken cancellationToken) - { - return FetchAsyncInternal(null, cancellationToken); - } - - internal Task FetchAsyncInternal(IDictionary queryString, CancellationToken cancellationToken) - { - return taskQueue.Enqueue(toAwait => FetchAsyncInternal(toAwait, queryString, cancellationToken), - cancellationToken); - } - - internal Task FetchIfNeededAsyncInternal( - Task toAwait, CancellationToken cancellationToken) - { - if (!IsDataAvailable) - { - return FetchAsyncInternal(toAwait, null, cancellationToken); - } - return Task.FromResult(this); - } - - /// - /// If this AVObject has not been fetched (i.e. returns - /// false), fetches this object with the data from the server. - /// - /// The cancellation token. - internal Task FetchIfNeededAsyncInternal(CancellationToken cancellationToken) - { - return taskQueue.Enqueue(toAwait => FetchIfNeededAsyncInternal(toAwait, cancellationToken), - cancellationToken); - } - - /// - /// Fetches all of the objects that don't have data in the provided list. - /// - /// The list passed in for convenience. - public static Task> FetchAllIfNeededAsync( - IEnumerable objects) where T : AVObject - { - return FetchAllIfNeededAsync(objects, CancellationToken.None); - } - - /// - /// Fetches all of the objects that don't have data in the provided list. - /// - /// The objects to fetch. - /// The cancellation token. - /// The list passed in for convenience. - public static Task> FetchAllIfNeededAsync( - IEnumerable objects, CancellationToken cancellationToken) where T : AVObject - { - return AVObject.EnqueueForAll(objects.Cast(), (Task toAwait) => - { - return FetchAllInternalAsync(objects, false, toAwait, cancellationToken); - }, cancellationToken); - } - - /// - /// Fetches all of the objects in the provided list. - /// - /// The objects to fetch. - /// The list passed in for convenience. - public static Task> FetchAllAsync( - IEnumerable objects) where T : AVObject - { - return FetchAllAsync(objects, CancellationToken.None); - } - - /// - /// Fetches all of the objects in the provided list. - /// - /// The objects to fetch. - /// The cancellation token. - /// The list passed in for convenience. - public static Task> FetchAllAsync( - IEnumerable objects, CancellationToken cancellationToken) where T : AVObject - { - return AVObject.EnqueueForAll(objects.Cast(), (Task toAwait) => - { - return FetchAllInternalAsync(objects, true, toAwait, cancellationToken); - }, cancellationToken); - } - - /// - /// Fetches all of the objects in the list. - /// - /// The objects to fetch. - /// If false, only objects without data will be fetched. - /// A task to await before starting. - /// The cancellation token. - /// The list passed in for convenience. - private static Task> FetchAllInternalAsync( - IEnumerable objects, bool force, Task toAwait, CancellationToken cancellationToken) where T : AVObject - { - return toAwait.OnSuccess(_ => - { - if (objects.Any(obj => { return obj.state.ObjectId == null; })) - { - throw new InvalidOperationException("You cannot fetch objects that haven't already been saved."); - } - - var objectsToFetch = (from obj in objects - where force || !obj.IsDataAvailable - select obj).ToList(); - - if (objectsToFetch.Count == 0) - { - return Task.FromResult(objects); - } - - // Do one Find for each class. - var findsByClass = - (from obj in objectsToFetch - group obj.ObjectId by obj.ClassName into classGroup - where classGroup.Count() > 0 - select new - { - ClassName = classGroup.Key, - FindTask = new AVQuery(classGroup.Key) - .WhereContainedIn("objectId", classGroup) - .FindAsync(cancellationToken) - }).ToDictionary(pair => pair.ClassName, pair => pair.FindTask); - - // Wait for all the Finds to complete. - return Task.WhenAll(findsByClass.Values.ToList()).OnSuccess(__ => - { - if (cancellationToken.IsCancellationRequested) - { - return objects; - } - - // Merge the data from the Finds into the input objects. - var pairs = from obj in objectsToFetch - from result in findsByClass[obj.ClassName].Result - where result.ObjectId == obj.ObjectId - select new { obj, result }; - foreach (var pair in pairs) - { - pair.obj.MergeFromObject(pair.result); - pair.obj.hasBeenFetched = true; - } - - return objects; - }); - }).Unwrap(); - } - - #endregion - - #region Delete Object - - internal Task DeleteAsync(Task toAwait, CancellationToken cancellationToken) - { - if (ObjectId == null) - { - return Task.FromResult(0); - } - - string sessionToken = AVUser.CurrentSessionToken; - - return toAwait.OnSuccess(_ => - { - return ObjectController.DeleteAsync(State, sessionToken, cancellationToken); - }).Unwrap().OnSuccess(_ => IsDirty = true); - } - - /// - /// Deletes this object on the server. - /// - public Task DeleteAsync() - { - return DeleteAsync(CancellationToken.None); - } - - /// - /// Deletes this object on the server. - /// - /// The cancellation token. - public Task DeleteAsync(CancellationToken cancellationToken) - { - return taskQueue.Enqueue(toAwait => DeleteAsync(toAwait, cancellationToken), - cancellationToken); - } - - /// - /// Deletes each object in the provided list. - /// - /// The objects to delete. - public static Task DeleteAllAsync(IEnumerable objects) where T : AVObject - { - return DeleteAllAsync(objects, CancellationToken.None); - } - - /// - /// Deletes each object in the provided list. - /// - /// The objects to delete. - /// The cancellation token. - public static Task DeleteAllAsync( - IEnumerable objects, CancellationToken cancellationToken) where T : AVObject - { - var uniqueObjects = new HashSet(objects.OfType().ToList(), - new IdentityEqualityComparer()); - - return AVObject.EnqueueForAll(uniqueObjects, toAwait => - { - var states = uniqueObjects.Select(t => t.state).ToList(); - return toAwait.OnSuccess(_ => - { - var deleteTasks = ObjectController.DeleteAllAsync(states, - AVUser.CurrentSessionToken, - cancellationToken); - - return Task.WhenAll(deleteTasks); - }).Unwrap().OnSuccess(t => - { - // Dirty all objects in memory. - foreach (var obj in uniqueObjects) - { - obj.IsDirty = true; - } - - return (object)null; - }); - }, cancellationToken); - } - - #endregion - - private static void CollectDirtyChildren(object node, - IList dirtyChildren, - ICollection seen, - ICollection seenNew) - { - foreach (var obj in DeepTraversal(node).OfType()) - { - ICollection scopedSeenNew; - // Check for cycles of new objects. Any such cycle means it will be impossible to save - // this collection of objects, so throw an exception. - if (obj.ObjectId != null) - { - scopedSeenNew = new HashSet(new IdentityEqualityComparer()); - } - else - { - if (seenNew.Contains(obj)) - { - throw new InvalidOperationException("Found a circular dependency while saving"); - } - scopedSeenNew = new HashSet(seenNew, new IdentityEqualityComparer()); - scopedSeenNew.Add(obj); - } - - // Check for cycles of any object. If this occurs, then there's no problem, but - // we shouldn't recurse any deeper, because it would be an infinite recursion. - if (seen.Contains(obj)) - { - return; - } - seen.Add(obj); - - // Recurse into this object's children looking for dirty children. - // We only need to look at the child object's current estimated data, - // because that's the only data that might need to be saved now. - CollectDirtyChildren(obj.estimatedData, dirtyChildren, seen, scopedSeenNew); - - if (obj.CheckIsDirty(false)) - { - dirtyChildren.Add(obj); - } - } - } - - /// - /// Helper version of CollectDirtyChildren so that callers don't have to add the internally - /// used parameters. - /// - private static void CollectDirtyChildren(object node, IList dirtyChildren) - { - CollectDirtyChildren(node, - dirtyChildren, - new HashSet(new IdentityEqualityComparer()), - new HashSet(new IdentityEqualityComparer())); - } - - /// - /// Returns true if the given object can be serialized for saving as a value - /// that is pointed to by a AVObject. - /// - private static bool CanBeSerializedAsValue(object value) - { - return DeepTraversal(value, yieldRoot: true) - .OfType() - .All(o => o.ObjectId != null); - } - - private bool CanBeSerialized - { - get - { - // This method is only used for batching sets of objects for saveAll - // and when saving children automatically. Since it's only used to - // determine whether or not save should be called on them, it only - // needs to examine their current values, so we use estimatedData. - lock (mutex) - { - return CanBeSerializedAsValue(estimatedData); - } - } - } - - /// - /// Adds a task to the queue for all of the given objects. - /// - private static Task EnqueueForAll(IEnumerable objects, - Func> taskStart, CancellationToken cancellationToken) - { - // The task that will be complete when all of the child queues indicate they're ready to start. - var readyToStart = new TaskCompletionSource(); - - // First, we need to lock the mutex for the queue for every object. We have to hold this - // from at least when taskStart() is called to when obj.taskQueue enqueue is called, so - // that saves actually get executed in the order they were setup by taskStart(). - // The locks have to be sorted so that we always acquire them in the same order. - // Otherwise, there's some risk of deadlock. - var lockSet = new LockSet(objects.Select(o => o.taskQueue.Mutex)); - - lockSet.Enter(); - try - { - - // The task produced by taskStart. By running this immediately, we allow everything prior - // to toAwait to run before waiting for all of the queues on all of the objects. - Task fullTask = taskStart(readyToStart.Task); - - // Add fullTask to each of the objects' queues. - var childTasks = new List(); - foreach (AVObject obj in objects) - { - obj.taskQueue.Enqueue((Task task) => - { - childTasks.Add(task); - return fullTask; - }, cancellationToken); - } - - // When all of the objects' queues are ready, signal fullTask that it's ready to go on. - Task.WhenAll(childTasks.ToArray()).ContinueWith((Task task) => - { - readyToStart.SetResult(null); - }); - - return fullTask; - } - finally - { - lockSet.Exit(); - } - } - - /// - /// Removes a key from the object's data if it exists. - /// - /// The key to remove. - public virtual void Remove(string key) - { - lock (mutex) - { - CheckKeyIsMutable(key); - - PerformOperation(key, AVDeleteOperation.Instance); - } - } - - - private IEnumerable ApplyOperations(IDictionary operations, - IDictionary map) - { - List appliedKeys = new List(); - lock (mutex) - { - foreach (var pair in operations) - { - object oldValue; - map.TryGetValue(pair.Key, out oldValue); - var newValue = pair.Value.Apply(oldValue, pair.Key); - if (newValue != AVDeleteOperation.DeleteToken) - { - map[pair.Key] = newValue; - } - else - { - map.Remove(pair.Key); - } - appliedKeys.Add(pair.Key); - } - } - return appliedKeys; - } - - /// - /// Regenerates the estimatedData map from the serverData and operations. - /// - internal void RebuildEstimatedData() - { - IEnumerable changedKeys = null; - - lock (mutex) - { - //estimatedData.Clear(); - List converdKeys = new List(); - foreach (var item in state) - { - var key = item.Key; - var value = item.Value; - if (!estimatedData.ContainsKey(key)) - { - converdKeys.Add(key); - } - else - { - var oldValue = estimatedData[key]; - if (oldValue != value) - { - converdKeys.Add(key); - } - estimatedData.Remove(key); - } - estimatedData.Add(item); - } - changedKeys = converdKeys; - foreach (var operations in operationSetQueue) - { - var appliedKeys = ApplyOperations(operations, estimatedData); - changedKeys = converdKeys.Concat(appliedKeys); - } - // We've just applied a bunch of operations to estimatedData which - // may have changed all of its keys. Notify of all keys and properties - // mapped to keys being changed. - OnFieldsChanged(changedKeys); - } - } - - /// - /// PerformOperation is like setting a value at an index, but instead of - /// just taking a new value, it takes a AVFieldOperation that modifies the value. - /// - internal void PerformOperation(string key, IAVFieldOperation operation) - { - lock (mutex) - { - var ifDirtyBeforePerform = this.IsDirty; - object oldValue; - estimatedData.TryGetValue(key, out oldValue); - object newValue = operation.Apply(oldValue, key); - if (newValue != AVDeleteOperation.DeleteToken) - { - estimatedData[key] = newValue; - } - else - { - estimatedData.Remove(key); - } - - IAVFieldOperation oldOperation; - bool wasDirty = CurrentOperations.Count > 0; - CurrentOperations.TryGetValue(key, out oldOperation); - var newOperation = operation.MergeWithPrevious(oldOperation); - CurrentOperations[key] = newOperation; - if (!wasDirty) - { - OnPropertyChanged("IsDirty"); - if (ifDirtyBeforePerform != wasDirty) - { - OnPropertyUpdated("IsDirty", ifDirtyBeforePerform, wasDirty); - } - } - - OnFieldsChanged(new[] { key }); - OnPropertyUpdated(key, oldValue, newValue); - } - } - - /// - /// Override to run validations on key/value pairs. Make sure to still - /// call the base version. - /// - internal virtual void OnSettingValue(ref string key, ref object value) - { - if (key == null) - { - throw new ArgumentNullException("key"); - } - } - - /// - /// Gets or sets a value on the object. It is recommended to name - /// keys in partialCamelCaseLikeThis. - /// - /// The key for the object. Keys must be alphanumeric plus underscore - /// and start with a letter. - /// The property is - /// retrieved and is not found. - /// The value for the key. - virtual public object this[string key] - { - get - { - lock (mutex) - { - CheckGetAccess(key); - - var value = estimatedData[key]; - - // A relation may be deserialized without a parent or key. Either way, - // make sure it's consistent. - var relation = value as AVRelationBase; - if (relation != null) - { - relation.EnsureParentAndKey(this, key); - } - - return value; - } - } - set - { - lock (mutex) - { - CheckKeyIsMutable(key); - - Set(key, value); - } - } - } - - /// - /// Perform Set internally which is not gated by mutability check. - /// - /// key for the object. - /// the value for the key. - internal void Set(string key, object value) - { - lock (mutex) - { - OnSettingValue(ref key, ref value); - - if (!AVEncoder.IsValidType(value)) - { - throw new ArgumentException("Invalid type for value: " + value.GetType().ToString()); - } - - PerformOperation(key, new AVSetOperation(value)); - } - } - - internal void SetIfDifferent(string key, T value) - { - T current; - bool hasCurrent = TryGetValue(key, out current); - if (value == null) - { - if (hasCurrent) - { - PerformOperation(key, AVDeleteOperation.Instance); - } - return; - } - if (!hasCurrent || !value.Equals(current)) - { - Set(key, value); - } - } - - #region Atomic Increment - - /// - /// Atomically increments the given key by 1. - /// - /// The key to increment. - public void Increment(string key) - { - Increment(key, 1); - } - - /// - /// Atomically increments the given key by the given number. - /// - /// The key to increment. - /// The amount to increment by. - public void Increment(string key, long amount) - { - lock (mutex) - { - CheckKeyIsMutable(key); - - PerformOperation(key, new AVIncrementOperation(amount)); - } - } - - /// - /// Atomically increments the given key by the given number. - /// - /// The key to increment. - /// The amount to increment by. - public void Increment(string key, double amount) - { - lock (mutex) - { - CheckKeyIsMutable(key); - - PerformOperation(key, new AVIncrementOperation(amount)); - } - } - - #endregion - - /// - /// Atomically adds an object to the end of the list associated with the given key. - /// - /// The key. - /// The object to add. - public void AddToList(string key, object value) - { - AddRangeToList(key, new[] { value }); - } - - /// - /// Atomically adds objects to the end of the list associated with the given key. - /// - /// The key. - /// The objects to add. - public void AddRangeToList(string key, IEnumerable values) - { - lock (mutex) - { - CheckKeyIsMutable(key); - - PerformOperation(key, new AVAddOperation(values.Cast())); - - OnCollectionPropertyUpdated(key, NotifyCollectionUpdatedAction.Add, null, values); - } - } - - /// - /// Atomically adds an object to the end of the list associated with the given key, - /// only if it is not already present in the list. The position of the insert is not - /// guaranteed. - /// - /// The key. - /// The object to add. - public void AddUniqueToList(string key, object value) - { - AddRangeUniqueToList(key, new object[] { value }); - } - - /// - /// Atomically adds objects to the end of the list associated with the given key, - /// only if they are not already present in the list. The position of the inserts are not - /// guaranteed. - /// - /// The key. - /// The objects to add. - public void AddRangeUniqueToList(string key, IEnumerable values) - { - lock (mutex) - { - CheckKeyIsMutable(key); - - PerformOperation(key, new AVAddUniqueOperation(values.Cast())); - } - } - - /// - /// Atomically removes all instances of the objects in - /// from the list associated with the given key. - /// - /// The key. - /// The objects to remove. - public void RemoveAllFromList(string key, IEnumerable values) - { - lock (mutex) - { - CheckKeyIsMutable(key); - - PerformOperation(key, new AVRemoveOperation(values.Cast())); - - OnCollectionPropertyUpdated(key, NotifyCollectionUpdatedAction.Remove, values, null); - } - } - - /// - /// Returns whether this object has a particular key. - /// - /// The key to check for - public bool ContainsKey(string key) - { - lock (mutex) - { - return estimatedData.ContainsKey(key); - } - } - - /// - /// Gets a value for the key of a particular type. - /// The type to convert the value to. Supported types are - /// AVObject and its descendents, LeanCloud types such as AVRelation and AVGeopoint, - /// primitive types,IList<T>, IDictionary<string, T>, and strings. - /// The key of the element to get. - /// The property is - /// retrieved and is not found. - /// - public T Get(string key) - { - return Conversion.To(this[key]); - } - - /// - /// Access or create a Relation value for a key. - /// - /// The type of object to create a relation for. - /// The key for the relation field. - /// A AVRelation for the key. - public AVRelation GetRelation(string key) where T : AVObject - { - // All the sanity checking is done when add or remove is called. - AVRelation relation = null; - TryGetValue(key, out relation); - return relation ?? new AVRelation(this, key); - } - - /// - /// Get relation revserse query. - /// - /// AVObject - /// parent className - /// key - /// - public AVQuery GetRelationRevserseQuery(string parentClassName, string key) where T : AVObject - { - if (string.IsNullOrEmpty(parentClassName)) - { - throw new ArgumentNullException("parentClassName", "can not query a relation without parentClassName."); - } - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentNullException("key", "can not query a relation without key."); - } - return new AVQuery(parentClassName).WhereEqualTo(key, this); - } - - /// - /// Populates result with the value for the key, if possible. - /// - /// The desired type for the value. - /// The key to retrieve a value for. - /// The value for the given key, converted to the - /// requested type, or null if unsuccessful. - /// true if the lookup and conversion succeeded, otherwise - /// false. - public virtual bool TryGetValue(string key, out T result) - { - lock (mutex) - { - if (ContainsKey(key)) - { - try - { - var temp = Conversion.To(this[key]); - result = temp; - return true; - } - catch (InvalidCastException) - { - result = default(T); - return false; - } - } - result = default(T); - return false; - } - } - - /// - /// Gets whether the AVObject has been fetched. - /// - public bool IsDataAvailable - { - get - { - lock (mutex) - { - return hasBeenFetched; - } - } - } - - private bool CheckIsDataAvailable(string key) - { - lock (mutex) - { - return IsDataAvailable || estimatedData.ContainsKey(key); - } - } - - private void CheckGetAccess(string key) - { - lock (mutex) - { - if (!CheckIsDataAvailable(key)) - { - throw new InvalidOperationException( - "AVObject has no data for this key. Call FetchIfNeededAsync() to get the data."); - } - } - } - - private void CheckKeyIsMutable(string key) - { - if (!IsKeyMutable(key)) - { - throw new InvalidOperationException( - "Cannot change the `" + key + "` property of a `" + ClassName + "` object."); - } - } - - protected virtual bool IsKeyMutable(string key) - { - return true; - } - - /// - /// A helper function for checking whether two AVObjects point to - /// the same object in the cloud. - /// - public bool HasSameId(AVObject other) - { - lock (mutex) - { - return other != null && - object.Equals(ClassName, other.ClassName) && - object.Equals(ObjectId, other.ObjectId); - } - } - - internal IDictionary CurrentOperations - { - get - { - lock (mutex) - { - return operationSetQueue.Last.Value; - } - } - } - - /// - /// Gets a set view of the keys contained in this object. This does not include createdAt, - /// updatedAt, or objectId. It does include things like username and ACL. - /// - public ICollection Keys - { - get - { - lock (mutex) - { - return estimatedData.Keys; - } - } - } - - /// - /// Gets or sets the AVACL governing this object. - /// - [AVFieldName("ACL")] - public AVACL ACL - { - get { return GetProperty(null, "ACL"); } - set { SetProperty(value, "ACL"); } - } - - /// - /// Returns true if this object was created by the LeanCloud server when the - /// object might have already been there (e.g. in the case of a Facebook - /// login) - /// -#if !UNITY - public -#else - internal -#endif - bool IsNew - { - get - { - return state.IsNew; - } -#if !UNITY - internal -#endif - set - { - MutateState(mutableClone => - { - mutableClone.IsNew = value; - }); - OnPropertyChanged("IsNew"); - } - } - - /// - /// Gets the last time this object was updated as the server sees it, so that if you make changes - /// to a AVObject, then wait a while, and then call , the updated time - /// will be the time of the call rather than the time the object was - /// changed locally. - /// - [AVFieldName("updatedAt")] - public DateTime? UpdatedAt - { - get - { - return state.UpdatedAt; - } - } - - /// - /// Gets the first time this object was saved as the server sees it, so that if you create a - /// AVObject, then wait a while, and then call , the - /// creation time will be the time of the first call rather than - /// the time the object was created locally. - /// - [AVFieldName("createdAt")] - public DateTime? CreatedAt - { - get - { - return state.CreatedAt; - } - } - - /// - /// Indicates whether this AVObject has unsaved changes. - /// - public bool IsDirty - { - get - { - lock (mutex) { return CheckIsDirty(true); } - } - internal set - { - lock (mutex) - { - dirty = value; - OnPropertyChanged("IsDirty"); - } - } - } - - /// - /// Indicates whether key is unsaved for this AVObject. - /// - /// The key to check for. - /// true if the key has been altered and not saved yet, otherwise - /// false. - public bool IsKeyDirty(string key) - { - lock (mutex) - { - return CurrentOperations.ContainsKey(key); - } - } - - private bool CheckIsDirty(bool considerChildren) - { - lock (mutex) - { - return (dirty || CurrentOperations.Count > 0 || (considerChildren && HasDirtyChildren)); - } - } - - /// - /// Gets or sets the object id. An object id is assigned as soon as an object is - /// saved to the server. The combination of a and an - /// uniquely identifies an object in your application. - /// - [AVFieldName("objectId")] - public string ObjectId - { - get - { - return state.ObjectId; - } - set - { - IsDirty = true; - SetObjectIdInternal(value); - } - } - /// - /// Sets the objectId without marking dirty. - /// - /// The new objectId - private void SetObjectIdInternal(string objectId) - { - lock (mutex) - { - MutateState(mutableClone => - { - mutableClone.ObjectId = objectId; - }); - OnPropertyChanged("ObjectId"); - } - } - - /// - /// Gets the class name for the AVObject. - /// - public string ClassName - { - get - { - return state.ClassName; - } - } - - /// - /// Adds a value for the given key, throwing an Exception if the key - /// already has a value. - /// - /// - /// This allows you to use collection initialization syntax when creating AVObjects, - /// such as: - /// - /// var obj = new AVObject("MyType") - /// { - /// {"name", "foo"}, - /// {"count", 10}, - /// {"found", false} - /// }; - /// - /// - /// The key for which a value should be set. - /// The value for the key. - public void Add(string key, object value) - { - lock (mutex) - { - if (this.ContainsKey(key)) - { - throw new ArgumentException("Key already exists", key); - } - this[key] = value; - } - } - - IEnumerator> IEnumerable> - .GetEnumerator() - { - lock (mutex) - { - return estimatedData.GetEnumerator(); - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - lock (mutex) - { - return ((IEnumerable>)this).GetEnumerator(); - } - } - - /// - /// Gets a for the type of object specified by - /// - /// - /// The class name of the object. - /// A new . - public static AVQuery GetQuery(string className) - { - // Since we can't return a AVQuery (due to strong-typing with - // generics), we'll require you to go through subclasses. This is a better - // experience anyway, especially with LINQ integration, since you'll get - // strongly-typed queries and compile-time checking of property names and - // types. - if (SubclassingController.GetType(className) != null) - { - throw new ArgumentException( - "Use the class-specific query properties for class " + className, "className"); - } - return new AVQuery(className); - } - - /// - /// Raises change notifications for all properties associated with the given - /// field names. If fieldNames is null, this will notify for all known field-linked - /// properties (e.g. this happens when we recalculate all estimated data from scratch) - /// - protected virtual void OnFieldsChanged(IEnumerable fieldNames) - { - var mappings = SubclassingController.GetPropertyMappings(ClassName); - IEnumerable properties; - - if (fieldNames != null && mappings != null) - { - properties = from m in mappings - join f in fieldNames on m.Value equals f - select m.Key; - } - else if (mappings != null) - { - properties = mappings.Keys; - } - else - { - properties = Enumerable.Empty(); - } - - foreach (var property in properties) - { - OnPropertyChanged(property); - } - OnPropertyChanged("Item[]"); - } - - /// - /// Raises change notifications for a property. Passing null or the empty string - /// notifies the binding framework that all properties/indexes have changed. - /// Passing "Item[]" tells the binding framework that all indexed values - /// have changed (but not all properties) - /// - protected virtual void OnPropertyChanged( -#if !UNITY -[CallerMemberName] string propertyName = null -#else -string propertyName -#endif -) - { - propertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - private SynchronizedEventHandler propertyChanged = - new SynchronizedEventHandler(); - /// - /// Occurs when a property value changes. - /// - public event PropertyChangedEventHandler PropertyChanged - { - add - { - propertyChanged.Add(value); - } - remove - { - propertyChanged.Remove(value); - } - } - - private SynchronizedEventHandler propertyUpdated = - new SynchronizedEventHandler(); - - public event PropertyUpdatedEventHandler PropertyUpdated - { - add - { - propertyUpdated.Add(value); - } - remove - { - propertyUpdated.Remove(value); - } - } - - protected virtual void OnPropertyUpdated(string propertyName, object newValue, object oldValue) - { - propertyUpdated.Invoke(this, new PropertyUpdatedEventArgs(propertyName, oldValue, newValue)); - } - - private SynchronizedEventHandler collectionUpdated = -new SynchronizedEventHandler(); - - public event CollectionPropertyUpdatedEventHandler CollectionPropertyUpdated - { - add - { - collectionUpdated.Add(value); - } - remove - { - collectionUpdated.Remove(value); - } - } - - protected virtual void OnCollectionPropertyUpdated(string propertyName, NotifyCollectionUpdatedAction action, IEnumerable oldValues, IEnumerable newValues) - { - collectionUpdated?.Invoke(this, new CollectionPropertyUpdatedEventArgs(propertyName, action, oldValues, newValues)); - } - } - - public interface INotifyPropertyUpdated - { - event PropertyUpdatedEventHandler PropertyUpdated; - } - - public interface INotifyCollectionPropertyUpdated - { - event CollectionPropertyUpdatedEventHandler CollectionPropertyUpdated; - } - - public enum NotifyCollectionUpdatedAction - { - Add, - Remove - } - - public class CollectionPropertyUpdatedEventArgs : PropertyChangedEventArgs - { - public CollectionPropertyUpdatedEventArgs(string propertyName, NotifyCollectionUpdatedAction collectionAction, IEnumerable oldValues, IEnumerable newValues) : base(propertyName) - { - CollectionAction = collectionAction; - OldValues = oldValues; - NewValues = newValues; - } - - public IEnumerable OldValues { get; set; } - - public IEnumerable NewValues { get; set; } - - public NotifyCollectionUpdatedAction CollectionAction { get; set; } - } - - public class PropertyUpdatedEventArgs : PropertyChangedEventArgs - { - public PropertyUpdatedEventArgs(string propertyName, object oldValue, object newValue) : base(propertyName) - { - OldValue = oldValue; - NewValue = newValue; - } - - public object OldValue { get; private set; } - public object NewValue { get; private set; } - } - - public delegate void PropertyUpdatedEventHandler(object sender, PropertyUpdatedEventArgs args); - - public delegate void CollectionPropertyUpdatedEventHandler(object sender, CollectionPropertyUpdatedEventArgs args); -} diff --git a/Storage/Source/Public/AVQuery.cs b/Storage/Source/Public/AVQuery.cs deleted file mode 100644 index 9c85f8a..0000000 --- a/Storage/Source/Public/AVQuery.cs +++ /dev/null @@ -1,360 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Storage.Internal; - -namespace LeanCloud -{ - - /// - /// The AVQuery class defines a query that is used to fetch AVObjects. The - /// most common use case is finding all objects that match a query through the - /// method. - /// - /// - /// This sample code fetches all objects of - /// class "MyClass": - /// - /// - /// AVQuery query = new AVQuery("MyClass"); - /// IEnumerable<AVObject> result = await query.FindAsync(); - /// - /// - /// A AVQuery can also be used to retrieve a single object whose id is known, - /// through the method. For example, this sample code - /// fetches an object of class "MyClass" and id myId. - /// - /// - /// AVQuery query = new AVQuery("MyClass"); - /// AVObject result = await query.GetAsync(myId); - /// - /// - /// A AVQuery can also be used to count the number of objects that match the - /// query without retrieving all of those objects. For example, this sample code - /// counts the number of objects of the class "MyClass". - /// - /// - /// AVQuery query = new AVQuery("MyClass"); - /// int count = await query.CountAsync(); - /// - /// - public class AVQuery : AVQueryPair, T>, IAVQuery - where T : AVObject - { - internal static IAVQueryController QueryController - { - get - { - return AVPlugins.Instance.QueryController; - } - } - - internal static IObjectSubclassingController SubclassingController - { - get - { - return AVPlugins.Instance.SubclassingController; - } - } - - /// - /// 调试时可以用来查看最终的发送的查询语句 - /// - private string JsonString - { - get - { - return AVClient.SerializeJsonString(this.BuildParameters(true)); - } - } - - /// - /// Private constructor for composition of queries. A Source query is required, - /// but the remaining values can be null if they won't be changed in this - /// composition. - /// - private AVQuery(AVQuery 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) - { - - } - - //internal override AVQuery CreateInstance( - // AVQuery 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) - //{ - // return new AVQuery(this, where, replacementOrderBy, thenBy, skip, limit, includes, selectedKeys, redirectClassNameForKey); - //} - - public override AVQuery 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) - { - return new AVQuery(this, where, replacementOrderBy, thenBy, skip, limit, includes, selectedKeys, redirectClassNameForKey); - } - - - /// - /// Constructs a query based upon the AVObject subclass used as the generic parameter for the AVQuery. - /// - public AVQuery() - : this(SubclassingController.GetClassName(typeof(T))) - { - } - - /// - /// Constructs a query. A default query with no further parameters will retrieve - /// all s of the provided class. - /// - /// The name of the class to retrieve AVObjects for. - public AVQuery(string className) - : base(className) - { - - } - - /// - /// Constructs a query that is the or of the given queries. - /// - /// The list of AVQueries to 'or' together. - /// A AVQquery that is the 'or' of the passed in queries. - public static AVQuery Or(IEnumerable> queries) - { - string className = null; - var orValue = new List>(); - // We need to cast it to non-generic IEnumerable because of AOT-limitation - var nonGenericQueries = (IEnumerable)queries; - foreach (var obj in nonGenericQueries) - { - var q = (AVQuery)obj; - if (className != null && q.className != className) - { - throw new ArgumentException( - "All of the queries in an or query must be on the same class."); - } - className = q.className; - var parameters = q.BuildParameters(); - if (parameters.Count == 0) - { - continue; - } - object where; - if (!parameters.TryGetValue("where", out where) || parameters.Count > 1) - { - throw new ArgumentException( - "None of the queries in an or query can have non-filtering clauses"); - } - orValue.Add(where as IDictionary); - } - return new AVQuery(new AVQuery(className), - where: new Dictionary { - { "$or", orValue} - }); - } - - /// - /// Retrieves a list of AVObjects that satisfy this query from LeanCloud. - /// - /// The cancellation token. - /// The list of AVObjects that match this query. - public override Task> FindAsync(CancellationToken cancellationToken) - { - return AVUser.GetCurrentUserAsync().OnSuccess(t => - { - return QueryController.FindAsync(this, t.Result, cancellationToken); - }).Unwrap().OnSuccess(t => - { - IEnumerable states = t.Result; - return (from state in states - select AVObject.FromState(state, ClassName)); - }); - } - - /// - /// Retrieves at most one AVObject that satisfies this query. - /// - /// The cancellation token. - /// A single AVObject that satisfies this query, or else null. - public override Task FirstOrDefaultAsync(CancellationToken cancellationToken) - { - return AVUser.GetCurrentUserAsync().OnSuccess(t => - { - return QueryController.FirstAsync(this, t.Result, cancellationToken); - }).Unwrap().OnSuccess(t => - { - IObjectState state = t.Result; - return state == null ? default(T) : AVObject.FromState(state, ClassName); - }); - } - - /// - /// Retrieves at most one AVObject that satisfies this query. - /// - /// The cancellation token. - /// A single AVObject that satisfies this query. - /// If no results match the query. - public override Task FirstAsync(CancellationToken cancellationToken) - { - return FirstOrDefaultAsync(cancellationToken).OnSuccess(t => - { - if (t.Result == null) - { - throw new AVException(AVException.ErrorCode.ObjectNotFound, - "No results matched the query."); - } - return t.Result; - }); - } - - /// - /// Counts the number of objects that match this query. - /// - /// The cancellation token. - /// The number of objects that match this query. - public override Task CountAsync(CancellationToken cancellationToken) - { - return AVUser.GetCurrentUserAsync().OnSuccess(t => - { - return QueryController.CountAsync(this, t.Result, cancellationToken); - }).Unwrap(); - } - - /// - /// Constructs a AVObject whose id is already known by fetching data - /// from the server. - /// - /// ObjectId of the AVObject to fetch. - /// The cancellation token. - /// The AVObject for the given objectId. - public override Task GetAsync(string objectId, CancellationToken cancellationToken) - { - AVQuery singleItemQuery = new AVQuery(className) - .WhereEqualTo("objectId", objectId); - singleItemQuery = new AVQuery(singleItemQuery, includes: this.includes, selectedKeys: this.selectedKeys, limit: 1); - return singleItemQuery.FindAsync(cancellationToken).OnSuccess(t => - { - var result = t.Result.FirstOrDefault(); - if (result == null) - { - throw new AVException(AVException.ErrorCode.ObjectNotFound, - "Object with the given objectId not found."); - } - return result; - }); - } - - #region CQL - /// - /// 执行 CQL 查询 - /// - /// CQL 语句 - /// CancellationToken - /// 返回符合条件的对象集合 - public static Task> DoCloudQueryAsync(string cql, CancellationToken cancellationToken) - { - var queryString = string.Format("cloudQuery?cql={0}", Uri.EscapeDataString(cql)); - - return rebuildObjectFromCloudQueryResult(queryString); - } - - /// - /// 执行 CQL 查询 - /// - /// - /// - public static Task> DoCloudQueryAsync(string cql) - { - return DoCloudQueryAsync(cql, CancellationToken.None); - } - - /// - /// 执行 CQL 查询 - /// - /// 带有占位符的模板 cql 语句 - /// 占位符对应的参数数组 - /// - public static Task> DoCloudQueryAsync(string cqlTeamplate, params object[] pvalues) - { - string queryStringTemplate = "cloudQuery?cql={0}&pvalues={1}"; - string pSrting = Json.Encode(pvalues); - string queryString = string.Format(queryStringTemplate, Uri.EscapeDataString(cqlTeamplate), Uri.EscapeDataString(pSrting)); - - return rebuildObjectFromCloudQueryResult(queryString); - } - - internal static Task> rebuildObjectFromCloudQueryResult(string queryString) - { - var command = new AVCommand(queryString, - method: "GET", - sessionToken: AVUser.CurrentSessionToken, - data: null); - - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: CancellationToken.None).OnSuccess(t => - { - var items = t.Result.Item2["results"] as IList; - var className = t.Result.Item2["className"].ToString(); - - IEnumerable states = (from item in items - select AVObjectCoder.Instance.Decode(item as IDictionary, AVDecoder.Instance)); - - return (from state in states - select AVObject.FromState(state, className)); - }); - } - - #endregion - - /// - /// Determines whether the specified object is equal to the current object. - /// - /// The object to compare with the current object. - /// true if the specified object is equal to the current object; otherwise, false - public override bool Equals(object obj) - { - if (obj == null || !(obj is AVQuery)) - { - return false; - } - - var other = obj as AVQuery; - return Object.Equals(this.className, other.ClassName) && - this.where.CollectionsEqual(other.where) && - this.orderBy.CollectionsEqual(other.orderBy) && - this.includes.CollectionsEqual(other.includes) && - this.selectedKeys.CollectionsEqual(other.selectedKeys) && - Object.Equals(this.skip, other.skip) && - Object.Equals(this.limit, other.limit); - } - - public override int GetHashCode() - { - return base.GetHashCode(); - } - } -} diff --git a/Storage/Source/Public/AVQueryExtensions.cs b/Storage/Source/Public/AVQueryExtensions.cs deleted file mode 100644 index c726b41..0000000 --- a/Storage/Source/Public/AVQueryExtensions.cs +++ /dev/null @@ -1,818 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; - -namespace LeanCloud -{ - /// - /// Provides extension methods for to support - /// Linq-style queries. - /// - public static class AVQueryExtensions - { - private static readonly MethodInfo getMethod; - private static readonly MethodInfo stringContains; - private static readonly MethodInfo stringStartsWith; - private static readonly MethodInfo stringEndsWith; - private static readonly MethodInfo containsMethod; - private static readonly MethodInfo notContainsMethod; - private static readonly MethodInfo containsKeyMethod; - private static readonly MethodInfo notContainsKeyMethod; - private static readonly Dictionary functionMappings; - static AVQueryExtensions() - { - getMethod = GetMethod(obj => obj.Get(null)).GetGenericMethodDefinition(); - stringContains = GetMethod(str => str.Contains(null)); - stringStartsWith = GetMethod(str => str.StartsWith(null)); - stringEndsWith = GetMethod(str => str.EndsWith(null)); - functionMappings = new Dictionary { - { - stringContains, - GetMethod>(q => q.WhereContains(null, null)) - }, - { - stringStartsWith, - GetMethod>(q => q.WhereStartsWith(null, null)) - }, - { - stringEndsWith, - GetMethod>(q => q.WhereEndsWith(null,null)) - }, - }; - containsMethod = GetMethod( - o => AVQueryExtensions.ContainsStub(null, null)).GetGenericMethodDefinition(); - notContainsMethod = GetMethod( - o => AVQueryExtensions.NotContainsStub(null, null)) - .GetGenericMethodDefinition(); - - containsKeyMethod = GetMethod(o => AVQueryExtensions.ContainsKeyStub(null, null)); - notContainsKeyMethod = GetMethod( - o => AVQueryExtensions.NotContainsKeyStub(null, null)); - } - - /// - /// Gets a MethodInfo for a top-level method call. - /// - private static MethodInfo GetMethod(Expression> expression) - { - return (expression.Body as MethodCallExpression).Method; - } - - /// - /// When a query is normalized, this is a placeholder to indicate we should - /// add a WhereContainedIn() clause. - /// - private static bool ContainsStub(object collection, T value) - { - throw new NotImplementedException( - "Exists only for expression translation as a placeholder."); - } - - /// - /// When a query is normalized, this is a placeholder to indicate we should - /// add a WhereNotContainedIn() clause. - /// - private static bool NotContainsStub(object collection, T value) - { - throw new NotImplementedException( - "Exists only for expression translation as a placeholder."); - } - - /// - /// When a query is normalized, this is a placeholder to indicate that we should - /// add a WhereExists() clause. - /// - private static bool ContainsKeyStub(AVObject obj, string key) - { - throw new NotImplementedException( - "Exists only for expression translation as a placeholder."); - } - - /// - /// When a query is normalized, this is a placeholder to indicate that we should - /// add a WhereDoesNotExist() clause. - /// - private static bool NotContainsKeyStub(AVObject obj, string key) - { - throw new NotImplementedException( - "Exists only for expression translation as a placeholder."); - } - - /// - /// Evaluates an expression and throws if the expression has components that can't be - /// evaluated (e.g. uses the parameter that's only represented by an object on the server). - /// - private static object GetValue(Expression exp) - { - try - { - return Expression.Lambda( - typeof(Func<>).MakeGenericType(exp.Type), exp).Compile().DynamicInvoke(); - } - catch (Exception e) - { - throw new InvalidOperationException("Unable to evaluate expression: " + exp, e); - } - } - - /// - /// Checks whether the MethodCallExpression is a call to AVObject.Get(), - /// which is the call we normalize all indexing into the AVObject to. - /// - private static bool IsAVObjectGet(MethodCallExpression node) - { - if (node == null || node.Object == null) - { - return false; - } - if (!typeof(AVObject).GetTypeInfo().IsAssignableFrom(node.Object.Type.GetTypeInfo())) - { - return false; - } - return node.Method.IsGenericMethod && node.Method.GetGenericMethodDefinition() == getMethod; - } - - - /// - /// Visits an Expression, converting AVObject.Get/AVObject[]/AVObject.Property, - /// and nested indices into a single call to AVObject.Get() with a "field path" like - /// "foo.bar.baz" - /// - private class ObjectNormalizer : ExpressionVisitor - { - protected override Expression VisitIndex(IndexExpression node) - { - var visitedObject = Visit(node.Object); - var indexer = visitedObject as MethodCallExpression; - if (IsAVObjectGet(indexer)) - { - var indexValue = GetValue(node.Arguments[0]) as string; - if (indexValue == null) - { - throw new InvalidOperationException("Index must be a string"); - } - var newPath = GetValue(indexer.Arguments[0]) + "." + indexValue; - return Expression.Call(indexer.Object, - getMethod.MakeGenericMethod(node.Type), - Expression.Constant(newPath, typeof(string))); - } - return base.VisitIndex(node); - } - - /// - /// Check for a AVFieldName attribute and use that as the path component, turning - /// properties like foo.ObjectId into foo.Get("objectId") - /// - protected override Expression VisitMember(MemberExpression node) - { - var fieldName = node.Member.GetCustomAttribute(); - if (fieldName != null && - typeof(AVObject).GetTypeInfo().IsAssignableFrom(node.Expression.Type.GetTypeInfo())) - { - var newPath = fieldName.FieldName; - return Expression.Call(node.Expression, - getMethod.MakeGenericMethod(node.Type), - Expression.Constant(newPath, typeof(string))); - } - return base.VisitMember(node); - } - - /// - /// If a AVObject.Get() call has been cast, just change the generic parameter. - /// - protected override Expression VisitUnary(UnaryExpression node) - { - var methodCall = Visit(node.Operand) as MethodCallExpression; - if ((node.NodeType == ExpressionType.Convert || - node.NodeType == ExpressionType.ConvertChecked) && - IsAVObjectGet(methodCall)) - { - return Expression.Call(methodCall.Object, - getMethod.MakeGenericMethod(node.Type), - methodCall.Arguments); - } - return base.VisitUnary(node); - } - - protected override Expression VisitMethodCall(MethodCallExpression node) - { - if (node.Method.Name == "get_Item" && node.Object is ParameterExpression) - { - var indexPath = GetValue(node.Arguments[0]) as string; - return Expression.Call(node.Object, - getMethod.MakeGenericMethod(typeof(object)), - Expression.Constant(indexPath, typeof(string))); - } - - if (node.Method.Name == "get_Item" || IsAVObjectGet(node)) - { - var visitedObject = Visit(node.Object); - var indexer = visitedObject as MethodCallExpression; - if (IsAVObjectGet(indexer)) - { - var indexValue = GetValue(node.Arguments[0]) as string; - if (indexValue == null) - { - throw new InvalidOperationException("Index must be a string"); - } - var newPath = GetValue(indexer.Arguments[0]) + "." + indexValue; - return Expression.Call(indexer.Object, - getMethod.MakeGenericMethod(node.Type), - Expression.Constant(newPath, typeof(string))); - } - } - return base.VisitMethodCall(node); - } - } - - /// - /// Normalizes Where expressions. - /// - private class WhereNormalizer : ExpressionVisitor - { - - /// - /// Normalizes binary operators. <, >, <=, >= !=, and == - /// This puts the AVObject.Get() on the left side of the operation - /// (reversing it if necessary), and normalizes the AVObject.Get() - /// - protected override Expression VisitBinary(BinaryExpression node) - { - var leftTransformed = new ObjectNormalizer().Visit(node.Left) as MethodCallExpression; - var rightTransformed = new ObjectNormalizer().Visit(node.Right) as MethodCallExpression; - - MethodCallExpression objectExpression; - Expression filterExpression; - bool inverted; - if (leftTransformed != null) - { - objectExpression = leftTransformed; - filterExpression = node.Right; - inverted = false; - } - else - { - objectExpression = rightTransformed; - filterExpression = node.Left; - inverted = true; - } - - try - { - switch (node.NodeType) - { - case ExpressionType.GreaterThan: - if (inverted) - { - return Expression.LessThan(objectExpression, filterExpression); - } - else - { - return Expression.GreaterThan(objectExpression, filterExpression); - } - case ExpressionType.GreaterThanOrEqual: - if (inverted) - { - return Expression.LessThanOrEqual(objectExpression, filterExpression); - } - else - { - return Expression.GreaterThanOrEqual(objectExpression, filterExpression); - } - case ExpressionType.LessThan: - if (inverted) - { - return Expression.GreaterThan(objectExpression, filterExpression); - } - else - { - return Expression.LessThan(objectExpression, filterExpression); - } - case ExpressionType.LessThanOrEqual: - if (inverted) - { - return Expression.GreaterThanOrEqual(objectExpression, filterExpression); - } - else - { - return Expression.LessThanOrEqual(objectExpression, filterExpression); - } - case ExpressionType.Equal: - return Expression.Equal(objectExpression, filterExpression); - case ExpressionType.NotEqual: - return Expression.NotEqual(objectExpression, filterExpression); - } - } - catch (ArgumentException) - { - throw new InvalidOperationException("Operation not supported: " + node); - } - return base.VisitBinary(node); - } - - /// - /// If a ! operator is used, this removes the ! and instead calls the equivalent - /// function (so e.g. == becomes !=, < becomes >=, Contains becomes NotContains) - /// - protected override Expression VisitUnary(UnaryExpression node) - { - // Normalizes inversion - if (node.NodeType == ExpressionType.Not) - { - var visitedOperand = Visit(node.Operand); - var binaryOperand = visitedOperand as BinaryExpression; - if (binaryOperand != null) - { - switch (binaryOperand.NodeType) - { - case ExpressionType.GreaterThan: - return Expression.LessThanOrEqual(binaryOperand.Left, binaryOperand.Right); - case ExpressionType.GreaterThanOrEqual: - return Expression.LessThan(binaryOperand.Left, binaryOperand.Right); - case ExpressionType.LessThan: - return Expression.GreaterThanOrEqual(binaryOperand.Left, binaryOperand.Right); - case ExpressionType.LessThanOrEqual: - return Expression.GreaterThan(binaryOperand.Left, binaryOperand.Right); - case ExpressionType.Equal: - return Expression.NotEqual(binaryOperand.Left, binaryOperand.Right); - case ExpressionType.NotEqual: - return Expression.Equal(binaryOperand.Left, binaryOperand.Right); - } - } - - var methodCallOperand = visitedOperand as MethodCallExpression; - if (methodCallOperand != null) - { - if (methodCallOperand.Method.IsGenericMethod) - { - if (methodCallOperand.Method.GetGenericMethodDefinition() == containsMethod) - { - var genericNotContains = notContainsMethod.MakeGenericMethod( - methodCallOperand.Method.GetGenericArguments()); - return Expression.Call(genericNotContains, methodCallOperand.Arguments.ToArray()); - } - if (methodCallOperand.Method.GetGenericMethodDefinition() == notContainsMethod) - { - var genericContains = containsMethod.MakeGenericMethod( - methodCallOperand.Method.GetGenericArguments()); - return Expression.Call(genericContains, methodCallOperand.Arguments.ToArray()); - } - } - if (methodCallOperand.Method == containsKeyMethod) - { - return Expression.Call(notContainsKeyMethod, methodCallOperand.Arguments.ToArray()); - } - if (methodCallOperand.Method == notContainsKeyMethod) - { - return Expression.Call(containsKeyMethod, methodCallOperand.Arguments.ToArray()); - } - } - } - return base.VisitUnary(node); - } - - /// - /// Normalizes .Equals into == and Contains() into the appropriate stub. - /// - protected override Expression VisitMethodCall(MethodCallExpression node) - { - // Convert .Equals() into == - if (node.Method.Name == "Equals" && - node.Method.ReturnType == typeof(bool) && - node.Method.GetParameters().Length == 1) - { - var obj = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; - var parameter = new ObjectNormalizer().Visit(node.Arguments[0]) as MethodCallExpression; - if ((IsAVObjectGet(obj) && (obj.Object is ParameterExpression)) || - (IsAVObjectGet(parameter) && (parameter.Object is ParameterExpression))) - { - return Expression.Equal(node.Object, node.Arguments[0]); - } - } - - // Convert the .Contains() into a ContainsStub - if (node.Method != stringContains && - node.Method.Name == "Contains" && - node.Method.ReturnType == typeof(bool) && - node.Method.GetParameters().Length <= 2) - { - var collection = node.Method.GetParameters().Length == 1 ? - node.Object : - node.Arguments[0]; - var parameterIndex = node.Method.GetParameters().Length - 1; - var parameter = new ObjectNormalizer().Visit(node.Arguments[parameterIndex]) - as MethodCallExpression; - if (IsAVObjectGet(parameter) && (parameter.Object is ParameterExpression)) - { - var genericContains = containsMethod.MakeGenericMethod(parameter.Type); - return Expression.Call(genericContains, collection, parameter); - } - var target = new ObjectNormalizer().Visit(collection) as MethodCallExpression; - var element = node.Arguments[parameterIndex]; - if (IsAVObjectGet(target) && (target.Object is ParameterExpression)) - { - var genericContains = containsMethod.MakeGenericMethod(element.Type); - return Expression.Call(genericContains, target, element); - } - } - - // Convert obj["foo.bar"].ContainsKey("baz") into obj.ContainsKey("foo.bar.baz") - if (node.Method.Name == "ContainsKey" && - node.Method.ReturnType == typeof(bool) && - node.Method.GetParameters().Length == 1) - { - var getter = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; - Expression target = null; - string path = null; - if (IsAVObjectGet(getter) && getter.Object is ParameterExpression) - { - target = getter.Object; - path = GetValue(getter.Arguments[0]) + "." + GetValue(node.Arguments[0]); - return Expression.Call(containsKeyMethod, target, Expression.Constant(path)); - } - else if (node.Object is ParameterExpression) - { - target = node.Object; - path = GetValue(node.Arguments[0]) as string; - } - if (target != null && path != null) - { - return Expression.Call(containsKeyMethod, target, Expression.Constant(path)); - } - } - return base.VisitMethodCall(node); - } - } - - /// - /// Converts a normalized method call expression into the appropriate AVQuery clause. - /// - private static AVQuery WhereMethodCall( - this AVQuery source, Expression> expression, MethodCallExpression node) - where T : AVObject - { - if (IsAVObjectGet(node) && (node.Type == typeof(bool) || node.Type == typeof(bool?))) - { - // This is a raw boolean field access like 'where obj.Get("foo")' - return source.WhereEqualTo(GetValue(node.Arguments[0]) as string, true); - } - - MethodInfo translatedMethod; - if (functionMappings.TryGetValue(node.Method, out translatedMethod)) - { - var objTransformed = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; - if (!(IsAVObjectGet(objTransformed) && - objTransformed.Object == expression.Parameters[0])) - { - throw new InvalidOperationException( - "The left-hand side of a supported function call must be a AVObject field access."); - } - var fieldPath = GetValue(objTransformed.Arguments[0]); - var containedIn = GetValue(node.Arguments[0]); - var queryType = translatedMethod.DeclaringType.GetGenericTypeDefinition() - .MakeGenericType(typeof(T)); - translatedMethod = ReflectionHelpers.GetMethod(queryType, - translatedMethod.Name, - translatedMethod.GetParameters().Select(p => p.ParameterType).ToArray()); - return translatedMethod.Invoke(source, new[] { fieldPath, containedIn }) as AVQuery; - } - - if (node.Arguments[0] == expression.Parameters[0]) - { - // obj.ContainsKey("foo") --> query.WhereExists("foo") - if (node.Method == containsKeyMethod) - { - return source.WhereExists(GetValue(node.Arguments[1]) as string); - } - // !obj.ContainsKey("foo") --> query.WhereDoesNotExist("foo") - if (node.Method == notContainsKeyMethod) - { - return source.WhereDoesNotExist(GetValue(node.Arguments[1]) as string); - } - } - - if (node.Method.IsGenericMethod) - { - if (node.Method.GetGenericMethodDefinition() == containsMethod) - { - // obj.Get>("path").Contains(someValue) - if (IsAVObjectGet(node.Arguments[0] as MethodCallExpression)) - { - return source.WhereEqualTo( - GetValue(((MethodCallExpression)node.Arguments[0]).Arguments[0]) as string, - GetValue(node.Arguments[1])); - } - // someList.Contains(obj.Get("path")) - if (IsAVObjectGet(node.Arguments[1] as MethodCallExpression)) - { - var collection = GetValue(node.Arguments[0]) as System.Collections.IEnumerable; - return source.WhereContainedIn( - GetValue(((MethodCallExpression)node.Arguments[1]).Arguments[0]) as string, - collection.Cast()); - } - } - - if (node.Method.GetGenericMethodDefinition() == notContainsMethod) - { - // !obj.Get>("path").Contains(someValue) - if (IsAVObjectGet(node.Arguments[0] as MethodCallExpression)) - { - return source.WhereNotEqualTo( - GetValue(((MethodCallExpression)node.Arguments[0]).Arguments[0]) as string, - GetValue(node.Arguments[1])); - } - // !someList.Contains(obj.Get("path")) - if (IsAVObjectGet(node.Arguments[1] as MethodCallExpression)) - { - var collection = GetValue(node.Arguments[0]) as System.Collections.IEnumerable; - return source.WhereNotContainedIn( - GetValue(((MethodCallExpression)node.Arguments[1]).Arguments[0]) as string, - collection.Cast()); - } - } - } - throw new InvalidOperationException(node.Method + " is not a supported method call in a where expression."); - } - - /// - /// Converts a normalized binary expression into the appropriate AVQuery clause. - /// - private static AVQuery WhereBinaryExpression( - this AVQuery source, Expression> expression, BinaryExpression node) - where T : AVObject - { - var leftTransformed = new ObjectNormalizer().Visit(node.Left) as MethodCallExpression; - - if (!(IsAVObjectGet(leftTransformed) && - leftTransformed.Object == expression.Parameters[0])) - { - throw new InvalidOperationException( - "Where expressions must have one side be a field operation on a AVObject."); - } - - var fieldPath = GetValue(leftTransformed.Arguments[0]) as string; - var filterValue = GetValue(node.Right); - - if (filterValue != null && !AVEncoder.IsValidType(filterValue)) - { - throw new InvalidOperationException( - "Where clauses must use types compatible with AVObjects."); - } - - switch (node.NodeType) - { - case ExpressionType.GreaterThan: - return source.WhereGreaterThan(fieldPath, filterValue); - case ExpressionType.GreaterThanOrEqual: - return source.WhereGreaterThanOrEqualTo(fieldPath, filterValue); - case ExpressionType.LessThan: - return source.WhereLessThan(fieldPath, filterValue); - case ExpressionType.LessThanOrEqual: - return source.WhereLessThanOrEqualTo(fieldPath, filterValue); - case ExpressionType.Equal: - return source.WhereEqualTo(fieldPath, filterValue); - case ExpressionType.NotEqual: - return source.WhereNotEqualTo(fieldPath, filterValue); - default: - throw new InvalidOperationException( - "Where expressions do not support this operator."); - } - } - - /// - /// Filters a query based upon the predicate provided. - /// - /// The type of AVObject being queried for. - /// The base to which - /// the predicate will be added. - /// A function to test each AVObject for a condition. - /// The predicate must be able to be represented by one of the standard Where - /// functions on AVQuery - /// A new AVQuery whose results will match the given predicate as - /// well as the Source's filters. - public static AVQuery Where( - this AVQuery source, Expression> predicate) - where TSource : AVObject - { - // Handle top-level logic operators && and || - var binaryExpression = predicate.Body as BinaryExpression; - if (binaryExpression != null) - { - if (binaryExpression.NodeType == ExpressionType.AndAlso) - { - return source - .Where(Expression.Lambda>( - binaryExpression.Left, predicate.Parameters)) - .Where(Expression.Lambda>( - binaryExpression.Right, predicate.Parameters)); - } - if (binaryExpression.NodeType == ExpressionType.OrElse) - { - var left = source.Where(Expression.Lambda>( - binaryExpression.Left, predicate.Parameters)); - var right = source.Where(Expression.Lambda>( - binaryExpression.Right, predicate.Parameters)); - return left.Or(right); - } - } - - var normalized = new WhereNormalizer().Visit(predicate.Body); - - var methodCallExpr = normalized as MethodCallExpression; - if (methodCallExpr != null) - { - return source.WhereMethodCall(predicate, methodCallExpr); - } - - var binaryExpr = normalized as BinaryExpression; - if (binaryExpr != null) - { - return source.WhereBinaryExpression(predicate, binaryExpr); - } - - var unaryExpr = normalized as UnaryExpression; - if (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Not) - { - var node = unaryExpr.Operand as MethodCallExpression; - if (IsAVObjectGet(node) && (node.Type == typeof(bool) || node.Type == typeof(bool?))) - { - // This is a raw boolean field access like 'where !obj.Get("foo")' - return source.WhereNotEqualTo(GetValue(node.Arguments[0]) as string, true); - } - } - - throw new InvalidOperationException( - "Encountered an unsupported expression for AVQueries."); - } - - /// - /// Normalizes an OrderBy's keySelector expression and then extracts the path - /// from the AVObject.Get() call. - /// - private static string GetOrderByPath( - Expression> keySelector) - { - string result = null; - var normalized = new ObjectNormalizer().Visit(keySelector.Body); - var callExpr = normalized as MethodCallExpression; - if (IsAVObjectGet(callExpr) && callExpr.Object == keySelector.Parameters[0]) - { - // We're operating on the parameter - result = GetValue(callExpr.Arguments[0]) as string; - } - if (result == null) - { - throw new InvalidOperationException( - "OrderBy expression must be a field access on a AVObject."); - } - return result; - } - - /// - /// Orders a query based upon the key selector provided. - /// - /// The type of AVObject being queried for. - /// The type of key returned by keySelector. - /// The query to order. - /// A function to extract a key from the AVObject. - /// A new AVQuery based on Source whose results will be ordered by - /// the key specified in the keySelector. - public static AVQuery OrderBy( - this AVQuery source, Expression> keySelector) - where TSource : AVObject - { - return source.OrderBy(GetOrderByPath(keySelector)); - } - - /// - /// Orders a query based upon the key selector provided. - /// - /// The type of AVObject being queried for. - /// The type of key returned by keySelector. - /// The query to order. - /// A function to extract a key from the AVObject. - /// A new AVQuery based on Source whose results will be ordered by - /// the key specified in the keySelector. - public static AVQuery OrderByDescending( - this AVQuery source, Expression> keySelector) - where TSource : AVObject - { - return source.OrderByDescending(GetOrderByPath(keySelector)); - } - - /// - /// Performs a subsequent ordering of a query based upon the key selector provided. - /// - /// The type of AVObject being queried for. - /// The type of key returned by keySelector. - /// The query to order. - /// A function to extract a key from the AVObject. - /// A new AVQuery based on Source whose results will be ordered by - /// the key specified in the keySelector. - public static AVQuery ThenBy( - this AVQuery source, Expression> keySelector) - where TSource : AVObject - { - return source.ThenBy(GetOrderByPath(keySelector)); - } - - /// - /// Performs a subsequent ordering of a query based upon the key selector provided. - /// - /// The type of AVObject being queried for. - /// The type of key returned by keySelector. - /// The query to order. - /// A function to extract a key from the AVObject. - /// A new AVQuery based on Source whose results will be ordered by - /// the key specified in the keySelector. - public static AVQuery ThenByDescending( - this AVQuery source, Expression> keySelector) - where TSource : AVObject - { - return source.ThenByDescending(GetOrderByPath(keySelector)); - } - - /// - /// Correlates the elements of two queries based on matching keys. - /// - /// The type of AVObjects of the first query. - /// The type of AVObjects of the second query. - /// The type of the keys returned by the key selector - /// functions. - /// The type of the result. This must match either - /// TOuter or TInner - /// The first query to join. - /// The query to join to the first query. - /// A function to extract a join key from the results of - /// the first query. - /// A function to extract a join key from the results of - /// the second query. - /// A function to select either the outer or inner query - /// result to determine which query is the base query. - /// A new AVQuery with a WhereMatchesQuery or WhereMatchesKeyInQuery - /// clause based upon the query indicated in the . - public static AVQuery Join( - this AVQuery outer, - AVQuery inner, - Expression> outerKeySelector, - Expression> innerKeySelector, - Expression> resultSelector) - where TOuter : AVObject - where TInner : AVObject - where TResult : AVObject - { - // resultSelector must select either the inner object or the outer object. If it's the inner - // object, reverse the query. - if (resultSelector.Body == resultSelector.Parameters[1]) - { - // The inner object was selected. - return inner.Join( - outer, - innerKeySelector, - outerKeySelector, - (i, o) => i) as AVQuery; - } - if (resultSelector.Body != resultSelector.Parameters[0]) - { - throw new InvalidOperationException("Joins must select either the outer or inner object."); - } - - // Normalize both selectors - Expression outerNormalized = new ObjectNormalizer().Visit(outerKeySelector.Body); - Expression innerNormalized = new ObjectNormalizer().Visit(innerKeySelector.Body); - MethodCallExpression outerAsGet = outerNormalized as MethodCallExpression; - MethodCallExpression innerAsGet = innerNormalized as MethodCallExpression; - if (IsAVObjectGet(outerAsGet) && outerAsGet.Object == outerKeySelector.Parameters[0]) - { - var outerKey = GetValue(outerAsGet.Arguments[0]) as string; - - if (IsAVObjectGet(innerAsGet) && innerAsGet.Object == innerKeySelector.Parameters[0]) - { - // Both are key accesses, so treat this as a WhereMatchesKeyInQuery - var innerKey = GetValue(innerAsGet.Arguments[0]) as string; - return outer.WhereMatchesKeyInQuery(outerKey, innerKey, inner) as AVQuery; - } - - if (innerKeySelector.Body == innerKeySelector.Parameters[0]) - { - // The inner selector is on the result of the query itself, so treat this as a - // WhereMatchesQuery - return outer.WhereMatchesQuery(outerKey, inner) as AVQuery; - } - throw new InvalidOperationException( - "The key for the joined object must be a AVObject or a field access " + - "on the AVObject."); - } - - // TODO (hallucinogen): If we ever support "and" queries fully and/or support a "where this object - // matches some key in some other query" (as opposed to requiring a key on this query), we - // can add support for even more types of joins. - - throw new InvalidOperationException( - "The key for the selected object must be a field access on the AVObject."); - } - } -} diff --git a/Storage/Source/Public/AVRelation.cs b/Storage/Source/Public/AVRelation.cs deleted file mode 100644 index fa7f13e..0000000 --- a/Storage/Source/Public/AVRelation.cs +++ /dev/null @@ -1,175 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Text; - -namespace LeanCloud -{ - /// - /// A common base class for AVRelations. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public abstract class AVRelationBase : IJsonConvertible - { - private AVObject parent; - private string key; - private string targetClassName; - - internal AVRelationBase(AVObject parent, string key) - { - EnsureParentAndKey(parent, key); - } - - internal AVRelationBase(AVObject parent, string key, string targetClassName) - : this(parent, key) - { - this.targetClassName = targetClassName; - } - - internal static IObjectSubclassingController SubclassingController - { - get - { - return AVPlugins.Instance.SubclassingController; - } - } - - internal void EnsureParentAndKey(AVObject parent, string key) - { - this.parent = this.parent ?? parent; - this.key = this.key ?? key; - Debug.Assert(this.parent == parent, "Relation retrieved from two different objects"); - Debug.Assert(this.key == key, "Relation retrieved from two different keys"); - } - - internal void Add(AVObject obj) - { - var change = new AVRelationOperation(new[] { obj }, null); - parent.PerformOperation(key, change); - targetClassName = change.TargetClassName; - } - - internal void Remove(AVObject obj) - { - var change = new AVRelationOperation(null, new[] { obj }); - parent.PerformOperation(key, change); - targetClassName = change.TargetClassName; - } - - IDictionary IJsonConvertible.ToJSON() - { - return new Dictionary { - { "__type", "Relation"}, - { "className", targetClassName} - }; - } - - internal AVQuery GetQuery() where T : AVObject - { - if (targetClassName != null) - { - return new AVQuery(targetClassName) - .WhereRelatedTo(parent, key); - } - - return new AVQuery(parent.ClassName) - .RedirectClassName(key) - .WhereRelatedTo(parent, key); - } - - internal AVQuery GetReverseQuery(T target) where T : AVObject - { - if (target.ObjectId == null) - { - throw new ArgumentNullException("target.ObjectId", "can not query a relation without target ObjectId."); - } - - return new AVQuery(parent.ClassName).WhereEqualTo(key, target); - } - - internal string TargetClassName - { - get - { - return targetClassName; - } - set - { - targetClassName = value; - } - } - - /// - /// Produces the proper AVRelation<T> instance for the given classname. - /// - internal static AVRelationBase CreateRelation(AVObject parent, - string key, - string targetClassName) - { - var targetType = SubclassingController.GetType(targetClassName) ?? typeof(AVObject); - - Expression>> createRelationExpr = - () => CreateRelation(parent, key, targetClassName); - var createRelationMethod = - ((MethodCallExpression)createRelationExpr.Body) - .Method - .GetGenericMethodDefinition() - .MakeGenericMethod(targetType); - return (AVRelationBase)createRelationMethod.Invoke(null, new object[] { parent, key, targetClassName }); - } - - private static AVRelation CreateRelation(AVObject parent, string key, string targetClassName) - where T : AVObject - { - return new AVRelation(parent, key, targetClassName); - } - } - - /// - /// Provides access to all of the children of a many-to-many relationship. Each instance of - /// AVRelation is associated with a particular parent and key. - /// - /// The type of the child objects. - public sealed class AVRelation : AVRelationBase where T : AVObject - { - - internal AVRelation(AVObject parent, string key) : base(parent, key) { } - - internal AVRelation(AVObject parent, string key, string targetClassName) - : base(parent, key, targetClassName) { } - - /// - /// Adds an object to this relation. The object must already have been saved. - /// - /// The object to add. - public void Add(T obj) - { - base.Add(obj); - } - - /// - /// Removes an object from this relation. The object must already have been saved. - /// - /// The object to remove. - public void Remove(T obj) - { - base.Remove(obj); - } - - /// - /// Gets a query that can be used to query the objects in this relation. - /// - public AVQuery Query - { - get - { - return base.GetQuery(); - } - } - } -} diff --git a/Storage/Source/Public/AVRole.cs b/Storage/Source/Public/AVRole.cs deleted file mode 100644 index 848832b..0000000 --- a/Storage/Source/Public/AVRole.cs +++ /dev/null @@ -1,111 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace LeanCloud -{ - /// - /// Represents a Role on the LeanCloud server. AVRoles represent groupings - /// of s for the purposes of granting permissions (e.g. - /// specifying a for a . Roles - /// are specified by their sets of child users and child roles, all of which are granted - /// any permissions that the parent role has. - /// - /// Roles must have a name (that cannot be changed after creation of the role), - /// and must specify an ACL. - /// - [AVClassName("_Role")] - public class AVRole : AVObject - { - private static readonly Regex namePattern = new Regex("^[0-9a-zA-Z_\\- ]+$"); - - /// - /// Constructs a new AVRole. You must assign a name and ACL to the role. - /// - public AVRole() : base() { } - - /// - /// Constructs a new AVRole with the given name. - /// - /// The name of the role to create. - /// The ACL for this role. Roles must have an ACL. - public AVRole(string name, AVACL acl) - : this() - { - Name = name; - ACL = acl; - } - - /// - /// Gets the name of the role. - /// - [AVFieldName("name")] - public string Name - { - get { return GetProperty("Name"); } - set { SetProperty(value, "Name"); } - } - - /// - /// Gets the for the s that are - /// direct children of this role. These users are granted any privileges that - /// this role has been granted (e.g. read or write access through ACLs). You can - /// add or remove child users from the role through this relation. - /// - [AVFieldName("users")] - public AVRelation Users - { - get { return GetRelationProperty("Users"); } - } - - /// - /// Gets the for the s that are - /// direct children of this role. These roles' users are granted any privileges that - /// this role has been granted (e.g. read or write access through ACLs). You can - /// add or remove child roles from the role through this relation. - /// - [AVFieldName("roles")] - public AVRelation Roles - { - get { return GetRelationProperty("Roles"); } - } - - internal override void OnSettingValue(ref string key, ref object value) - { - base.OnSettingValue(ref key, ref value); - if (key == "name") - { - if (ObjectId != null) - { - throw new InvalidOperationException( - "A role's name can only be set before it has been saved."); - } - if (!(value is string)) - { - throw new ArgumentException("A role's name must be a string.", "value"); - } - if (!namePattern.IsMatch((string)value)) - { - throw new ArgumentException( - "A role's name can only contain alphanumeric characters, _, -, and spaces.", - "value"); - } - } - } - - /// - /// Gets a over the Role collection. - /// - public static AVQuery Query - { - get - { - return new AVQuery(); - } - } - } -} diff --git a/Storage/Source/Public/AVSession.cs b/Storage/Source/Public/AVSession.cs deleted file mode 100644 index e915b40..0000000 --- a/Storage/Source/Public/AVSession.cs +++ /dev/null @@ -1,111 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud -{ - /// - /// Represents a session of a user for a LeanCloud application. - /// - [AVClassName("_Session")] - public class AVSession : AVObject - { - private static readonly HashSet readOnlyKeys = new HashSet { - "sessionToken", "createdWith", "restricted", "user", "expiresAt", "installationId" - }; - - protected override bool IsKeyMutable(string key) - { - return !readOnlyKeys.Contains(key); - } - - /// - /// Gets the session token for a user, if they are logged in. - /// - [AVFieldName("sessionToken")] - public string SessionToken - { - get { return GetProperty(null, "SessionToken"); } - } - - /// - /// Constructs a for AVSession. - /// - public static AVQuery Query - { - get - { - return new AVQuery(); - } - } - - internal static IAVSessionController SessionController - { - get - { - return AVPlugins.Instance.SessionController; - } - } - - /// - /// Gets the current object related to the current user. - /// - public static Task GetCurrentSessionAsync() - { - return GetCurrentSessionAsync(CancellationToken.None); - } - - /// - /// Gets the current object related to the current user. - /// - /// The cancellation token - public static Task GetCurrentSessionAsync(CancellationToken cancellationToken) - { - return AVUser.GetCurrentUserAsync().OnSuccess(t1 => - { - AVUser user = t1.Result; - if (user == null) - { - return Task.FromResult((AVSession)null); - } - - string sessionToken = user.SessionToken; - if (sessionToken == null) - { - return Task.FromResult((AVSession)null); - } - - return SessionController.GetSessionAsync(sessionToken, cancellationToken).OnSuccess(t => - { - AVSession session = AVObject.FromState(t.Result, "_Session"); - return session; - }); - }).Unwrap(); - } - - internal static Task RevokeAsync(string sessionToken, CancellationToken cancellationToken) - { - if (sessionToken == null || !SessionController.IsRevocableSessionToken(sessionToken)) - { - return Task.FromResult(0); - } - return SessionController.RevokeAsync(sessionToken, cancellationToken); - } - - internal static Task UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) - { - if (sessionToken == null || SessionController.IsRevocableSessionToken(sessionToken)) - { - return Task.FromResult(sessionToken); - } - - return SessionController.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken).OnSuccess(t => - { - AVSession session = AVObject.FromState(t.Result, "_Session"); - return session.SessionToken; - }); - } - } -} diff --git a/Storage/Source/Public/AVStatus.cs b/Storage/Source/Public/AVStatus.cs deleted file mode 100644 index 7a7bc65..0000000 --- a/Storage/Source/Public/AVStatus.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud -{ - /// - /// 事件流系统中的一条状态 - /// - [AVClassName("_Status")] - public class AVStatus : AVObject - { - private static readonly HashSet readOnlyKeys = new HashSet { - "messageId", "inboxType", "data","Source" - }; - - protected override bool IsKeyMutable(string key) - { - return !readOnlyKeys.Contains(key); - } - } -} diff --git a/Storage/Source/Public/AVUploadProgressEventArgs.cs b/Storage/Source/Public/AVUploadProgressEventArgs.cs deleted file mode 100644 index dfa3112..0000000 --- a/Storage/Source/Public/AVUploadProgressEventArgs.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace LeanCloud { - /// - /// Represents upload progress. - /// - public class AVUploadProgressEventArgs : EventArgs { - public AVUploadProgressEventArgs() { } - - /// - /// Gets the progress (a number between 0.0 and 1.0) of an upload. - /// - public double Progress { get; set; } - } -} diff --git a/Storage/Source/Public/AVUser.cs b/Storage/Source/Public/AVUser.cs deleted file mode 100644 index f6cae24..0000000 --- a/Storage/Source/Public/AVUser.cs +++ /dev/null @@ -1,1310 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud { - /// - /// Represents a user for a LeanCloud application. - /// - [AVClassName("_User")] - public class AVUser : AVObject { - private static readonly IDictionary authProviders = - new Dictionary(); - - private static readonly HashSet readOnlyKeys = new HashSet { - "sessionToken", "isNew" - }; - - internal static IAVUserController UserController { - get { - return AVPlugins.Instance.UserController; - } - } - - internal static IAVCurrentUserController CurrentUserController { - get { - return AVPlugins.Instance.CurrentUserController; - } - } - - /// - /// Whether the AVUser has been authenticated on this device. Only an authenticated - /// AVUser can be saved and deleted. - /// - [Obsolete("This property is deprecated, please use IsAuthenticatedAsync instead.")] - public bool IsAuthenticated { - get { - lock (mutex) { - return SessionToken != null && - CurrentUser != null && - CurrentUser.ObjectId == ObjectId; - } - } - } - - /// - /// Whether the AVUser has been authenticated on this device, and the AVUser's session token is expired. - /// Only an authenticated AVUser can be saved and deleted. - /// - public Task IsAuthenticatedAsync() { - lock (mutex) { - if (SessionToken == null || CurrentUser == null || CurrentUser.ObjectId != ObjectId) { - return Task.FromResult(false); - } - } - var command = new AVCommand(String.Format("users/me?session_token={0}", CurrentSessionToken), - method: "GET", - data: null); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t => { - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - - /// - /// Refresh this user's session token, and current session token will be invalid. - /// - public Task RefreshSessionTokenAsync(CancellationToken cancellationToken) { - return UserController.RefreshSessionTokenAsync(ObjectId, SessionToken, cancellationToken).OnSuccess(t => { - var serverState = t.Result; - HandleSave(serverState); - }); - } - - /// - /// Removes a key from the object's data if it exists. - /// - /// The key to remove. - /// Cannot remove the username key. - public override void Remove(string key) { - if (key == "username") { - throw new ArgumentException("Cannot remove the username key."); - } - base.Remove(key); - } - - protected override bool IsKeyMutable(string key) { - return !readOnlyKeys.Contains(key); - } - - internal override void HandleSave(IObjectState serverState) { - base.HandleSave(serverState); - - SynchronizeAllAuthData(); - CleanupAuthData(); - - MutateState(mutableClone => { - mutableClone.ServerData.Remove("password"); - }); - } - - /// - /// authenticated token. - /// - public string SessionToken { - get { - if (State.ContainsKey("sessionToken")) { - return State["sessionToken"] as string; - } - return null; - } - } - - internal static string CurrentSessionToken { - get { - Task sessionTokenTask = GetCurrentSessionTokenAsync(); - sessionTokenTask.Wait(); - return sessionTokenTask.Result; - } - } - - internal static Task GetCurrentSessionTokenAsync(CancellationToken cancellationToken = default) { - return CurrentUserController.GetCurrentSessionTokenAsync(cancellationToken); - } - - internal Task SetSessionTokenAsync(string newSessionToken) { - return SetSessionTokenAsync(newSessionToken, CancellationToken.None); - } - - internal Task SetSessionTokenAsync(string newSessionToken, CancellationToken cancellationToken) { - MutateState(mutableClone => { - mutableClone.ServerData["sessionToken"] = newSessionToken; - }); - - return SaveCurrentUserAsync(this); - } - - /// - /// Gets or sets the username. - /// - [AVFieldName("username")] - public string Username { - get { return GetProperty(null, "Username"); } - set { SetProperty(value, "Username"); } - } - - /// - /// Sets the password. - /// - [AVFieldName("password")] - public string Password { - private get { return GetProperty(null, "Password"); } - set { SetProperty(value, "Password"); } - } - - /// - /// Sets the email address. - /// - [AVFieldName("email")] - public string Email { - get { return GetProperty(null, "Email"); } - set { SetProperty(value, "Email"); } - } - - /// - /// 用户手机号。 - /// - [AVFieldName("mobilePhoneNumber")] - public string MobilePhoneNumber { - get { - return GetProperty(null, "MobilePhoneNumber"); - } - set { - SetProperty(value, "MobilePhoneNumber"); - } - } - - /// - /// 用户手机号是否已经验证 - /// - /// true if mobile phone verified; otherwise, false. - [AVFieldName("mobilePhoneVerified")] - public bool MobilePhoneVerified { - get { - return GetProperty(false, "MobilePhoneVerified"); - } - set { - SetProperty(value, "MobilePhoneVerified"); - } - } - - /// - /// 判断用户是否为匿名用户 - /// - public bool IsAnonymous { - get { - bool rtn = false; - if (this.AuthData != null) { - rtn = this.AuthData.Keys.Contains("anonymous"); - } - return rtn; - } - } - - internal Task SignUpAsync(Task toAwait, CancellationToken cancellationToken) { - return this.Create(toAwait, cancellationToken).OnSuccess(_ => SaveCurrentUserAsync(this)).Unwrap(); - } - - /// - /// Signs up a new user. This will create a new AVUser on the server and will also persist the - /// session on disk so that you can access the user using . A username and - /// password must be set before calling SignUpAsync. - /// - public Task SignUpAsync() { - return SignUpAsync(CancellationToken.None); - } - - /// - /// Signs up a new user. This will create a new AVUser on the server and will also persist the - /// session on disk so that you can access the user using . A username and - /// password must be set before calling SignUpAsync. - /// - /// The cancellation token. - public Task SignUpAsync(CancellationToken cancellationToken) { - return taskQueue.Enqueue(toAwait => SignUpAsync(toAwait, cancellationToken), - cancellationToken); - } - - #region 事件流系统相关 API - - /// - /// 关注某个用户 - /// - /// 被关注的用户 - /// - public Task FollowAsync(string userObjectId) { - return this.FollowAsync(userObjectId, null); - } - - /// - /// 关注某个用户 - /// - /// 被关注的用户Id - /// 关注的时候附加属性 - /// - public Task FollowAsync(string userObjectId, IDictionary data) { - if (data != null) { - data = this.EncodeForSaving(data); - } - var command = new AVCommand(string.Format("users/{0}/friendship/{1}", this.ObjectId, userObjectId), - method: "POST", - sessionToken: CurrentSessionToken, - data: data); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t => { - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - - /// - /// 取关某一个用户 - /// - /// - /// - public Task UnfollowAsync(string userObjectId) { - var command = new AVCommand(string.Format("users/{0}/friendship/{1}", this.ObjectId, userObjectId), - method: "DELETE", - sessionToken: CurrentSessionToken, - data: null); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t => { - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - - /// - /// 获取当前用户的关注者的查询 - /// - /// - public AVQuery GetFollowerQuery() { - AVQuery query = new AVQuery(); - query.RelativeUri = string.Format("users/{0}/followers", this.ObjectId); - return query; - } - - /// - /// 获取当前用户所关注的用户的查询 - /// - /// - public AVQuery GetFolloweeQuery() { - AVQuery query = new AVQuery(); - query.RelativeUri = string.Format("users/{0}/followees", this.ObjectId); - return query; - } - - /// - /// 同时查询关注了当前用户的关注者和当前用户所关注的用户 - /// - /// - public AVQuery GetFollowersAndFolloweesQuery() { - AVQuery query = new AVQuery(); - query.RelativeUri = string.Format("users/{0}/followersAndFollowees", this.ObjectId); - return query; - } - - /// - /// 获取当前用户的关注者 - /// - /// - public Task> GetFollowersAsync() { - return this.GetFollowerQuery().FindAsync(); - } - - /// - /// 获取当前用户所关注的用户 - /// - /// - public Task> GetFolloweesAsync() { - return this.GetFolloweeQuery().FindAsync(); - } - - - //public Task SendStatusAsync() - //{ - - //} - - #endregion - - /// - /// Logs in a user with a username and password. On success, this saves the session to disk so you - /// can retrieve the currently logged in user using . - /// - /// The username to log in with. - /// The password to log in with. - /// The newly logged-in user. - public static Task LogInAsync(string username, string password) { - return LogInAsync(username, password, CancellationToken.None); - } - - /// - /// Logs in a user with a username and password. On success, this saves the session to disk so you - /// can retrieve the currently logged in user using . - /// - /// The username to log in with. - /// The password to log in with. - /// The cancellation token. - /// The newly logged-in user. - public static Task LogInAsync(string username, - string password, - CancellationToken cancellationToken) { - return UserController.LogInAsync(username, null, password, cancellationToken).OnSuccess(t => { - AVUser user = AVObject.FromState(t.Result, "_User"); - return SaveCurrentUserAsync(user).OnSuccess(_ => user); - }).Unwrap(); - } - - /// - /// Logs in a user with a username and password. On success, this saves the session to disk so you - /// can retrieve the currently logged in user using . - /// - /// The session token to authorize with - /// The user if authorization was successful - public static Task BecomeAsync(string sessionToken) { - return BecomeAsync(sessionToken, CancellationToken.None); - } - - /// - /// Logs in a user with a username and password. On success, this saves the session to disk so you - /// can retrieve the currently logged in user using . - /// - /// The session token to authorize with - /// The cancellation token. - /// The user if authorization was successful - public static Task BecomeAsync(string sessionToken, CancellationToken cancellationToken) { - return UserController.GetUserAsync(sessionToken, cancellationToken).OnSuccess(t => { - AVUser user = AVObject.FromState(t.Result, "_User"); - return SaveCurrentUserAsync(user).OnSuccess(_ => user); - }).Unwrap(); - } - - protected override Task SaveAsync(Task toAwait, CancellationToken cancellationToken) { - lock (mutex) { - if (ObjectId == null) { - throw new InvalidOperationException("You must call SignUpAsync before calling SaveAsync."); - } - return base.SaveAsync(toAwait, cancellationToken).OnSuccess(_ => { - if (!CurrentUserController.IsCurrent(this)) { - return Task.FromResult(0); - } - return SaveCurrentUserAsync(this); - }).Unwrap(); - } - } - - internal override Task FetchAsyncInternal(Task toAwait, IDictionary queryString, CancellationToken cancellationToken) { - return base.FetchAsyncInternal(toAwait, queryString, cancellationToken).OnSuccess(t => { - if (!CurrentUserController.IsCurrent(this)) { - return Task.FromResult(t.Result); - } - // If this is already the current user, refresh its state on disk. - return SaveCurrentUserAsync(this).OnSuccess(_ => t.Result); - }).Unwrap(); - } - - /// - /// Logs out the currently logged in user session. This will remove the session from disk, log out of - /// linked services, and future calls to will return null. - /// - /// - /// Typically, you should use , unless you are managing your own threading. - /// - public static void LogOut() { - // TODO (hallucinogen): this will without a doubt fail in Unity. But what else can we do? - LogOutAsync().Wait(); - } - - /// - /// Logs out the currently logged in user session. This will remove the session from disk, log out of - /// linked services, and future calls to will return null. - /// - /// - /// This is preferable to using , unless your code is already running from a - /// background thread. - /// - public static Task LogOutAsync() { - return LogOutAsync(CancellationToken.None); - } - - /// - /// Logs out the currently logged in user session. This will remove the session from disk, log out of - /// linked services, and future calls to will return null. - /// - /// This is preferable to using , unless your code is already running from a - /// background thread. - /// - public static Task LogOutAsync(CancellationToken cancellationToken) { - return GetCurrentUserAsync().OnSuccess(t => { - LogOutWithProviders(); - - AVUser user = t.Result; - if (user == null) { - return Task.FromResult(0); - } - - return user.taskQueue.Enqueue(toAwait => user.LogOutAsync(toAwait, cancellationToken), cancellationToken); - }).Unwrap(); - } - - internal Task LogOutAsync(Task toAwait, CancellationToken cancellationToken) { - string oldSessionToken = SessionToken; - if (oldSessionToken == null) { - return Task.FromResult(0); - } - - // Cleanup in-memory session. - MutateState(mutableClone => { - mutableClone.ServerData.Remove("sessionToken"); - }); - var revokeSessionTask = AVSession.RevokeAsync(oldSessionToken, cancellationToken); - return Task.WhenAll(revokeSessionTask, CurrentUserController.LogOutAsync(cancellationToken)); - } - - private static void LogOutWithProviders() { - foreach (var provider in authProviders.Values) { - provider.Deauthenticate(); - } - } - - /// - /// Gets the currently logged in AVUser with a valid session, either from memory or disk - /// if necessary. - /// - public static AVUser CurrentUser { - get { - var userTask = GetCurrentUserAsync(); - // TODO (hallucinogen): this will without a doubt fail in Unity. How should we fix it? - userTask.Wait(); - return userTask.Result; - } - } - - public static Task GetCurrentAsync() { - var userTask = GetCurrentUserAsync(); - return userTask; - } - - /// - /// Gets the currently logged in AVUser with a valid session, either from memory or disk - /// if necessary, asynchronously. - /// - public static Task GetCurrentUserAsync() { - return GetCurrentUserAsync(CancellationToken.None); - } - - /// - /// Gets the currently logged in AVUser with a valid session, either from memory or disk - /// if necessary, asynchronously. - /// - internal static Task GetCurrentUserAsync(CancellationToken cancellationToken) { - return CurrentUserController.GetAsync(cancellationToken); - } - - private static Task SaveCurrentUserAsync(AVUser user) { - return SaveCurrentUserAsync(user, CancellationToken.None); - } - - private static Task SaveCurrentUserAsync(AVUser user, CancellationToken cancellationToken) { - return CurrentUserController.SetAsync(user, cancellationToken); - } - - internal static void ClearInMemoryUser() { - CurrentUserController.ClearFromMemory(); - } - - /// - /// Constructs a for AVUsers. - /// - public static AVQuery Query { - get { - return new AVQuery(); - } - } - - #region Legacy / Revocable Session Tokens - - private static readonly object isRevocableSessionEnabledMutex = new object(); - private static bool isRevocableSessionEnabled; - - /// - /// Tells server to use revocable session on LogIn and SignUp, even when App's Settings - /// has "Require Revocable Session" turned off. Issues network request in background to - /// migrate the sessionToken on disk to revocable session. - /// - /// The Task that upgrades the session. - public static Task EnableRevocableSessionAsync() { - return EnableRevocableSessionAsync(CancellationToken.None); - } - - /// - /// Tells server to use revocable session on LogIn and SignUp, even when App's Settings - /// has "Require Revocable Session" turned off. Issues network request in background to - /// migrate the sessionToken on disk to revocable session. - /// - /// The Task that upgrades the session. - public static Task EnableRevocableSessionAsync(CancellationToken cancellationToken) { - lock (isRevocableSessionEnabledMutex) { - isRevocableSessionEnabled = true; - } - - return GetCurrentUserAsync(cancellationToken).OnSuccess(t => { - var user = t.Result; - return user.UpgradeToRevocableSessionAsync(cancellationToken); - }); - } - - internal static void DisableRevocableSession() { - lock (isRevocableSessionEnabledMutex) { - isRevocableSessionEnabled = false; - } - } - - internal static bool IsRevocableSessionEnabled { - get { - lock (isRevocableSessionEnabledMutex) { - return isRevocableSessionEnabled; - } - } - } - - internal Task UpgradeToRevocableSessionAsync() { - return UpgradeToRevocableSessionAsync(CancellationToken.None); - } - - public Task UpgradeToRevocableSessionAsync(CancellationToken cancellationToken) { - return taskQueue.Enqueue(toAwait => UpgradeToRevocableSessionAsync(toAwait, cancellationToken), - cancellationToken); - } - - internal Task UpgradeToRevocableSessionAsync(Task toAwait, CancellationToken cancellationToken) { - string sessionToken = SessionToken; - - return toAwait.OnSuccess(_ => { - return AVSession.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken); - }).Unwrap().OnSuccess(t => { - return SetSessionTokenAsync(t.Result); - }).Unwrap(); - } - - #endregion - - /// - /// Requests a password reset email to be sent to the specified email address associated with the - /// user account. This email allows the user to securely reset their password on the LeanCloud site. - /// - /// The email address associated with the user that forgot their password. - public static Task RequestPasswordResetAsync(string email) { - return RequestPasswordResetAsync(email, CancellationToken.None); - } - - /// - /// Requests a password reset email to be sent to the specified email address associated with the - /// user account. This email allows the user to securely reset their password on the LeanCloud site. - /// - /// The email address associated with the user that forgot their password. - /// The cancellation token. - public static Task RequestPasswordResetAsync(string email, - CancellationToken cancellationToken) { - return UserController.RequestPasswordResetAsync(email, cancellationToken); - } - - /// - /// Updates current user's password. Need the user's old password, - /// - /// The password. - /// Old password. - /// New password. - /// Cancellation token. - public Task UpdatePassword(string oldPassword, string newPassword, CancellationToken cancellationToken) { - return UserController.UpdatePasswordAsync(ObjectId, SessionToken, oldPassword, newPassword, cancellationToken); - } - - /// - /// Gets the authData for this user. - /// - internal IDictionary> AuthData { - get { - IDictionary> authData; - if (this.TryGetValue>>( - "authData", out authData)) { - return authData; - } - return null; - } - private set { - this["authData"] = value; - } - } - - private static IAVAuthenticationProvider GetProvider(string providerName) { - IAVAuthenticationProvider provider; - if (authProviders.TryGetValue(providerName, out provider)) { - return provider; - } - return null; - } - - /// - /// Removes null values from authData (which exist temporarily for unlinking) - /// - private void CleanupAuthData() { - lock (mutex) { - if (!CurrentUserController.IsCurrent(this)) { - return; - } - var authData = AuthData; - - if (authData == null) { - return; - } - - foreach (var pair in new Dictionary>(authData)) { - if (pair.Value == null) { - authData.Remove(pair.Key); - } - } - } - } - - /// - /// Synchronizes authData for all providers. - /// - private void SynchronizeAllAuthData() { - lock (mutex) { - var authData = AuthData; - - if (authData == null) { - return; - } - - foreach (var pair in authData) { - SynchronizeAuthData(GetProvider(pair.Key)); - } - } - } - - private void SynchronizeAuthData(IAVAuthenticationProvider provider) { - bool restorationSuccess = false; - lock (mutex) { - var authData = AuthData; - if (authData == null || provider == null) { - return; - } - IDictionary data; - if (authData.TryGetValue(provider.AuthType, out data)) { - restorationSuccess = provider.RestoreAuthentication(data); - } - } - - if (!restorationSuccess) { - this.UnlinkFromAsync(provider.AuthType, CancellationToken.None); - } - } - - public Task LinkWithAsync(string authType, IDictionary data, CancellationToken cancellationToken) { - return taskQueue.Enqueue(toAwait => { - AuthData = new Dictionary>(); - AuthData[authType] = data; - return SaveAsync(cancellationToken); - }, cancellationToken); - } - - public Task LinkWithAsync(string authType, CancellationToken cancellationToken) { - var provider = GetProvider(authType); - return provider.AuthenticateAsync(cancellationToken) - .OnSuccess(t => LinkWithAsync(authType, t.Result, cancellationToken)) - .Unwrap(); - } - - /// - /// Unlinks a user from a service. - /// - public Task UnlinkFromAsync(string authType, CancellationToken cancellationToken) { - return LinkWithAsync(authType, null, cancellationToken); - } - - /// - /// Checks whether a user is linked to a service. - /// - internal bool IsLinked(string authType) { - lock (mutex) { - return AuthData != null && AuthData.ContainsKey(authType) && AuthData[authType] != null; - } - } - - internal static Task LogInWithAsync(string authType, - IDictionary data, - bool failOnNotExist, - CancellationToken cancellationToken) { - AVUser user = null; - - return UserController.LogInAsync(authType, data, failOnNotExist, cancellationToken).OnSuccess(t => { - user = AVObject.FromState(t.Result, "_User"); - - lock (user.mutex) { - if (user.AuthData == null) { - user.AuthData = new Dictionary>(); - } - user.AuthData[authType] = data; - user.SynchronizeAllAuthData(); - } - - return SaveCurrentUserAsync(user); - }).Unwrap().OnSuccess(t => user); - } - - internal static Task LogInWithAsync(string authType, - CancellationToken cancellationToken) { - var provider = GetProvider(authType); - return provider.AuthenticateAsync(cancellationToken) - .OnSuccess(authData => LogInWithAsync(authType, authData.Result, false, cancellationToken)) - .Unwrap(); - } - - internal static void RegisterProvider(IAVAuthenticationProvider provider) { - authProviders[provider.AuthType] = provider; - if (CurrentUser != null) { - CurrentUser.SynchronizeAuthData(provider); - } - } - - #region 手机号登录 - - internal static Task LogInWithParametersAsync(Dictionary strs, CancellationToken cancellationToken) { - AVUser avUser = CreateWithoutData(null); - - return UserController.LogInWithParametersAsync("login", strs, cancellationToken).OnSuccess(t => { - var user = CreateWithoutData(null); - user.HandleFetchResult(t.Result); - return SaveCurrentUserAsync(user).OnSuccess(_ => user); - }).Unwrap(); - } - - /// - /// 以手机号和密码实现登陆。 - /// - /// 手机号 - /// 密码 - /// - public static Task LogInByMobilePhoneNumberAsync(string mobilePhoneNumber, string password) { - return LogInByMobilePhoneNumberAsync(mobilePhoneNumber, password, CancellationToken.None); - } - - /// - /// 用邮箱作和密码匹配登录 - /// - /// 邮箱 - /// 密码 - /// - public static Task LogInByEmailAsync(string email, string password, CancellationToken cancellationToken = default) { - return UserController.LogInAsync(null, email, password, cancellationToken).OnSuccess(t => { - AVUser user = FromState(t.Result, "_User"); - return SaveCurrentUserAsync(user).OnSuccess(_ => user); - }).Unwrap(); - } - - - /// - /// 以手机号和密码匹配登陆 - /// - /// 手机号 - /// 密码 - /// - /// - public static Task LogInByMobilePhoneNumberAsync(string mobilePhoneNumber, string password, CancellationToken cancellationToken) { - Dictionary strs = new Dictionary() - { - { "mobilePhoneNumber", mobilePhoneNumber }, - { "password", password } - }; - return LogInWithParametersAsync(strs, cancellationToken); - } - - /// - /// 以手机号和验证码登陆 - /// - /// 手机号 - /// 短信验证码 - /// - /// - public static Task LogInBySmsCodeAsync(string mobilePhoneNumber, string smsCode, CancellationToken cancellationToken = default) { - Dictionary strs = new Dictionary() - { - { "mobilePhoneNumber", mobilePhoneNumber }, - { "smsCode", smsCode } - }; - return LogInWithParametersAsync(strs, cancellationToken); - } - - /// - /// Requests the login SMS code asynchronous. - /// - /// The mobile phone number. - /// - public static Task RequestLogInSmsCodeAsync(string mobilePhoneNumber) { - return RequestLogInSmsCodeAsync(mobilePhoneNumber, CancellationToken.None); - } - - /// - /// Requests the login SMS code asynchronous. - /// - /// The mobile phone number. - /// Validate token. - /// - public static Task RequestLogInSmsCodeAsync(string mobilePhoneNumber, string validateToken) { - return RequestLogInSmsCodeAsync(mobilePhoneNumber, null, CancellationToken.None); - } - - /// - /// Requests the login SMS code asynchronous. - /// - /// The mobile phone number. - /// The cancellation token. - /// - public static Task RequestLogInSmsCodeAsync(string mobilePhoneNumber, CancellationToken cancellationToken) { - return RequestLogInSmsCodeAsync(mobilePhoneNumber, null, cancellationToken); - } - - /// - /// Requests the login SMS code asynchronous. - /// - /// The mobile phone number. - /// Validate token. - /// The cancellation token. - /// - public static Task RequestLogInSmsCodeAsync(string mobilePhoneNumber, string validateToken, CancellationToken cancellationToken) { - Dictionary strs = new Dictionary { - { "mobilePhoneNumber", mobilePhoneNumber }, - }; - if (string.IsNullOrEmpty(validateToken)) { - strs.Add("validate_token", validateToken); - } - var command = new AVCommand("requestLoginSmsCode", "POST", CurrentSessionToken, data: strs); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t => { - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - - /// - /// 手机号一键登录 - /// - /// 手机号 - /// 短信验证码 - /// - public static Task SignUpOrLogInByMobilePhoneAsync(string mobilePhoneNumber, string smsCode, CancellationToken cancellationToken) { - Dictionary strs = new Dictionary() - { - { "mobilePhoneNumber", mobilePhoneNumber }, - { "smsCode", smsCode } - }; - return UserController.LogInWithParametersAsync("usersByMobilePhone", strs, cancellationToken).OnSuccess(t => { - var user = CreateWithoutData(null); - user.HandleFetchResult(t.Result); - return SaveCurrentUserAsync(user).OnSuccess(_ => user); - }).Unwrap(); - } - - /// - /// 手机号一键登录 - /// - /// signup or login by mobile phone async. - /// 手机号 - /// 短信验证码 - public static Task SignUpOrLogInByMobilePhoneAsync(string mobilePhoneNumber, string smsCode) { - return SignUpOrLogInByMobilePhoneAsync(mobilePhoneNumber, smsCode, CancellationToken.None); - } - - #region mobile sms shortcode sign up & log in. - /// - /// Send sign up sms code async. - /// - /// The sign up sms code async. - /// Mobile phone number. - public static Task SendSignUpSmsCodeAsync(string mobilePhoneNumber) { - return AVCloud.RequestSMSCodeAsync(mobilePhoneNumber); - } - - /// - /// Sign up by mobile phone async. - /// - /// The up by mobile phone async. - /// Mobile phone number. - /// Sms code. - public static Task SignUpByMobilePhoneAsync(string mobilePhoneNumber, string smsCode) { - return SignUpOrLogInByMobilePhoneAsync(mobilePhoneNumber, smsCode); - } - - /// - /// Send log in sms code async. - /// - /// The log in sms code async. - /// Mobile phone number. - public static Task SendLogInSmsCodeAsync(string mobilePhoneNumber) { - return RequestLogInSmsCodeAsync(mobilePhoneNumber); - } - - /// - /// Log in by mobile phone async. - /// - /// The in by mobile phone async. - /// Mobile phone number. - /// Sms code. - public static Task LogInByMobilePhoneAsync(string mobilePhoneNumber, string smsCode) { - return LogInBySmsCodeAsync(mobilePhoneNumber, smsCode); - } - #endregion - #endregion - - #region 重置密码 - /// - /// 请求重置密码,需要传入注册时使用的手机号。 - /// - /// 注册时使用的手机号 - /// - public static Task RequestPasswordResetBySmsCode(string mobilePhoneNumber) { - return RequestPasswordResetBySmsCode(mobilePhoneNumber, null, CancellationToken.None); - } - - /// - /// 请求重置密码,需要传入注册时使用的手机号。 - /// - /// 注册时使用的手机号 - /// cancellationToken - /// - public static Task RequestPasswordResetBySmsCode(string mobilePhoneNumber, CancellationToken cancellationToken) { - return RequestPasswordResetBySmsCode(mobilePhoneNumber, null, cancellationToken); - } - - /// - /// 请求重置密码,需要传入注册时使用的手机号。 - /// - /// 注册时使用的手机号 - /// Validate token. - /// - public static Task RequestPasswordResetBySmsCode(string mobilePhoneNumber, string validateToken) { - return RequestPasswordResetBySmsCode(mobilePhoneNumber, validateToken, CancellationToken.None); - } - - /// - /// 请求重置密码,需要传入注册时使用的手机号。 - /// - /// 注册时使用的手机号 - /// Validate token. - /// cancellationToken - /// - public static Task RequestPasswordResetBySmsCode(string mobilePhoneNumber, string validateToken, CancellationToken cancellationToken) { - string currentSessionToken = AVUser.CurrentSessionToken; - Dictionary strs = new Dictionary() - { - { "mobilePhoneNumber", mobilePhoneNumber }, - }; - if (string.IsNullOrEmpty(validateToken)) { - strs.Add("validate_token", validateToken); - } - var command = new AVCommand("requestPasswordResetBySmsCode", "POST", currentSessionToken, data: strs); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t => { - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - - /// - /// 通过验证码重置密码。 - /// - /// 新密码 - /// 6位数验证码 - /// - public static Task ResetPasswordBySmsCodeAsync(string newPassword, string smsCode) { - return AVUser.ResetPasswordBySmsCodeAsync(newPassword, smsCode, CancellationToken.None); - } - - /// - /// 通过验证码重置密码。 - /// - /// 新密码 - /// 6位数验证码 - /// cancellationToken - /// - public static Task ResetPasswordBySmsCodeAsync(string newPassword, string smsCode, CancellationToken cancellationToken) { - string currentSessionToken = AVUser.CurrentSessionToken; - Dictionary strs = new Dictionary() - { - { "password", newPassword } - }; - var command = new AVCommand("resetPasswordBySmsCode/" + smsCode, "PUT", currentSessionToken, data: strs); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t => { - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - - /// - /// 发送认证码到需要认证的手机上 - /// - /// 手机号 - /// - public static Task RequestMobilePhoneVerifyAsync(string mobilePhoneNumber) { - return RequestMobilePhoneVerifyAsync(mobilePhoneNumber, null, CancellationToken.None); - } - - /// - /// 发送认证码到需要认证的手机上 - /// - /// 手机号 - /// Validate token. - /// - public static Task RequestMobilePhoneVerifyAsync(string mobilePhoneNumber, string validateToken) { - return RequestMobilePhoneVerifyAsync(mobilePhoneNumber, validateToken, CancellationToken.None); - } - - /// - /// 发送认证码到需要认证的手机上 - /// - /// 手机号 - /// CancellationToken - /// - public static Task RequestMobilePhoneVerifyAsync(string mobilePhoneNumber, CancellationToken cancellationToken) { - return RequestMobilePhoneVerifyAsync(mobilePhoneNumber, null, cancellationToken); - } - - /// - /// 发送认证码到需要认证的手机上 - /// - /// 手机号 - /// Validate token. - /// CancellationToken - /// - public static Task RequestMobilePhoneVerifyAsync(string mobilePhoneNumber, string validateToken, CancellationToken cancellationToken) { - string currentSessionToken = CurrentSessionToken; - Dictionary strs = new Dictionary() - { - { "mobilePhoneNumber", mobilePhoneNumber } - }; - if (string.IsNullOrEmpty(validateToken)) { - strs.Add("validate_token", validateToken); - } - var command = new AVCommand("requestMobilePhoneVerify", "POST", currentSessionToken, data: strs); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t => { - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - - /// - /// 验证手机验证码是否为有效值 - /// - /// 手机收到的验证码 - /// 手机号,可选 - /// - public static Task VerifyMobilePhoneAsync(string code, string mobilePhoneNumber) { - var command = new AVCommand("verifyMobilePhone/" + code.Trim() + "?mobilePhoneNumber=" + mobilePhoneNumber.Trim(), - "POST", - null, - data: null); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t => { - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - - /// - /// 验证手机验证码是否为有效值 - /// - /// 手机收到的验证码 - /// - public static Task VerifyMobilePhoneAsync(string code) { - var command = new AVCommand("verifyMobilePhone/" + code.Trim(), - "POST", - null, - data: null); - - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t => { - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - - /// - /// 验证手机验证码是否为有效值 - /// - /// 手机收到的验证码 - /// cancellationToken - /// - public static Task VerifyMobilePhoneAsync(string code, CancellationToken cancellationToken) { - return VerifyMobilePhoneAsync(code, CancellationToken.None); - } - - #endregion - - #region 邮箱验证 - /// - /// 申请发送验证邮箱的邮件,一周之内有效 - /// 如果该邮箱已经验证通过,会直接返回 True,并不会真正发送邮件 - /// 注意,不能频繁的调用此接口,一天之内只允许向同一个邮箱发送验证邮件 3 次,超过调用次数,会直接返回错误 - /// - /// 邮箱地址 - /// - public static Task RequestEmailVerifyAsync(string email) { - Dictionary strs = new Dictionary { - { "email", email } - }; - var command = new AVCommand("requestEmailVerify", - "POST", - null, - data: strs); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t => { - return AVClient.IsSuccessStatusCode(t.Result.Item1); - }); - } - #endregion - - #region in no-local-storage enviroment - - internal Task Create() { - return Create(CancellationToken.None); - } - - internal Task Create(CancellationToken cancellationToken) { - return taskQueue.Enqueue(toAwait => Create(toAwait, cancellationToken), - cancellationToken); - } - - internal Task Create(Task toAwait, CancellationToken cancellationToken) { - if (AuthData == null) { - // TODO (hallucinogen): make an Extension of Task to create Task with exception/canceled. - if (string.IsNullOrEmpty(Username)) { - TaskCompletionSource tcs = new TaskCompletionSource(); - tcs.TrySetException(new InvalidOperationException("Cannot sign up user with an empty name.")); - return tcs.Task; - } - if (string.IsNullOrEmpty(Password)) { - TaskCompletionSource tcs = new TaskCompletionSource(); - tcs.TrySetException(new InvalidOperationException("Cannot sign up user with an empty password.")); - return tcs.Task; - } - } - if (!string.IsNullOrEmpty(ObjectId)) { - TaskCompletionSource tcs = new TaskCompletionSource(); - tcs.TrySetException(new InvalidOperationException("Cannot sign up a user that already exists.")); - return tcs.Task; - } - - IDictionary currentOperations = StartSave(); - - return toAwait.OnSuccess(_ => { - return UserController.SignUpAsync(State, currentOperations, cancellationToken); - }).Unwrap().ContinueWith(t => { - if (t.IsFaulted || t.IsCanceled) { - HandleFailedSave(currentOperations); - } else { - var serverState = t.Result; - HandleSave(serverState); - } - return t; - }).Unwrap(); - } - #endregion - - - #region task session token for http request - internal static Task TakeSessionToken(string sesstionToken = null) { - var sessionTokenTask = Task.FromResult(sesstionToken); - if (sesstionToken == null) - sessionTokenTask = AVUser.GetCurrentAsync().OnSuccess(u => { - if (u.Result != null) - return u.Result.SessionToken; - return null; - }); - return sessionTokenTask; - } - #endregion - - - #region AVUser Extension - public IDictionary> GetAuthData() { - return AuthData; - } - - /// - /// use 3rd auth data to sign up or log in.if user with the same auth data exits,it will transfer as log in. - /// - /// OAuth data, like {"accessToken":"xxxxxx"} - /// auth platform,maybe "facebook"/"twiiter"/"weibo"/"weixin" .etc - /// - /// - public static Task LogInWithAuthDataAsync(IDictionary data, - string platform, - AVUserAuthDataLogInOption options = null, - CancellationToken cancellationToken = default) { - if (options == null) { - options = new AVUserAuthDataLogInOption(); - } - return LogInWithAsync(platform, data, options.FailOnNotExist, cancellationToken); - } - - public static Task LogInWithAuthDataAndUnionIdAsync( - IDictionary authData, - string platform, - string unionId, - AVUserAuthDataLogInOption options = null, - CancellationToken cancellationToken = default) { - if (options == null) { - options = new AVUserAuthDataLogInOption(); - } - MergeAuthData(authData, unionId, options); - return LogInWithAsync(platform, authData, options.FailOnNotExist, cancellationToken); - } - - public static Task LogInAnonymouslyAsync(CancellationToken cancellationToken = default) { - var data = new Dictionary { - { "id", Guid.NewGuid().ToString() } - }; - var options = new AVUserAuthDataLogInOption(); - return LogInWithAuthDataAsync(data, "anonymous", options, cancellationToken); - } - - [Obsolete("please use LogInWithAuthDataAsync instead.")] - public static Task LogInWithAsync(string authType, IDictionary data, CancellationToken cancellationToken) { - return LogInWithAsync(authType, data, false, cancellationToken); - } - - /// - /// link a 3rd auth account to the user. - /// - /// OAuth data, like {"accessToken":"xxxxxx"} - /// auth platform,maybe "facebook"/"twiiter"/"weibo"/"weixin" .etc - /// - /// - public Task AssociateAuthDataAsync(IDictionary data, string platform, CancellationToken cancellationToken = default) { - return LinkWithAsync(platform, data, cancellationToken); - } - - public Task AssociateAuthDataAndUnionIdAsync( - IDictionary authData, - string platform, - string unionId, - AVUserAuthDataLogInOption options = null, - CancellationToken cancellationToken = default) { - if (options == null) { - options = new AVUserAuthDataLogInOption(); - } - MergeAuthData(authData, unionId, options); - return LinkWithAsync(platform, authData, cancellationToken); - } - - /// - /// unlink a 3rd auth account from the user. - /// - /// auth platform,maybe "facebook"/"twiiter"/"weibo"/"weixin" .etc - /// - /// - public Task DisassociateWithAuthDataAsync(string platform, CancellationToken cancellationToken = default) { - return UnlinkFromAsync(platform, cancellationToken); - } - - /// 合并为支持 AuthData 的格式 - static void MergeAuthData(IDictionary authData, string unionId, AVUserAuthDataLogInOption options) { - authData["platform"] = options.UnionIdPlatform; - authData["main_account"] = options.AsMainAccount; - authData["unionid"] = unionId; - } - #endregion - } -} diff --git a/Storage/Source/Public/AVUserAuthDataLogInOption.cs b/Storage/Source/Public/AVUserAuthDataLogInOption.cs deleted file mode 100644 index 1918c2b..0000000 --- a/Storage/Source/Public/AVUserAuthDataLogInOption.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace LeanCloud { - /// - /// AuthData 登陆选项 - /// - public class AVUserAuthDataLogInOption { - - /// - /// unionId platform - /// - /// unionId platform. - public string UnionIdPlatform; - - /// - /// If true, the unionId will be associated with the user. - /// - /// true If true, the unionId will be associated with the user. false. - public bool AsMainAccount; - - /// - /// If true, the login request will fail when no user matches this authData exists. - /// - /// true If true, the login request will fail when no user matches this authData exists. false. - public bool FailOnNotExist; - - public AVUserAuthDataLogInOption() { - UnionIdPlatform = "weixin"; - AsMainAccount = false; - FailOnNotExist = false; - } - } -} diff --git a/Storage/Source/Public/IAVQuery.cs b/Storage/Source/Public/IAVQuery.cs deleted file mode 100644 index 7e883d0..0000000 --- a/Storage/Source/Public/IAVQuery.cs +++ /dev/null @@ -1,2068 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Storage.Internal; - -namespace LeanCloud -{ - /// - /// Query 对象的基础接口 - /// - public interface IAVQuery - { - - } - - /// - /// LeanCloud 存储对象的接触接口 - /// - public interface IAVObject - { - - } - - public abstract class AVQueryBase : IAVQuery - where T : IAVObject - { - internal string className; - internal Dictionary where; - internal ReadOnlyCollection orderBy; - internal ReadOnlyCollection includes; - internal ReadOnlyCollection selectedKeys; - internal String redirectClassNameForKey; - internal int? skip; - internal int? limit; - - /// - /// 构建查询字符串 - /// - /// 是否包含 ClassName - /// - public IDictionary BuildParameters(bool includeClassName = false) - { - Dictionary result = new Dictionary(); - if (where != null) - { - result["where"] = PointerOrLocalIdEncoder.Instance.Encode(where); - } - if (orderBy != null) - { - result["order"] = string.Join(",", orderBy.ToArray()); - } - if (skip != null) - { - result["skip"] = skip.Value; - } - if (limit != null) - { - result["limit"] = limit.Value; - } - if (includes != null) - { - result["include"] = string.Join(",", includes.ToArray()); - } - if (selectedKeys != null) - { - result["keys"] = string.Join(",", selectedKeys.ToArray()); - } - if (includeClassName) - { - result["className"] = className; - } - if (redirectClassNameForKey != null) - { - result["redirectClassNameForKey"] = redirectClassNameForKey; - } - return result; - } - - public virtual Dictionary Where - { - get - { - return this.where; - } - set - { - this.where = value; - } - } - - public virtual IDictionary MergeWhere(IDictionary primary, IDictionary secondary) - { - if (secondary == null) - { - return primary; - } - var newWhere = new Dictionary(primary); - foreach (var pair in secondary) - { - var condition = pair.Value as IDictionary; - if (newWhere.ContainsKey(pair.Key)) - { - var oldCondition = newWhere[pair.Key] as IDictionary; - if (oldCondition == null || condition == null) - { - throw new ArgumentException("More than one where clause for the given key provided."); - } - var newCondition = new Dictionary(oldCondition); - foreach (var conditionPair in condition) - { - if (newCondition.ContainsKey(conditionPair.Key)) - { - throw new ArgumentException("More than one condition for the given key provided."); - } - newCondition[conditionPair.Key] = conditionPair.Value; - } - newWhere[pair.Key] = newCondition; - } - else - { - newWhere[pair.Key] = pair.Value; - } - } - return newWhere; - } - } - - public abstract class AVQueryPair - where S : IAVQuery - where T : IAVObject - - { - protected readonly string className; - protected readonly Dictionary where; - protected readonly ReadOnlyCollection orderBy; - protected readonly ReadOnlyCollection includes; - protected readonly ReadOnlyCollection selectedKeys; - protected readonly string redirectClassNameForKey; - protected readonly int? skip; - protected readonly int? limit; - - internal string ClassName { get { return className; } } - - private string relativeUri; - internal string RelativeUri - { - get - { - string rtn = string.Empty; - if (string.IsNullOrEmpty(relativeUri)) - { - rtn = "classes/" + Uri.EscapeDataString(this.className); - } - else - { - rtn = relativeUri; - } - return rtn; - } - set - { - relativeUri = value; - } - } - public Dictionary Condition - { - get { return this.where; } - } - - protected AVQueryPair() - { - - } - - public abstract S 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); - - /// - /// Private constructor for composition of queries. A Source query is required, - /// but the remaining values can be null if they won't be changed in this - /// composition. - /// - protected AVQueryPair(AVQueryPair 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) - { - if (source == null) - { - throw new ArgumentNullException("Source"); - } - - className = source.className; - this.where = source.where; - this.orderBy = source.orderBy; - this.skip = source.skip; - this.limit = source.limit; - this.includes = source.includes; - this.selectedKeys = source.selectedKeys; - this.redirectClassNameForKey = source.redirectClassNameForKey; - - if (where != null) - { - var newWhere = MergeWhereClauses(where); - this.where = new Dictionary(newWhere); - } - - if (replacementOrderBy != null) - { - this.orderBy = new ReadOnlyCollection(replacementOrderBy.ToList()); - } - - if (thenBy != null) - { - if (this.orderBy == null) - { - throw new ArgumentException("You must call OrderBy before calling ThenBy."); - } - var newOrderBy = new List(this.orderBy); - newOrderBy.AddRange(thenBy); - this.orderBy = new ReadOnlyCollection(newOrderBy); - } - - // Remove duplicates. - if (this.orderBy != null) - { - var newOrderBy = new HashSet(this.orderBy); - this.orderBy = new ReadOnlyCollection(newOrderBy.ToList()); - } - - if (skip != null) - { - this.skip = (this.skip ?? 0) + skip; - } - - if (limit != null) - { - this.limit = limit; - } - - if (includes != null) - { - var newIncludes = MergeIncludes(includes); - this.includes = new ReadOnlyCollection(newIncludes.ToList()); - } - - if (selectedKeys != null) - { - var newSelectedKeys = MergeSelectedKeys(selectedKeys); - this.selectedKeys = new ReadOnlyCollection(newSelectedKeys.ToList()); - } - - if (redirectClassNameForKey != null) - { - this.redirectClassNameForKey = redirectClassNameForKey; - } - } - - public AVQueryPair(string className) - { - if (string.IsNullOrEmpty(className)) - { - throw new ArgumentNullException("className", "Must specify a AVObject class name when creating a AVQuery."); - } - this.className = className; - } - - private HashSet MergeIncludes(IEnumerable includes) - { - if (this.includes == null) - { - return new HashSet(includes); - } - var newIncludes = new HashSet(this.includes); - foreach (var item in includes) - { - newIncludes.Add(item); - } - return newIncludes; - } - - private HashSet MergeSelectedKeys(IEnumerable selectedKeys) - { - if (this.selectedKeys == null) - { - return new HashSet(selectedKeys); - } - var newSelectedKeys = new HashSet(this.selectedKeys); - foreach (var item in selectedKeys) - { - newSelectedKeys.Add(item); - } - return newSelectedKeys; - } - - private IDictionary MergeWhereClauses(IDictionary where) - { - return MergeWhere(this.where, where); - } - - public virtual IDictionary MergeWhere(IDictionary primary, IDictionary secondary) - { - if (secondary == null) - { - return primary; - } - if (primary == null) - { - return secondary; - } - var newWhere = new Dictionary(primary); - foreach (var pair in secondary) - { - var condition = pair.Value as IDictionary; - if (newWhere.ContainsKey(pair.Key)) - { - var oldCondition = newWhere[pair.Key] as IDictionary; - if (oldCondition == null || condition == null) - { - throw new ArgumentException("More than one where clause for the given key provided."); - } - var newCondition = new Dictionary(oldCondition); - foreach (var conditionPair in condition) - { - if (newCondition.ContainsKey(conditionPair.Key)) - { - throw new ArgumentException("More than one condition for the given key provided."); - } - newCondition[conditionPair.Key] = conditionPair.Value; - } - newWhere[pair.Key] = newCondition; - } - else - { - newWhere[pair.Key] = pair.Value; - } - } - return newWhere; - } - - /// - /// Constructs a query that is the or of the given queries. - /// - /// The list of AVQueries to 'or' together. - /// A AVeQquery that is the 'or' of the passed in queries. - public static Q Or(IEnumerable queries) - where Q : AVQueryBase - where O : IAVObject - { - string className = null; - var orValue = new List>(); - // We need to cast it to non-generic IEnumerable because of AOT-limitation - var nonGenericQueries = (IEnumerable)queries; - Q current = null; - foreach (var obj in nonGenericQueries) - { - var q = (Q)obj; - current = q; - if (className != null && q.className != className) - { - throw new ArgumentException( - "All of the queries in an or query must be on the same class."); - } - className = q.className; - var parameters = q.BuildParameters(); - if (parameters.Count == 0) - { - continue; - } - object where; - if (!parameters.TryGetValue("where", out where) || parameters.Count > 1) - { - throw new ArgumentException( - "None of the queries in an or query can have non-filtering clauses"); - } - orValue.Add(where as IDictionary); - } - current.Where = new Dictionary() - { - {"$or", orValue} - }; - return current; - } - - #region Order By - - /// - /// Sorts the results in ascending order by the given key. - /// This will override any existing ordering for the query. - /// - /// The key to order by. - /// A new query with the additional constraint. - public virtual S OrderBy(string key) - { - return CreateInstance(replacementOrderBy: new List { key }); - } - - /// - /// Sorts the results in descending order by the given key. - /// This will override any existing ordering for the query. - /// - /// The key to order by. - /// A new query with the additional constraint. - public virtual S OrderByDescending(string key) - { - return CreateInstance(replacementOrderBy: new List { "-" + key }); - } - - /// - /// Sorts the results in ascending order by the given key, after previous - /// ordering has been applied. - /// - /// This method can only be called if there is already an - /// or - /// on this query. - /// - /// The key to order by. - /// A new query with the additional constraint. - public virtual S ThenBy(string key) - { - return CreateInstance(thenBy: new List { key }); - } - - /// - /// Sorts the results in descending order by the given key, after previous - /// ordering has been applied. - /// - /// This method can only be called if there is already an - /// or on this query. - /// - /// The key to order by. - /// A new query with the additional constraint. - public virtual S ThenByDescending(string key) - { - return CreateInstance(thenBy: new List { "-" + key }); - } - - #endregion - - /// - /// Include nested AVObjects for the provided key. You can use dot notation - /// to specify which fields in the included objects should also be fetched. - /// - /// The key that should be included. - /// A new query with the additional constraint. - public virtual S Include(string key) - { - return CreateInstance(includes: new List { key }); - } - - /// - /// Restrict the fields of returned AVObjects to only include the provided key. - /// If this is called multiple times, then all of the keys specified in each of - /// the calls will be included. - /// - /// The key that should be included. - /// A new query with the additional constraint. - public virtual S Select(string key) - { - return CreateInstance(selectedKeys: new List { key }); - } - - /// - /// Skips a number of results before returning. This is useful for pagination - /// of large queries. Chaining multiple skips together will cause more results - /// to be skipped. - /// - /// The number of results to skip. - /// A new query with the additional constraint. - public virtual S Skip(int count) - { - return CreateInstance(skip: count); - } - - /// - /// Controls the maximum number of results that are returned. Setting a negative - /// limit denotes retrieval without a limit. Chaining multiple limits - /// results in the last limit specified being used. The default limit is - /// 100, with a maximum of 1000 results being returned at a time. - /// - /// The maximum number of results to return. - /// A new query with the additional constraint. - public virtual S Limit(int count) - { - return CreateInstance(limit: count); - } - - internal virtual S RedirectClassName(String key) - { - return CreateInstance(redirectClassNameForKey: key); - } - - #region Where - - /// - /// Adds a constraint to the query that requires a particular key's value to be - /// contained in the provided list of values. - /// - /// The key to check. - /// The values that will match. - /// A new query with the additional constraint. - public virtual S WhereContainedIn(string key, IEnumerable values) - { - return CreateInstance(where: new Dictionary { - { key, new Dictionary{{"$in", values.ToList()}}} - }); - } - - /// - /// Add a constraint to the querey that requires a particular key's value to be - /// a list containing all of the elements in the provided list of values. - /// - /// The key to check. - /// The values that will match. - /// A new query with the additional constraint. - public virtual S WhereContainsAll(string key, IEnumerable values) - { - return CreateInstance(where: new Dictionary { - { key, new Dictionary{{"$all", values.ToList()}}} - }); - } - - /// - /// Adds a constraint for finding string values that contain a provided string. - /// This will be slow for large data sets. - /// - /// The key that the string to match is stored in. - /// The substring that the value must contain. - /// A new query with the additional constraint. - public virtual S WhereContains(string key, string substring) - { - return CreateInstance(where: new Dictionary { - { key, new Dictionary{{"$regex", RegexQuote(substring)}}} - }); - } - - /// - /// Adds a constraint for finding objects that do not contain a given key. - /// - /// The key that should not exist. - /// A new query with the additional constraint. - public virtual S WhereDoesNotExist(string key) - { - return CreateInstance(where: new Dictionary{ - { key, new Dictionary{{"$exists", false}}} - }); - } - - /// - /// Adds a constraint to the query that requires that a particular key's value - /// does not match another AVQuery. This only works on keys whose values are - /// AVObjects or lists of AVObjects. - /// - /// The key to check. - /// The query that the value should not match. - /// A new query with the additional constraint. - public virtual S WhereDoesNotMatchQuery(string key, AVQuery query) - where TOther : AVObject - { - return CreateInstance(where: new Dictionary { - { key, new Dictionary{{"$notInQuery", query.BuildParameters(true)}}} - }); - } - - /// - /// Adds a constraint for finding string values that end with a provided string. - /// This will be slow for large data sets. - /// - /// The key that the string to match is stored in. - /// The substring that the value must end with. - /// A new query with the additional constraint. - public virtual S WhereEndsWith(string key, string suffix) - { - return CreateInstance(where: new Dictionary { - { key, new Dictionary{{"$regex", RegexQuote(suffix) + "$"}}} - }); - } - - /// - /// Adds a constraint to the query that requires a particular key's value to be - /// equal to the provided value. - /// - /// The key to check. - /// The value that the AVObject must contain. - /// A new query with the additional constraint. - public virtual S WhereEqualTo(string key, object value) - { - return CreateInstance(where: new Dictionary { - { key, value} - }); - } - - /// - /// Adds a constraint to the query that requires a particular key's size to be - /// equal to the provided size. - /// - /// The size equal to. - /// The key to check. - /// The value that the size must be. - /// A new query with the additional constraint. - public virtual S WhereSizeEqualTo(string key, uint size) - { - return CreateInstance(where: new Dictionary { - { key, new Dictionary{{"$size", size}}} - }); - } - - /// - /// Adds a constraint for finding objects that contain a given key. - /// - /// The key that should exist. - /// A new query with the additional constraint. - public virtual S WhereExists(string key) - { - return CreateInstance(where: new Dictionary{ - { key, new Dictionary{{"$exists", true}}} - }); - } - - /// - /// Adds a constraint to the query that requires a particular key's value to be - /// greater than the provided value. - /// - /// The key to check. - /// The value that provides a lower bound. - /// A new query with the additional constraint. - public virtual S WhereGreaterThan(string key, object value) - { - return CreateInstance(where: new Dictionary{ - { key, new Dictionary{{"$gt", value}}} - }); - } - - /// - /// Adds a constraint to the query that requires a particular key's value to be - /// greater or equal to than the provided value. - /// - /// The key to check. - /// The value that provides a lower bound. - /// A new query with the additional constraint. - public virtual S WhereGreaterThanOrEqualTo(string key, object value) - { - return CreateInstance(where: new Dictionary{ - { key, new Dictionary{{"$gte", value}}} - }); - } - - /// - /// Adds a constraint to the query that requires a particular key's value to be - /// less than the provided value. - /// - /// The key to check. - /// The value that provides an upper bound. - /// A new query with the additional constraint. - public virtual S WhereLessThan(string key, object value) - { - return CreateInstance(where: new Dictionary{ - { key, new Dictionary{{"$lt", value}}} - }); - } - - /// - /// Adds a constraint to the query that requires a particular key's value to be - /// less than or equal to the provided value. - /// - /// The key to check. - /// The value that provides a lower bound. - /// A new query with the additional constraint. - public virtual S WhereLessThanOrEqualTo(string key, object value) - { - return CreateInstance(where: new Dictionary{ - { key, new Dictionary{{"$lte", value}}} - }); - } - - /// - /// Adds a regular expression constraint for finding string values that match the provided - /// regular expression. This may be slow for large data sets. - /// - /// The key that the string to match is stored in. - /// The regular expression pattern to match. The Regex must - /// have the options flag set. - /// Any of the following supported PCRE modifiers: - /// i - Case insensitive search - /// m Search across multiple lines of input - /// A new query with the additional constraint. - public virtual S WhereMatches(string key, Regex regex, string modifiers) - { - if (!regex.Options.HasFlag(RegexOptions.ECMAScript)) - { - throw new ArgumentException( - "Only ECMAScript-compatible regexes are supported. Please use the ECMAScript RegexOptions flag when creating your regex."); - } - return CreateInstance(where: new Dictionary { - { key, EncodeRegex(regex, modifiers)} - }); - } - - /// - /// Adds a regular expression constraint for finding string values that match the provided - /// regular expression. This may be slow for large data sets. - /// - /// The key that the string to match is stored in. - /// The regular expression pattern to match. The Regex must - /// have the options flag set. - /// A new query with the additional constraint. - public virtual S WhereMatches(string key, Regex regex) - { - return WhereMatches(key, regex, null); - } - - /// - /// Adds a regular expression constraint for finding string values that match the provided - /// regular expression. This may be slow for large data sets. - /// - /// The key that the string to match is stored in. - /// The PCRE regular expression pattern to match. - /// Any of the following supported PCRE modifiers: - /// i - Case insensitive search - /// m Search across multiple lines of input - /// A new query with the additional constraint. - public virtual S WhereMatches(string key, string pattern, string modifiers = null) - { - return WhereMatches(key, new Regex(pattern, RegexOptions.ECMAScript), modifiers); - } - - /// - /// Adds a regular expression constraint for finding string values that match the provided - /// regular expression. This may be slow for large data sets. - /// - /// The key that the string to match is stored in. - /// The PCRE regular expression pattern to match. - /// A new query with the additional constraint. - public virtual S WhereMatches(string key, string pattern) - { - return WhereMatches(key, pattern, null); - } - - /// - /// Adds a constraint to the query that requires a particular key's value - /// to match a value for a key in the results of another AVQuery. - /// - /// The key whose value is being checked. - /// The key in the objects from the subquery to look in. - /// The subquery to run - /// A new query with the additional constraint. - public virtual S WhereMatchesKeyInQuery(string key, - string keyInQuery, - AVQuery query) where TOther : AVObject - { - var parameters = new Dictionary { - { "query", query.BuildParameters(true)}, - { "key", keyInQuery} - }; - return CreateInstance(where: new Dictionary { - { key, new Dictionary{{"$select", parameters}}} - }); - } - - /// - /// Adds a constraint to the query that requires a particular key's value - /// does not match any value for a key in the results of another AVQuery. - /// - /// The key whose value is being checked. - /// The key in the objects from the subquery to look in. - /// The subquery to run - /// A new query with the additional constraint. - public virtual S WhereDoesNotMatchesKeyInQuery(string key, - string keyInQuery, - AVQuery query) where TOther : AVObject - { - var parameters = new Dictionary { - { "query", query.BuildParameters(true)}, - { "key", keyInQuery} - }; - return CreateInstance(where: new Dictionary { - { key, new Dictionary{{"$dontSelect", parameters}}} - }); - } - - /// - /// Adds a constraint to the query that requires that a particular key's value - /// matches another AVQuery. This only works on keys whose values are - /// AVObjects or lists of AVObjects. - /// - /// The key to check. - /// The query that the value should match. - /// A new query with the additional constraint. - public virtual S WhereMatchesQuery(string key, AVQuery query) - where TOther : AVObject - { - return CreateInstance(where: new Dictionary { - { key, new Dictionary{{"$inQuery", query.BuildParameters(true)}}} - }); - } - - /// - /// Adds a proximity-based constraint for finding objects with keys whose GeoPoint - /// values are near the given point. - /// - /// The key that the AVGeoPoint is stored in. - /// The reference AVGeoPoint. - /// A new query with the additional constraint. - public virtual S WhereNear(string key, AVGeoPoint point) - { - return CreateInstance(where: new Dictionary { - { key, new Dictionary{{"$nearSphere", point}}} - }); - } - - /// - /// Adds a constraint to the query that requires a particular key's value to be - /// contained in the provided list of values. - /// - /// The key to check. - /// The values that will match. - /// A new query with the additional constraint. - public virtual S WhereNotContainedIn(string key, IEnumerable values) - { - return CreateInstance(where: new Dictionary { - { key, new Dictionary{{"$nin", values.ToList()}}} - }); - } - - /// - /// Adds a constraint to the query that requires a particular key's value not - /// to be equal to the provided value. - /// - /// The key to check. - /// The value that that must not be equalled. - /// A new query with the additional constraint. - public virtual S WhereNotEqualTo(string key, object value) - { - return CreateInstance(where: new Dictionary { - { key, new Dictionary{{"$ne", value}}} - }); - } - - /// - /// Adds a constraint for finding string values that start with the provided string. - /// This query will use the backend index, so it will be fast even with large data sets. - /// - /// The key that the string to match is stored in. - /// The substring that the value must start with. - /// A new query with the additional constraint. - public virtual S WhereStartsWith(string key, string suffix) - { - return CreateInstance(where: new Dictionary { - { key, new Dictionary{{"$regex", "^" + RegexQuote(suffix)}}} - }); - } - - /// - /// Add a constraint to the query that requires a particular key's coordinates to be - /// contained within a given rectangular geographic bounding box. - /// - /// The key to be constrained. - /// The lower-left inclusive corner of the box. - /// The upper-right inclusive corner of the box. - /// A new query with the additional constraint. - public virtual S WhereWithinGeoBox(string key, - AVGeoPoint southwest, - AVGeoPoint northeast) - { - - return this.CreateInstance(where: new Dictionary - { - { - key, - new Dictionary - { - { - "$within", - new Dictionary { - { "$box", new[] {southwest, northeast}} - } - } - } - } - }); - } - - /// - /// Adds a proximity-based constraint for finding objects with keys whose GeoPoint - /// values are near the given point and within the maximum distance given. - /// - /// The key that the AVGeoPoint is stored in. - /// The reference AVGeoPoint. - /// The maximum distance (in radians) of results to return. - /// A new query with the additional constraint. - public virtual S WhereWithinDistance( - string key, AVGeoPoint point, AVGeoDistance maxDistance) - { - var nearWhere = new Dictionary { - { key, new Dictionary{{"$nearSphere", point}}} - }; - var mergedWhere = MergeWhere(nearWhere, new Dictionary { - { key, new Dictionary{{"$maxDistance", maxDistance.Radians}}} - }); - return CreateInstance(where: mergedWhere); - } - - internal virtual S WhereRelatedTo(AVObject parent, string key) - { - return CreateInstance(where: new Dictionary { - { - "$relatedTo", - new Dictionary { - { "object", parent}, - { "key", key} - } - } - }); - } - - #endregion - - /// - /// Retrieves a list of AVObjects that satisfy this query from LeanCloud. - /// - /// The list of AVObjects that match this query. - public virtual Task> FindAsync() - { - return FindAsync(CancellationToken.None); - } - - /// - /// Retrieves a list of AVObjects that satisfy this query from LeanCloud. - /// - /// The cancellation token. - /// The list of AVObjects that match this query. - public abstract Task> FindAsync(CancellationToken cancellationToken); - - - /// - /// Retrieves at most one AVObject that satisfies this query. - /// - /// A single AVObject that satisfies this query, or else null. - public virtual Task FirstOrDefaultAsync() - { - return FirstOrDefaultAsync(CancellationToken.None); - } - - /// - /// Retrieves at most one AVObject that satisfies this query. - /// - /// The cancellation token. - /// A single AVObject that satisfies this query, or else null. - public abstract Task FirstOrDefaultAsync(CancellationToken cancellationToken); - - /// - /// Retrieves at most one AVObject that satisfies this query. - /// - /// A single AVObject that satisfies this query. - /// If no results match the query. - public virtual Task FirstAsync() - { - return FirstAsync(CancellationToken.None); - } - - /// - /// Retrieves at most one AVObject that satisfies this query. - /// - /// The cancellation token. - /// A single AVObject that satisfies this query. - /// If no results match the query. - public abstract Task FirstAsync(CancellationToken cancellationToken); - - /// - /// Counts the number of objects that match this query. - /// - /// The number of objects that match this query. - public virtual Task CountAsync() - { - return CountAsync(CancellationToken.None); - } - - /// - /// Counts the number of objects that match this query. - /// - /// The cancellation token. - /// The number of objects that match this query. - public abstract Task CountAsync(CancellationToken cancellationToken); - - /// - /// Constructs a AVObject whose id is already known by fetching data - /// from the server. - /// - /// ObjectId of the AVObject to fetch. - /// The AVObject for the given objectId. - public virtual Task GetAsync(string objectId) - { - return GetAsync(objectId, CancellationToken.None); - } - - /// - /// Constructs a AVObject whose id is already known by fetching data - /// from the server. - /// - /// ObjectId of the AVObject to fetch. - /// The cancellation token. - /// The AVObject for the given objectId. - public abstract Task GetAsync(string objectId, CancellationToken cancellationToken); - - internal object GetConstraint(string key) - { - return where == null ? null : where.GetOrDefault(key, null); - } - - /// - /// 构建查询字符串 - /// - /// 是否包含 ClassName - /// - public IDictionary BuildParameters(bool includeClassName = false) - { - Dictionary result = new Dictionary(); - if (where != null) - { - result["where"] = PointerOrLocalIdEncoder.Instance.Encode(where); - } - if (orderBy != null) - { - result["order"] = string.Join(",", orderBy.ToArray()); - } - if (skip != null) - { - result["skip"] = skip.Value; - } - if (limit != null) - { - result["limit"] = limit.Value; - } - if (includes != null) - { - result["include"] = string.Join(",", includes.ToArray()); - } - if (selectedKeys != null) - { - result["keys"] = string.Join(",", selectedKeys.ToArray()); - } - if (includeClassName) - { - result["className"] = className; - } - if (redirectClassNameForKey != null) - { - result["redirectClassNameForKey"] = redirectClassNameForKey; - } - return result; - } - - private string RegexQuote(string input) - { - return "\\Q" + input.Replace("\\E", "\\E\\\\E\\Q") + "\\E"; - } - - private string GetRegexOptions(Regex regex, string modifiers) - { - string result = modifiers ?? ""; - if (regex.Options.HasFlag(RegexOptions.IgnoreCase) && !modifiers.Contains("i")) - { - result += "i"; - } - if (regex.Options.HasFlag(RegexOptions.Multiline) && !modifiers.Contains("m")) - { - result += "m"; - } - return result; - } - - private IDictionary EncodeRegex(Regex regex, string modifiers) - { - var options = GetRegexOptions(regex, modifiers); - var dict = new Dictionary(); - dict["$regex"] = regex.ToString(); - if (!string.IsNullOrEmpty(options)) - { - dict["$options"] = options; - } - return dict; - } - } - - //public abstract class AVQueryBase : IAVQueryTuple - // where T : IAVObject - //{ - // protected readonly string className; - // protected readonly Dictionary where; - // protected readonly ReadOnlyCollection orderBy; - // protected readonly ReadOnlyCollection includes; - // protected readonly ReadOnlyCollection selectedKeys; - // protected readonly String redirectClassNameForKey; - // protected readonly int? skip; - // protected readonly int? limit; - - // internal string ClassName { get { return className; } } - - // private string relativeUri; - // internal string RelativeUri - // { - // get - // { - // string rtn = string.Empty; - // if (string.IsNullOrEmpty(relativeUri)) - // { - // rtn = "classes/" + Uri.EscapeDataString(this.className); - // } - // else - // { - // rtn = relativeUri; - // } - // return rtn; - // } - // set - // { - // relativeUri = value; - // } - // } - // public Dictionary Condition - // { - // get { return this.where; } - // } - - // protected AVQueryBase() - // { - - // } - - // internal abstract S CreateInstance(AVQueryBase 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); - - // /// - // /// Private constructor for composition of queries. A Source query is required, - // /// but the remaining values can be null if they won't be changed in this - // /// composition. - // /// - // protected AVQueryBase(AVQueryBase 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) - // { - // if (source == null) - // { - // throw new ArgumentNullException("Source"); - // } - - // className = source.className; - // this.where = source.where; - // this.orderBy = source.orderBy; - // this.skip = source.skip; - // this.limit = source.limit; - // this.includes = source.includes; - // this.selectedKeys = source.selectedKeys; - // this.redirectClassNameForKey = source.redirectClassNameForKey; - - // if (where != null) - // { - // var newWhere = MergeWhereClauses(where); - // this.where = new Dictionary(newWhere); - // } - - // if (replacementOrderBy != null) - // { - // this.orderBy = new ReadOnlyCollection(replacementOrderBy.ToList()); - // } - - // if (thenBy != null) - // { - // if (this.orderBy == null) - // { - // throw new ArgumentException("You must call OrderBy before calling ThenBy."); - // } - // var newOrderBy = new List(this.orderBy); - // newOrderBy.AddRange(thenBy); - // this.orderBy = new ReadOnlyCollection(newOrderBy); - // } - - // // Remove duplicates. - // if (this.orderBy != null) - // { - // var newOrderBy = new HashSet(this.orderBy); - // this.orderBy = new ReadOnlyCollection(newOrderBy.ToList()); - // } - - // if (skip != null) - // { - // this.skip = (this.skip ?? 0) + skip; - // } - - // if (limit != null) - // { - // this.limit = limit; - // } - - // if (includes != null) - // { - // var newIncludes = MergeIncludes(includes); - // this.includes = new ReadOnlyCollection(newIncludes.ToList()); - // } - - // if (selectedKeys != null) - // { - // var newSelectedKeys = MergeSelectedKeys(selectedKeys); - // this.selectedKeys = new ReadOnlyCollection(newSelectedKeys.ToList()); - // } - - // if (redirectClassNameForKey != null) - // { - // this.redirectClassNameForKey = redirectClassNameForKey; - // } - // } - - // public AVQueryBase(string className) - // { - // if (string.IsNullOrEmpty(className)) - // { - // throw new ArgumentNullException("className", "Must specify a AVObject class name when creating a AVQuery."); - // } - // this.className = className; - // } - - // private HashSet MergeIncludes(IEnumerable includes) - // { - // if (this.includes == null) - // { - // return new HashSet(includes); - // } - // var newIncludes = new HashSet(this.includes); - // foreach (var item in includes) - // { - // newIncludes.Add(item); - // } - // return newIncludes; - // } - - // private HashSet MergeSelectedKeys(IEnumerable selectedKeys) - // { - // if (this.selectedKeys == null) - // { - // return new HashSet(selectedKeys); - // } - // var newSelectedKeys = new HashSet(this.selectedKeys); - // foreach (var item in selectedKeys) - // { - // newSelectedKeys.Add(item); - // } - // return newSelectedKeys; - // } - - // private IDictionary MergeWhereClauses(IDictionary where) - // { - // return MergeWhere(this.where, where); - // } - - // public virtual IDictionary MergeWhere(IDictionary primary, IDictionary secondary) - // { - // if (secondary == null) - // { - // return primary; - // } - // var newWhere = new Dictionary(primary); - // foreach (var pair in secondary) - // { - // var condition = pair.Value as IDictionary; - // if (newWhere.ContainsKey(pair.Key)) - // { - // var oldCondition = newWhere[pair.Key] as IDictionary; - // if (oldCondition == null || condition == null) - // { - // throw new ArgumentException("More than one where clause for the given key provided."); - // } - // var newCondition = new Dictionary(oldCondition); - // foreach (var conditionPair in condition) - // { - // if (newCondition.ContainsKey(conditionPair.Key)) - // { - // throw new ArgumentException("More than one condition for the given key provided."); - // } - // newCondition[conditionPair.Key] = conditionPair.Value; - // } - // newWhere[pair.Key] = newCondition; - // } - // else - // { - // newWhere[pair.Key] = pair.Value; - // } - // } - // return newWhere; - // } - - // ///// - // ///// Constructs a query that is the or of the given queries. - // ///// - // ///// The list of AVQueries to 'or' together. - // ///// A AVQquery that is the 'or' of the passed in queries. - // //public static AVQuery Or(IEnumerable> queries) - // //{ - // // string className = null; - // // var orValue = new List>(); - // // // We need to cast it to non-generic IEnumerable because of AOT-limitation - // // var nonGenericQueries = (IEnumerable)queries; - // // foreach (var obj in nonGenericQueries) - // // { - // // var q = (AVQuery)obj; - // // if (className != null && q.className != className) - // // { - // // throw new ArgumentException( - // // "All of the queries in an or query must be on the same class."); - // // } - // // className = q.className; - // // var parameters = q.BuildParameters(); - // // if (parameters.Count == 0) - // // { - // // continue; - // // } - // // object where; - // // if (!parameters.TryGetValue("where", out where) || parameters.Count > 1) - // // { - // // throw new ArgumentException( - // // "None of the queries in an or query can have non-filtering clauses"); - // // } - // // orValue.Add(where as IDictionary); - // // } - // // return new AVQuery(new AVQuery(className), - // // where: new Dictionary { - // // {"$or", orValue} - // // }); - // //} - - // #region Order By - - // /// - // /// Sorts the results in ascending order by the given key. - // /// This will override any existing ordering for the query. - // /// - // /// The key to order by. - // /// A new query with the additional constraint. - // public virtual S OrderBy(string key) - // { - // return CreateInstance( replacementOrderBy: new List { key }); - // } - - // /// - // /// Sorts the results in descending order by the given key. - // /// This will override any existing ordering for the query. - // /// - // /// The key to order by. - // /// A new query with the additional constraint. - // public virtual S OrderByDescending(string key) - // { - // return CreateInstance( replacementOrderBy: new List { "-" + key }); - // } - - // /// - // /// Sorts the results in ascending order by the given key, after previous - // /// ordering has been applied. - // /// - // /// This method can only be called if there is already an - // /// or - // /// on this query. - // /// - // /// The key to order by. - // /// A new query with the additional constraint. - // public virtual S ThenBy(string key) - // { - // return CreateInstance( thenBy: new List { key }); - // } - - // /// - // /// Sorts the results in descending order by the given key, after previous - // /// ordering has been applied. - // /// - // /// This method can only be called if there is already an - // /// or on this query. - // /// - // /// The key to order by. - // /// A new query with the additional constraint. - // public virtual S ThenByDescending(string key) - // { - // return CreateInstance( thenBy: new List { "-" + key }); - // } - - // #endregion - - // /// - // /// Include nested AVObjects for the provided key. You can use dot notation - // /// to specify which fields in the included objects should also be fetched. - // /// - // /// The key that should be included. - // /// A new query with the additional constraint. - // public virtual S Include(string key) - // { - // return CreateInstance( includes: new List { key }); - // } - - // /// - // /// Restrict the fields of returned AVObjects to only include the provided key. - // /// If this is called multiple times, then all of the keys specified in each of - // /// the calls will be included. - // /// - // /// The key that should be included. - // /// A new query with the additional constraint. - // public virtual S Select(string key) - // { - // return CreateInstance( selectedKeys: new List { key }); - // } - - // /// - // /// Skips a number of results before returning. This is useful for pagination - // /// of large queries. Chaining multiple skips together will cause more results - // /// to be skipped. - // /// - // /// The number of results to skip. - // /// A new query with the additional constraint. - // public virtual S Skip(int count) - // { - // return CreateInstance( skip: count); - // } - - // /// - // /// Controls the maximum number of results that are returned. Setting a negative - // /// limit denotes retrieval without a limit. Chaining multiple limits - // /// results in the last limit specified being used. The default limit is - // /// 100, with a maximum of 1000 results being returned at a time. - // /// - // /// The maximum number of results to return. - // /// A new query with the additional constraint. - // public virtual S Limit(int count) - // { - // return CreateInstance( limit: count); - // } - - // internal virtual S RedirectClassName(String key) - // { - // return CreateInstance( redirectClassNameForKey: key); - // } - - // #region Where - - // /// - // /// Adds a constraint to the query that requires a particular key's value to be - // /// contained in the provided list of values. - // /// - // /// The key to check. - // /// The values that will match. - // /// A new query with the additional constraint. - // public virtual S WhereContainedIn(string key, IEnumerable values) - // { - // return CreateInstance( where: new Dictionary { - // { key, new Dictionary{{"$in", values.ToList()}}} - // }); - // } - - // /// - // /// Add a constraint to the querey that requires a particular key's value to be - // /// a list containing all of the elements in the provided list of values. - // /// - // /// The key to check. - // /// The values that will match. - // /// A new query with the additional constraint. - // public virtual S WhereContainsAll(string key, IEnumerable values) - // { - // return CreateInstance( where: new Dictionary { - // { key, new Dictionary{{"$all", values.ToList()}}} - // }); - // } - - // /// - // /// Adds a constraint for finding string values that contain a provided string. - // /// This will be slow for large data sets. - // /// - // /// The key that the string to match is stored in. - // /// The substring that the value must contain. - // /// A new query with the additional constraint. - // public virtual S WhereContains(string key, string substring) - // { - // return CreateInstance( where: new Dictionary { - // { key, new Dictionary{{"$regex", RegexQuote(substring)}}} - // }); - // } - - // /// - // /// Adds a constraint for finding objects that do not contain a given key. - // /// - // /// The key that should not exist. - // /// A new query with the additional constraint. - // public virtual S WhereDoesNotExist(string key) - // { - // return CreateInstance( where: new Dictionary{ - // { key, new Dictionary{{"$exists", false}}} - // }); - // } - - // /// - // /// Adds a constraint to the query that requires that a particular key's value - // /// does not match another AVQuery. This only works on keys whose values are - // /// AVObjects or lists of AVObjects. - // /// - // /// The key to check. - // /// The query that the value should not match. - // /// A new query with the additional constraint. - // public virtual S WhereDoesNotMatchQuery(string key, AVQuery query) - // where TOther : AVObject - // { - // return CreateInstance( where: new Dictionary { - // { key, new Dictionary{{"$notInQuery", query.BuildParameters(true)}}} - // }); - // } - - // /// - // /// Adds a constraint for finding string values that end with a provided string. - // /// This will be slow for large data sets. - // /// - // /// The key that the string to match is stored in. - // /// The substring that the value must end with. - // /// A new query with the additional constraint. - // public virtual S WhereEndsWith(string key, string suffix) - // { - // return CreateInstance( where: new Dictionary { - // { key, new Dictionary{{"$regex", RegexQuote(suffix) + "$"}}} - // }); - // } - - // /// - // /// Adds a constraint to the query that requires a particular key's value to be - // /// equal to the provided value. - // /// - // /// The key to check. - // /// The value that the AVObject must contain. - // /// A new query with the additional constraint. - // public virtual S WhereEqualTo(string key, object value) - // { - // return CreateInstance( where: new Dictionary { - // { key, value} - // }); - // } - - // /// - // /// Adds a constraint to the query that requires a particular key's size to be - // /// equal to the provided size. - // /// - // /// The size equal to. - // /// The key to check. - // /// The value that the size must be. - // /// A new query with the additional constraint. - // public virtual S WhereSizeEqualTo(string key, uint size) - // { - // return CreateInstance( where: new Dictionary { - // { key, new Dictionary{{"$size", size}}} - // }); - // } - - // /// - // /// Adds a constraint for finding objects that contain a given key. - // /// - // /// The key that should exist. - // /// A new query with the additional constraint. - // public virtual S WhereExists(string key) - // { - // return CreateInstance( where: new Dictionary{ - // { key, new Dictionary{{"$exists", true}}} - // }); - // } - - // /// - // /// Adds a constraint to the query that requires a particular key's value to be - // /// greater than the provided value. - // /// - // /// The key to check. - // /// The value that provides a lower bound. - // /// A new query with the additional constraint. - // public virtual S WhereGreaterThan(string key, object value) - // { - // return CreateInstance( where: new Dictionary{ - // { key, new Dictionary{{"$gt", value}}} - // }); - // } - - // /// - // /// Adds a constraint to the query that requires a particular key's value to be - // /// greater or equal to than the provided value. - // /// - // /// The key to check. - // /// The value that provides a lower bound. - // /// A new query with the additional constraint. - // public virtual S WhereGreaterThanOrEqualTo(string key, object value) - // { - // return CreateInstance( where: new Dictionary{ - // { key, new Dictionary{{"$gte", value}}} - // }); - // } - - // /// - // /// Adds a constraint to the query that requires a particular key's value to be - // /// less than the provided value. - // /// - // /// The key to check. - // /// The value that provides an upper bound. - // /// A new query with the additional constraint. - // public virtual S WhereLessThan(string key, object value) - // { - // return CreateInstance( where: new Dictionary{ - // { key, new Dictionary{{"$lt", value}}} - // }); - // } - - // /// - // /// Adds a constraint to the query that requires a particular key's value to be - // /// less than or equal to the provided value. - // /// - // /// The key to check. - // /// The value that provides a lower bound. - // /// A new query with the additional constraint. - // public virtual S WhereLessThanOrEqualTo(string key, object value) - // { - // return CreateInstance( where: new Dictionary{ - // { key, new Dictionary{{"$lte", value}}} - // }); - // } - - // /// - // /// Adds a regular expression constraint for finding string values that match the provided - // /// regular expression. This may be slow for large data sets. - // /// - // /// The key that the string to match is stored in. - // /// The regular expression pattern to match. The Regex must - // /// have the options flag set. - // /// Any of the following supported PCRE modifiers: - // /// i - Case insensitive search - // /// m Search across multiple lines of input - // /// A new query with the additional constraint. - // public virtual S WhereMatches(string key, Regex regex, string modifiers) - // { - // if (!regex.Options.HasFlag(RegexOptions.ECMAScript)) - // { - // throw new ArgumentException( - // "Only ECMAScript-compatible regexes are supported. Please use the ECMAScript RegexOptions flag when creating your regex."); - // } - // return CreateInstance( where: new Dictionary { - // { key, EncodeRegex(regex, modifiers)} - // }); - // } - - // /// - // /// Adds a regular expression constraint for finding string values that match the provided - // /// regular expression. This may be slow for large data sets. - // /// - // /// The key that the string to match is stored in. - // /// The regular expression pattern to match. The Regex must - // /// have the options flag set. - // /// A new query with the additional constraint. - // public virtual S WhereMatches(string key, Regex regex) - // { - // return WhereMatches(key, regex, null); - // } - - // /// - // /// Adds a regular expression constraint for finding string values that match the provided - // /// regular expression. This may be slow for large data sets. - // /// - // /// The key that the string to match is stored in. - // /// The PCRE regular expression pattern to match. - // /// Any of the following supported PCRE modifiers: - // /// i - Case insensitive search - // /// m Search across multiple lines of input - // /// A new query with the additional constraint. - // public virtual S WhereMatches(string key, string pattern, string modifiers = null) - // { - // return WhereMatches(key, new Regex(pattern, RegexOptions.ECMAScript), modifiers); - // } - - // /// - // /// Adds a regular expression constraint for finding string values that match the provided - // /// regular expression. This may be slow for large data sets. - // /// - // /// The key that the string to match is stored in. - // /// The PCRE regular expression pattern to match. - // /// A new query with the additional constraint. - // public virtual S WhereMatches(string key, string pattern) - // { - // return WhereMatches(key, pattern, null); - // } - - // /// - // /// Adds a constraint to the query that requires a particular key's value - // /// to match a value for a key in the results of another AVQuery. - // /// - // /// The key whose value is being checked. - // /// The key in the objects from the subquery to look in. - // /// The subquery to run - // /// A new query with the additional constraint. - // public virtual S WhereMatchesKeyInQuery(string key, - // string keyInQuery, - // AVQuery query) where TOther : AVObject - // { - // var parameters = new Dictionary { - // { "query", query.BuildParameters(true)}, - // { "key", keyInQuery} - // }; - // return CreateInstance( where: new Dictionary { - // { key, new Dictionary{{"$select", parameters}}} - // }); - // } - - // /// - // /// Adds a constraint to the query that requires a particular key's value - // /// does not match any value for a key in the results of another AVQuery. - // /// - // /// The key whose value is being checked. - // /// The key in the objects from the subquery to look in. - // /// The subquery to run - // /// A new query with the additional constraint. - // public virtual S WhereDoesNotMatchesKeyInQuery(string key, - // string keyInQuery, - // AVQuery query) where TOther : AVObject - // { - // var parameters = new Dictionary { - // { "query", query.BuildParameters(true)}, - // { "key", keyInQuery} - // }; - // return CreateInstance( where: new Dictionary { - // { key, new Dictionary{{"$dontSelect", parameters}}} - // }); - // } - - // /// - // /// Adds a constraint to the query that requires that a particular key's value - // /// matches another AVQuery. This only works on keys whose values are - // /// AVObjects or lists of AVObjects. - // /// - // /// The key to check. - // /// The query that the value should match. - // /// A new query with the additional constraint. - // public virtual S WhereMatchesQuery(string key, AVQuery query) - // where TOther : AVObject - // { - // return CreateInstance( where: new Dictionary { - // { key, new Dictionary{{"$inQuery", query.BuildParameters(true)}}} - // }); - // } - - // /// - // /// Adds a proximity-based constraint for finding objects with keys whose GeoPoint - // /// values are near the given point. - // /// - // /// The key that the AVGeoPoint is stored in. - // /// The reference AVGeoPoint. - // /// A new query with the additional constraint. - // public virtual S WhereNear(string key, AVGeoPoint point) - // { - // return CreateInstance( where: new Dictionary { - // { key, new Dictionary{{"$nearSphere", point}}} - // }); - // } - - // /// - // /// Adds a constraint to the query that requires a particular key's value to be - // /// contained in the provided list of values. - // /// - // /// The key to check. - // /// The values that will match. - // /// A new query with the additional constraint. - // public virtual S WhereNotContainedIn(string key, IEnumerable values) - // { - // return CreateInstance( where: new Dictionary { - // { key, new Dictionary{{"$nin", values.ToList()}}} - // }); - // } - - // /// - // /// Adds a constraint to the query that requires a particular key's value not - // /// to be equal to the provided value. - // /// - // /// The key to check. - // /// The value that that must not be equalled. - // /// A new query with the additional constraint. - // public virtual S WhereNotEqualTo(string key, object value) - // { - // return CreateInstance( where: new Dictionary { - // { key, new Dictionary{{"$ne", value}}} - // }); - // } - - // /// - // /// Adds a constraint for finding string values that start with the provided string. - // /// This query will use the backend index, so it will be fast even with large data sets. - // /// - // /// The key that the string to match is stored in. - // /// The substring that the value must start with. - // /// A new query with the additional constraint. - // public virtual S WhereStartsWith(string key, string suffix) - // { - // return CreateInstance( where: new Dictionary { - // { key, new Dictionary{{"$regex", "^" + RegexQuote(suffix)}}} - // }); - // } - - // /// - // /// Add a constraint to the query that requires a particular key's coordinates to be - // /// contained within a given rectangular geographic bounding box. - // /// - // /// The key to be constrained. - // /// The lower-left inclusive corner of the box. - // /// The upper-right inclusive corner of the box. - // /// A new query with the additional constraint. - // public virtual S WhereWithinGeoBox(string key, - // AVGeoPoint southwest, - // AVGeoPoint northeast) - // { - - // return this.CreateInstance( where: new Dictionary - // { - // { - // key, - // new Dictionary - // { - // { - // "$within", - // new Dictionary { - // { "$box", new[] {southwest, northeast}} - // } - // } - // } - // } - // }); - // } - - // /// - // /// Adds a proximity-based constraint for finding objects with keys whose GeoPoint - // /// values are near the given point and within the maximum distance given. - // /// - // /// The key that the AVGeoPoint is stored in. - // /// The reference AVGeoPoint. - // /// The maximum distance (in radians) of results to return. - // /// A new query with the additional constraint. - // public virtual S WhereWithinDistance( - // string key, AVGeoPoint point, AVGeoDistance maxDistance) - // { - // var nearWhere = new Dictionary { - // { key, new Dictionary{{"$nearSphere", point}}} - // }; - // var mergedWhere = MergeWhere(nearWhere, new Dictionary { - // { key, new Dictionary{{"$maxDistance", maxDistance.Radians}}} - // }); - // return CreateInstance( where: mergedWhere); - // } - - // internal virtual S WhereRelatedTo(AVObject parent, string key) - // { - // return CreateInstance( where: new Dictionary { - // { - // "$relatedTo", - // new Dictionary { - // { "object", parent}, - // { "key", key} - // } - // } - // }); - // } - - // #endregion - - // /// - // /// Retrieves a list of AVObjects that satisfy this query from LeanCloud. - // /// - // /// The list of AVObjects that match this query. - // public virtual Task> FindAsync() - // { - // return FindAsync(CancellationToken.None); - // } - - // /// - // /// Retrieves a list of AVObjects that satisfy this query from LeanCloud. - // /// - // /// The cancellation token. - // /// The list of AVObjects that match this query. - // public abstract Task> FindAsync(CancellationToken cancellationToken); - - - // /// - // /// Retrieves at most one AVObject that satisfies this query. - // /// - // /// A single AVObject that satisfies this query, or else null. - // public virtual Task FirstOrDefaultAsync() - // { - // return FirstOrDefaultAsync(CancellationToken.None); - // } - - // /// - // /// Retrieves at most one AVObject that satisfies this query. - // /// - // /// The cancellation token. - // /// A single AVObject that satisfies this query, or else null. - // public abstract Task FirstOrDefaultAsync(CancellationToken cancellationToken); - - // /// - // /// Retrieves at most one AVObject that satisfies this query. - // /// - // /// A single AVObject that satisfies this query. - // /// If no results match the query. - // public virtual Task FirstAsync() - // { - // return FirstAsync(CancellationToken.None); - // } - - // /// - // /// Retrieves at most one AVObject that satisfies this query. - // /// - // /// The cancellation token. - // /// A single AVObject that satisfies this query. - // /// If no results match the query. - // public abstract Task FirstAsync(CancellationToken cancellationToken); - - // /// - // /// Counts the number of objects that match this query. - // /// - // /// The number of objects that match this query. - // public virtual Task CountAsync() - // { - // return CountAsync(CancellationToken.None); - // } - - // /// - // /// Counts the number of objects that match this query. - // /// - // /// The cancellation token. - // /// The number of objects that match this query. - // public abstract Task CountAsync(CancellationToken cancellationToken); - - // /// - // /// Constructs a AVObject whose id is already known by fetching data - // /// from the server. - // /// - // /// ObjectId of the AVObject to fetch. - // /// The AVObject for the given objectId. - // public virtual Task GetAsync(string objectId) - // { - // return GetAsync(objectId, CancellationToken.None); - // } - - // /// - // /// Constructs a AVObject whose id is already known by fetching data - // /// from the server. - // /// - // /// ObjectId of the AVObject to fetch. - // /// The cancellation token. - // /// The AVObject for the given objectId. - // public abstract Task GetAsync(string objectId, CancellationToken cancellationToken); - - // internal object GetConstraint(string key) - // { - // return where == null ? null : where.GetOrDefault(key, null); - // } - - // /// - // /// 构建查询字符串 - // /// - // /// 是否包含 ClassName - // /// - // public IDictionary BuildParameters(bool includeClassName = false) - // { - // Dictionary result = new Dictionary(); - // if (where != null) - // { - // result["where"] = PointerOrLocalIdEncoder.Instance.Encode(where); - // } - // if (orderBy != null) - // { - // result["order"] = string.Join(",", orderBy.ToArray()); - // } - // if (skip != null) - // { - // result["skip"] = skip.Value; - // } - // if (limit != null) - // { - // result["limit"] = limit.Value; - // } - // if (includes != null) - // { - // result["include"] = string.Join(",", includes.ToArray()); - // } - // if (selectedKeys != null) - // { - // result["keys"] = string.Join(",", selectedKeys.ToArray()); - // } - // if (includeClassName) - // { - // result["className"] = className; - // } - // if (redirectClassNameForKey != null) - // { - // result["redirectClassNameForKey"] = redirectClassNameForKey; - // } - // return result; - // } - - // private string RegexQuote(string input) - // { - // return "\\Q" + input.Replace("\\E", "\\E\\\\E\\Q") + "\\E"; - // } - - // private string GetRegexOptions(Regex regex, string modifiers) - // { - // string result = modifiers ?? ""; - // if (regex.Options.HasFlag(RegexOptions.IgnoreCase) && !modifiers.Contains("i")) - // { - // result += "i"; - // } - // if (regex.Options.HasFlag(RegexOptions.Multiline) && !modifiers.Contains("m")) - // { - // result += "m"; - // } - // return result; - // } - - // private IDictionary EncodeRegex(Regex regex, string modifiers) - // { - // var options = GetRegexOptions(regex, modifiers); - // var dict = new Dictionary(); - // dict["$regex"] = regex.ToString(); - // if (!string.IsNullOrEmpty(options)) - // { - // dict["$options"] = options; - // } - // return dict; - // } - - // /// - // /// Serves as the default hash function. - // /// - // /// A hash code for the current object. - // public override int GetHashCode() - // { - // // TODO (richardross): Implement this. - // return 0; - // } - //} -} diff --git a/Storage/Source/Public/LeaderBoard/AVLeaderboard.cs b/Storage/Source/Public/LeaderBoard/AVLeaderboard.cs deleted file mode 100644 index 8713c1b..0000000 --- a/Storage/Source/Public/LeaderBoard/AVLeaderboard.cs +++ /dev/null @@ -1,479 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Collections.Generic; -using LeanCloud.Storage.Internal; -using System.IO; -using System.Text; - -namespace LeanCloud { - /// - /// 排行榜顺序 - /// - public enum AVLeaderboardOrder { - /// - /// 升序 - /// - ASCENDING, - /// - /// 降序 - /// - DESCENDING - } - - /// - /// 排行榜更新策略 - /// - public enum AVLeaderboardUpdateStrategy { - /// - /// 更好地 - /// - BETTER, - /// - /// 最近的 - /// - LAST, - /// - /// 总和 - /// - SUM, - } - - /// - /// 排行榜刷新频率 - /// - public enum AVLeaderboardVersionChangeInterval { - /// - /// 从不 - /// - NEVER, - /// - /// 每天 - /// - DAY, - /// - /// 每周 - /// - WEEK, - /// - /// 每月 - /// - MONTH - } - - /// - /// 排行榜类 - /// - public class AVLeaderboard { - /// - /// 成绩名字 - /// - /// The name of the statistic. - public string StatisticName { - get; private set; - } - - /// - /// 排行榜顺序 - /// - /// The order. - public AVLeaderboardOrder Order { - get; private set; - } - - /// - /// 排行榜更新策略 - /// - /// The update strategy. - public AVLeaderboardUpdateStrategy UpdateStrategy { - get; private set; - } - - /// - /// 排行榜版本更新频率 - /// - /// The version change intervak. - public AVLeaderboardVersionChangeInterval VersionChangeInterval { - get; private set; - } - - /// - /// 版本号 - /// - /// The version. - public int Version { - get; private set; - } - - /// - /// 下次重置时间 - /// - /// The next reset at. - public DateTime NextResetAt { - get; private set; - } - - /// - /// 创建时间 - /// - /// The created at. - public DateTime CreatedAt { - get; private set; - } - - /// - /// Leaderboard 构造方法 - /// - /// 成绩名称 - AVLeaderboard(string statisticName) { - StatisticName = statisticName; - } - - AVLeaderboard() { - } - - /// - /// 创建排行榜对象 - /// - /// 排行榜对象 - /// 名称 - /// 排序方式 - /// 版本更新频率 - /// 成绩更新策略 - public static Task CreateLeaderboard(string statisticName, - AVLeaderboardOrder order = AVLeaderboardOrder.DESCENDING, - AVLeaderboardUpdateStrategy updateStrategy = AVLeaderboardUpdateStrategy.BETTER, - AVLeaderboardVersionChangeInterval versionChangeInterval = AVLeaderboardVersionChangeInterval.WEEK) { - - if (string.IsNullOrEmpty(statisticName)) { - throw new ArgumentNullException(nameof(statisticName)); - } - var data = new Dictionary { - { "statisticName", statisticName }, - { "order", order.ToString().ToLower() }, - { "versionChangeInterval", versionChangeInterval.ToString().ToLower() }, - { "updateStrategy", updateStrategy.ToString().ToLower() }, - }; - var command = new AVCommand("leaderboard/leaderboards", "POST", data: data); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).OnSuccess(t => { - try { - var leaderboard = Parse(t.Result.Item2); - return leaderboard; - } catch (Exception e) { - throw new AVException(AVException.ErrorCode.InvalidJSON, e.Message); - } - }); - } - - /// - /// 创建排行榜对象 - /// - /// 排行榜对象 - /// 名称 - public static AVLeaderboard CreateWithoutData(string statisticName) { - if (string.IsNullOrEmpty(statisticName)) { - throw new ArgumentNullException(nameof(statisticName)); - } - return new AVLeaderboard(statisticName); - } - - /// - /// 获取排行榜对象 - /// - /// 排行榜对象 - /// 名称 - public static Task GetLeaderboard(string statisticName) { - return CreateWithoutData(statisticName).Fetch(); - } - - /// - /// 更新用户成绩 - /// - /// 更新的成绩 - /// 用户 - /// 成绩 - /// 是否强行覆盖 - public static Task> UpdateStatistics(AVUser user, Dictionary statistics, bool overwrite = false) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - if (statistics == null || statistics.Count == 0) { - throw new ArgumentNullException(nameof(statistics)); - } - var data = new List(); - foreach (var statistic in statistics) { - var kv = new Dictionary { - { "statisticName", statistic.Key }, - { "statisticValue", statistic.Value }, - }; - data.Add(kv); - } - var path = string.Format("leaderboard/users/{0}/statistics", user.ObjectId); - if (overwrite) { - path = string.Format("{0}?overwrite=1", path); - } - var dataStr = Json.Encode(data); - var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(dataStr)); - var command = new AVCommand(path, "POST", contentType: "application/json", sessionToken: user.SessionToken, stream: dataStream); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).OnSuccess(t => { - try { - List statisticList = new List(); - List list = t.Result.Item2["results"] as List; - foreach (object obj in list) { - statisticList.Add(AVStatistic.Parse(obj as IDictionary)); - } - return statisticList; - } catch (Exception e) { - throw new AVException(AVException.ErrorCode.InvalidJSON, e.Message); - } - }); - } - - /// - /// 获取用户成绩 - /// - /// 成绩列表 - /// 用户 - /// 名称列表 - public static Task> GetStatistics(AVUser user, List statisticNames = null) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - var path = string.Format("leaderboard/users/{0}/statistics", user.ObjectId); - if (statisticNames != null && statisticNames.Count > 0) { - var names = string.Join(",", statisticNames.ToArray()); - path = string.Format("{0}?statistics={1}", path, names); - } - var sessionToken = AVUser.CurrentUser?.SessionToken; - var command = new AVCommand(path, "GET", sessionToken, data: null); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).OnSuccess(t => { - try { - List statisticList = new List(); - List list = t.Result.Item2["results"] as List; - foreach (object obj in list) { - statisticList.Add(AVStatistic.Parse(obj as IDictionary)); - } - return statisticList; - } catch (Exception e) { - throw new AVException(AVException.ErrorCode.InvalidJSON, e.Message); - } - }); - } - - /// - /// 删除用户成绩 - /// - /// 用户 - /// 名称列表 - public static Task DeleteStatistics(AVUser user, List statisticNames) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - if (statisticNames == null || statisticNames.Count == 0) { - throw new ArgumentNullException(nameof(statisticNames)); - } - var path = string.Format("leaderboard/users/{0}/statistics", user.ObjectId); - var names = string.Join(",", statisticNames.ToArray()); - path = string.Format("{0}?statistics={1}", path, names); - var command = new AVCommand(path, "DELETE", sessionToken: user.SessionToken, data: null); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command); - } - - /// - /// 获取排行榜历史数据 - /// - /// 排行榜归档列表 - /// 跳过数量 - /// 分页数量 - public Task> GetArchives(int skip = 0, int limit = 10) { - var path = string.Format("leaderboard/leaderboards/{0}/archives", StatisticName); - path = string.Format("{0}?skip={1}&limit={2}", path, skip, limit); - var command = new AVCommand(path, "GET", data: null); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).OnSuccess(t => { - List archives = new List(); - List list = t.Result.Item2["results"] as List; - foreach (object obj in list) { - archives.Add(AVLeaderboardArchive.Parse(obj as IDictionary)); - } - return archives; - }); - } - - /// - /// 获取排行榜结果 - /// - /// 排名列表 - public Task> GetResults(int version = -1, int skip = 0, int limit = 10, List selectUserKeys = null, - List includeStatistics = null) { - return GetResults(null, version, skip, limit, selectUserKeys, includeStatistics); - } - - /// - /// 获取用户及附近的排名 - /// - /// 排名列表 - /// 用户 - /// 版本号 - /// 跳过数量 - /// 分页数量 - /// 包含的玩家的字段列表 - /// 包含的其他排行榜名称 - public Task> GetResultsAroundUser(int version = -1, int skip = 0, int limit = 10, - List selectUserKeys = null, - List includeStatistics = null) { - return GetResults(AVUser.CurrentUser, version, skip, limit, selectUserKeys, includeStatistics); - } - - Task> GetResults(AVUser user, - int version, int skip, int limit, - List selectUserKeys, - List includeStatistics) { - - var path = string.Format("leaderboard/leaderboards/{0}/ranks", StatisticName); - if (user != null) { - path = string.Format("{0}/{1}", path, user.ObjectId); - } - path = string.Format("{0}?skip={1}&limit={2}", path, skip, limit); - if (version != -1) { - path = string.Format("{0}&version={1}", path, version); - } - if (selectUserKeys != null) { - var keys = string.Join(",", selectUserKeys.ToArray()); - path = string.Format("{0}&includeUser={1}", path, keys); - } - if (includeStatistics != null) { - var statistics = string.Join(",", includeStatistics.ToArray()); - path = string.Format("{0}&includeStatistics={1}", path, statistics); - } - var command = new AVCommand(path, "GET", data: null); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).OnSuccess(t => { - try { - List rankingList = new List(); - List list = t.Result.Item2["results"] as List; - foreach (object obj in list) { - rankingList.Add(AVRanking.Parse(obj as IDictionary)); - } - return rankingList; - } catch (Exception e) { - throw new AVException(AVException.ErrorCode.InvalidJSON, e.Message); - } - }); - } - - /// - /// 设置更新策略 - /// - /// 排行榜对象 - /// 更新策略 - public Task UpdateUpdateStrategy(AVLeaderboardUpdateStrategy updateStrategy) { - var data = new Dictionary { - { "updateStrategy", updateStrategy.ToString().ToLower() } - }; - return Update(data).OnSuccess(t => { - UpdateStrategy = (AVLeaderboardUpdateStrategy)Enum.Parse(typeof(AVLeaderboardUpdateStrategy), t.Result["updateStrategy"].ToString().ToUpper()); - return this; - }); - } - - /// - /// 设置版本更新频率 - /// - /// 排行榜对象 - /// 版本更新频率 - public Task UpdateVersionChangeInterval(AVLeaderboardVersionChangeInterval versionChangeInterval) { - var data = new Dictionary { - { "versionChangeInterval", versionChangeInterval.ToString().ToLower() } - }; - return Update(data).OnSuccess(t => { - VersionChangeInterval = (AVLeaderboardVersionChangeInterval)Enum.Parse(typeof(AVLeaderboardVersionChangeInterval), t.Result["versionChangeInterval"].ToString().ToUpper()); - return this; - }); - } - - Task> Update(Dictionary data) { - var path = string.Format("leaderboard/leaderboards/{0}", StatisticName); - var command = new AVCommand(path, "PUT", data: data); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).OnSuccess(t => { - return t.Result.Item2; - }); - } - - /// - /// 拉取排行榜数据 - /// - /// 排行榜对象 - public Task Fetch() { - var path = string.Format("leaderboard/leaderboards/{0}", StatisticName); - var command = new AVCommand(path, "GET", data: null); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).OnSuccess(t => { - try { - // 反序列化 Leaderboard 对象 - var leaderboard = Parse(t.Result.Item2); - return leaderboard; - } catch (Exception e) { - throw new AVException(AVException.ErrorCode.InvalidJSON, e.Message); - } - }); - } - - /// - /// 重置排行榜 - /// - /// 排行榜对象 - public Task Reset() { - var path = string.Format("leaderboard/leaderboards/{0}/incrementVersion", StatisticName); - var command = new AVCommand(path, "PUT", data: null); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).OnSuccess(t => { - try { - Init(t.Result.Item2); - return this; - } catch (Exception e) { - throw new AVException(AVException.ErrorCode.InvalidJSON, e.Message); - } - }); - } - - /// - /// 销毁排行榜 - /// - public Task Destroy() { - var path = string.Format("leaderboard/leaderboards/{0}", StatisticName); - var command = new AVCommand(path, "DELETE", data: null); - return AVPlugins.Instance.CommandRunner.RunCommandAsync(command); - } - - static AVLeaderboard Parse(IDictionary data) { - if (data == null) { - throw new ArgumentNullException(nameof(data)); - } - var leaderboard = new AVLeaderboard(); - leaderboard.Init(data); - return leaderboard; - } - - void Init(IDictionary data) { - if (data == null) { - throw new ArgumentNullException(nameof(data)); - } - object nameObj; - if (data.TryGetValue("statisticName", out nameObj)) { - StatisticName = nameObj.ToString(); - } - object orderObj; - if (data.TryGetValue("order", out orderObj)) { - Order = (AVLeaderboardOrder)Enum.Parse(typeof(AVLeaderboardOrder), orderObj.ToString().ToUpper()); - } - object strategyObj; - if (data.TryGetValue("updateStrategy", out strategyObj)) { - UpdateStrategy = (AVLeaderboardUpdateStrategy)Enum.Parse(typeof(AVLeaderboardUpdateStrategy), strategyObj.ToString().ToUpper()); - } - object intervalObj; - if (data.TryGetValue("versionChangeInterval", out intervalObj)) { - VersionChangeInterval = (AVLeaderboardVersionChangeInterval)Enum.Parse(typeof(AVLeaderboardVersionChangeInterval), intervalObj.ToString().ToUpper()); - } - object versionObj; - if (data.TryGetValue("version", out versionObj)) { - Version = int.Parse(versionObj.ToString()); - } - } - } -} diff --git a/Storage/Source/Public/LeaderBoard/AVLeaderboardArchive.cs b/Storage/Source/Public/LeaderBoard/AVLeaderboardArchive.cs deleted file mode 100644 index a9df41f..0000000 --- a/Storage/Source/Public/LeaderBoard/AVLeaderboardArchive.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using LeanCloud.Storage.Internal; - -namespace LeanCloud { - /// - /// 归档的排行榜 - /// - public class AVLeaderboardArchive { - /// - /// 名称 - /// - /// The name of the statistic. - public string StatisticName { - get; internal set; - } - - /// - /// 版本号 - /// - /// The version. - public int Version { - get; internal set; - } - - /// - /// 状态 - /// - /// The status. - public string Status { - get; internal set; - } - - /// - /// 下载地址 - /// - /// The URL. - public string Url { - get; internal set; - } - - /// - /// 激活时间 - /// - /// The activated at. - public DateTime ActivatedAt { - get; internal set; - } - - /// - /// 归档时间 - /// - /// The deactivated at. - public DateTime DeactivatedAt { - get; internal set; - } - - AVLeaderboardArchive() { - } - - internal static AVLeaderboardArchive Parse(IDictionary data) { - if (data == null) { - throw new ArgumentNullException(nameof(data)); - } - AVLeaderboardArchive archive = new AVLeaderboardArchive { - StatisticName = data["statisticName"].ToString(), - Version = int.Parse(data["version"].ToString()), - Status = data["status"].ToString(), - Url = data["url"].ToString(), - ActivatedAt = (DateTime)AVDecoder.Instance.Decode(data["activatedAt"]), - DeactivatedAt = (DateTime)AVDecoder.Instance.Decode(data["activatedAt"]) - }; - return archive; - } - } -} diff --git a/Storage/Source/Public/LeaderBoard/AVRanking.cs b/Storage/Source/Public/LeaderBoard/AVRanking.cs deleted file mode 100644 index 6d5fa90..0000000 --- a/Storage/Source/Public/LeaderBoard/AVRanking.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using LeanCloud.Storage.Internal; - -namespace LeanCloud { - /// - /// 排名类 - /// - public class AVRanking { - /// - /// 名次 - /// - /// The rank. - public int Rank { - get; private set; - } - - /// - /// 用户 - /// - /// The user. - public AVUser User { - get; private set; - } - - public string StatisticName { - get; private set; - } - - /// - /// 分数 - /// - /// The value. - public double Value { - get; private set; - } - - /// - /// 成绩 - /// - /// The included statistics. - public List IncludedStatistics { - get; private set; - } - - AVRanking() { - } - - internal static AVRanking Parse(IDictionary data) { - if (data == null) { - throw new ArgumentNullException(nameof(data)); - } - var ranking = new AVRanking { - Rank = int.Parse(data["rank"].ToString()), - User = AVDecoder.Instance.Decode(data["user"]) as AVUser, - StatisticName = data["statisticName"].ToString(), - Value = double.Parse(data["statisticValue"].ToString()) - }; - object statisticsObj; - if (data.TryGetValue("statistics", out statisticsObj)) { - ranking.IncludedStatistics = new List(); - var statisticsObjList = statisticsObj as List; - foreach (object statisticObj in statisticsObjList) { - var statistic = AVStatistic.Parse(statisticObj as IDictionary); - ranking.IncludedStatistics.Add(statistic); - } - } - - return ranking; - } - } -} diff --git a/Storage/Source/Public/LeaderBoard/AVStatistic.cs b/Storage/Source/Public/LeaderBoard/AVStatistic.cs deleted file mode 100644 index 6d4fdcb..0000000 --- a/Storage/Source/Public/LeaderBoard/AVStatistic.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud { - /// - /// 成绩类 - /// - public class AVStatistic { - /// - /// 排行榜名称 - /// - /// The name. - public string Name { - get; private set; - } - - /// - /// 成绩值 - /// - /// The value. - public double Value { - get; private set; - } - - /// - /// 排行榜版本 - /// - /// The version. - public int Version { - get; internal set; - } - - public AVStatistic(string name, double value) { - Name = name; - Value = value; - } - - AVStatistic() { } - - internal static AVStatistic Parse(IDictionary data) { - if (data == null) { - throw new ArgumentNullException(nameof(data)); - } - AVStatistic statistic = new AVStatistic { - Name = data["statisticName"].ToString(), - Value = double.Parse(data["statisticValue"].ToString()), - Version = int.Parse(data["version"].ToString()) - }; - return statistic; - } - } -} diff --git a/Storage/Source/Public/Unity/AVInitializeBehaviour.cs b/Storage/Source/Public/Unity/AVInitializeBehaviour.cs deleted file mode 100644 index 1012c30..0000000 --- a/Storage/Source/Public/Unity/AVInitializeBehaviour.cs +++ /dev/null @@ -1,75 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using UnityEngine; - -namespace LeanCloud -{ - public class AVCoreExtensions : MonoBehaviour - { - [RuntimeInitializeOnLoadMethod] - static void OnRuntimeMethodLoad() { - var go = new GameObject { - name = "AVCoreExtensions" - }; - DontDestroyOnLoad(go); - var avce = go.AddComponent(); - - Dispatcher.Instance.GameObject = go; - - // Kick off the dispatcher. - avce.StartCoroutine(Dispatcher.Instance.DispatcherCoroutine); - - AVInitializeBehaviour.IsWebPlayer = Application.platform == RuntimePlatform.WebGLPlayer; - } - } - - /// - /// Mandatory MonoBehaviour for scenes that use LeanCloud. Set the application ID and .NET key - /// in the editor. - /// - // TODO (hallucinogen): somehow because of Push, we need this class to be added in a GameObject - // called `AVInitializeBehaviour`. We might want to fix this. - public class AVInitializeBehaviour : MonoBehaviour - { - [SerializeField] - /// - /// The LeanCloud applicationId used in this app. You can get this value from the LeanCloud website. - /// - public string applicationID; - - - [SerializeField] - /// - /// The LeanCloud applicationKey used in this app. You can get this value from the LeanCloud website. - /// - public string applicationKey; - - [SerializeField] - /// - /// The service region. - /// - public AVClient.Configuration.AVRegion region; - - - [SerializeField] - /// - /// Use this uri as cloud function server host. This is used for local development. - /// - public string engineServer; - - [SerializeField] - /// - /// Whether use production stage to process request or not. - /// - public bool useProduction = true; - - /// - /// Gets or sets a value indicating whether this is web player. - /// - /// true if is web player; otherwise, false. - public static bool IsWebPlayer { get; set; } - } -} diff --git a/Storage/Source/Public/Utilities/Conversion.cs b/Storage/Source/Public/Utilities/Conversion.cs deleted file mode 100644 index f802f5f..0000000 --- a/Storage/Source/Public/Utilities/Conversion.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Collections.Generic; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Utilities -{ - /// - /// A set of utilities for converting generic types between each other. - /// - public static class Conversion - { - /// - /// Converts a value to the requested type -- coercing primitives to - /// the desired type, wrapping lists and dictionaries appropriately, - /// or else returning null. - /// - /// This should be used on any containers that might be coming from a - /// user to normalize the collection types. Collection types coming from - /// JSON deserialization can be safely assumed to be lists or dictionaries of - /// objects. - /// - public static T As(object value) where T : class - { - return ConvertTo(value) as T; - } - - /// - /// Converts a value to the requested type -- coercing primitives to - /// the desired type, wrapping lists and dictionaries appropriately, - /// or else throwing an exception. - /// - /// This should be used on any containers that might be coming from a - /// user to normalize the collection types. Collection types coming from - /// JSON deserialization can be safely assumed to be lists or dictionaries of - /// objects. - /// - public static T To(object value) - { - return (T)ConvertTo(value); - } - - /// - /// Converts a value to the requested type -- coercing primitives to - /// the desired type, wrapping lists and dictionaries appropriately, - /// or else passing the object along to the caller unchanged. - /// - /// This should be used on any containers that might be coming from a - /// user to normalize the collection types. Collection types coming from - /// JSON deserialization can be safely assumed to be lists or dictionaries of - /// objects. - /// - internal static object ConvertTo(object value) - { - if (value is T || value == null) - { - return value; - } - - if (ReflectionHelpers.IsPrimitive(typeof(T))) - { - return (T)Convert.ChangeType(value, typeof(T)); - } - - if (ReflectionHelpers.IsConstructedGenericType(typeof(T))) - { - // Add lifting for nullables. Only supports conversions between primitives. - if (ReflectionHelpers.IsNullable(typeof(T))) - { - var innerType = ReflectionHelpers.GetGenericTypeArguments(typeof(T))[0]; - if (ReflectionHelpers.IsPrimitive(innerType)) - { - return (T)Convert.ChangeType(value, innerType); - } - } - Type listType = GetInterfaceType(value.GetType(), typeof(IList<>)); - var la = typeof(T).GetGenericTypeDefinition(); - var ilb = typeof(IList<>); - var lb = typeof(List<>); - if (listType != null && - (la == ilb || la == lb)) - { - var wrapperType = typeof(FlexibleListWrapper<,>) - .MakeGenericType(ReflectionHelpers.GetGenericTypeArguments(typeof(T))[0], - ReflectionHelpers.GetGenericTypeArguments(listType)[0]); - return Activator.CreateInstance(wrapperType, value); - } - Type dictType = GetInterfaceType(value.GetType(), typeof(IDictionary<,>)); - var da = typeof(T).GetGenericTypeDefinition(); - var db = typeof(IDictionary<,>); - if (dictType != null && - da == db) - { - var wrapperType = typeof(FlexibleDictionaryWrapper<,>) - .MakeGenericType(ReflectionHelpers.GetGenericTypeArguments(typeof(T))[1], - ReflectionHelpers.GetGenericTypeArguments(dictType)[1]); - return Activator.CreateInstance(wrapperType, value); - } - } - - return value; - } - - /// - /// Holds a dictionary that maps a cache of interface types for related concrete types. - /// The lookup is slow the first time for each type because it has to enumerate all interface - /// on the object type, but made fast by the cache. - /// - /// The map is: - /// (object type, generic interface type) => constructed generic type - /// - private static readonly Dictionary, Type> interfaceLookupCache = - new Dictionary, Type>(); - private static Type GetInterfaceType(Type objType, Type genericInterfaceType) - { - // Side note: It so sucks to have to do this. What a piece of crap bit of code - // Unfortunately, .NET doesn't provide any of the right hooks to do this for you - // *sigh* - if (ReflectionHelpers.IsConstructedGenericType(genericInterfaceType)) - { - genericInterfaceType = genericInterfaceType.GetGenericTypeDefinition(); - } - var cacheKey = new Tuple(objType, genericInterfaceType); - if (interfaceLookupCache.ContainsKey(cacheKey)) - { - return interfaceLookupCache[cacheKey]; - } - foreach (var type in ReflectionHelpers.GetInterfaces(objType)) - { - if (ReflectionHelpers.IsConstructedGenericType(type) && - type.GetGenericTypeDefinition() == genericInterfaceType) - { - return interfaceLookupCache[cacheKey] = type; - } - } - return null; - } - } -} diff --git a/Storage/Storage.PCL/Properties/AssemblyInfo.cs b/Storage/Storage.PCL/Properties/AssemblyInfo.cs deleted file mode 100644 index 5ec291d..0000000 --- a/Storage/Storage.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("Storage.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/Storage/Storage.PCL/Storage.PCL.csproj b/Storage/Storage.PCL/Storage.PCL.csproj deleted file mode 100644 index 99cc1ce..0000000 --- a/Storage/Storage.PCL/Storage.PCL.csproj +++ /dev/null @@ -1,363 +0,0 @@ - - - - Debug - AnyCPU - {659D19F0-9A40-42C0-886C-555E64F16848} - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Storage.PCL - Storage.PCL - v4.5 - Profile111 - 0.1.0 - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - - - true - bin\Release - prompt - 4 - - - - - Internal\IAVCorePlugins.cs - - - Internal\AVCorePlugins.cs - - - Internal\AppRouter\AppRouterController.cs - - - Internal\AppRouter\AppRouterState.cs - - - Internal\AppRouter\IAppRouterController.cs - - - Internal\Authentication\IAVAuthenticationProvider.cs - - - Internal\Cloud\Controller\IAVCloudCodeController.cs - - - Internal\Cloud\Controller\AVCloudCodeController.cs - - - Internal\Command\IAVCommandRunner.cs - - - Internal\Command\AVCommand.cs - - - Internal\Command\AVCommandRunner.cs - - - Internal\Config\Controller\IAVConfigController.cs - - - Internal\Config\Controller\IAVCurrentConfigController.cs - - - Internal\Config\Controller\AVConfigController.cs - - - Internal\Config\Controller\AVCurrentConfigController.cs - - - Internal\Encoding\NoObjectsEncoder.cs - - - Internal\Encoding\AVDecoder.cs - - - Internal\Encoding\AVEncoder.cs - - - Internal\Encoding\AVObjectCoder.cs - - - Internal\Encoding\PointerOrLocalIdEncoder.cs - - - Internal\File\Controller\AWSS3FileController.cs - - - Internal\File\Controller\IAVFileController.cs - - - Internal\File\Controller\AVFileController.cs - - - Internal\File\Controller\QCloudCosFileController.cs - - - Internal\File\Controller\QiniuFileController.cs - - - Internal\File\Cryptography\MD5\MD5.cs - - - Internal\File\Cryptography\SHA1\SHA1CryptoServiceProvider.cs - - - Internal\File\State\FileState.cs - - - Internal\HttpClient\HttpRequest.cs - - - Internal\HttpClient\IHttpClient.cs - - - Internal\HttpClient\Portable\HttpClient.Portable.cs - - - Internal\InstallationId\Controller\IInstallationIdController.cs - - - Internal\InstallationId\Controller\InstallationIdController.cs - - - Internal\Object\Controller\IAVObjectController.cs - - - Internal\Object\Controller\IAVObjectCurrentController.cs - - - Internal\Object\Controller\AVObjectController.cs - - - Internal\Object\State\IObjectState.cs - - - Internal\Object\State\MutableObjectState.cs - - - Internal\Object\Subclassing\IObjectSubclassingController.cs - - - Internal\Object\Subclassing\ObjectSubclassInfo.cs - - - Internal\Object\Subclassing\ObjectSubclassingController.cs - - - Internal\Operation\IAVFieldOperation.cs - - - Internal\Operation\AVAddOperation.cs - - - Internal\Operation\AVAddUniqueOperation.cs - - - Internal\Operation\AVDeleteOperation.cs - - - Internal\Operation\AVFieldOperations.cs - - - Internal\Operation\AVIncrementOperation.cs - - - Internal\Operation\AVRelationOperation.cs - - - Internal\Operation\AVRemoveOperation.cs - - - Internal\Operation\AVSetOperation.cs - - - Internal\Query\Controller\IAVQueryController.cs - - - Internal\Query\Controller\AVQueryController.cs - - - Internal\Session\Controller\IAVSessionController.cs - - - Internal\Session\Controller\AVSessionController.cs - - - Internal\Storage\IStorageController.cs - - - Internal\Storage\Portable\StorageController.cs - - - Internal\User\Controller\IAVCurrentUserController.cs - - - Internal\User\Controller\IAVUserController.cs - - - Internal\User\Controller\AVCurrentUserController.cs - - - Internal\User\Controller\AVUserController.cs - - - Internal\Utilities\FlexibleDictionaryWrapper.cs - - - Internal\Utilities\FlexibleListWrapper.cs - - - Internal\Utilities\IJsonConvertible.cs - - - Internal\Utilities\IdentityEqualityComparer.cs - - - Internal\Utilities\InternalExtensions.cs - - - Internal\Utilities\Json.cs - - - Internal\Utilities\LockSet.cs - - - Internal\Utilities\AVConfigExtensions.cs - - - Internal\Utilities\AVFileExtensions.cs - - - Internal\Utilities\AVObjectExtensions.cs - - - Internal\Utilities\AVQueryExtensions.cs - - - Internal\Utilities\AVRelationExtensions.cs - - - Internal\Utilities\AVSessionExtensions.cs - - - Internal\Utilities\AVUserExtensions.cs - - - Internal\Utilities\ReflectionHelpers.cs - - - Internal\Utilities\SynchronizedEventHandler.cs - - - Internal\Utilities\TaskQueue.cs - - - Internal\Utilities\XamarinAttributes.cs - - - Public\AVACL.cs - - - Public\AVClassNameAttribute.cs - - - Public\AVClient.cs - - - Public\AVCloud.cs - - - Public\AVConfig.cs - - - Public\AVDownloadProgressEventArgs.cs - - - Public\AVException.cs - - - Public\AVExtensions.cs - - - Public\AVFieldNameAttribute.cs - - - Public\AVFile.cs - - - Public\AVGeoDistance.cs - - - Public\AVGeoPoint.cs - - - Public\AVObject.cs - - - Public\AVQuery.cs - - - Public\AVQueryExtensions.cs - - - Public\AVRelation.cs - - - Public\AVRole.cs - - - Public\AVSession.cs - - - Public\AVStatus.cs - - - Public\AVUploadProgressEventArgs.cs - - - Public\AVUser.cs - - - Public\AVUserAuthDataLogInOption.cs - - - Public\IAVQuery.cs - - - Public\LeaderBoard\AVLeaderboard.cs - - - Public\LeaderBoard\AVLeaderboardArchive.cs - - - Public\LeaderBoard\AVRanking.cs - - - Public\LeaderBoard\AVStatistic.cs - - - Public\Utilities\Conversion.cs - - - - - - - - ..\..\packages\PCLStorage.1.0.2\lib\portable-net45+wp8+wpa81+win8+monoandroid+monotouch+Xamarin.iOS+Xamarin.Mac\PCLStorage.Abstractions.dll - - - ..\..\packages\PCLStorage.1.0.2\lib\portable-net45+wp8+wpa81+win8+monoandroid+monotouch+Xamarin.iOS+Xamarin.Mac\PCLStorage.dll - - - - \ No newline at end of file diff --git a/Storage/Storage.PCL/packages.config b/Storage/Storage.PCL/packages.config deleted file mode 100644 index 5306567..0000000 --- a/Storage/Storage.PCL/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Storage/Storage.Test/Utils.cs b/Storage/Storage.Test/Utils.cs deleted file mode 100644 index 8b93ff2..0000000 --- a/Storage/Storage.Test/Utils.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using LeanCloud; -using LeanCloud.Common; -using NUnit.Framework; - -namespace LeanCloud.Test { - public static class Utils { - public static void InitNorthChina(bool master = false) { - if (master) { - Init("BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz", "pbf6Nk5seyjilexdpyrPwjSp", "https://avoscloud.com", "https://avoscloud.com", "qKH9ryRagHKvXeRRVkiUiHeb"); - } else { - Init("BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz", "pbf6Nk5seyjilexdpyrPwjSp", "https://avoscloud.com", "https://avoscloud.com"); - } - } - - public static void InitEastChina(bool master = false) { - if (master) { - Init("4eTwHdYhMaNBUpl1SrTr7GLC-9Nh9j0Va", "GSD6DtdgGWlWolivN4qhWtlE", "https://4eTwHdYh.api.lncldapi.com", "https://4eTwHdYh.engine.lncldapi.com", "eqEp4n89h4zanWFskDDpIwL4"); - } else { - Init("4eTwHdYhMaNBUpl1SrTr7GLC-9Nh9j0Va", "GSD6DtdgGWlWolivN4qhWtlE", "https://4eTwHdYh.api.lncldapi.com", "https://4eTwHdYh.engine.lncldapi.com"); - } - } - - public static void InitOldEastChina(bool master = false) { - if (master) { - Init("SpT4SjWdvM9TSvCTKk6rqYQ9-9Nh9j0Va", "4NvN2OfdsWFC7qzzNcNS6paS", "https://4eTwHdYh.api.lncldapi.com", "https://4eTwHdYh.engine.lncldapi.com", "eqEp4n89h4zanWFskDDpIwL4"); - } else { - Init("SpT4SjWdvM9TSvCTKk6rqYQ9-9Nh9j0Va", "4NvN2OfdsWFC7qzzNcNS6paS", "https://4eTwHdYh.api.lncldapi.com", "https://4eTwHdYh.engine.lncldapi.com"); - } - } - - public static void InitUS(bool master = false) { - if (master) { - Init("MFAS1GnOyomRLSQYRaxdgdPz-MdYXbMMI", "p42JUxdxb95K5G8187t5ba3l", "https://MFAS1GnO.api.lncldglobal.com", "https://MFAS1GnO.engine.lncldglobal.com", "Ahb1wdFLwMgKwEaEicHRXbCY"); - } else { - Init("MFAS1GnOyomRLSQYRaxdgdPz-MdYXbMMI", "p42JUxdxb95K5G8187t5ba3l", "https://MFAS1GnO.api.lncldglobal.com", "https://MFAS1GnO.engine.lncldglobal.com"); - } - } - - static void Init(string appId, string appKey, string apiServer, string engineServer, string masterKey = null) { - AVClient.Initialize(new AVClient.Configuration { - ApplicationId = appId, - ApplicationKey = appKey, - MasterKey = masterKey, - ApiServer = apiServer, - EngineServer = engineServer - }); - AVClient.UseMasterKey = !string.IsNullOrEmpty(masterKey); - AVClient.HttpLog(TestContext.Out.WriteLine); - } - - internal static void Print(LogLevel level, string info) { - switch (level) { - case LogLevel.Debug: - TestContext.Out.WriteLine($"[DEBUG] {info}"); - break; - case LogLevel.Warn: - TestContext.Out.WriteLine($"[WARNING] {info}"); - break; - case LogLevel.Error: - TestContext.Out.WriteLine($"[ERROR] {info}"); - break; - default: - TestContext.Out.WriteLine(info); - break; - } - } - } -} diff --git a/Storage/Storage.Unity/Properties/AssemblyInfo.cs b/Storage/Storage.Unity/Properties/AssemblyInfo.cs deleted file mode 100644 index 78f7194..0000000 --- a/Storage/Storage.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("Storage.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/Storage/Storage.Unity/Storage.Unity.csproj b/Storage/Storage.Unity/Storage.Unity.csproj deleted file mode 100644 index 259956b..0000000 --- a/Storage/Storage.Unity/Storage.Unity.csproj +++ /dev/null @@ -1,368 +0,0 @@ - - - - Debug - AnyCPU - {A0D50BCB-E50E-4AAE-8E7D-24BF5AE33DAC} - Library - Storage.Unity - Storage.Unity - v4.5 - 0.1.0 - - - true - full - false - bin\Debug - DEBUG;UNITY; - prompt - 4 - false - - - true - bin\Release - UNITY; - prompt - 4 - false - - - - - ..\..\Libs\UnityEngine.dll - - - - - - Internal\IAVCorePlugins.cs - - - Internal\AVCorePlugins.cs - - - Internal\AppRouter\AppRouterController.cs - - - Internal\AppRouter\AppRouterState.cs - - - Internal\AppRouter\IAppRouterController.cs - - - Internal\Cloud\Controller\IAVCloudCodeController.cs - - - Internal\Cloud\Controller\AVCloudCodeController.cs - - - Internal\Command\IAVCommandRunner.cs - - - Internal\Command\AVCommand.cs - - - Internal\Command\AVCommandRunner.cs - - - Internal\Config\Controller\IAVConfigController.cs - - - Internal\Config\Controller\IAVCurrentConfigController.cs - - - Internal\Config\Controller\AVConfigController.cs - - - Internal\Config\Controller\AVCurrentConfigController.cs - - - Internal\Dispatcher\Unity\UnityDispatcher.cs - - - Internal\Encoding\NoObjectsEncoder.cs - - - Internal\Encoding\AVDecoder.cs - - - Internal\Encoding\AVEncoder.cs - - - Internal\Encoding\AVObjectCoder.cs - - - Internal\Encoding\PointerOrLocalIdEncoder.cs - - - Internal\File\Controller\AWSS3FileController.cs - - - Internal\File\Controller\IAVFileController.cs - - - Internal\File\Controller\AVFileController.cs - - - Internal\File\Controller\QCloudCosFileController.cs - - - Internal\File\Controller\QiniuFileController.cs - - - Internal\File\Cryptography\MD5\MD5.cs - - - Internal\File\Cryptography\SHA1\SHA1CryptoServiceProvider.cs - - - Internal\File\State\FileState.cs - - - Internal\HttpClient\HttpRequest.cs - - - Internal\HttpClient\IHttpClient.cs - - - Internal\HttpClient\Unity\HttpClient.Unity.cs - - - Internal\InstallationId\Controller\IInstallationIdController.cs - - - Internal\InstallationId\Controller\InstallationIdController.cs - - - Internal\Object\Controller\IAVObjectController.cs - - - Internal\Object\Controller\IAVObjectCurrentController.cs - - - Internal\Object\Controller\AVObjectController.cs - - - Internal\Object\State\IObjectState.cs - - - Internal\Object\State\MutableObjectState.cs - - - Internal\Object\Subclassing\IObjectSubclassingController.cs - - - Internal\Object\Subclassing\ObjectSubclassInfo.cs - - - Internal\Object\Subclassing\ObjectSubclassingController.cs - - - Internal\Operation\IAVFieldOperation.cs - - - Internal\Operation\AVAddOperation.cs - - - Internal\Operation\AVAddUniqueOperation.cs - - - Internal\Operation\AVDeleteOperation.cs - - - Internal\Operation\AVFieldOperations.cs - - - Internal\Operation\AVIncrementOperation.cs - - - Internal\Operation\AVRelationOperation.cs - - - Internal\Operation\AVRemoveOperation.cs - - - Internal\Operation\AVSetOperation.cs - - - Internal\Query\Controller\IAVQueryController.cs - - - Internal\Query\Controller\AVQueryController.cs - - - Internal\Session\Controller\IAVSessionController.cs - - - Internal\Session\Controller\AVSessionController.cs - - - Internal\Storage\IStorageController.cs - - - Internal\Storage\Unity\StorageController.cs - - - Internal\User\Controller\IAVCurrentUserController.cs - - - Internal\User\Controller\IAVUserController.cs - - - Internal\User\Controller\AVCurrentUserController.cs - - - Internal\User\Controller\AVUserController.cs - - - Internal\Utilities\FlexibleDictionaryWrapper.cs - - - Internal\Utilities\FlexibleListWrapper.cs - - - Internal\Utilities\IJsonConvertible.cs - - - Internal\Utilities\IdentityEqualityComparer.cs - - - Internal\Utilities\InternalExtensions.cs - - - Internal\Utilities\Json.cs - - - Internal\Utilities\LockSet.cs - - - Internal\Utilities\AVConfigExtensions.cs - - - Internal\Utilities\AVFileExtensions.cs - - - Internal\Utilities\AVObjectExtensions.cs - - - Internal\Utilities\AVQueryExtensions.cs - - - Internal\Utilities\AVRelationExtensions.cs - - - Internal\Utilities\AVSessionExtensions.cs - - - Internal\Utilities\AVUserExtensions.cs - - - Internal\Utilities\ReflectionHelpers.cs - - - Internal\Utilities\SynchronizedEventHandler.cs - - - Internal\Utilities\TaskQueue.cs - - - Internal\Utilities\XamarinAttributes.cs - - - Public\AVACL.cs - - - Public\AVClassNameAttribute.cs - - - Public\AVClient.cs - - - Public\AVCloud.cs - - - Public\AVConfig.cs - - - Public\AVDownloadProgressEventArgs.cs - - - Public\AVException.cs - - - Public\AVExtensions.cs - - - Public\AVFieldNameAttribute.cs - - - Public\AVFile.cs - - - Public\AVGeoDistance.cs - - - Public\AVGeoPoint.cs - - - Public\AVObject.cs - - - Public\AVQuery.cs - - - Public\AVQueryExtensions.cs - - - Public\AVRelation.cs - - - Public\AVRole.cs - - - Public\AVSession.cs - - - Public\AVStatus.cs - - - Public\AVUploadProgressEventArgs.cs - - - Public\AVUser.cs - - - Public\AVUserAuthDataLogInOption.cs - - - Public\IAVQuery.cs - - - Public\LeaderBoard\AVLeaderboard.cs - - - Public\LeaderBoard\AVLeaderboardArchive.cs - - - Public\LeaderBoard\AVRanking.cs - - - Public\LeaderBoard\AVStatistic.cs - - - Public\Unity\AVInitializeBehaviour.cs - - - Public\Utilities\Conversion.cs - - - Internal\Authentication\IAVAuthenticationProvider.cs - - - - - - - \ No newline at end of file diff --git a/Storage/Storage/Storage.csproj b/Storage/Storage.csproj similarity index 85% rename from Storage/Storage/Storage.csproj rename to Storage/Storage.csproj index 5ae4643..7793a5f 100644 --- a/Storage/Storage/Storage.csproj +++ b/Storage/Storage.csproj @@ -4,6 +4,7 @@ netstandard2.0 0.1.0 LeanCloud.Storage + true @@ -12,9 +13,6 @@ - - - @@ -24,4 +22,7 @@ + + + diff --git a/Storage/Storage/Internal_/AVCorePlugins.cs b/Storage/Storage/Internal_/AVCorePlugins.cs deleted file mode 100644 index 3ae2718..0000000 --- a/Storage/Storage/Internal_/AVCorePlugins.cs +++ /dev/null @@ -1,170 +0,0 @@ -using LeanCloud.Common; - -namespace LeanCloud.Storage.Internal { - public class AVPlugins { - private static readonly object instanceMutex = new object(); - private static AVPlugins instance; - public static AVPlugins Instance { - get { - lock (instanceMutex) { - instance = instance ?? new AVPlugins(); - return instance; - } - } - } - - private readonly object mutex = new object(); - - #region Server Controllers - - private AppRouterController appRouterController; - private AVCommandRunner commandRunner; - - private AVCloudCodeController cloudCodeController; - private AVFileController fileController; - private AVQueryController queryController; - private AVUserController userController; - private ObjectSubclassingController subclassingController; - - #endregion - - #region Current Instance Controller - - private InstallationIdController installationIdController; - - #endregion - - public void Reset() { - lock (mutex) { - AppRouterController = null; - CommandRunner = null; - - CloudCodeController = null; - FileController = null; - UserController = null; - SubclassingController = null; - - InstallationIdController = null; - } - } - - public AppRouterController AppRouterController { - get { - lock (mutex) { - var conf = AVClient.CurrentConfiguration; - appRouterController = appRouterController ?? new AppRouterController(conf.ApplicationId, conf.ApiServer); - return appRouterController; - } - } - set { - lock (mutex) { - appRouterController = value; - } - } - } - - public AVCommandRunner CommandRunner { - get { - lock (mutex) { - commandRunner = commandRunner ?? new AVCommandRunner(); - return commandRunner; - } - } - set { - lock (mutex) { - commandRunner = value; - } - } - } - - public AVCloudCodeController CloudCodeController { - get { - lock (mutex) { - cloudCodeController = cloudCodeController ?? new AVCloudCodeController(); - return cloudCodeController; - } - } - set { - lock (mutex) { - cloudCodeController = value; - } - } - } - - public AVFileController FileController { - get { - if (fileController != null) { - return fileController; - } - fileController = new AVFileController(); - return fileController; - } - set { - lock (mutex) { - fileController = value; - } - } - } - - public AVQueryController QueryController { - get { - lock (mutex) { - if (queryController == null) { - queryController = new AVQueryController(); - } - return queryController; - } - } - set { - lock (mutex) { - queryController = value; - } - } - } - - public AVUserController UserController { - get { - lock (mutex) { - userController = userController ?? new AVUserController(); - return userController; - } - } - set { - lock (mutex) { - userController = value; - } - } - } - - public ObjectSubclassingController SubclassingController { - get { - lock (mutex) { - if (subclassingController == null) { - subclassingController = new ObjectSubclassingController(); - //subclassingController.AddRegisterHook(typeof(AVUser), () => CurrentUserController.ClearFromMemory()); - } - return subclassingController; - } - } - set { - lock (mutex) { - subclassingController = value; - } - } - } - - public InstallationIdController InstallationIdController { - get { - lock (mutex) { - installationIdController = installationIdController ?? new InstallationIdController(); - return installationIdController; - } - } - set { - lock (mutex) { - installationIdController = value; - } - } - } - } -} diff --git a/Storage/Storage/Internal_/Cloud/Controller/AVCloudCodeController.cs b/Storage/Storage/Internal_/Cloud/Controller/AVCloudCodeController.cs deleted file mode 100644 index bc6c063..0000000 --- a/Storage/Storage/Internal_/Cloud/Controller/AVCloudCodeController.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Utilities; -using System.Net.Http; - -namespace LeanCloud.Storage.Internal { - public class AVCloudCodeController { - public async Task CallFunctionAsync(string name, - IDictionary parameters, - CancellationToken cancellationToken) { - var command = new EngineCommand { - Path = $"functions/{Uri.EscapeUriString(name)}", - Method = HttpMethod.Post, - Content = parameters ?? new Dictionary() - }; - var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - var decoded = AVDecoder.Instance.Decode(data.Item2) as IDictionary; - if (!decoded.ContainsKey("result")) { - return default; - } - return Conversion.To(decoded["result"]); - } - - public async Task RPCFunction(string name, IDictionary parameters, CancellationToken cancellationToken) { - var command = new EngineCommand { - Path = $"call/{Uri.EscapeUriString(name)}", - Method = HttpMethod.Post, - Content = parameters ?? new Dictionary() - }; - var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - var decoded = AVDecoder.Instance.Decode(data.Item2) as IDictionary; - if (!decoded.ContainsKey("result")) { - return default; - } - return Conversion.To(decoded["result"]); - } - } -} diff --git a/Storage/Storage/Internal_/Command/AVCommand.cs b/Storage/Storage/Internal_/Command/AVCommand.cs deleted file mode 100644 index 085ec6c..0000000 --- a/Storage/Storage/Internal_/Command/AVCommand.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; - -namespace LeanCloud.Storage.Internal { - public class AVCommand { - // 不同服务对应的服务器地址不同 - public virtual string Server => AVClient.CurrentConfiguration.ApiServer; - - public string Path { - get; set; - } - - public HttpMethod Method { - get; set; - } - - public Dictionary Headers { - get; set; - } - - public object Content { - get; set; - } - - internal Uri Uri { - get { - return new Uri($"{Server}/{AVClient.APIVersion}/{Path}"); - } - } - } -} diff --git a/Storage/Storage/Internal_/Command/AVCommandRunner.cs b/Storage/Storage/Internal_/Command/AVCommandRunner.cs deleted file mode 100644 index bbd6887..0000000 --- a/Storage/Storage/Internal_/Command/AVCommandRunner.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; -using System.Linq; -using Newtonsoft.Json; -using LeanCloud.Common; - -namespace LeanCloud.Storage.Internal { - /// - /// Command Runner. - /// - public class AVCommandRunner { - const string APPLICATION_JSON = "application/json"; - const string USE_PRODUCTION = "1"; - const string USE_DEVELOPMENT = "0"; - - private readonly HttpClient httpClient; - - public AVCommandRunner() { - httpClient = new HttpClient(); - ProductHeaderValue product = new ProductHeaderValue(AVClient.Name, AVClient.Version); - httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(product)); - httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(APPLICATION_JSON)); - - var conf = AVClient.CurrentConfiguration; - // App ID - httpClient.DefaultRequestHeaders.Add("X-LC-Id", conf.ApplicationId); - // App Signature - long timestamp = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds; - if (!string.IsNullOrEmpty(conf.MasterKey) && AVClient.UseMasterKey) { - string sign = MD5.GetMd5String(timestamp + conf.MasterKey); - httpClient.DefaultRequestHeaders.Add("X-LC-Sign", $"{sign},{timestamp},master"); - } else { - string sign = MD5.GetMd5String(timestamp + conf.ApplicationKey); - httpClient.DefaultRequestHeaders.Add("X-LC-Sign", $"{sign},{timestamp}"); - } - // TODO Session - - // Production - httpClient.DefaultRequestHeaders.Add("X-LC-Prod", AVClient.UseProduction ? USE_PRODUCTION : USE_DEVELOPMENT); - } - - - /// - /// - /// - /// - /// - /// - public async Task> RunCommandAsync(AVCommand command,CancellationToken cancellationToken = default) { - string content = JsonConvert.SerializeObject(command.Content); - var request = new HttpRequestMessage { - RequestUri = command.Uri, - Method = command.Method, - Content = new StringContent(content) - }; - - request.Content.Headers.ContentType = new MediaTypeHeaderValue(APPLICATION_JSON); - // 特殊 Headers - if (command.Headers != null) { - foreach (KeyValuePair header in command.Headers) { - request.Headers.Add(header.Key, header.Value); - } - } - // Session Token - if (!request.Headers.Contains("X-LC-Session") && - AVUser.CurrentUser != null && - !string.IsNullOrEmpty(AVUser.CurrentUser.SessionToken)) { - request.Headers.Add("X-LC-Session", AVUser.CurrentUser.SessionToken); - } - HttpUtils.PrintRequest(httpClient, request, content); - - var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); - request.Dispose(); - - var resultString = await response.Content.ReadAsStringAsync(); - response.Dispose(); - HttpUtils.PrintResponse(response, resultString); - - var ret = new Tuple(response.StatusCode, resultString); - - var responseCode = ret.Item1; - var contentString = ret.Item2; - - if (responseCode >= HttpStatusCode.InternalServerError) { - // Server error, return InternalServerError. - throw new AVException(AVException.ErrorCode.InternalServerError, contentString); - } - - if (responseCode < HttpStatusCode.OK || responseCode > HttpStatusCode.PartialContent) { - // 错误处理 - var data = JsonConvert.DeserializeObject>(contentString, new LeanCloudJsonConverter()); - if (data.TryGetValue("code", out object codeObj)) { - AVException.ErrorCode code = (AVException.ErrorCode)Enum.ToObject(typeof(AVException.ErrorCode), codeObj); - string detail = data["error"] as string; - throw new AVException(code, detail); - } else { - throw new AVException(AVException.ErrorCode.OtherCause, contentString); - } - } - - if (contentString != null) { - try { - var data = JsonConvert.DeserializeObject(contentString, new LeanCloudJsonConverter()); - return new Tuple(responseCode, (T)data); - } catch (Exception e) { - throw new AVException(AVException.ErrorCode.OtherCause, - "Invalid response from server", e); - } - } - - return new Tuple(responseCode, default); - } - - - // TODO (hallucinogen): move this out to a class to be used by Analytics - private const int MaximumBatchSize = 50; - - internal async Task>> ExecuteBatchRequests(IList requests, - CancellationToken cancellationToken) { - var results = new List>(); - int batchSize = requests.Count; - - IEnumerable remaining = requests; - while (batchSize > MaximumBatchSize) { - var process = remaining.Take(MaximumBatchSize).ToList(); - remaining = remaining.Skip(MaximumBatchSize); - - results.AddRange(await ExecuteBatchRequest(process, cancellationToken)); - - batchSize = remaining.Count(); - } - results.AddRange(await ExecuteBatchRequest(remaining.ToList(), cancellationToken)); - - return results; - } - - internal async Task>> ExecuteBatchRequest(IList requests, CancellationToken cancellationToken) { - var tasks = new List>>(); - int batchSize = requests.Count; - var tcss = new List>>(); - for (int i = 0; i < batchSize; ++i) { - var tcs = new TaskCompletionSource>(); - tcss.Add(tcs); - tasks.Add(tcs.Task); - } - - var encodedRequests = requests.Select(r => { - var results = new Dictionary { - { "method", r.Method.Method }, - { "path", $"/{AVClient.APIVersion}/{r.Path}" }, - }; - - if (r.Content != null) { - results["body"] = r.Content; - } - return results; - }).Cast().ToList(); - var command = new AVCommand { - Path = "batch", - Method = HttpMethod.Post, - Content = new Dictionary { - { "requests", encodedRequests } - } - }; - - try { - List> result = new List>(); - var response = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - return response.Item2.Cast>().ToList(); - } catch (Exception e) { - throw e; - } - } - } -} diff --git a/Storage/Storage/Internal_/Command/EngineCommand.cs b/Storage/Storage/Internal_/Command/EngineCommand.cs deleted file mode 100644 index 506ba27..0000000 --- a/Storage/Storage/Internal_/Command/EngineCommand.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace LeanCloud.Storage.Internal { - public class EngineCommand : AVCommand { - public override string Server => AVClient.CurrentConfiguration.EngineServer; - } -} diff --git a/Storage/Storage/Internal_/Command/RTMCommand.cs b/Storage/Storage/Internal_/Command/RTMCommand.cs deleted file mode 100644 index a73ffc8..0000000 --- a/Storage/Storage/Internal_/Command/RTMCommand.cs +++ /dev/null @@ -1,7 +0,0 @@ -using LeanCloud; - -namespace LeanCloud.Storage.Internal { - public class RTMCommand : AVCommand { - public override string Server => AVClient.CurrentConfiguration.RTMServer; - } -} diff --git a/Storage/Storage/Internal_/Encoding/AVDecoder.cs b/Storage/Storage/Internal_/Encoding/AVDecoder.cs deleted file mode 100644 index e18992e..0000000 --- a/Storage/Storage/Internal_/Encoding/AVDecoder.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.Globalization; -using LeanCloud.Utilities; - -namespace LeanCloud.Storage.Internal { - public class AVDecoder { - // This class isn't really a Singleton, but since it has no state, it's more efficient to get - // the default instance. - private static readonly AVDecoder instance = new AVDecoder(); - public static AVDecoder Instance { - get { - return instance; - } - } - - // Prevent default constructor. - private AVDecoder() { } - - public object Decode(object data) { - // 如果是字典类型 - if (data is IDictionary dict) { - if (dict.ContainsKey("__op")) { - return AVFieldOperations.Decode(dict); - } - if (dict.TryGetValue("__type", out object type)) { - string typeString = type as string; - switch (typeString) { - case "Date": - return ParseDate(dict["iso"] as string); - case "Bytes": - return Convert.FromBase64String(dict["base64"] as string); - case "Pointer": { - if (dict.Keys.Count > 3) { - return DecodeAVObject(dict); - } - return DecodePointer(dict["className"] as string, dict["objectId"] as string); - } - case "GeoPoint": - return new AVGeoPoint(Conversion.To(dict["latitude"]), - Conversion.To(dict["longitude"])); - case "Object": - return DecodeAVObject(dict); - case "Relation": - return AVRelationBase.CreateRelation(null, null, dict["className"] as string); - default: - break; - } - } - var converted = new Dictionary(); - foreach (var pair in dict) { - converted[pair.Key] = Decode(pair.Value); - } - return converted; - } - // 如果是数组类型 - if (data is IList list) { - return (from item in list - select Decode(item)).ToList(); - } - // 原样返回 - return data; - } - - protected virtual object DecodePointer(string className, string objectId) { - return AVObject.CreateWithoutData(className, objectId); - } - - protected virtual object DecodeAVObject(IDictionary dict) { - var className = dict["className"] as string; - var state = AVObjectCoder.Instance.Decode(dict, this); - return AVObject.FromState(state, className); - } - - public virtual IList DecodeList(object data) { - IList rtn = null; - var list = (IList)data; - if (list != null) { - rtn = new List(); - foreach (var item in list) { - rtn.Add((T)item); - } - } - return rtn; - } - - public static DateTime ParseDate(string input) { - return DateTime.ParseExact(input, - AVClient.DateFormatStrings, - CultureInfo.InvariantCulture, - DateTimeStyles.AssumeUniversal); - } - } -} diff --git a/Storage/Storage/Internal_/Encoding/AVEncoder.cs b/Storage/Storage/Internal_/Encoding/AVEncoder.cs deleted file mode 100644 index 2a104d8..0000000 --- a/Storage/Storage/Internal_/Encoding/AVEncoder.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using LeanCloud.Utilities; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Storage.Internal { - /// - /// A AVEncoder can be used to transform objects such as into JSON - /// data structures. - /// - /// - public abstract class AVEncoder { - public static bool IsValidType(object value) { - return value == null || - ReflectionHelpers.IsPrimitive(value.GetType()) || - value is string || - value is AVObject || - value is AVACL || - value is AVGeoPoint || - value is AVRelationBase || - value is DateTime || - value is byte[] || - Conversion.As>(value) != null || - Conversion.As>(value) != null; - } - - public object Encode(object value) { - // If this object has a special encoding, encode it and return the - // encoded object. Otherwise, just return the original object. - if (value is DateTime) { - return new Dictionary { - { "__type", "Date" }, - { "iso", ((DateTime)value).ToUniversalTime().ToString(AVClient.DateFormatStrings.First(), CultureInfo.InvariantCulture) } - }; - } - - if (value is byte[] bytes) { - return new Dictionary { - { "__type", "Bytes" }, - { "base64", Convert.ToBase64String(bytes) } - }; - } - - if (value is AVObject obj) { - return EncodeAVObject(obj); - } - - if (value is IJsonConvertible jsonConvertible) { - return jsonConvertible.ToJSON(); - } - - if (value is IDictionary) { - IDictionary dict = value as IDictionary; - var json = new Dictionary(); - foreach (var key in dict.Keys) { - object v = dict[key]; - json[key.ToString()] = Encode(v); - } - return json; - } - - if (value is IList) { - IList list = value as IList; - return EncodeList(list); - } - - if (value is IAVFieldOperation operation) { - return operation.Encode(); - } - - return value; - } - - protected abstract IDictionary EncodeAVObject(AVObject value); - - private object EncodeList(IList list) { - List newArray = new List(); - foreach (object item in list) { - newArray.Add(Encode(item)); - } - return newArray; - } - } -} diff --git a/Storage/Storage/Internal_/Encoding/AVObjectCoder.cs b/Storage/Storage/Internal_/Encoding/AVObjectCoder.cs deleted file mode 100644 index 1629084..0000000 --- a/Storage/Storage/Internal_/Encoding/AVObjectCoder.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal -{ - // TODO: (richardross) refactor entire LeanCloud coder interfaces. - public class AVObjectCoder - { - private static readonly AVObjectCoder instance = new AVObjectCoder(); - public static AVObjectCoder Instance - { - get - { - return instance; - } - } - - // Prevent default constructor. - private AVObjectCoder() { } - - public IDictionary Encode(T state, - IDictionary operations, - AVEncoder encoder) where T : IObjectState - { - var result = new Dictionary(); - foreach (var pair in operations) - { - // AVRPCSerialize the data - var operation = pair.Value; - - result[pair.Key] = encoder.Encode(operation); - } - - return result; - } - - public IObjectState Decode(IDictionary data, - AVDecoder decoder) - { - IDictionary serverData = new Dictionary(); - var mutableData = new Dictionary(data); - string objectId = ExtractFromDictionary(mutableData, "objectId", (obj) => - { - return obj as string; - }); - DateTime? createdAt = ExtractFromDictionary(mutableData, "createdAt", (obj) => - { - return (DateTime)obj; - }); - DateTime? updatedAt = ExtractFromDictionary(mutableData, "updatedAt", (obj) => - { - return (DateTime)obj; - }); - - AVACL acl = ExtractFromDictionary(mutableData, "ACL", (obj) => - { - return new AVACL(obj as IDictionary); - }); - - string className = ExtractFromDictionary(mutableData, "className", obj => - { - return obj as string; - }); - - if (createdAt != null && updatedAt == null) - { - updatedAt = createdAt; - } - - // Bring in the new server data. - foreach (var pair in mutableData) - { - if (pair.Key == "__type" || pair.Key == "className") - { - continue; - } - - var value = pair.Value; - serverData[pair.Key] = decoder.Decode(value); - } - - return new MutableObjectState - { - ObjectId = objectId, - ACL = acl, - CreatedAt = createdAt, - UpdatedAt = updatedAt, - ServerData = serverData, - ClassName = className - }; - } - - private T ExtractFromDictionary(IDictionary data, string key, Func action) - { - T result = default; - if (data.TryGetValue(key, out object val)) { - result = action(val); - data.Remove(key); - } - - return result; - } - } -} diff --git a/Storage/Storage/Internal_/Encoding/PointerOrLocalIdEncoder.cs b/Storage/Storage/Internal_/Encoding/PointerOrLocalIdEncoder.cs deleted file mode 100644 index ba50743..0000000 --- a/Storage/Storage/Internal_/Encoding/PointerOrLocalIdEncoder.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; - -namespace LeanCloud.Storage.Internal -{ - /// - /// A that encode as pointers. If the object - /// does not have an , uses a local id. - /// - public class PointerOrLocalIdEncoder : AVEncoder - { - // This class isn't really a Singleton, but since it has no state, it's more efficient to get - // the default instance. - private static readonly PointerOrLocalIdEncoder instance = new PointerOrLocalIdEncoder(); - public static PointerOrLocalIdEncoder Instance - { - get - { - return instance; - } - } - - protected override IDictionary EncodeAVObject(AVObject value) - { - if (value.ObjectId == null) - { - // TODO (hallucinogen): handle local id. For now we throw. - throw new ArgumentException("Cannot create a pointer to an object without an objectId"); - } - - return new Dictionary { - {"__type", "Pointer"}, - { "className", value.ClassName}, - { "objectId", value.ObjectId} - }; - } - - public IDictionary EncodeAVObject(AVObject value, bool isPointer) - { - if (isPointer) - { - return EncodeAVObject(value); - } - var operations = value.GetCurrentOperations(); - var operationJSON = AVObject.ToJSONObjectForSaving(operations); - var objectJSON = value.ToDictionary(kvp => kvp.Key, kvp => PointerOrLocalIdEncoder.Instance.Encode(kvp.Value)); - foreach (var kvp in operationJSON) { - objectJSON[kvp.Key] = kvp.Value; - } - if (value.CreatedAt.HasValue) { - objectJSON["createdAt"] = value.CreatedAt.Value.ToString(AVClient.DateFormatStrings.First(), CultureInfo.InvariantCulture); - } - if (value.UpdatedAt.HasValue) { - objectJSON["updatedAt"] = value.UpdatedAt.Value.ToString(AVClient.DateFormatStrings.First(), CultureInfo.InvariantCulture); - } - if(!string.IsNullOrEmpty(value.ObjectId)) { - objectJSON["objectId"] = value.ObjectId; - } - if (value.ACL != null) { - objectJSON["acl"] = Encode(value.ACL); - } - objectJSON["className"] = value.ClassName; - objectJSON["__type"] = "Object"; - return objectJSON; - } - } -} diff --git a/Storage/Storage/Internal_/File/Controller/AVFileController.cs b/Storage/Storage/Internal_/File/Controller/AVFileController.cs deleted file mode 100644 index 66657f7..0000000 --- a/Storage/Storage/Internal_/File/Controller/AVFileController.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using System.Net; -using System.Net.Http; -using System.Collections.Generic; -using System.Linq; - -namespace LeanCloud.Storage.Internal { - public interface IFileUploader { - Task Upload(FileState state, Stream dataStream, IDictionary fileToken, IProgress progress, - CancellationToken cancellationToken); - } - - public class AVFileController { - const string QCloud = "qcloud"; - const string AWS = "s3"; - - //public async Task SaveAsync(FileState state, - // Stream dataStream, - // IProgress progress, - // CancellationToken cancellationToken = default) { - // if (state.Url != null) { - // return await SaveWithUrl(state); - // } - - // var data = await GetFileToken(state, cancellationToken); - // var fileToken = data.Item2; - // var provider = fileToken["provider"] as string; - // switch (provider) { - // case QCloud: - // return await new QCloudUploader().Upload(state, dataStream, fileToken, progress, cancellationToken); - // case AWS: - // return await new AWSUploader().Upload(state, dataStream, fileToken, progress, cancellationToken); - // default: - // return await new QiniuUploader().Upload(state, dataStream, fileToken, progress, cancellationToken); - // } - //} - - public async Task DeleteAsync(FileState state, CancellationToken cancellationToken) { - var command = new AVCommand { - Path = $"files/{state.ObjectId}", - Method = HttpMethod.Delete - }; - await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken: cancellationToken); - } - - internal async Task SaveWithUrl(FileState state) { - Dictionary strs = new Dictionary { - { "url", state.Url.ToString() }, - { "name", state.Name }, - { "mime_type", state.MimeType }, - { "metaData", state.MetaData } - }; - AVCommand cmd = null; - - if (!string.IsNullOrEmpty(state.ObjectId)) { - cmd = new AVCommand { - Path = $"files/{state.ObjectId}", - Method = HttpMethod.Put, - Content = strs - }; - } else { - cmd = new AVCommand { - Path = "files", - Method = HttpMethod.Post, - Content = strs - }; - } - - var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(cmd); - var result = data.Item2; - state.ObjectId = result["objectId"].ToString(); - return state; - } - - internal async Task>> GetFileToken(string name, IDictionary metaData, CancellationToken cancellationToken = default) { - IDictionary parameters = new Dictionary { - { "name", name }, - { "key", GetUniqueName(name) }, - { "__type", "File" }, - { "mime_type", AVFile.GetMIMEType(name) }, - { "metaData", metaData } - }; - - var command = new AVCommand { - Path = "fileTokens", - Method = HttpMethod.Post, - Content = parameters - }; - return await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - } - - public async Task GetAsync(string objectId, CancellationToken cancellationToken) { - var command = new AVCommand { - Path = $"files/{objectId}", - Method = HttpMethod.Get - }; - var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - var jsonData = data.Item2; - cancellationToken.ThrowIfCancellationRequested(); - return new FileState { - ObjectId = jsonData["objectId"] as string, - Name = jsonData["name"] as string, - Url = new Uri(jsonData["url"] as string, UriKind.Absolute), - }; - } - - internal static string GetUniqueName(string name) { - string key = Guid.NewGuid().ToString(); - string extension = Path.GetExtension(name); - key += extension; - return key; - } - - internal static string Random(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 double CalcProgress(double already, double total) { - var pv = (1.0 * already / total); - return Math.Round(pv, 3); - } - } -} diff --git a/Storage/Storage/Internal_/File/Controller/AWSUploader.cs b/Storage/Storage/Internal_/File/Controller/AWSUploader.cs deleted file mode 100644 index a134041..0000000 --- a/Storage/Storage/Internal_/File/Controller/AWSUploader.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Threading; -using System.IO; -using System.Collections.Generic; -using System.Net.Http; -using System.Net.Http.Headers; - -namespace LeanCloud.Storage.Internal { - internal class AWSUploader { - internal string UploadUrl { - get; set; - } - - internal string MimeType { - get; set; - } - - internal Stream Stream { - get; set; - } - - internal async Task Upload(CancellationToken cancellationToken = default) { - HttpClient client = new HttpClient(); - HttpRequestMessage request = new HttpRequestMessage { - RequestUri = new Uri(UploadUrl), - Method = HttpMethod.Put, - Content = new StreamContent(Stream) - }; - request.Headers.Add("Cache-Control", "public, max-age=31536000"); - request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(MimeType); - request.Content.Headers.ContentLength = Stream.Length; - HttpResponseMessage response = await client.SendAsync(request, cancellationToken); - response.Dispose(); - client.Dispose(); - request.Dispose(); - } - } -} diff --git a/Storage/Storage/Internal_/File/Controller/QCloudUploader.cs b/Storage/Storage/Internal_/File/Controller/QCloudUploader.cs deleted file mode 100644 index bfea7aa..0000000 --- a/Storage/Storage/Internal_/File/Controller/QCloudUploader.cs +++ /dev/null @@ -1,216 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Common; - -namespace LeanCloud.Storage.Internal { - internal class QCloudUploader { - private object mutex = new object(); - - bool done; - private long sliceSize = (long)CommonSize.KB512; - - internal string FileName { - get; set; - } - - internal string UploadUrl { - get; set; - } - - internal string Token { - get; set; - } - - internal string Bucket { - get; set; - } - - internal Stream Stream { - get; set; - } - - internal async Task Upload(CancellationToken cancellationToken = default) { - var result = await FileSlice(cancellationToken); - if (done) { - return; - } - var response = result; - var resumeData = response["data"] as IDictionary; - if (resumeData.ContainsKey("access_url")) { - return; - } - var sliceSession = resumeData["session"].ToString(); - var sliceOffset = long.Parse(resumeData["offset"].ToString()); - await UploadSlice(sliceSession, sliceOffset, Stream, null, cancellationToken); - } - - async Task UploadSlice( - string sessionId, - long offset, - Stream dataStream, - IProgress progress, - CancellationToken cancellationToken) { - - long dataLength = dataStream.Length; - if (progress != null) { - lock (mutex) { - progress.Report(new AVUploadProgressEventArgs() { - Progress = AVFileController.CalcProgress(offset, dataLength) - }); - } - } - - if (offset == dataLength) { - return; - } - - var sliceFile = GetNextBinary(offset, dataStream); - var result = await ExcuteUpload(sessionId, offset, sliceFile, cancellationToken); - offset += sliceFile.Length; - if (offset == dataLength) { - done = true; - return; - } - var response = result; - var resumeData = response["data"] as IDictionary; - var sliceSession = resumeData["session"].ToString(); - await UploadSlice(sliceSession, offset, dataStream, progress, cancellationToken); - } - - Task> ExcuteUpload(string sessionId, long offset, byte[] sliceFile, CancellationToken cancellationToken) { - var body = new Dictionary(); - body.Add("op", "upload_slice"); - body.Add("session", sessionId); - body.Add("offset", offset.ToString()); - - return PostToQCloud(body, sliceFile, cancellationToken); - } - - async Task> FileSlice(CancellationToken cancellationToken) { - SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider(); - var body = new Dictionary(); - if (Stream.Length <= (long)CommonSize.KB512) { - body.Add("op", "upload"); - body.Add("sha", HexStringFromBytes(sha1.ComputeHash(Stream))); - var wholeFile = GetNextBinary(0, Stream); - var result = await PostToQCloud(body, wholeFile, cancellationToken); - return result; - } else { - body.Add("op", "upload_slice"); - body.Add("filesize", Stream.Length); - body.Add("sha", HexStringFromBytes(sha1.ComputeHash(Stream))); - body.Add("slice_size", (long)CommonSize.KB512); - } - - return await PostToQCloud(body, null, cancellationToken); - } - public static string HexStringFromBytes(byte[] bytes) { - var sb = new StringBuilder(); - foreach (byte b in bytes) { - var hex = b.ToString("x2"); - sb.Append(hex); - } - return sb.ToString(); - } - - public static string SHA1HashStringForUTF8String(string s) { - byte[] bytes = Encoding.UTF8.GetBytes(s); - - SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider(); - byte[] hashBytes = sha1.ComputeHash(bytes); - - return HexStringFromBytes(hashBytes); - } - - async Task> PostToQCloud( - Dictionary body, - byte[] sliceFile, - CancellationToken cancellationToken) { - - string contentType; - long contentLength; - - var tempStream = HttpUploadFile(sliceFile, FileName, out contentType, out contentLength, body); - - var client = new HttpClient(); - var request = new HttpRequestMessage { - RequestUri = new Uri(UploadUrl), - Method = HttpMethod.Post, - Content = new StreamContent(tempStream) - }; - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", Token); - request.Content.Headers.Add("Content-Type", contentType); - - var response = await client.SendAsync(request); - client.Dispose(); - request.Dispose(); - var content = await response.Content.ReadAsStringAsync(); - response.Dispose(); - // 修改反序列化返回 - return await JsonUtils.DeserializeObjectAsync>(content, new LeanCloudJsonConverter()); - } - public static Stream HttpUploadFile(byte[] file, string fileName, out string contentType, out long contentLength, IDictionary nvc) { - string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); - byte[] boundarybytes = StringToAscii("\r\n--" + boundary + "\r\n"); - contentType = "multipart/form-data; boundary=" + boundary; - - MemoryStream rs = new MemoryStream(); - - string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}"; - foreach (string key in nvc.Keys) { - rs.Write(boundarybytes, 0, boundarybytes.Length); - string formitem = string.Format(formdataTemplate, key, nvc[key]); - byte[] formitembytes = System.Text.Encoding.UTF8.GetBytes(formitem); - rs.Write(formitembytes, 0, formitembytes.Length); - } - rs.Write(boundarybytes, 0, boundarybytes.Length); - - if (file != null) { - string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n"; - string header = string.Format(headerTemplate, "fileContent", fileName, "application/octet-stream"); - byte[] headerbytes = System.Text.Encoding.UTF8.GetBytes(header); - rs.Write(headerbytes, 0, headerbytes.Length); - - rs.Write(file, 0, file.Length); - } - - byte[] trailer = StringToAscii("\r\n--" + boundary + "--\r\n"); - rs.Write(trailer, 0, trailer.Length); - contentLength = rs.Length; - - rs.Position = 0; - var tempBuffer = new byte[rs.Length]; - rs.Read(tempBuffer, 0, tempBuffer.Length); - - return new MemoryStream(tempBuffer); - } - - public static byte[] StringToAscii(string s) { - byte[] retval = new byte[s.Length]; - for (int ix = 0; ix < s.Length; ++ix) { - char ch = s[ix]; - if (ch <= 0x7f) retval[ix] = (byte)ch; - else retval[ix] = (byte)'?'; - } - return retval; - } - - byte[] GetNextBinary(long completed, Stream dataStream) { - if (completed + sliceSize > dataStream.Length) { - sliceSize = dataStream.Length - completed; - } - - byte[] chunkBinary = new byte[sliceSize]; - dataStream.Seek(completed, SeekOrigin.Begin); - dataStream.Read(chunkBinary, 0, (int)sliceSize); - return chunkBinary; - } - } -} diff --git a/Storage/Storage/Internal_/File/Controller/QiniuUploader.cs b/Storage/Storage/Internal_/File/Controller/QiniuUploader.cs deleted file mode 100644 index aa2f8bb..0000000 --- a/Storage/Storage/Internal_/File/Controller/QiniuUploader.cs +++ /dev/null @@ -1,294 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Net.Http; -using System.Net.Http.Headers; -using LeanCloud.Common; - -namespace LeanCloud.Storage.Internal { - internal enum CommonSize : long { - MB4 = 1024 * 1024 * 4, - MB1 = 1024 * 1024, - KB512 = 1024 * 1024 / 2, - KB256 = 1024 * 1024 / 4 - } - - internal class QiniuUploader { - private static readonly int BLOCKSIZE = 1024 * 1024 * 4; - internal static string UP_HOST = "https://up.qbox.me"; - private readonly object mutex = new object(); - - public int counter; - public Stream frozenData; - public string bucketId; - public string bucket; - public string token; - public long completed; - public List block_ctxes = new List(); - - internal string Key { - get; set; - } - - internal string Token { - get; set; - } - - internal string MimeType { - get; set; - } - - internal IDictionary MetaData { - get; set; - } - - internal Stream Stream { - get; set; - } - - internal async Task Upload(CancellationToken cancellationToken = default) { - await UploadNextChunk(string.Empty, 0, null); - } - - async Task UploadNextChunk(string context, long offset, IProgress progress) { - var totalSize = Stream.Length; - var remainingSize = totalSize - completed; - - if (progress != null) { - lock (mutex) { - progress.Report(new AVUploadProgressEventArgs() { - Progress = AVFileController.CalcProgress(completed, totalSize) - }); - } - } - if (completed == totalSize) { - await QiniuMakeFile(totalSize, block_ctxes.ToArray(), CancellationToken.None); - } else if (completed % BLOCKSIZE == 0) { - var firstChunkBinary = GetChunkBinary(); - - var blockSize = remainingSize > BLOCKSIZE ? BLOCKSIZE : remainingSize; - var result = await MakeBlock(firstChunkBinary, blockSize); - var dict = result; - var ctx = dict["ctx"].ToString(); - offset = long.Parse(dict["offset"].ToString()); - var host = dict["host"].ToString(); - - completed += firstChunkBinary.Length; - if (completed % BLOCKSIZE == 0 || completed == totalSize) { - block_ctxes.Add(ctx); - } - - await UploadNextChunk(ctx, offset, progress); - } else { - var chunkBinary = GetChunkBinary(); - var result = await PutChunk(chunkBinary, context, offset); - var dict = result; - var ctx = dict["ctx"].ToString(); - - offset = long.Parse(dict["offset"].ToString()); - var host = dict["host"].ToString(); - completed += chunkBinary.Length; - if (completed % BLOCKSIZE == 0 || completed == totalSize) { - block_ctxes.Add(ctx); - } - await UploadNextChunk(ctx, offset, progress); - } - } - - byte[] GetChunkBinary() { - long chunkSize = (long)CommonSize.MB1; - if (completed + chunkSize > Stream.Length) { - chunkSize = Stream.Length - completed; - } - byte[] chunkBinary = new byte[chunkSize]; - Stream.Seek(completed, SeekOrigin.Begin); - Stream.Read(chunkBinary, 0, (int)chunkSize); - return chunkBinary; - } - - IList> GetQiniuRequestHeaders() { - IList> makeBlockHeaders = new List>(); - string authHead = "UpToken " + Token; - makeBlockHeaders.Add(new KeyValuePair("Authorization", authHead)); - return makeBlockHeaders; - } - - async Task> MakeBlock(byte[] firstChunkBinary, long blcokSize = 4194304) { - MemoryStream firstChunkData = new MemoryStream(firstChunkBinary, 0, firstChunkBinary.Length); - - var client = new HttpClient(); - var request = new HttpRequestMessage { - RequestUri = new Uri($"{UP_HOST}/mkblk/{blcokSize}"), - Method = HttpMethod.Post, - Content = new StreamContent(firstChunkData) - }; - var headers = GetQiniuRequestHeaders(); - foreach (var header in headers) { - request.Headers.Add(header.Key, header.Value); - } - - request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); - var response = await client.SendAsync(request); - client.Dispose(); - request.Dispose(); - var content = await response.Content.ReadAsStringAsync(); - response.Dispose(); - return await JsonUtils.DeserializeObjectAsync>(content, new LeanCloudJsonConverter()); - } - - async Task> PutChunk(byte[] chunkBinary, string LastChunkctx, long currentChunkOffsetInBlock) { - MemoryStream chunkData = new MemoryStream(chunkBinary, 0, chunkBinary.Length); - var client = new HttpClient(); - var request = new HttpRequestMessage { - RequestUri = new Uri($"{UP_HOST}/bput/{LastChunkctx}/{currentChunkOffsetInBlock}"), - Method = HttpMethod.Post, - Content = new StreamContent(chunkData) - }; - var headers = GetQiniuRequestHeaders(); - foreach (var header in headers) { - request.Headers.Add(header.Key, header.Value); - } - var response = await client.SendAsync(request); - client.Dispose(); - request.Dispose(); - var content = await response.Content.ReadAsStringAsync(); - response.Dispose(); - return await JsonUtils.DeserializeObjectAsync>(content); - } - - internal async Task> QiniuMakeFile(long fsize, string[] ctxes, CancellationToken cancellationToken) { - StringBuilder urlBuilder = new StringBuilder(); - urlBuilder.AppendFormat("{0}/mkfile/{1}", UP_HOST, fsize); - if (!string.IsNullOrEmpty(Key)) { - urlBuilder.AppendFormat("/key/{0}", ToBase64URLSafe(Key)); - } - var metaData = GetMetaData(); - - StringBuilder sb = new StringBuilder(); - foreach (string _key in metaData.Keys) { - sb.AppendFormat("/{0}/{1}", _key, ToBase64URLSafe(metaData[_key].ToString())); - } - urlBuilder.Append(sb.ToString()); - - int proCount = ctxes.Length; - Stream body = new MemoryStream(); - - for (int i = 0; i < proCount; i++) { - byte[] bctx = StringToAscii(ctxes[i]); - body.Write(bctx, 0, bctx.Length); - if (i != proCount - 1) { - body.WriteByte((byte)','); - } - } - body.Seek(0, SeekOrigin.Begin); - - var client = new HttpClient(); - var request = new HttpRequestMessage { - RequestUri = new Uri(urlBuilder.ToString()), - Method = HttpMethod.Post, - Content = new StreamContent(body) - }; - request.Headers.Add("Authorization", $"UpToken {Token}"); - request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("text/plain"); - - var response = await client.SendAsync(request); - client.Dispose(); - request.Dispose(); - var content = await response.Content.ReadAsStringAsync(); - response.Dispose(); - return await JsonUtils.DeserializeObjectAsync>(content); - } - - internal void MergeFromJSON(FileState state, IDictionary jsonData) { - lock (this.mutex) { - string url = jsonData["url"] as string; - state.Url = new Uri(url, UriKind.Absolute); - state.bucketId = FetchBucketId(url); - state.token = jsonData["token"] as string; - state.bucket = jsonData["bucket"] as string; - state.ObjectId = jsonData["objectId"] as string; - } - } - - string FetchBucketId(string url) { - var elements = url.Split('/'); - - return elements[elements.Length - 1]; - } - - public static byte[] StringToAscii(string s) { - byte[] retval = new byte[s.Length]; - for (int ix = 0; ix < s.Length; ++ix) { - char ch = s[ix]; - if (ch <= 0x7f) - retval[ix] = (byte)ch; - else - retval[ix] = (byte)'?'; - } - return retval; - } - - public static string ToBase64URLSafe(string str) { - return Encode(str); - } - - public static string Encode(byte[] bs) { - if (bs == null || bs.Length == 0) - return ""; - string encodedStr = Convert.ToBase64String(bs); - encodedStr = encodedStr.Replace('+', '-').Replace('/', '_'); - return encodedStr; - } - - public static string Encode(string text) { - if (string.IsNullOrEmpty(text)) - return ""; - byte[] bs = Encoding.UTF8.GetBytes(text); - string encodedStr = Convert.ToBase64String(bs); - encodedStr = encodedStr.Replace('+', '-').Replace('/', '_'); - return encodedStr; - } - - internal static string GetMD5Code(Stream data) { - MD5 md5 = new MD5CryptoServiceProvider(); - byte[] retVal = md5.ComputeHash(data); - - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < retVal.Length; i++) { - sb.Append(retVal[i].ToString("x2")); - } - return sb.ToString(); - - } - - internal IDictionary GetMetaData() { - IDictionary rtn = new Dictionary(); - - if (MetaData != null) { - foreach (var meta in MetaData) { - rtn.Add(meta.Key, meta.Value); - } - } - MergeDic(rtn, "mime_type", AVFile.GetMIMEType(MimeType)); - MergeDic(rtn, "size", Stream.Length); - MergeDic(rtn, "_checksum", GetMD5Code(Stream)); - if (AVUser.CurrentUser != null) - if (AVUser.CurrentUser.ObjectId != null) - MergeDic(rtn, "owner", AVUser.CurrentUser.ObjectId); - - return rtn; - } - - internal void MergeDic(IDictionary dic, string key, object value) { - if (dic.ContainsKey(key)) { - dic[key] = value; - } else { - dic.Add(key, value); - } - } - } -} diff --git a/Storage/Storage/Internal_/File/Cryptography/MD5/MD5.cs b/Storage/Storage/Internal_/File/Cryptography/MD5/MD5.cs deleted file mode 100644 index 53efa04..0000000 --- a/Storage/Storage/Internal_/File/Cryptography/MD5/MD5.cs +++ /dev/null @@ -1,566 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.IO; - -namespace LeanCloud.Storage.Internal -{ - /// - /// 弥补Windows Phone 8 API没有自带MD5加密的拓展方法。 - /// - internal class MD5CryptoServiceProvider : MD5 - { - public MD5CryptoServiceProvider() - : base() - { - } - } - /// - /// Summary description for MD5. - /// - internal class MD5 : IDisposable - { - static public MD5 Create(string hashName) - { - if (hashName == "MD5") - return new MD5(); - else - throw new NotSupportedException(); - } - - static public string GetMd5String(String source) - { - MD5 md = MD5CryptoServiceProvider.Create(); - byte[] hash; - - //Create a new instance of ASCIIEncoding to - //convert the string into an array of Unicode bytes. - UTF8Encoding enc = new UTF8Encoding(); - // ASCIIEncoding enc = new ASCIIEncoding(); - - //Convert the string into an array of bytes. - byte[] buffer = enc.GetBytes(source); - - //Create the hash value from the array of bytes. - hash = md.ComputeHash(buffer); - - StringBuilder sb = new StringBuilder(); - foreach (byte b in hash) - sb.Append(b.ToString("x2")); - return sb.ToString(); - } - - static public MD5 Create() - { - return new MD5(); - } - - #region base implementation of the MD5 - #region constants - private const byte S11 = 7; - private const byte S12 = 12; - private const byte S13 = 17; - private const byte S14 = 22; - private const byte S21 = 5; - private const byte S22 = 9; - private const byte S23 = 14; - private const byte S24 = 20; - private const byte S31 = 4; - private const byte S32 = 11; - private const byte S33 = 16; - private const byte S34 = 23; - private const byte S41 = 6; - private const byte S42 = 10; - private const byte S43 = 15; - private const byte S44 = 21; - private static readonly byte[] PADDING = { - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - #endregion - - #region F, G, H and I are basic MD5 functions. - static private uint F(uint x, uint y, uint z) - { - return (((x) & (y)) | ((~x) & (z))); - } - static private uint G(uint x, uint y, uint z) - { - return (((x) & (z)) | ((y) & (~z))); - } - static private uint H(uint x, uint y, uint z) - { - return ((x) ^ (y) ^ (z)); - } - static private uint I(uint x, uint y, uint z) - { - return ((y) ^ ((x) | (~z))); - } - #endregion - - #region rotates x left n bits. - /// - /// rotates x left n bits. - /// - /// - /// - /// - static private uint ROTATE_LEFT(uint x, byte n) - { - return (((x) << (n)) | ((x) >> (32 - (n)))); - } - #endregion - - #region FF, GG, HH, and II transformations - /// FF, GG, HH, and II transformations - /// for rounds 1, 2, 3, and 4. - /// Rotation is separate from addition to prevent recomputation. - static private void FF(ref uint a, uint b, uint c, uint d, uint x, byte s, uint ac) - { - (a) += F((b), (c), (d)) + (x) + (uint)(ac); - (a) = ROTATE_LEFT((a), (s)); - (a) += (b); - } - static private void GG(ref uint a, uint b, uint c, uint d, uint x, byte s, uint ac) - { - (a) += G((b), (c), (d)) + (x) + (uint)(ac); - (a) = ROTATE_LEFT((a), (s)); - (a) += (b); - } - static private void HH(ref uint a, uint b, uint c, uint d, uint x, byte s, uint ac) - { - (a) += H((b), (c), (d)) + (x) + (uint)(ac); - (a) = ROTATE_LEFT((a), (s)); - (a) += (b); - } - static private void II(ref uint a, uint b, uint c, uint d, uint x, byte s, uint ac) - { - (a) += I((b), (c), (d)) + (x) + (uint)(ac); - (a) = ROTATE_LEFT((a), (s)); - (a) += (b); - } - #endregion - - #region context info - /// - /// state (ABCD) - /// - uint[] state = new uint[4]; - - /// - /// number of bits, modulo 2^64 (lsb first) - /// - uint[] count = new uint[2]; - - /// - /// input buffer - /// - byte[] buffer = new byte[64]; - #endregion - - internal MD5() - { - Initialize(); - } - - /// - /// MD5 initialization. Begins an MD5 operation, writing a new context. - /// - /// - /// The RFC named it "MD5Init" - /// - public virtual void Initialize() - { - count[0] = count[1] = 0; - - // Load magic initialization constants. - state[0] = 0x67452301; - state[1] = 0xefcdab89; - state[2] = 0x98badcfe; - state[3] = 0x10325476; - } - - /// - /// MD5 block update operation. Continues an MD5 message-digest - /// operation, processing another message block, and updating the - /// context. - /// - /// - /// - /// - /// The RFC Named it MD5Update - protected virtual void HashCore(byte[] input, int offset, int count) - { - int i; - int index; - int partLen; - - // Compute number of bytes mod 64 - index = (int)((this.count[0] >> 3) & 0x3F); - - // Update number of bits - if ((this.count[0] += (uint)((uint)count << 3)) < ((uint)count << 3)) - this.count[1]++; - this.count[1] += ((uint)count >> 29); - - partLen = 64 - index; - - // Transform as many times as possible. - if (count >= partLen) - { - Buffer.BlockCopy(input, offset, this.buffer, index, partLen); - Transform(this.buffer, 0); - - for (i = partLen; i + 63 < count; i += 64) - Transform(input, offset + i); - - index = 0; - } - else - i = 0; - - // Buffer remaining input - Buffer.BlockCopy(input, offset + i, this.buffer, index, count - i); - } - - /// - /// MD5 finalization. Ends an MD5 message-digest operation, writing the - /// the message digest and zeroizing the context. - /// - /// message digest - /// The RFC named it MD5Final - protected virtual byte[] HashFinal() - { - byte[] digest = new byte[16]; - byte[] bits = new byte[8]; - int index, padLen; - - // Save number of bits - Encode(bits, 0, this.count, 0, 8); - - // Pad out to 56 mod 64. - index = (int)((uint)(this.count[0] >> 3) & 0x3f); - padLen = (index < 56) ? (56 - index) : (120 - index); - HashCore(PADDING, 0, padLen); - - // Append length (before padding) - HashCore(bits, 0, 8); - - // Store state in digest - Encode(digest, 0, state, 0, 16); - - // Zeroize sensitive information. - count[0] = count[1] = 0; - state[0] = 0; - state[1] = 0; - state[2] = 0; - state[3] = 0; - - // initialize again, to be ready to use - Initialize(); - - return digest; - } - - /// - /// MD5 basic transformation. Transforms state based on 64 bytes block. - /// - /// - /// - private void Transform(byte[] block, int offset) - { - uint a = state[0], b = state[1], c = state[2], d = state[3]; - uint[] x = new uint[16]; - Decode(x, 0, block, offset, 64); - - // Round 1 - FF(ref a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */ - FF(ref d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */ - FF(ref c, d, a, b, x[2], S13, 0x242070db); /* 3 */ - FF(ref b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */ - FF(ref a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */ - FF(ref d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */ - FF(ref c, d, a, b, x[6], S13, 0xa8304613); /* 7 */ - FF(ref b, c, d, a, x[7], S14, 0xfd469501); /* 8 */ - FF(ref a, b, c, d, x[8], S11, 0x698098d8); /* 9 */ - FF(ref d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */ - FF(ref c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ - FF(ref b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ - FF(ref a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ - FF(ref d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ - FF(ref c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ - FF(ref b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ - - // Round 2 - GG(ref a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */ - GG(ref d, a, b, c, x[6], S22, 0xc040b340); /* 18 */ - GG(ref c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ - GG(ref b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */ - GG(ref a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */ - GG(ref d, a, b, c, x[10], S22, 0x2441453); /* 22 */ - GG(ref c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ - GG(ref b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */ - GG(ref a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */ - GG(ref d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ - GG(ref c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */ - GG(ref b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */ - GG(ref a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ - GG(ref d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */ - GG(ref c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */ - GG(ref b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ - - // Round 3 - HH(ref a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */ - HH(ref d, a, b, c, x[8], S32, 0x8771f681); /* 34 */ - HH(ref c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ - HH(ref b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ - HH(ref a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */ - HH(ref d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */ - HH(ref c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */ - HH(ref b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ - HH(ref a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ - HH(ref d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */ - HH(ref c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */ - HH(ref b, c, d, a, x[6], S34, 0x4881d05); /* 44 */ - HH(ref a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */ - HH(ref d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ - HH(ref c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ - HH(ref b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */ - - // Round 4 - II(ref a, b, c, d, x[0], S41, 0xf4292244); /* 49 */ - II(ref d, a, b, c, x[7], S42, 0x432aff97); /* 50 */ - II(ref c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ - II(ref b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */ - II(ref a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ - II(ref d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */ - II(ref c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ - II(ref b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */ - II(ref a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */ - II(ref d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ - II(ref c, d, a, b, x[6], S43, 0xa3014314); /* 59 */ - II(ref b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ - II(ref a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */ - II(ref d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ - II(ref c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */ - II(ref b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */ - - state[0] += a; - state[1] += b; - state[2] += c; - state[3] += d; - - // Zeroize sensitive information. - for (int i = 0; i < x.Length; i++) - x[i] = 0; - } - - /// - /// Encodes input (uint) into output (byte). Assumes len is - /// multiple of 4. - /// - /// - /// - /// - /// - /// - private static void Encode(byte[] output, int outputOffset, uint[] input, int inputOffset, int count) - { - int i, j; - int end = outputOffset + count; - for (i = inputOffset, j = outputOffset; j < end; i++, j += 4) - { - output[j] = (byte)(input[i] & 0xff); - output[j + 1] = (byte)((input[i] >> 8) & 0xff); - output[j + 2] = (byte)((input[i] >> 16) & 0xff); - output[j + 3] = (byte)((input[i] >> 24) & 0xff); - } - } - - /// - /// Decodes input (byte) into output (uint). Assumes len is - /// a multiple of 4. - /// - /// - /// - /// - /// - /// - static private void Decode(uint[] output, int outputOffset, byte[] input, int inputOffset, int count) - { - int i, j; - int end = inputOffset + count; - for (i = outputOffset, j = inputOffset; j < end; i++, j += 4) - output[i] = ((uint)input[j]) | (((uint)input[j + 1]) << 8) | (((uint)input[j + 2]) << 16) | (((uint)input[j + 3]) << 24); - } - #endregion - - #region expose the same interface as the regular MD5 object - - protected byte[] HashValue; - protected int State; - public virtual bool CanReuseTransform - { - get - { - return true; - } - } - - public virtual bool CanTransformMultipleBlocks - { - get - { - return true; - } - } - public virtual byte[] Hash - { - get - { - if (this.State != 0) - throw new InvalidOperationException(); - return (byte[])HashValue.Clone(); - } - } - public virtual int HashSize - { - get - { - return HashSizeValue; - } - } - protected int HashSizeValue = 128; - - public virtual int InputBlockSize - { - get - { - return 1; - } - } - public virtual int OutputBlockSize - { - get - { - return 1; - } - } - - public void Clear() - { - Dispose(true); - } - - public byte[] ComputeHash(byte[] buffer) - { - return ComputeHash(buffer, 0, buffer.Length); - } - public byte[] ComputeHash(byte[] buffer, int offset, int count) - { - Initialize(); - HashCore(buffer, offset, count); - HashValue = HashFinal(); - return (byte[])HashValue.Clone(); - } - - public byte[] ComputeHash(Stream inputStream) - { - Initialize(); - int count; - byte[] buffer = new byte[4096]; - while (0 < (count = inputStream.Read(buffer, 0, 4096))) - { - HashCore(buffer, 0, count); - } - HashValue = HashFinal(); - return (byte[])HashValue.Clone(); - } - - public int TransformBlock( - byte[] inputBuffer, - int inputOffset, - int inputCount, - byte[] outputBuffer, - int outputOffset - ) - { - if (inputBuffer == null) - { - throw new ArgumentNullException("inputBuffer"); - } - if (inputOffset < 0) - { - throw new ArgumentOutOfRangeException("inputOffset"); - } - if ((inputCount < 0) || (inputCount > inputBuffer.Length)) - { - throw new ArgumentException("inputCount"); - } - if ((inputBuffer.Length - inputCount) < inputOffset) - { - throw new ArgumentOutOfRangeException("inputOffset"); - } - if (this.State == 0) - { - Initialize(); - this.State = 1; - } - - HashCore(inputBuffer, inputOffset, inputCount); - if ((inputBuffer != outputBuffer) || (inputOffset != outputOffset)) - { - Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount); - } - return inputCount; - } - public byte[] TransformFinalBlock( - byte[] inputBuffer, - int inputOffset, - int inputCount - ) - { - if (inputBuffer == null) - { - throw new ArgumentNullException("inputBuffer"); - } - if (inputOffset < 0) - { - throw new ArgumentOutOfRangeException("inputOffset"); - } - if ((inputCount < 0) || (inputCount > inputBuffer.Length)) - { - throw new ArgumentException("inputCount"); - } - if ((inputBuffer.Length - inputCount) < inputOffset) - { - throw new ArgumentOutOfRangeException("inputOffset"); - } - if (this.State == 0) - { - Initialize(); - } - HashCore(inputBuffer, inputOffset, inputCount); - HashValue = HashFinal(); - byte[] buffer = new byte[inputCount]; - Buffer.BlockCopy(inputBuffer, inputOffset, buffer, 0, inputCount); - this.State = 0; - return buffer; - } - #endregion - - protected virtual void Dispose(bool disposing) - { - if (!disposing) - Initialize(); - } - public void Dispose() - { - Dispose(true); - } - } -} - diff --git a/Storage/Storage/Internal_/File/Cryptography/SHA1/SHA1CryptoServiceProvider.cs b/Storage/Storage/Internal_/File/Cryptography/SHA1/SHA1CryptoServiceProvider.cs deleted file mode 100644 index 74f6e09..0000000 --- a/Storage/Storage/Internal_/File/Cryptography/SHA1/SHA1CryptoServiceProvider.cs +++ /dev/null @@ -1,495 +0,0 @@ -// -// System.Security.Cryptography.SHA1CryptoServiceProvider.cs -// -// Authors: -// Matthew S. Ford (Matthew.S.Ford@Rose-Hulman.Edu) -// Sebastien Pouliot (sebastien@ximian.com) -// -// Copyright 2001 by Matthew S. Ford. -// Copyright (C) 2004, 2005, 2008 Novell, Inc (http://www.novell.com) -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -// Note: -// The MS Framework includes two (almost) identical class for SHA1. -// SHA1Managed is a 100% managed implementation. -// SHA1CryptoServiceProvider (this file) is a wrapper on CryptoAPI. -// Mono must provide those two class for binary compatibility. -// In our case both class are wrappers around a managed internal class SHA1Internal. - -using System.IO; -using System; -using System.Runtime.InteropServices; - -namespace LeanCloud.Storage.Internal -{ - - internal class SHA1Internal - { - - private const int BLOCK_SIZE_BYTES = 64; - private uint[] _H; // these are my chaining variables - private ulong count; - private byte[] _ProcessingBuffer; // Used to start data when passed less than a block worth. - private int _ProcessingBufferCount; // Counts how much data we have stored that still needs processed. - private uint[] buff; - - public SHA1Internal() - { - _H = new uint[5]; - _ProcessingBuffer = new byte[BLOCK_SIZE_BYTES]; - buff = new uint[80]; - - Initialize(); - } - - public void HashCore(byte[] rgb, int ibStart, int cbSize) - { - int i; - - if (_ProcessingBufferCount != 0) - { - if (cbSize < (BLOCK_SIZE_BYTES - _ProcessingBufferCount)) - { - System.Buffer.BlockCopy(rgb, ibStart, _ProcessingBuffer, _ProcessingBufferCount, cbSize); - _ProcessingBufferCount += cbSize; - return; - } - else - { - i = (BLOCK_SIZE_BYTES - _ProcessingBufferCount); - System.Buffer.BlockCopy(rgb, ibStart, _ProcessingBuffer, _ProcessingBufferCount, i); - ProcessBlock(_ProcessingBuffer, 0); - _ProcessingBufferCount = 0; - ibStart += i; - cbSize -= i; - } - } - - for (i = 0; i < cbSize - cbSize % BLOCK_SIZE_BYTES; i += BLOCK_SIZE_BYTES) - { - ProcessBlock(rgb, (uint)(ibStart + i)); - } - - if (cbSize % BLOCK_SIZE_BYTES != 0) - { - System.Buffer.BlockCopy(rgb, cbSize - cbSize % BLOCK_SIZE_BYTES + ibStart, _ProcessingBuffer, 0, cbSize % BLOCK_SIZE_BYTES); - _ProcessingBufferCount = cbSize % BLOCK_SIZE_BYTES; - } - } - - public byte[] HashFinal() - { - byte[] hash = new byte[20]; - - ProcessFinalBlock(_ProcessingBuffer, 0, _ProcessingBufferCount); - - for (int i = 0; i < 5; i++) - { - for (int j = 0; j < 4; j++) - { - hash[i * 4 + j] = (byte)(_H[i] >> (8 * (3 - j))); - } - } - - return hash; - } - - public void Initialize() - { - count = 0; - _ProcessingBufferCount = 0; - - _H[0] = 0x67452301; - _H[1] = 0xefcdab89; - _H[2] = 0x98badcfe; - _H[3] = 0x10325476; - _H[4] = 0xC3D2E1F0; - } - - private void ProcessBlock(byte[] inputBuffer, uint inputOffset) - { - uint a, b, c, d, e; - - count += BLOCK_SIZE_BYTES; - - // abc removal would not work on the fields - uint[] _H = this._H; - uint[] buff = this.buff; - InitialiseBuff(buff, inputBuffer, inputOffset); - FillBuff(buff); - - a = _H[0]; - b = _H[1]; - c = _H[2]; - d = _H[3]; - e = _H[4]; - - // This function was unrolled because it seems to be doubling our performance with current compiler/VM. - // Possibly roll up if this changes. - - // ---- Round 1 -------- - int i = 0; - while (i < 20) - { - e += ((a << 5) | (a >> 27)) + (((c ^ d) & b) ^ d) + 0x5A827999 + buff[i]; - b = (b << 30) | (b >> 2); - - d += ((e << 5) | (e >> 27)) + (((b ^ c) & a) ^ c) + 0x5A827999 + buff[i + 1]; - a = (a << 30) | (a >> 2); - - c += ((d << 5) | (d >> 27)) + (((a ^ b) & e) ^ b) + 0x5A827999 + buff[i + 2]; - e = (e << 30) | (e >> 2); - - b += ((c << 5) | (c >> 27)) + (((e ^ a) & d) ^ a) + 0x5A827999 + buff[i + 3]; - d = (d << 30) | (d >> 2); - - a += ((b << 5) | (b >> 27)) + (((d ^ e) & c) ^ e) + 0x5A827999 + buff[i + 4]; - c = (c << 30) | (c >> 2); - i += 5; - } - - // ---- Round 2 -------- - while (i < 40) - { - e += ((a << 5) | (a >> 27)) + (b ^ c ^ d) + 0x6ED9EBA1 + buff[i]; - b = (b << 30) | (b >> 2); - - d += ((e << 5) | (e >> 27)) + (a ^ b ^ c) + 0x6ED9EBA1 + buff[i + 1]; - a = (a << 30) | (a >> 2); - - c += ((d << 5) | (d >> 27)) + (e ^ a ^ b) + 0x6ED9EBA1 + buff[i + 2]; - e = (e << 30) | (e >> 2); - - b += ((c << 5) | (c >> 27)) + (d ^ e ^ a) + 0x6ED9EBA1 + buff[i + 3]; - d = (d << 30) | (d >> 2); - - a += ((b << 5) | (b >> 27)) + (c ^ d ^ e) + 0x6ED9EBA1 + buff[i + 4]; - c = (c << 30) | (c >> 2); - i += 5; - } - - // ---- Round 3 -------- - while (i < 60) - { - e += ((a << 5) | (a >> 27)) + ((b & c) | (b & d) | (c & d)) + 0x8F1BBCDC + buff[i]; - b = (b << 30) | (b >> 2); - - d += ((e << 5) | (e >> 27)) + ((a & b) | (a & c) | (b & c)) + 0x8F1BBCDC + buff[i + 1]; - a = (a << 30) | (a >> 2); - - c += ((d << 5) | (d >> 27)) + ((e & a) | (e & b) | (a & b)) + 0x8F1BBCDC + buff[i + 2]; - e = (e << 30) | (e >> 2); - - b += ((c << 5) | (c >> 27)) + ((d & e) | (d & a) | (e & a)) + 0x8F1BBCDC + buff[i + 3]; - d = (d << 30) | (d >> 2); - - a += ((b << 5) | (b >> 27)) + ((c & d) | (c & e) | (d & e)) + 0x8F1BBCDC + buff[i + 4]; - c = (c << 30) | (c >> 2); - i += 5; - } - - // ---- Round 4 -------- - while (i < 80) - { - e += ((a << 5) | (a >> 27)) + (b ^ c ^ d) + 0xCA62C1D6 + buff[i]; - b = (b << 30) | (b >> 2); - - d += ((e << 5) | (e >> 27)) + (a ^ b ^ c) + 0xCA62C1D6 + buff[i + 1]; - a = (a << 30) | (a >> 2); - - c += ((d << 5) | (d >> 27)) + (e ^ a ^ b) + 0xCA62C1D6 + buff[i + 2]; - e = (e << 30) | (e >> 2); - - b += ((c << 5) | (c >> 27)) + (d ^ e ^ a) + 0xCA62C1D6 + buff[i + 3]; - d = (d << 30) | (d >> 2); - - a += ((b << 5) | (b >> 27)) + (c ^ d ^ e) + 0xCA62C1D6 + buff[i + 4]; - c = (c << 30) | (c >> 2); - i += 5; - } - - _H[0] += a; - _H[1] += b; - _H[2] += c; - _H[3] += d; - _H[4] += e; - } - - private static void InitialiseBuff(uint[] buff, byte[] input, uint inputOffset) - { - buff[0] = (uint)((input[inputOffset + 0] << 24) | (input[inputOffset + 1] << 16) | (input[inputOffset + 2] << 8) | (input[inputOffset + 3])); - buff[1] = (uint)((input[inputOffset + 4] << 24) | (input[inputOffset + 5] << 16) | (input[inputOffset + 6] << 8) | (input[inputOffset + 7])); - buff[2] = (uint)((input[inputOffset + 8] << 24) | (input[inputOffset + 9] << 16) | (input[inputOffset + 10] << 8) | (input[inputOffset + 11])); - buff[3] = (uint)((input[inputOffset + 12] << 24) | (input[inputOffset + 13] << 16) | (input[inputOffset + 14] << 8) | (input[inputOffset + 15])); - buff[4] = (uint)((input[inputOffset + 16] << 24) | (input[inputOffset + 17] << 16) | (input[inputOffset + 18] << 8) | (input[inputOffset + 19])); - buff[5] = (uint)((input[inputOffset + 20] << 24) | (input[inputOffset + 21] << 16) | (input[inputOffset + 22] << 8) | (input[inputOffset + 23])); - buff[6] = (uint)((input[inputOffset + 24] << 24) | (input[inputOffset + 25] << 16) | (input[inputOffset + 26] << 8) | (input[inputOffset + 27])); - buff[7] = (uint)((input[inputOffset + 28] << 24) | (input[inputOffset + 29] << 16) | (input[inputOffset + 30] << 8) | (input[inputOffset + 31])); - buff[8] = (uint)((input[inputOffset + 32] << 24) | (input[inputOffset + 33] << 16) | (input[inputOffset + 34] << 8) | (input[inputOffset + 35])); - buff[9] = (uint)((input[inputOffset + 36] << 24) | (input[inputOffset + 37] << 16) | (input[inputOffset + 38] << 8) | (input[inputOffset + 39])); - buff[10] = (uint)((input[inputOffset + 40] << 24) | (input[inputOffset + 41] << 16) | (input[inputOffset + 42] << 8) | (input[inputOffset + 43])); - buff[11] = (uint)((input[inputOffset + 44] << 24) | (input[inputOffset + 45] << 16) | (input[inputOffset + 46] << 8) | (input[inputOffset + 47])); - buff[12] = (uint)((input[inputOffset + 48] << 24) | (input[inputOffset + 49] << 16) | (input[inputOffset + 50] << 8) | (input[inputOffset + 51])); - buff[13] = (uint)((input[inputOffset + 52] << 24) | (input[inputOffset + 53] << 16) | (input[inputOffset + 54] << 8) | (input[inputOffset + 55])); - buff[14] = (uint)((input[inputOffset + 56] << 24) | (input[inputOffset + 57] << 16) | (input[inputOffset + 58] << 8) | (input[inputOffset + 59])); - buff[15] = (uint)((input[inputOffset + 60] << 24) | (input[inputOffset + 61] << 16) | (input[inputOffset + 62] << 8) | (input[inputOffset + 63])); - } - - private static void FillBuff(uint[] buff) - { - uint val; - for (int i = 16; i < 80; i += 8) - { - val = buff[i - 3] ^ buff[i - 8] ^ buff[i - 14] ^ buff[i - 16]; - buff[i] = (val << 1) | (val >> 31); - - val = buff[i - 2] ^ buff[i - 7] ^ buff[i - 13] ^ buff[i - 15]; - buff[i + 1] = (val << 1) | (val >> 31); - - val = buff[i - 1] ^ buff[i - 6] ^ buff[i - 12] ^ buff[i - 14]; - buff[i + 2] = (val << 1) | (val >> 31); - - val = buff[i + 0] ^ buff[i - 5] ^ buff[i - 11] ^ buff[i - 13]; - buff[i + 3] = (val << 1) | (val >> 31); - - val = buff[i + 1] ^ buff[i - 4] ^ buff[i - 10] ^ buff[i - 12]; - buff[i + 4] = (val << 1) | (val >> 31); - - val = buff[i + 2] ^ buff[i - 3] ^ buff[i - 9] ^ buff[i - 11]; - buff[i + 5] = (val << 1) | (val >> 31); - - val = buff[i + 3] ^ buff[i - 2] ^ buff[i - 8] ^ buff[i - 10]; - buff[i + 6] = (val << 1) | (val >> 31); - - val = buff[i + 4] ^ buff[i - 1] ^ buff[i - 7] ^ buff[i - 9]; - buff[i + 7] = (val << 1) | (val >> 31); - } - } - - private void ProcessFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) - { - ulong total = count + (ulong)inputCount; - int paddingSize = (56 - (int)(total % BLOCK_SIZE_BYTES)); - - if (paddingSize < 1) - paddingSize += BLOCK_SIZE_BYTES; - - int length = inputCount + paddingSize + 8; - byte[] fooBuffer = (length == 64) ? _ProcessingBuffer : new byte[length]; - - for (int i = 0; i < inputCount; i++) - { - fooBuffer[i] = inputBuffer[i + inputOffset]; - } - - fooBuffer[inputCount] = 0x80; - for (int i = inputCount + 1; i < inputCount + paddingSize; i++) - { - fooBuffer[i] = 0x00; - } - - // I deal in bytes. The algorithm deals in bits. - ulong size = total << 3; - AddLength(size, fooBuffer, inputCount + paddingSize); - ProcessBlock(fooBuffer, 0); - - if (length == 128) - ProcessBlock(fooBuffer, 64); - } - - internal void AddLength(ulong length, byte[] buffer, int position) - { - buffer[position++] = (byte)(length >> 56); - buffer[position++] = (byte)(length >> 48); - buffer[position++] = (byte)(length >> 40); - buffer[position++] = (byte)(length >> 32); - buffer[position++] = (byte)(length >> 24); - buffer[position++] = (byte)(length >> 16); - buffer[position++] = (byte)(length >> 8); - buffer[position] = (byte)(length); - } - } - - public sealed class SHA1CryptoServiceProvider : SHA1 - { - - private SHA1Internal sha; - - public SHA1CryptoServiceProvider() - { - sha = new SHA1Internal(); - } - - ~SHA1CryptoServiceProvider() - { - Dispose(false); - } - - protected override void Dispose(bool disposing) - { - // nothing new to do (managed implementation) - base.Dispose(disposing); - } - - protected override void HashCore(byte[] rgb, int ibStart, int cbSize) - { - State = 1; - sha.HashCore(rgb, ibStart, cbSize); - } - - protected override byte[] HashFinal() - { - State = 0; - return sha.HashFinal(); - } - - public override void Initialize() - { - sha.Initialize(); - } - } - - public abstract class SHA1 : HashAlgorithm - { - protected SHA1() - { - HashSizeValue = 160; - } - } - - public abstract class HashAlgorithm : IDisposable - { - protected int HashSizeValue; - protected internal byte[] HashValue; - protected int State = 0; - - private bool m_bDisposed = false; - - protected HashAlgorithm() { } - - // - // public properties - // - - public virtual int HashSize - { - get { return HashSizeValue; } - } - - // - // public methods - // - - public byte[] ComputeHash(Stream inputStream) - { - if (m_bDisposed) - throw new ObjectDisposedException(null); - - // Default the buffer size to 4K. - byte[] buffer = new byte[4096]; - int bytesRead; - do - { - bytesRead = inputStream.Read(buffer, 0, 4096); - if (bytesRead > 0) - { - HashCore(buffer, 0, bytesRead); - } - } while (bytesRead > 0); - - HashValue = HashFinal(); - byte[] Tmp = (byte[])HashValue.Clone(); - Initialize(); - return (Tmp); - } - - public byte[] ComputeHash(byte[] buffer) - { - if (m_bDisposed) - throw new ObjectDisposedException(null); - - // Do some validation - if (buffer == null) throw new ArgumentNullException("buffer"); - - HashCore(buffer, 0, buffer.Length); - HashValue = HashFinal(); - byte[] Tmp = (byte[])HashValue.Clone(); - Initialize(); - return (Tmp); - } - - // ICryptoTransform methods - - // we assume any HashAlgorithm can take input a byte at a time - public virtual int InputBlockSize - { - get { return (1); } - } - - public virtual int OutputBlockSize - { - get { return (1); } - } - - public virtual bool CanTransformMultipleBlocks - { - get { return (true); } - } - - public virtual bool CanReuseTransform - { - get { return (true); } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public void Clear() - { - (this as IDisposable).Dispose(); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - if (HashValue != null) - Array.Clear(HashValue, 0, HashValue.Length); - HashValue = null; - m_bDisposed = true; - } - } - - // - // abstract public methods - // - - public abstract void Initialize(); - - protected abstract void HashCore(byte[] array, int ibStart, int cbSize); - - protected abstract byte[] HashFinal(); - } -} diff --git a/Storage/Storage/Internal_/File/State/FileState.cs b/Storage/Storage/Internal_/File/State/FileState.cs deleted file mode 100644 index cb619aa..0000000 --- a/Storage/Storage/Internal_/File/State/FileState.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace LeanCloud.Storage.Internal { - public class FileState { - public string ObjectId { get; internal set; } - public string Name { get; internal set; } - public string CloudName { get; set; } - public string MimeType { get; internal set; } - public Uri Url { get; internal set; } - public IDictionary MetaData { get; internal set; } - public long Size { get; internal set; } - public long FixedChunkSize { get; internal set; } - - public int counter; - public Stream frozenData; - public string bucketId; - public string bucket; - public string token; - public long completed; - public List block_ctxes = new List(); - - public static FileState NewFromDict(IDictionary dict) { - FileState state = new FileState { - ObjectId = dict["objectId"] as string, - Url = new Uri(dict["url"] as string, UriKind.Absolute) - }; - if (dict.TryGetValue("name", out object name)) { - state.Name = name as string; - } - if (dict.TryGetValue("metaData", out object metaData)) { - state.MetaData = metaData as Dictionary; - } - return state; - } - } -} diff --git a/Storage/Storage/Internal_/InstallationId/Controller/InstallationIdController.cs b/Storage/Storage/Internal_/InstallationId/Controller/InstallationIdController.cs deleted file mode 100644 index b722d4c..0000000 --- a/Storage/Storage/Internal_/InstallationId/Controller/InstallationIdController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.IO; - -namespace LeanCloud.Storage.Internal { - /// - /// 临时方案,后面将有 Android 和 iOS 提供 device token - /// - public class InstallationIdController { - private string installationId; - private readonly object mutex = new object(); - - public string Get() { - if (installationId == null) { - lock (mutex) { - if (installationId == null) { - string installationPath = "installation.conf"; - // 文件读取或从 Native 平台读取 - if (System.IO.File.Exists(installationPath)) { - using (StreamReader reader = new StreamReader(installationPath)) { - installationId = reader.ReadToEnd(); - if (installationId != null) { - return installationId; - } - } - } - // 生成新的 device token - Guid newInstallationId = Guid.NewGuid(); - installationId = newInstallationId.ToString(); - // 写回文件 - using (StreamWriter writer = new StreamWriter(installationPath)) { - writer.Write(installationId); - } - } - } - } - return installationId; - } - } -} diff --git a/Storage/Storage/Internal_/Object/State/IObjectState.cs b/Storage/Storage/Internal_/Object/State/IObjectState.cs deleted file mode 100644 index a22a4e3..0000000 --- a/Storage/Storage/Internal_/Object/State/IObjectState.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - public interface IObjectState : IEnumerable> { - string ClassName { get; } - string ObjectId { get; } - AVACL ACL { get; set; } - DateTime? UpdatedAt { get; } - DateTime? CreatedAt { get; } - object this[string key] { get; } - - bool ContainsKey(string key); - - IObjectState MutatedClone(Action func); - } -} diff --git a/Storage/Storage/Internal_/Object/State/MutableObjectState.cs b/Storage/Storage/Internal_/Object/State/MutableObjectState.cs deleted file mode 100644 index c141d95..0000000 --- a/Storage/Storage/Internal_/Object/State/MutableObjectState.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - public class MutableObjectState : IObjectState { - public string ClassName { get; set; } - public string ObjectId { get; set; } - public AVACL ACL { get; set; } - public DateTime? UpdatedAt { get; set; } - public DateTime? CreatedAt { get; set; } - - public IDictionary ServerData { - get; set; - } = new Dictionary(); - - public object this[string key] { - get { - return ServerData[key]; - } - } - - public bool ContainsKey(string key) { - return ServerData.ContainsKey(key); - } - - public void Apply(IDictionary operationSet) { - // Apply operationSet - foreach (var pair in operationSet) { - ServerData.TryGetValue(pair.Key, out object oldValue); - var newValue = pair.Value.Apply(oldValue, pair.Key); - if (newValue != AVDeleteOperation.DeleteToken) { - ServerData[pair.Key] = newValue; - } else { - ServerData.Remove(pair.Key); - } - } - } - - public void Apply(IObjectState other) { - if (other.ObjectId != null) { - ObjectId = other.ObjectId; - } - if (other.ACL != null) { - ACL = other.ACL; - } - if (other.UpdatedAt != null) { - UpdatedAt = other.UpdatedAt; - } - if (other.CreatedAt != null) { - CreatedAt = other.CreatedAt; - } - - foreach (var pair in other) { - ServerData[pair.Key] = pair.Value; - } - } - - public IObjectState MutatedClone(Action func) { - var clone = MutableClone(); - func(clone); - return clone; - } - - protected virtual MutableObjectState MutableClone() { - return new MutableObjectState { - ClassName = ClassName, - ObjectId = ObjectId, - CreatedAt = CreatedAt, - UpdatedAt = UpdatedAt, - ServerData = new Dictionary(ServerData) - }; - } - - IEnumerator> IEnumerable>.GetEnumerator() { - return ServerData.GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { - return ((IEnumerable>)this).GetEnumerator(); - } - } -} diff --git a/Storage/Storage/Internal_/Object/State/ObjectData.cs b/Storage/Storage/Internal_/Object/State/ObjectData.cs deleted file mode 100644 index 730adac..0000000 --- a/Storage/Storage/Internal_/Object/State/ObjectData.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - internal class ObjectData : IEnumerable> { - internal string ClassName { - get; set; - } - - internal string ObjectId { - get; set; - } - - internal AVACL ACL { - get; set; - } - - internal DateTime? CreatedAt { - get; set; - } - - internal DateTime? UpdatedAt { - get; set; - } - - internal Dictionary CustomProperties { - get; set; - } = new Dictionary(); - - internal object this[string key] { - get { - return CustomProperties[key]; - } - } - - internal bool ContainsKey(string key) { - return CustomProperties.ContainsKey(key); - } - - internal void Apply(Dictionary operations) { - foreach (KeyValuePair entry in operations) { - string propKey = entry.Key; - object propVal = entry.Value; - if (!CustomProperties.TryGetValue(propKey, out object oldVal)) { - continue; - } - object newVal = entry.Value.Apply(oldVal, propKey); - if (newVal == AVDeleteOperation.DeleteToken) { - CustomProperties.Remove(propKey); - } else { - CustomProperties[propKey] = newVal; - } - } - } - - #region IEnumerable - - public IEnumerator> GetEnumerator() { - return ((IEnumerable>)CustomProperties).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() { - return ((IEnumerable>)CustomProperties).GetEnumerator(); - } - - #endregion - } -} diff --git a/Storage/Storage/Internal_/Object/Subclassing/ObjectSubclassInfo.cs b/Storage/Storage/Internal_/Object/Subclassing/ObjectSubclassInfo.cs deleted file mode 100644 index 95be34c..0000000 --- a/Storage/Storage/Internal_/Object/Subclassing/ObjectSubclassInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace LeanCloud.Storage.Internal { - internal class ObjectSubclassInfo { - public ObjectSubclassInfo(Type type, ConstructorInfo constructor) { - TypeInfo = type.GetTypeInfo(); - ClassName = GetClassName(TypeInfo); - 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 TypeInfo TypeInfo { get; private set; } - public string ClassName { get; private set; } - public IDictionary PropertyMappings { get; private set; } - private ConstructorInfo Constructor { get; set; } - - public AVObject Instantiate() { - return (AVObject)Constructor.Invoke(null); - } - - internal static string GetClassName(TypeInfo type) { - var attribute = type.GetCustomAttribute(); - return attribute?.ClassName; - } - } -} diff --git a/Storage/Storage/Internal_/Object/Subclassing/ObjectSubclassingController.cs b/Storage/Storage/Internal_/Object/Subclassing/ObjectSubclassingController.cs deleted file mode 100644 index 3458459..0000000 --- a/Storage/Storage/Internal_/Object/Subclassing/ObjectSubclassingController.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Threading; - -namespace LeanCloud.Storage.Internal { - public class ObjectSubclassingController { - // Class names starting with _ are documented to be reserved. Use this one - // here to allow us to 'inherit' certain properties. - private static readonly string avObjectClassName = "_AVObject"; - - private readonly ReaderWriterLockSlim mutex; - private readonly IDictionary registeredSubclasses; - private readonly Dictionary registerActions; - - public ObjectSubclassingController() { - mutex = new ReaderWriterLockSlim(); - registeredSubclasses = new Dictionary(); - registerActions = new Dictionary(); - - // Register the AVObject subclass, so we get access to the ACL, - // objectId, and other AVFieldName properties. - RegisterSubclass(typeof(AVObject)); - } - - public string GetClassName(Type type) { - return type == typeof(AVObject) - ? avObjectClassName - : ObjectSubclassInfo.GetClassName(type.GetTypeInfo()); - } - - public Type GetType(string className) { - mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo info); - mutex.ExitReadLock(); - - return info?.TypeInfo.AsType(); - } - - public bool IsTypeValid(string className, Type type) { - mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo subclassInfo); - mutex.ExitReadLock(); - - return subclassInfo == null - ? type == typeof(AVObject) - : subclassInfo.TypeInfo == type.GetTypeInfo(); - } - - public void RegisterSubclass(Type type) { - TypeInfo typeInfo = type.GetTypeInfo(); - if (!typeof(AVObject).GetTypeInfo().IsAssignableFrom(typeInfo)) { - throw new ArgumentException("Cannot register a type that is not a subclass of AVObject"); - } - - string className = GetClassName(type); - - try { - // Perform this as a single independent transaction, so we can never get into an - // intermediate state where we *theoretically* register the wrong class due to a - // TOCTTOU bug. - mutex.EnterWriteLock(); - - if (registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo previousInfo)) { - if (typeInfo.IsAssignableFrom(previousInfo.TypeInfo)) { - // Previous subclass is more specific or equal to the current type, do nothing. - return; - } - if (previousInfo.TypeInfo.IsAssignableFrom(typeInfo)) { - // Previous subclass is parent of new child, fallthrough and actually register - // this class. - /* Do nothing */ - } else { - throw new ArgumentException( - "Tried to register both " + previousInfo.TypeInfo.FullName + " and " + typeInfo.FullName + - " as the AVObject subclass of " + className + ". Cannot determine the right class " + - "to use because neither inherits from the other." - ); - } - } - - ConstructorInfo constructor = type.FindConstructor(); - if (constructor == null) { - throw new ArgumentException("Cannot register a type that does not implement the default constructor!"); - } - - registeredSubclasses[className] = new ObjectSubclassInfo(type, constructor); - } finally { - mutex.ExitWriteLock(); - } - - - mutex.EnterReadLock(); - registerActions.TryGetValue(className, out Action toPerform); - mutex.ExitReadLock(); - - toPerform?.Invoke(); - } - - public void UnregisterSubclass(Type type) { - mutex.EnterWriteLock(); - registeredSubclasses.Remove(GetClassName(type)); - mutex.ExitWriteLock(); - } - - public void AddRegisterHook(Type t, Action action) { - mutex.EnterWriteLock(); - registerActions.Add(GetClassName(t), action); - mutex.ExitWriteLock(); - } - - public AVObject Instantiate(string className) { - - mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo info); - mutex.ExitReadLock(); - - return info != null - ? info.Instantiate() - : new AVObject(className); - } - - public IDictionary GetPropertyMappings(string className) { - mutex.EnterReadLock(); - if (!registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo info)) { - _ = registeredSubclasses.TryGetValue(avObjectClassName, out info); - } - mutex.ExitReadLock(); - - return info.PropertyMappings; - } - - } -} diff --git a/Storage/Storage/Internal_/Operation/AVAddOperation.cs b/Storage/Storage/Internal_/Operation/AVAddOperation.cs deleted file mode 100644 index 6f836e4..0000000 --- a/Storage/Storage/Internal_/Operation/AVAddOperation.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using LeanCloud.Utilities; - -namespace LeanCloud.Storage.Internal { - public class AVAddOperation : IAVFieldOperation { - private readonly ReadOnlyCollection objects; - - public AVAddOperation(IEnumerable objects) { - this.objects = new ReadOnlyCollection(objects.ToList()); - } - - public object Encode() { - return new Dictionary { - { "__op", "Add" }, - { "objects", PointerOrLocalIdEncoder.Instance.Encode(objects) } - }; - } - - public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) { - if (previous == null) { - return this; - } - if (previous is AVDeleteOperation) { - return new AVSetOperation(objects.ToList()); - } - if (previous is AVSetOperation setOp) { - var oldList = Conversion.To>(setOp.Value); - return new AVSetOperation(oldList.Concat(objects).ToList()); - } - if (previous is AVAddOperation) { - return new AVAddOperation(((AVAddOperation)previous).Objects.Concat(objects)); - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } - - public object Apply(object oldValue, string key) { - if (oldValue == null) { - return objects.ToList(); - } - var oldList = Conversion.To>(oldValue); - return oldList.Concat(objects).ToList(); - } - - public IEnumerable Objects { - get { - return objects; - } - } - } -} diff --git a/Storage/Storage/Internal_/Operation/AVAddUniqueOperation.cs b/Storage/Storage/Internal_/Operation/AVAddUniqueOperation.cs deleted file mode 100644 index 6a820e9..0000000 --- a/Storage/Storage/Internal_/Operation/AVAddUniqueOperation.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using LeanCloud.Utilities; - -namespace LeanCloud.Storage.Internal { - public class AVAddUniqueOperation : IAVFieldOperation { - private ReadOnlyCollection objects; - public AVAddUniqueOperation(IEnumerable objects) { - this.objects = new ReadOnlyCollection(objects.Distinct().ToList()); - } - - public object Encode() { - return new Dictionary { - { "__op", "AddUnique" }, - { "objects", PointerOrLocalIdEncoder.Instance.Encode(objects) } - }; - } - - public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) { - if (previous == null) { - return this; - } - if (previous is AVDeleteOperation) { - return new AVSetOperation(objects.ToList()); - } - if (previous is AVSetOperation setOp) { - var oldList = Conversion.To>(setOp.Value); - var result = this.Apply(oldList, null); - return new AVSetOperation(result); - } - if (previous is AVAddUniqueOperation) { - var oldList = ((AVAddUniqueOperation)previous).Objects; - return new AVAddUniqueOperation((IList)this.Apply(oldList, null)); - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } - - public object Apply(object oldValue, string key) { - if (oldValue == null) { - return objects.ToList(); - } - var newList = Conversion.To>(oldValue).ToList(); - var comparer = AVFieldOperations.AVObjectComparer; - foreach (var objToAdd in objects) { - if (objToAdd is AVObject) { - var matchedObj = newList.FirstOrDefault(listObj => comparer.Equals(objToAdd, listObj)); - if (matchedObj == null) { - newList.Add(objToAdd); - } else { - var index = newList.IndexOf(matchedObj); - newList[index] = objToAdd; - } - } else if (!newList.Contains(objToAdd, comparer)) { - newList.Add(objToAdd); - } - } - return newList; - } - - public IEnumerable Objects { - get { - return objects; - } - } - } -} diff --git a/Storage/Storage/Internal_/Operation/AVDeleteOperation.cs b/Storage/Storage/Internal_/Operation/AVDeleteOperation.cs deleted file mode 100644 index 6b548ae..0000000 --- a/Storage/Storage/Internal_/Operation/AVDeleteOperation.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - /// - /// An operation where a field is deleted from the object. - /// - public class AVDeleteOperation : IAVFieldOperation { - internal static readonly object DeleteToken = new object(); - private static AVDeleteOperation _Instance = new AVDeleteOperation(); - - public static AVDeleteOperation Instance { - get { - return _Instance; - } - } - - private AVDeleteOperation() { } - - public object Encode() { - return new Dictionary { - {"__op", "Delete"} - }; - } - - public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) { - return this; - } - - public object Apply(object oldValue, string key) { - return DeleteToken; - } - } -} diff --git a/Storage/Storage/Internal_/Operation/AVFieldOperations.cs b/Storage/Storage/Internal_/Operation/AVFieldOperations.cs deleted file mode 100644 index 39a2d6a..0000000 --- a/Storage/Storage/Internal_/Operation/AVFieldOperations.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - public class AVObjectIdComparer : IEqualityComparer { - bool IEqualityComparer.Equals(object p1, object p2) { - if (p1 is AVObject avObj1 && p2 is AVObject avObj2) { - return object.Equals(avObj1.ObjectId, avObj2.ObjectId); - } - return object.Equals(p1, p2); - } - - public int GetHashCode(object p) { - if (p is AVObject avObject) { - return avObject.ObjectId.GetHashCode(); - } - return p.GetHashCode(); - } - } - - static class AVFieldOperations { - private static AVObjectIdComparer comparer; - - public static IAVFieldOperation Decode(IDictionary json) { - throw new NotImplementedException(); - } - - public static IEqualityComparer AVObjectComparer { - get { - if (comparer == null) { - comparer = new AVObjectIdComparer(); - } - return comparer; - } - } - } -} diff --git a/Storage/Storage/Internal_/Operation/AVIncrementOperation.cs b/Storage/Storage/Internal_/Operation/AVIncrementOperation.cs deleted file mode 100644 index b0922f1..0000000 --- a/Storage/Storage/Internal_/Operation/AVIncrementOperation.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace LeanCloud.Storage.Internal { - public class AVIncrementOperation : IAVFieldOperation { - private static readonly IDictionary, Func> adders; - - static AVIncrementOperation() { - // Defines adders for all of the implicit conversions: http://msdn.microsoft.com/en-US/library/y5b434w4(v=vs.80).aspx - adders = new Dictionary, Func> { - {new Tuple(typeof(sbyte), typeof(sbyte)), (left, right) => (sbyte)left + (sbyte)right}, - {new Tuple(typeof(sbyte), typeof(short)), (left, right) => (sbyte)left + (short)right}, - {new Tuple(typeof(sbyte), typeof(int)), (left, right) => (sbyte)left + (int)right}, - {new Tuple(typeof(sbyte), typeof(long)), (left, right) => (sbyte)left + (long)right}, - {new Tuple(typeof(sbyte), typeof(float)), (left, right) => (sbyte)left + (float)right}, - {new Tuple(typeof(sbyte), typeof(double)), (left, right) => (sbyte)left + (double)right}, - {new Tuple(typeof(sbyte), typeof(decimal)), (left, right) => (sbyte)left + (decimal)right}, - {new Tuple(typeof(byte), typeof(byte)), (left, right) => (byte)left + (byte)right}, - {new Tuple(typeof(byte), typeof(short)), (left, right) => (byte)left + (short)right}, - {new Tuple(typeof(byte), typeof(ushort)), (left, right) => (byte)left + (ushort)right}, - {new Tuple(typeof(byte), typeof(int)), (left, right) => (byte)left + (int)right}, - {new Tuple(typeof(byte), typeof(uint)), (left, right) => (byte)left + (uint)right}, - {new Tuple(typeof(byte), typeof(long)), (left, right) => (byte)left + (long)right}, - {new Tuple(typeof(byte), typeof(ulong)), (left, right) => (byte)left + (ulong)right}, - {new Tuple(typeof(byte), typeof(float)), (left, right) => (byte)left + (float)right}, - {new Tuple(typeof(byte), typeof(double)), (left, right) => (byte)left + (double)right}, - {new Tuple(typeof(byte), typeof(decimal)), (left, right) => (byte)left + (decimal)right}, - {new Tuple(typeof(short), typeof(short)), (left, right) => (short)left + (short)right}, - {new Tuple(typeof(short), typeof(int)), (left, right) => (short)left + (int)right}, - {new Tuple(typeof(short), typeof(long)), (left, right) => (short)left + (long)right}, - {new Tuple(typeof(short), typeof(float)), (left, right) => (short)left + (float)right}, - {new Tuple(typeof(short), typeof(double)), (left, right) => (short)left + (double)right}, - {new Tuple(typeof(short), typeof(decimal)), (left, right) => (short)left + (decimal)right}, - {new Tuple(typeof(ushort), typeof(ushort)), (left, right) => (ushort)left + (ushort)right}, - {new Tuple(typeof(ushort), typeof(int)), (left, right) => (ushort)left + (int)right}, - {new Tuple(typeof(ushort), typeof(uint)), (left, right) => (ushort)left + (uint)right}, - {new Tuple(typeof(ushort), typeof(long)), (left, right) => (ushort)left + (long)right}, - {new Tuple(typeof(ushort), typeof(ulong)), (left, right) => (ushort)left + (ulong)right}, - {new Tuple(typeof(ushort), typeof(float)), (left, right) => (ushort)left + (float)right}, - {new Tuple(typeof(ushort), typeof(double)), (left, right) => (ushort)left + (double)right}, - {new Tuple(typeof(ushort), typeof(decimal)), (left, right) => (ushort)left + (decimal)right}, - {new Tuple(typeof(int), typeof(int)), (left, right) => (int)left + (int)right}, - {new Tuple(typeof(int), typeof(long)), (left, right) => (int)left + (long)right}, - {new Tuple(typeof(int), typeof(float)), (left, right) => (int)left + (float)right}, - {new Tuple(typeof(int), typeof(double)), (left, right) => (int)left + (double)right}, - {new Tuple(typeof(int), typeof(decimal)), (left, right) => (int)left + (decimal)right}, - {new Tuple(typeof(uint), typeof(uint)), (left, right) => (uint)left + (uint)right}, - {new Tuple(typeof(uint), typeof(long)), (left, right) => (uint)left + (long)right}, - {new Tuple(typeof(uint), typeof(ulong)), (left, right) => (uint)left + (ulong)right}, - {new Tuple(typeof(uint), typeof(float)), (left, right) => (uint)left + (float)right}, - {new Tuple(typeof(uint), typeof(double)), (left, right) => (uint)left + (double)right}, - {new Tuple(typeof(uint), typeof(decimal)), (left, right) => (uint)left + (decimal)right}, - {new Tuple(typeof(long), typeof(long)), (left, right) => (long)left + (long)right}, - {new Tuple(typeof(long), typeof(float)), (left, right) => (long)left + (float)right}, - {new Tuple(typeof(long), typeof(double)), (left, right) => (long)left + (double)right}, - {new Tuple(typeof(long), typeof(decimal)), (left, right) => (long)left + (decimal)right}, - {new Tuple(typeof(char), typeof(char)), (left, right) => (char)left + (char)right}, - {new Tuple(typeof(char), typeof(ushort)), (left, right) => (char)left + (ushort)right}, - {new Tuple(typeof(char), typeof(int)), (left, right) => (char)left + (int)right}, - {new Tuple(typeof(char), typeof(uint)), (left, right) => (char)left + (uint)right}, - {new Tuple(typeof(char), typeof(long)), (left, right) => (char)left + (long)right}, - {new Tuple(typeof(char), typeof(ulong)), (left, right) => (char)left + (ulong)right}, - {new Tuple(typeof(char), typeof(float)), (left, right) => (char)left + (float)right}, - {new Tuple(typeof(char), typeof(double)), (left, right) => (char)left + (double)right}, - {new Tuple(typeof(char), typeof(decimal)), (left, right) => (char)left + (decimal)right}, - {new Tuple(typeof(float), typeof(float)), (left, right) => (float)left + (float)right}, - {new Tuple(typeof(float), typeof(double)), (left, right) => (float)left + (double)right}, - {new Tuple(typeof(ulong), typeof(ulong)), (left, right) => (ulong)left + (ulong)right}, - {new Tuple(typeof(ulong), typeof(float)), (left, right) => (ulong)left + (float)right}, - {new Tuple(typeof(ulong), typeof(double)), (left, right) => (ulong)left + (double)right}, - {new Tuple(typeof(ulong), typeof(decimal)), (left, right) => (ulong)left + (decimal)right}, - {new Tuple(typeof(double), typeof(double)), (left, right) => (double)left + (double)right}, - {new Tuple(typeof(decimal), typeof(decimal)), (left, right) => (decimal)left + (decimal)right} - }; - // Generate the adders in the other direction - foreach (var pair in adders.Keys.ToList()) { - if (pair.Item1.Equals(pair.Item2)) { - continue; - } - var reversePair = new Tuple(pair.Item2, pair.Item1); - var func = adders[pair]; - adders[reversePair] = (left, right) => func(right, left); - } - } - - private object amount; - - public AVIncrementOperation(object amount) { - this.amount = amount; - } - - public object Encode() { - return new Dictionary - { - {"__op", "Increment"}, - {"amount", amount} - }; - } - - private static object Add(object obj1, object obj2) { - Func adder; - if (adders.TryGetValue(new Tuple(obj1.GetType(), obj2.GetType()), out adder)) { - return adder(obj1, obj2); - } - throw new InvalidCastException("Cannot add " + obj1.GetType() + " to " + obj2.GetType()); - } - - public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) { - if (previous == null) { - return this; - } - if (previous is AVDeleteOperation) { - return new AVSetOperation(amount); - } - if (previous is AVSetOperation) { - var otherAmount = ((AVSetOperation)previous).Value; - if (otherAmount is string) { - throw new InvalidOperationException("Cannot increment a non-number type."); - } - var myAmount = amount; - return new AVSetOperation(Add(otherAmount, myAmount)); - } - if (previous is AVIncrementOperation) { - object otherAmount = ((AVIncrementOperation)previous).Amount; - object myAmount = amount; - return new AVIncrementOperation(Add(otherAmount, myAmount)); - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } - - public object Apply(object oldValue, string key) { - if (oldValue is string) { - throw new InvalidOperationException("Cannot increment a non-number type."); - } - object otherAmount = oldValue ?? 0; - object myAmount = amount; - return Add(otherAmount, myAmount); - } - - public object Amount { - get { - return amount; - } - } - } -} diff --git a/Storage/Storage/Internal_/Operation/AVRelationOperation.cs b/Storage/Storage/Internal_/Operation/AVRelationOperation.cs deleted file mode 100644 index dab4a5f..0000000 --- a/Storage/Storage/Internal_/Operation/AVRelationOperation.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - public class AVRelationOperation : IAVFieldOperation { - private readonly IList adds; - private readonly IList removes; - private readonly string targetClassName; - - private AVRelationOperation(IEnumerable adds, - IEnumerable removes, - string targetClassName) { - this.targetClassName = targetClassName; - this.adds = new ReadOnlyCollection(adds.ToList()); - this.removes = new ReadOnlyCollection(removes.ToList()); - } - - public AVRelationOperation(IEnumerable adds, - IEnumerable removes) { - adds = adds ?? new AVObject[0]; - removes = removes ?? new AVObject[0]; - this.targetClassName = adds.Concat(removes).Select(o => o.ClassName).FirstOrDefault(); - this.adds = new ReadOnlyCollection(IdsFromObjects(adds).ToList()); - this.removes = new ReadOnlyCollection(IdsFromObjects(removes).ToList()); - } - - public object Encode() { - var adds = this.adds - .Select(id => PointerOrLocalIdEncoder.Instance.Encode( - AVObject.CreateWithoutData(targetClassName, id))) - .ToList(); - var removes = this.removes - .Select(id => PointerOrLocalIdEncoder.Instance.Encode( - AVObject.CreateWithoutData(targetClassName, id))) - .ToList(); - var addDict = adds.Count == 0 ? null : new Dictionary { - {"__op", "AddRelation"}, - {"objects", adds} - }; - var removeDict = removes.Count == 0 ? null : new Dictionary { - {"__op", "RemoveRelation"}, - {"objects", removes} - }; - - if (addDict != null && removeDict != null) { - return new Dictionary { - {"__op", "Batch"}, - {"ops", new[] {addDict, removeDict}} - }; - } - return addDict ?? removeDict; - } - - public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) { - if (previous == null) { - return this; - } - if (previous is AVDeleteOperation) { - throw new InvalidOperationException("You can't modify a relation after deleting it."); - } - var other = previous as AVRelationOperation; - if (other != null) { - if (other.TargetClassName != TargetClassName) { - throw new InvalidOperationException( - string.Format("Related object must be of class {0}, but {1} was passed in.", - other.TargetClassName, - TargetClassName)); - } - var newAdd = adds.Union(other.adds.Except(removes)).ToList(); - var newRemove = removes.Union(other.removes.Except(adds)).ToList(); - return new AVRelationOperation(newAdd, newRemove, TargetClassName); - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } - - public object Apply(object oldValue, string key) { - if (adds.Count == 0 && removes.Count == 0) { - return null; - } - if (oldValue == null) { - return AVRelationBase.CreateRelation(null, key, targetClassName); - } - if (oldValue is AVRelationBase) { - var oldRelation = (AVRelationBase)oldValue; - var oldClassName = oldRelation.TargetClassName; - if (oldClassName != null && oldClassName != targetClassName) { - throw new InvalidOperationException("Related object must be a " + oldClassName - + ", but a " + targetClassName + " was passed in."); - } - oldRelation.TargetClassName = targetClassName; - return oldRelation; - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } - - public string TargetClassName { get { return targetClassName; } } - - private IEnumerable IdsFromObjects(IEnumerable objects) { - foreach (var obj in objects) { - if (obj.ObjectId == null) { - throw new ArgumentException( - "You can't add an unsaved AVObject to a relation."); - } - if (obj.ClassName != targetClassName) { - throw new ArgumentException(string.Format( - "Tried to create a AVRelation with 2 different types: {0} and {1}", - targetClassName, - obj.ClassName)); - } - } - return objects.Select(o => o.ObjectId).Distinct(); - } - } -} diff --git a/Storage/Storage/Internal_/Operation/AVRemoveOperation.cs b/Storage/Storage/Internal_/Operation/AVRemoveOperation.cs deleted file mode 100644 index 65c35e7..0000000 --- a/Storage/Storage/Internal_/Operation/AVRemoveOperation.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; - -using LeanCloud.Utilities; - -namespace LeanCloud.Storage.Internal { - public class AVRemoveOperation : IAVFieldOperation { - private ReadOnlyCollection objects; - public AVRemoveOperation(IEnumerable objects) { - this.objects = new ReadOnlyCollection(objects.Distinct().ToList()); - } - - public object Encode() { - return new Dictionary { - { "__op", "Remove" }, - { "objects", PointerOrLocalIdEncoder.Instance.Encode(objects) } - }; - } - - public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) { - if (previous == null) { - return this; - } - if (previous is AVDeleteOperation) { - return previous; - } - if (previous is AVSetOperation) { - var setOp = (AVSetOperation)previous; - var oldList = Conversion.As>(setOp.Value); - return new AVSetOperation(this.Apply(oldList, null)); - } - if (previous is AVRemoveOperation) { - var oldOp = (AVRemoveOperation)previous; - return new AVRemoveOperation(oldOp.Objects.Concat(objects)); - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } - - public object Apply(object oldValue, string key) { - if (oldValue == null) { - return new List(); - } - var oldList = Conversion.As>(oldValue); - return oldList.Except(objects, AVFieldOperations.AVObjectComparer).ToList(); - } - - public IEnumerable Objects { - get { - return objects; - } - } - } -} diff --git a/Storage/Storage/Internal_/Operation/AVSetOperation.cs b/Storage/Storage/Internal_/Operation/AVSetOperation.cs deleted file mode 100644 index 360b3fd..0000000 --- a/Storage/Storage/Internal_/Operation/AVSetOperation.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace LeanCloud.Storage.Internal { - public class AVSetOperation : IAVFieldOperation { - public AVSetOperation(object value) { - Value = value; - } - - public object Encode() { - return PointerOrLocalIdEncoder.Instance.Encode(Value); - } - - public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) { - return this; - } - - public object Apply(object oldValue, string key) { - return Value; - } - - public object Value { get; private set; } - } -} diff --git a/Storage/Storage/Internal_/Operation/IAVFieldOperation.cs b/Storage/Storage/Internal_/Operation/IAVFieldOperation.cs deleted file mode 100644 index 9e5bd97..0000000 --- a/Storage/Storage/Internal_/Operation/IAVFieldOperation.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace LeanCloud.Storage.Internal { - /// - /// A AVFieldOperation represents a modification to a value in a AVObject. - /// For example, setting, deleting, or incrementing a value are all different kinds of - /// AVFieldOperations. AVFieldOperations themselves can be considered to be - /// immutable. - /// - public interface IAVFieldOperation { - /// - /// Converts the AVFieldOperation to a data structure that can be converted to JSON and sent to - /// LeanCloud as part of a save operation. - /// - /// An object to be JSONified. - object Encode(); - - /// - /// Returns a field operation that is composed of a previous operation followed by - /// this operation. This will not mutate either operation. However, it may return - /// this if the current operation is not affected by previous changes. - /// For example: - /// {increment by 2}.MergeWithPrevious({set to 5}) -> {set to 7} - /// {set to 5}.MergeWithPrevious({increment by 2}) -> {set to 5} - /// {add "foo"}.MergeWithPrevious({delete}) -> {set to ["foo"]} - /// {delete}.MergeWithPrevious({add "foo"}) -> {delete} /// - /// The most recent operation on the field, or null if none. - /// A new AVFieldOperation or this. - IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous); - - /// - /// Returns a new estimated value based on a previous value and this operation. This - /// value is not intended to be sent to LeanCloud, but it is used locally on the client to - /// inspect the most likely current value for a field. - /// - /// The key and object are used solely for AVRelation to be able to construct objects - /// that refer back to their parents. - /// - /// The previous value for the field. - /// The key that this value is for. - /// The new value for the field. - object Apply(object oldValue, string key); - } -} diff --git a/Storage/Storage/Internal_/Query/Controller/AVQueryController.cs b/Storage/Storage/Internal_/Query/Controller/AVQueryController.cs deleted file mode 100644 index 3eb07b9..0000000 --- a/Storage/Storage/Internal_/Query/Controller/AVQueryController.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - public class AVQueryController { - public async Task> FindAsync(AVQuery query, CancellationToken cancellationToken) where T : AVObject { - IList items = await FindAsync>(query.Path, query.BuildParameters(), "results", cancellationToken); - return items.Select(item => AVObjectCoder.Instance.Decode(item as IDictionary, AVDecoder.Instance)); - } - - public async Task CountAsync(AVQuery query, CancellationToken cancellationToken) where T : AVObject { - var parameters = query.BuildParameters(); - parameters["limit"] = 0; - parameters["count"] = 1; - long ret = await FindAsync(query.Path, parameters, "count", cancellationToken); - return Convert.ToInt32(ret); - } - - public async Task FirstAsync(AVQuery query, CancellationToken cancellationToken) where T : AVObject { - var parameters = query.BuildParameters(); - parameters["limit"] = 1; - IList items = await FindAsync>(query.Path, query.BuildParameters(), "results", cancellationToken); - // Not found. Return empty state. - if (!(items.FirstOrDefault() is IDictionary item)) { - return (IObjectState)null; - } - return AVObjectCoder.Instance.Decode(item, AVDecoder.Instance); - } - - private async Task FindAsync(string path, - IDictionary parameters, - string key, - CancellationToken cancellationToken) { - var command = new AVCommand { - Path = $"{path}?{AVClient.BuildQueryString(parameters)}", - Method = HttpMethod.Get - }; - var result = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken: cancellationToken); - return (T)result.Item2[key]; - } - } -} diff --git a/Storage/Storage/Internal_/Query/IQueryCondition.cs b/Storage/Storage/Internal_/Query/IQueryCondition.cs deleted file mode 100644 index 5615ff4..0000000 --- a/Storage/Storage/Internal_/Query/IQueryCondition.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -namespace LeanCloud.Storage.Internal { - /// - /// 查询条件接口 - /// IEquatable 用于比对(替换)相同的查询条件 - /// IJsonConvertible 用于生成序列化 Dictionary - /// - public interface IQueryCondition : IEquatable, IJsonConvertible { - } -} diff --git a/Storage/Storage/Internal_/Query/QueryCompositionalCondition.cs b/Storage/Storage/Internal_/Query/QueryCompositionalCondition.cs deleted file mode 100644 index f2b87d8..0000000 --- a/Storage/Storage/Internal_/Query/QueryCompositionalCondition.cs +++ /dev/null @@ -1,270 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.Text.RegularExpressions; - -namespace LeanCloud.Storage.Internal { - public class QueryCombinedCondition : IQueryCondition { - public const string AND = "$and"; - public const string OR = "$or"; - - readonly List conditions; - readonly string composition; - - internal List orderBy; - internal HashSet includes; - internal HashSet selectedKeys; - internal int skip; - internal int limit; - - public QueryCombinedCondition(string composition = AND) { - conditions = new List(); - this.composition = composition; - skip = 0; - limit = 30; - } - - #region IQueryCondition - - public bool Equals(IQueryCondition other) { - return false; - } - - public IDictionary ToJSON() { - if (conditions == null || conditions.Count == 0) { - return null; - } - if (conditions.Count == 1) { - return conditions[0].ToJSON(); - } - List list = new List(); - foreach (IQueryCondition cond in conditions) { - list.Add(cond.ToJSON()); - } - return new Dictionary { - { composition, list } - }; - } - - #endregion - - #region where - - public void WhereContainedIn(string key, IEnumerable values) { - AddCondition(key, "$in", values.ToList()); - } - - public void WhereContainsAll(string key, IEnumerable values) { - AddCondition(key, "$all", values.ToList()); - } - - public void WhereContains(string key, string substring) { - AddCondition(key, "$regex", RegexQuote(substring)); - } - - public void WhereDoesNotExist(string key) { - AddCondition(key, "$exists", false); - } - - public void WhereDoesNotMatchQuery(string key, AVQuery query) where T : AVObject { - AddCondition(key, "$notInQuery", query.BuildParameters(query.ClassName)); - } - - public void WhereEndsWith(string key, string suffix) { - AddCondition(key, "$regex", RegexQuote(suffix) + "$"); - } - - public void WhereEqualTo(string key, object value) { - AddCondition(new QueryEqualCondition(key, value)); - } - - public void WhereSizeEqualTo(string key, uint size) { - AddCondition(key, "$size", size); - } - - public void WhereExists(string key) { - AddCondition(key, "$exists", true); - } - - public void WhereGreaterThan(string key, object value) { - AddCondition(key, "$gt", value); - } - - public void WhereGreaterThanOrEqualTo(string key, object value) { - AddCondition(key, "$gte", value); - } - - public void WhereLessThan(string key, object value) { - AddCondition(key, "$lt", value); - } - - public void WhereLessThanOrEqualTo(string key, object value) { - AddCondition(key, "$lte", value); - } - - public void WhereMatches(string key, Regex regex, string modifiers) { - if (!regex.Options.HasFlag(RegexOptions.ECMAScript)) { - throw new ArgumentException( - "Only ECMAScript-compatible regexes are supported. Please use the ECMAScript RegexOptions flag when creating your regex."); - } - AddCondition(key, "$regex", regex.ToString()); - AddCondition(key, "options", modifiers); - } - - public void WhereMatches(string key, Regex regex) { - WhereMatches(key, regex, null); - } - - public void WhereMatches(string key, string pattern, string modifiers) { - WhereMatches(key, new Regex(pattern, RegexOptions.ECMAScript), modifiers); - } - - public void WhereMatches(string key, string pattern) { - WhereMatches(key, pattern, null); - } - - public void WhereMatchesKeyInQuery(string key, string keyInQuery, AVQuery query) where T : AVObject { - var parameters = new Dictionary { - { "query", query.BuildParameters(query.ClassName)}, - { "key", keyInQuery} - }; - AddCondition(key, "$select", parameters); - } - - public void WhereDoesNotMatchesKeyInQuery(string key, string keyInQuery, AVQuery query) where T : AVObject { - var parameters = new Dictionary { - { "query", query.BuildParameters(query.ClassName)}, - { "key", keyInQuery} - }; - AddCondition(key, "$dontSelect", parameters); - } - - public void WhereMatchesQuery(string key, AVQuery query) where T : AVObject { - AddCondition(key, "$inQuery", query.BuildParameters(query.ClassName)); - } - - public void WhereNear(string key, AVGeoPoint point) { - AddCondition(key, "$nearSphere", point); - } - - public void WhereNotContainedIn(string key, IEnumerable values) { - AddCondition(key, "$nin", values.ToList()); - } - - public void WhereNotEqualTo(string key, object value) { - AddCondition(key, "$ne", value); - } - - public void WhereStartsWith(string key, string suffix) { - AddCondition(key, "$regex", "^" + RegexQuote(suffix)); - } - - public void WhereWithinGeoBox(string key, AVGeoPoint southwest, AVGeoPoint northeast) { - Dictionary value = new Dictionary { - { "$box", new[] { southwest, northeast } } - }; - AddCondition(key, "$within", value); - } - - public void WhereWithinDistance(string key, AVGeoPoint point, AVGeoDistance maxDistance) { - AddCondition(key, "$nearSphere", point); - AddCondition(key, "$maxDistance", maxDistance.Radians); - } - - public void WhereRelatedTo(AVObject parent, string key) { - AddCondition(new QueryRelatedCondition(parent, key)); - } - - #endregion - - public void OrderBy(string key) { - if (orderBy == null) { - orderBy = new List(); - } - orderBy.Add(key); - } - - public void OrderByDescending(string key) { - if (orderBy == null) { - orderBy = new List(); - } - orderBy.Add($"-{key}"); - } - - public void Include(string key) { - if (includes == null) { - includes = new HashSet(); - } - try { - includes.Add(key); - } catch (Exception e) { - AVClient.PrintLog(e.Message); - } - } - - public void Select(string key) { - if (selectedKeys == null) { - selectedKeys = new HashSet(); - } - try { - selectedKeys.Add(key); - } catch (Exception e) { - AVClient.PrintLog(e.Message); - } - } - - public void Skip(int count) { - skip = count; - } - - public void Limit(int count) { - limit = count; - } - - public void AddCondition(string key, string op, object value) { - QueryOperationCondition cond = new QueryOperationCondition(key, op, value); - AddCondition(cond); - } - - public void AddCondition(IQueryCondition condition) { - if (condition == null) { - return; - } - // 组合查询的 Key 为 null - conditions.RemoveAll(cond => { - return cond.Equals(condition); - }); - conditions.Add(condition); - } - - /// - /// 构建查询字符串 - /// - /// - public IDictionary BuildParameters(string className) { - Dictionary result = new Dictionary(); - if (conditions != null) { - result["where"] = ToJSON(); - } - if (orderBy != null) { - result["order"] = string.Join(",", orderBy.ToArray()); - } - if (includes != null) { - result["include"] = string.Join(",", includes.ToArray()); - } - if (selectedKeys != null) { - result["keys"] = string.Join(",", selectedKeys.ToArray()); - } - if (!string.IsNullOrEmpty(className)) { - result["className"] = className; - } - result["skip"] = skip; - result["limit"] = limit; - return result; - } - - string RegexQuote(string input) { - return "\\Q" + input.Replace("\\E", "\\E\\\\E\\Q") + "\\E"; - } - } -} diff --git a/Storage/Storage/Internal_/Query/QueryEqualCondition.cs b/Storage/Storage/Internal_/Query/QueryEqualCondition.cs deleted file mode 100644 index 9e9e152..0000000 --- a/Storage/Storage/Internal_/Query/QueryEqualCondition.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - internal class QueryEqualCondition : IQueryCondition { - readonly string key; - readonly object value; - - public QueryEqualCondition(string key, object value) { - this.key = key; - this.value = value; - } - - public bool Equals(IQueryCondition other) { - if (other is QueryEqualCondition otherCond) { - return key == otherCond.key; - } - return false; - } - - public IDictionary ToJSON() { - return new Dictionary { - { key, PointerOrLocalIdEncoder.Instance.Encode(value) } - }; - } - } -} diff --git a/Storage/Storage/Internal_/Query/QueryOperationCondition.cs b/Storage/Storage/Internal_/Query/QueryOperationCondition.cs deleted file mode 100644 index 40c2b2a..0000000 --- a/Storage/Storage/Internal_/Query/QueryOperationCondition.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - internal class QueryOperationCondition : IQueryCondition { - readonly string key; - readonly string op; - readonly object value; - - public QueryOperationCondition(string key, string op, object value) { - this.key = key; - this.op = op; - this.value = value; - } - - public bool Equals(IQueryCondition other) { - if (other is QueryOperationCondition otherCond) { - return key == otherCond.key && op == otherCond.op; - } - return false; - } - - public IDictionary ToJSON() { - return new Dictionary { - { key, new Dictionary { - { op, value } - } } - }; - } - } -} diff --git a/Storage/Storage/Internal_/Query/QueryRelatedCondition.cs b/Storage/Storage/Internal_/Query/QueryRelatedCondition.cs deleted file mode 100644 index 88e2ce3..0000000 --- a/Storage/Storage/Internal_/Query/QueryRelatedCondition.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - internal class QueryRelatedCondition : IQueryCondition { - AVObject parent; - string key; - - public QueryRelatedCondition(AVObject parent, string key) { - this.parent = parent; - this.key = key; - } - - public bool Equals(IQueryCondition other) { - if (other is QueryRelatedCondition otherCond) { - return key == otherCond.key; - } - return false; - } - - public IDictionary ToJSON() { - return new Dictionary { - { "$relatedTo", new Dictionary { - { "object", PointerOrLocalIdEncoder.Instance.Encode(parent) }, - { "key", key } - } } - }; - } - } -} diff --git a/Storage/Storage/Internal_/User/Controller/AVUserController.cs b/Storage/Storage/Internal_/User/Controller/AVUserController.cs deleted file mode 100644 index eab422f..0000000 --- a/Storage/Storage/Internal_/User/Controller/AVUserController.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using System.Net.Http; - -namespace LeanCloud.Storage.Internal { - public class AVUserController { - public async Task SignUpAsync(IObjectState state, IDictionary operations) { - var objectJSON = AVObject.ToJSONObjectForSaving(operations); - var command = new AVCommand { - Path = "classes/_User", - Method = HttpMethod.Post, - Content = objectJSON - }; - var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - var serverState = AVObjectCoder.Instance.Decode(ret.Item2, AVDecoder.Instance); - return serverState; - } - - public async Task LogInAsync(string username, string email, string password) { - var data = new Dictionary{ - { "password", password} - }; - if (username != null) { - data.Add("username", username); - } - if (email != null) { - data.Add("email", email); - } - var command = new AVCommand { - Path = "login", - Method = HttpMethod.Post, - Content = data - }; - var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - var serverState = AVObjectCoder.Instance.Decode(ret.Item2, AVDecoder.Instance); - return serverState; - } - - public async Task LogInAsync(string authType, IDictionary data, bool failOnNotExist) { - var authData = new Dictionary { - [authType] = data - }; - var path = failOnNotExist ? "users?failOnNotExist=true" : "users"; - var command = new AVCommand { - Path = path, - Method = HttpMethod.Post, - Content = new Dictionary { - { "authData", authData } - } - }; - var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - var serverState = AVObjectCoder.Instance.Decode(ret.Item2, AVDecoder.Instance); - return serverState; - } - - public async Task GetUserAsync(string sessionToken) { - var command = new AVCommand { - Path = "users/me", - Method = HttpMethod.Get, - Headers = new Dictionary { - { "X-LC-Session", sessionToken } - } - }; - var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - return AVObjectCoder.Instance.Decode(ret.Item2, AVDecoder.Instance); - } - - public async Task RequestPasswordResetAsync(string email) { - var command = new AVCommand { - Path = "requestPasswordReset", - Method = HttpMethod.Post, - Content = new Dictionary { - { "email", email} - } - }; - await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - public async Task LogInWithParametersAsync(string relativeUrl, IDictionary data) { - var command = new AVCommand { - Path = relativeUrl, - Method = HttpMethod.Post, - Content = data - }; - var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - var serverState = AVObjectCoder.Instance.Decode(ret.Item2, AVDecoder.Instance); - return serverState; - } - - public async Task UpdatePasswordAsync(string userId, string oldPassword, string newPassword) { - var command = new AVCommand { - Path = $"users/{userId}/updatePassword", - Method = HttpMethod.Put, - Content = new Dictionary { - { "old_password", oldPassword }, - { "new_password", newPassword }, - } - }; - var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - return AVObjectCoder.Instance.Decode(ret.Item2, AVDecoder.Instance); - } - - public async Task RefreshSessionTokenAsync(string userId) { - var command = new AVCommand { - Path = $"users/{userId}/refreshSessionToken", - Method = HttpMethod.Put - }; - var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - var serverState = AVObjectCoder.Instance.Decode(ret.Item2, AVDecoder.Instance); - return serverState; - } - } -} diff --git a/Storage/Storage/Internal_/Utilities/AVObjectExtensions.cs b/Storage/Storage/Internal_/Utilities/AVObjectExtensions.cs deleted file mode 100644 index 5ace09b..0000000 --- a/Storage/Storage/Internal_/Utilities/AVObjectExtensions.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using System.Linq; -using System.Globalization; -using System.ComponentModel; -using System.Collections; - -namespace LeanCloud.Storage.Internal -{ - /// - /// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc. - /// - /// These cannot be 'internal' anymore if we are fully modularizing things out, because - /// they are no longer a part of the same library, especially as we create things like - /// Installation inside push library. - /// - /// So this class contains a bunch of extension methods that can live inside another - /// namespace, which 'wrap' the intenral APIs that already exist. - /// - public static class AVObjectExtensions - { - public static T FromState(IObjectState state, string defaultClassName) where T : AVObject - { - return AVObject.FromState(state, defaultClassName); - } - - public static IObjectState GetState(this AVObject obj) - { - return obj.State; - } - - public static void HandleFetchResult(this AVObject obj, IObjectState serverState) - { - obj.HandleFetchResult(serverState); - } - - public static IDictionary GetCurrentOperations(this AVObject obj) - { - return obj.operationDict; - } - - public static IDictionary Encode(this AVObject obj) - { - return PointerOrLocalIdEncoder.Instance.EncodeAVObject(obj, false); - } - - public static IDictionary ServerDataToJSONObjectForSerialization(this AVObject obj) - { - return obj.ServerDataToJSONObjectForSerialization(); - } - - public static void Set(this AVObject obj, string key, object value) - { - obj.Set(key, value); - } - - public static void DisableHooks(this AVObject obj, IEnumerable hookKeys) - { - obj.Set("__ignore_hooks", hookKeys); - } - public static void DisableHook(this AVObject obj, string hookKey) - { - var newList = new List(); - if (obj.ContainsKey("__ignore_hooks")) - { - var hookKeys = obj.Get>("__ignore_hooks"); - newList = hookKeys.ToList(); - } - newList.Add(hookKey); - obj.DisableHooks(newList); - } - - public static void DisableAfterHook(this AVObject obj) - { - obj.DisableAfterSave(); - obj.DisableAfterUpdate(); - obj.DisableAfterDelete(); - } - - public static void DisableBeforeHook(this AVObject obj) - { - obj.DisableBeforeSave(); - obj.DisableBeforeDelete(); - obj.DisableBeforeUpdate(); - } - - public static void DisableBeforeSave(this AVObject obj) - { - obj.DisableHook("beforeSave"); - } - public static void DisableAfterSave(this AVObject obj) - { - obj.DisableHook("afterSave"); - } - public static void DisableBeforeUpdate(this AVObject obj) - { - obj.DisableHook("beforeUpdate"); - } - public static void DisableAfterUpdate(this AVObject obj) - { - obj.DisableHook("afterUpdate"); - } - public static void DisableBeforeDelete(this AVObject obj) - { - obj.DisableHook("beforeDelete"); - } - public static void DisableAfterDelete(this AVObject obj) - { - obj.DisableHook("afterDelete"); - } - } -} diff --git a/Storage/Storage/Internal_/Utilities/AVRelationExtensions.cs b/Storage/Storage/Internal_/Utilities/AVRelationExtensions.cs deleted file mode 100644 index c2d2c85..0000000 --- a/Storage/Storage/Internal_/Utilities/AVRelationExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - /// - /// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc. - /// - /// These cannot be 'internal' anymore if we are fully modularizing things out, because - /// they are no longer a part of the same library, especially as we create things like - /// Installation inside push library. - /// - /// So this class contains a bunch of extension methods that can live inside another - /// namespace, which 'wrap' the intenral APIs that already exist. - /// - public static class AVRelationExtensions { - public static AVRelation Create(AVObject parent, string childKey) where T : AVObject { - return new AVRelation(parent, childKey); - } - - public static AVRelation Create(AVObject parent, string childKey, string targetClassName) where T: AVObject { - return new AVRelation(parent, childKey, targetClassName); - } - - public static string GetTargetClassName(this AVRelation relation) where T : AVObject { - return relation.TargetClassName; - } - } -} diff --git a/Storage/Storage/Internal_/Utilities/FlexibleDictionaryWrapper.cs b/Storage/Storage/Internal_/Utilities/FlexibleDictionaryWrapper.cs deleted file mode 100644 index 5ee7d2c..0000000 --- a/Storage/Storage/Internal_/Utilities/FlexibleDictionaryWrapper.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using LeanCloud.Utilities; - -namespace LeanCloud.Storage.Internal { - /// - /// Provides a Dictionary implementation that can delegate to any other - /// dictionary, regardless of its value type. Used for coercion of - /// dictionaries when returning them to users. - /// - /// The resulting type of value in the dictionary. - /// The original type of value in the dictionary. - [Preserve(AllMembers = true, Conditional = false)] - public class FlexibleDictionaryWrapper : IDictionary { - private readonly IDictionary toWrap; - public FlexibleDictionaryWrapper(IDictionary toWrap) { - this.toWrap = toWrap; - } - - public void Add(string key, TOut value) { - toWrap.Add(key, (TIn)Conversion.ConvertTo(value)); - } - - public bool ContainsKey(string key) { - return toWrap.ContainsKey(key); - } - - public ICollection Keys { - get { return toWrap.Keys; } - } - - public bool Remove(string key) { - return toWrap.Remove(key); - } - - public bool TryGetValue(string key, out TOut value) { - TIn outValue; - bool result = toWrap.TryGetValue(key, out outValue); - value = (TOut)Conversion.ConvertTo(outValue); - return result; - } - - public ICollection Values { - get { - return toWrap.Values - .Select(item => (TOut)Conversion.ConvertTo(item)).ToList(); - } - } - - public TOut this[string key] { - get { - return (TOut)Conversion.ConvertTo(toWrap[key]); - } - set { - toWrap[key] = (TIn)Conversion.ConvertTo(value); - } - } - - public void Add(KeyValuePair item) { - toWrap.Add(new KeyValuePair(item.Key, - (TIn)Conversion.ConvertTo(item.Value))); - } - - public void Clear() { - toWrap.Clear(); - } - - public bool Contains(KeyValuePair item) { - return toWrap.Contains(new KeyValuePair(item.Key, - (TIn)Conversion.ConvertTo(item.Value))); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) { - var converted = from pair in toWrap - select new KeyValuePair(pair.Key, - (TOut)Conversion.ConvertTo(pair.Value)); - converted.ToList().CopyTo(array, arrayIndex); - } - - public int Count { - get { return toWrap.Count; } - } - - public bool IsReadOnly { - get { return toWrap.IsReadOnly; } - } - - public bool Remove(KeyValuePair item) { - return toWrap.Remove(new KeyValuePair(item.Key, - (TIn)Conversion.ConvertTo(item.Value))); - } - - public IEnumerator> GetEnumerator() { - foreach (var pair in toWrap) { - yield return new KeyValuePair(pair.Key, - (TOut)Conversion.ConvertTo(pair.Value)); - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - } -} diff --git a/Storage/Storage/Internal_/Utilities/FlexibleListWrapper.cs b/Storage/Storage/Internal_/Utilities/FlexibleListWrapper.cs deleted file mode 100644 index 3db78d6..0000000 --- a/Storage/Storage/Internal_/Utilities/FlexibleListWrapper.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using LeanCloud.Utilities; - -namespace LeanCloud.Storage.Internal { - /// - /// Provides a List implementation that can delegate to any other - /// list, regardless of its value type. Used for coercion of - /// lists when returning them to users. - /// - /// The resulting type of value in the list. - /// The original type of value in the list. - [Preserve(AllMembers = true, Conditional = false)] - public class FlexibleListWrapper : IList { - private IList toWrap; - public FlexibleListWrapper(IList toWrap) { - this.toWrap = toWrap; - } - - public int IndexOf(TOut item) { - return toWrap.IndexOf((TIn)Conversion.ConvertTo(item)); - } - - public void Insert(int index, TOut item) { - toWrap.Insert(index, (TIn)Conversion.ConvertTo(item)); - } - - public void RemoveAt(int index) { - toWrap.RemoveAt(index); - } - - public TOut this[int index] { - get { - return (TOut)Conversion.ConvertTo(toWrap[index]); - } - set { - toWrap[index] = (TIn)Conversion.ConvertTo(value); - } - } - - public void Add(TOut item) { - toWrap.Add((TIn)Conversion.ConvertTo(item)); - } - - public void Clear() { - toWrap.Clear(); - } - - public bool Contains(TOut item) { - return toWrap.Contains((TIn)Conversion.ConvertTo(item)); - } - - public void CopyTo(TOut[] array, int arrayIndex) { - toWrap.Select(item => (TOut)Conversion.ConvertTo(item)) - .ToList().CopyTo(array, arrayIndex); - } - - public int Count { - get { return toWrap.Count; } - } - - public bool IsReadOnly { - get { return toWrap.IsReadOnly; } - } - - public bool Remove(TOut item) { - return toWrap.Remove((TIn)Conversion.ConvertTo(item)); - } - - public IEnumerator GetEnumerator() { - foreach (var item in (IEnumerable)toWrap) { - yield return (TOut)Conversion.ConvertTo(item); - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - } -} diff --git a/Storage/Storage/Internal_/Utilities/IJsonConvertible.cs b/Storage/Storage/Internal_/Utilities/IJsonConvertible.cs deleted file mode 100644 index af29ef1..0000000 --- a/Storage/Storage/Internal_/Utilities/IJsonConvertible.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - /// - /// Represents an object that can be converted into JSON. - /// - public interface IJsonConvertible { - /// - /// Converts the object to a data structure that can be converted to JSON. - /// - /// An object to be JSONified. - IDictionary ToJSON(); - } -} diff --git a/Storage/Storage/Internal_/Utilities/IdentityEqualityComparer.cs b/Storage/Storage/Internal_/Utilities/IdentityEqualityComparer.cs deleted file mode 100644 index b13f674..0000000 --- a/Storage/Storage/Internal_/Utilities/IdentityEqualityComparer.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace LeanCloud.Storage.Internal { - /// - /// An equality comparer that uses the object identity (i.e. ReferenceEquals) - /// rather than .Equals, allowing identity to be used for checking equality in - /// ISets and IDictionaries. - /// - public class IdentityEqualityComparer : IEqualityComparer - where T : AVObject { - public bool Equals(T x, T y) { - return x.ClassName == y.ClassName && - x.ObjectId == y.ObjectId; - } - - public int GetHashCode(T obj) { - return RuntimeHelpers.GetHashCode(obj); - } - } -} diff --git a/Storage/Storage/Internal_/Utilities/InternalExtensions.cs b/Storage/Storage/Internal_/Utilities/InternalExtensions.cs deleted file mode 100644 index d7a963c..0000000 --- a/Storage/Storage/Internal_/Utilities/InternalExtensions.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.ExceptionServices; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - /// - /// Provides helper methods that allow us to use terser code elsewhere. - /// - public static class InternalExtensions { - /// - /// Ensures a task (even null) is awaitable. - /// - /// - /// - /// - public static Task Safe(this Task task) { - return task ?? Task.FromResult(default(T)); - } - - /// - /// Ensures a task (even null) is awaitable. - /// - /// - /// - public static Task Safe(this Task task) { - return task ?? Task.FromResult(null); - } - - public delegate void PartialAccessor(ref T arg); - - public static TValue GetOrDefault(this IDictionary self, - TKey key, - TValue defaultValue) { - TValue value; - if (self.TryGetValue(key, out value)) { - return value; - } - return defaultValue; - } - - public static bool CollectionsEqual(this IEnumerable a, IEnumerable b) { - return Equals(a, b) || - (a != null && b != null && - a.SequenceEqual(b)); - } - - public static Task OnSuccess(this Task task, - Func, TResult> continuation) { - return ((Task)task).OnSuccess(t => continuation((Task)t)); - } - - public static Task OnSuccess(this Task task, Action> continuation) { - return task.OnSuccess((Func, object>)(t => { - continuation(t); - return null; - })); - } - - public static Task OnSuccess(this Task task, - Func continuation) { - return task.ContinueWith(t => { - if (t.IsFaulted) { - var ex = t.Exception.Flatten(); - if (ex.InnerExceptions.Count == 1) { - ExceptionDispatchInfo.Capture(ex.InnerExceptions[0]).Throw(); - } else { - ExceptionDispatchInfo.Capture(ex).Throw(); - } - // Unreachable - return Task.FromResult(default(TResult)); - } else if (t.IsCanceled) { - var tcs = new TaskCompletionSource(); - tcs.SetCanceled(); - return tcs.Task; - } else { - return Task.FromResult(continuation(t)); - } - }).Unwrap(); - } - - public static Task OnSuccess(this Task task, Action continuation) { - return task.OnSuccess((Func)(t => { - continuation(t); - return null; - })); - } - - public static Task WhileAsync(Func> predicate, Func body) { - Func iterate = null; - iterate = () => { - return predicate().OnSuccess(t => { - if (!t.Result) { - return Task.FromResult(0); - } - return body().OnSuccess(_ => iterate()).Unwrap(); - }).Unwrap(); - }; - return iterate(); - } - } -} diff --git a/Storage/Storage/Internal_/Utilities/Json.cs b/Storage/Storage/Internal_/Utilities/Json.cs deleted file mode 100644 index 8babc7f..0000000 --- a/Storage/Storage/Internal_/Utilities/Json.cs +++ /dev/null @@ -1,554 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal -{ - /// - /// A simple recursive-descent JSON Parser based on the grammar defined at http://www.json.org - /// and http://tools.ietf.org/html/rfc4627 - /// - public class Json - { - /// - /// Place at the start of a regex to force the match to begin wherever the search starts (i.e. - /// anchored at the index of the first character of the search, even when that search starts - /// in the middle of the string). - /// - private static readonly string startOfString = "\\G"; - private static readonly char startObject = '{'; - private static readonly char endObject = '}'; - private static readonly char startArray = '['; - private static readonly char endArray = ']'; - private static readonly char valueSeparator = ','; - private static readonly char nameSeparator = ':'; - private static readonly char[] falseValue = "false".ToCharArray(); - private static readonly char[] trueValue = "true".ToCharArray(); - private static readonly char[] nullValue = "null".ToCharArray(); - private static readonly Regex numberValue = new Regex(startOfString + - @"-?(?:0|[1-9]\d*)(?\.\d+)?(?(?:e|E)(?:-|\+)?\d+)?"); - private static readonly Regex stringValue = new Regex(startOfString + - "\"(?(?:[^\\\\\"]|(?\\\\(?:[\\\\\"/bfnrt]|u[0-9a-fA-F]{4})))*)\"", - RegexOptions.Multiline); - - private static readonly Regex escapePattern = new Regex("\\\\|\"|[\u0000-\u001F]"); - - private class JsonStringParser - { - public string Input { get; private set; } - - public char[] InputAsArray { get; private set; } - - private int currentIndex; - public int CurrentIndex - { - get - { - return currentIndex; - } - } - - public void Skip(int skip) - { - currentIndex += skip; - } - - public JsonStringParser(string input) - { - Input = input; - InputAsArray = input.ToCharArray(); - } - - /// - /// Parses JSON object syntax (e.g. '{}') - /// - internal bool AVObject(out object output) - { - output = null; - int initialCurrentIndex = CurrentIndex; - if (!Accept(startObject)) - { - return false; - } - var dict = new Dictionary(); - while (true) - { - object pairValue; - if (!ParseMember(out pairValue)) - { - break; - } - var pair = pairValue as Tuple; - dict[pair.Item1] = pair.Item2; - if (!Accept(valueSeparator)) - { - break; - } - } - if (!Accept(endObject)) - { - return false; - } - output = dict; - return true; - } - - /// - /// Parses JSON member syntax (e.g. '"keyname" : null') - /// - private bool ParseMember(out object output) - { - output = null; - object key; - if (!ParseString(out key)) - { - return false; - } - if (!Accept(nameSeparator)) - { - return false; - } - object value; - if (!ParseValue(out value)) - { - return false; - } - output = new Tuple((string)key, value); - return true; - } - - /// - /// Parses JSON array syntax (e.g. '[]') - /// - internal bool ParseArray(out object output) - { - output = null; - if (!Accept(startArray)) - { - return false; - } - var list = new List(); - while (true) - { - object value; - if (!ParseValue(out value)) - { - break; - } - list.Add(value); - if (!Accept(valueSeparator)) - { - break; - } - } - if (!Accept(endArray)) - { - return false; - } - output = list; - return true; - } - - /// - /// Parses a value (i.e. the right-hand side of an object member assignment or - /// an element in an array) - /// - private bool ParseValue(out object output) - { - if (Accept(falseValue)) - { - output = false; - return true; - } - else if (Accept(nullValue)) - { - output = null; - return true; - } - else if (Accept(trueValue)) - { - output = true; - return true; - } - return AVObject(out output) || - ParseArray(out output) || - ParseNumber(out output) || - ParseString(out output); - } - - /// - /// Parses a JSON string (e.g. '"foo\u1234bar\n"') - /// - private bool ParseString(out object output) - { - output = null; - Match m; - if (!Accept(stringValue, out m)) - { - return false; - } - // handle escapes: - int offset = 0; - var contentCapture = m.Groups["content"]; - var builder = new StringBuilder(contentCapture.Value); - foreach (Capture escape in m.Groups["escape"].Captures) - { - int index = (escape.Index - contentCapture.Index) - offset; - offset += escape.Length - 1; - builder.Remove(index + 1, escape.Length - 1); - switch (escape.Value[1]) - { - case '\"': - builder[index] = '\"'; - break; - case '\\': - builder[index] = '\\'; - break; - case '/': - builder[index] = '/'; - break; - case 'b': - builder[index] = '\b'; - break; - case 'f': - builder[index] = '\f'; - break; - case 'n': - builder[index] = '\n'; - break; - case 'r': - builder[index] = '\r'; - break; - case 't': - builder[index] = '\t'; - break; - case 'u': - builder[index] = (char)ushort.Parse(escape.Value.Substring(2), NumberStyles.AllowHexSpecifier); - break; - default: - throw new ArgumentException("Unexpected escape character in string: " + escape.Value); - } - } - output = builder.ToString(); - return true; - } - - /// - /// Parses a number. Returns a long if the number is an integer or has an exponent, - /// otherwise returns a double. - /// - private bool ParseNumber(out object output) - { - output = null; - Match m; - if (!Accept(numberValue, out m)) - { - return false; - } - if (m.Groups["frac"].Length > 0 || m.Groups["exp"].Length > 0) - { - // It's a double. - output = double.Parse(m.Value, CultureInfo.InvariantCulture); - return true; - } - else - { - int temp = 0; - if (int.TryParse(m.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out temp)) - { - output = temp; - return true; - } - output = long.Parse(m.Value, CultureInfo.InvariantCulture); - return true; - } - } - - /// - /// Matches the string to a regex, consuming part of the string and returning the match. - /// - private bool Accept(Regex matcher, out Match match) - { - match = matcher.Match(Input, CurrentIndex); - if (match.Success) - { - Skip(match.Length); - } - return match.Success; - } - - /// - /// Find the first occurrences of a character, consuming part of the string. - /// - private bool Accept(char condition) - { - int step = 0; - int strLen = InputAsArray.Length; - int currentStep = currentIndex; - char currentChar; - - // Remove whitespace - while (currentStep < strLen && - ((currentChar = InputAsArray[currentStep]) == ' ' || - currentChar == '\r' || - currentChar == '\t' || - currentChar == '\n')) - { - ++step; - ++currentStep; - } - - bool match = (currentStep < strLen) && (InputAsArray[currentStep] == condition); - if (match) - { - ++step; - ++currentStep; - - // Remove whitespace - while (currentStep < strLen && - ((currentChar = InputAsArray[currentStep]) == ' ' || - currentChar == '\r' || - currentChar == '\t' || - currentChar == '\n')) - { - ++step; - ++currentStep; - } - - Skip(step); - } - return match; - } - - /// - /// Find the first occurrences of a string, consuming part of the string. - /// - private bool Accept(char[] condition) - { - int step = 0; - int strLen = InputAsArray.Length; - int currentStep = currentIndex; - char currentChar; - - // Remove whitespace - while (currentStep < strLen && - ((currentChar = InputAsArray[currentStep]) == ' ' || - currentChar == '\r' || - currentChar == '\t' || - currentChar == '\n')) - { - ++step; - ++currentStep; - } - - bool strMatch = true; - for (int i = 0; currentStep < strLen && i < condition.Length; ++i, ++currentStep) - { - if (InputAsArray[currentStep] != condition[i]) - { - strMatch = false; - break; - } - } - - bool match = (currentStep < strLen) && strMatch; - if (match) - { - Skip(step + condition.Length); - } - return match; - } - } - - /// - /// Parses a JSON-text as defined in http://tools.ietf.org/html/rfc4627, returning an - /// IDictionary<string, object> or an IList<object> depending on whether - /// the value was an array or dictionary. Nested objects also match these types. - /// - public static object Parse(string input) - { - object output; - input = input.Trim(); - JsonStringParser parser = new JsonStringParser(input); - - if ((parser.AVObject(out output) || - parser.ParseArray(out output)) && - parser.CurrentIndex == input.Length) - { - return output; - } - throw new ArgumentException("Input JSON was invalid."); - } - - /// - /// Encodes a dictionary into a JSON string. Supports values that are - /// IDictionary<string, object>, IList<object>, strings, - /// nulls, and any of the primitive types. - /// - public static string Encode(IDictionary dict) - { - if (dict == null) - { - throw new ArgumentNullException(); - } - if (dict.Count == 0) - { - return "{}"; - } - var builder = new StringBuilder("{"); - foreach (var pair in dict) - { - builder.Append(Encode(pair.Key)); - builder.Append(":"); - builder.Append(Encode(pair.Value)); - builder.Append(","); - } - builder[builder.Length - 1] = '}'; - return builder.ToString(); - } - - /// - /// Encodes a list into a JSON string. Supports values that are - /// IDictionary<string, object>, IList<object>, strings, - /// nulls, and any of the primitive types. - /// - public static string Encode(IList list) - { - if (list == null) - { - throw new ArgumentNullException(); - } - if (list.Count == 0) - { - return "[]"; - } - var builder = new StringBuilder("["); - foreach (var item in list) - { - builder.Append(Encode(item)); - builder.Append(","); - } - builder[builder.Length - 1] = ']'; - return builder.ToString(); - } - - public static string Encode(IList strList) - { - if (strList == null) - { - throw new ArgumentNullException(); - } - if (strList.Count == 0) - { - return "[]"; - } - StringBuilder stringBuilder = new StringBuilder("["); - foreach (object obj in strList) - { - stringBuilder.Append(Json.Encode(obj)); - stringBuilder.Append(","); - } - stringBuilder[stringBuilder.Length - 1] = ']'; - return stringBuilder.ToString(); - } - - public static string Encode(IList> dicList) - { - if (dicList == null) - { - throw new ArgumentNullException(); - } - if (dicList.Count == 0) - { - return "[]"; - } - StringBuilder stringBuilder = new StringBuilder("["); - foreach (object obj in dicList) - { - stringBuilder.Append(Json.Encode(obj)); - stringBuilder.Append(","); - } - stringBuilder[stringBuilder.Length - 1] = ']'; - return stringBuilder.ToString(); - } - - /// - /// Encodes an object into a JSON string. - /// - public static string Encode(object obj) - { - var dict = obj as IDictionary; - if (dict != null) - { - return Encode(dict); - } - var list = obj as IList; - if (list != null) - { - return Encode(list); - } - var dicList = obj as IList>; - if (dicList != null) - { - return Encode(dicList); - } - var strLists = obj as IList; - if (strLists != null) - { - return Encode(strLists); - } - var str = obj as string; - if (str != null) - { - str = escapePattern.Replace(str, m => - { - switch (m.Value[0]) - { - case '\\': - return "\\\\"; - case '\"': - return "\\\""; - case '\b': - return "\\b"; - case '\f': - return "\\f"; - case '\n': - return "\\n"; - case '\r': - return "\\r"; - case '\t': - return "\\t"; - default: - return "\\u" + ((ushort)m.Value[0]).ToString("x4"); - } - }); - return "\"" + str + "\""; - } - if (obj == null) - { - return "null"; - } - if (obj is bool) - { - if ((bool)obj) - { - return "true"; - } - else - { - return "false"; - } - } - if (!obj.GetType().GetTypeInfo().IsPrimitive) - { - throw new ArgumentException("Unable to encode objects of type " + obj.GetType()); - } - return Convert.ToString(obj, CultureInfo.InvariantCulture); - } - } -} diff --git a/Storage/Storage/Internal_/Utilities/LockSet.cs b/Storage/Storage/Internal_/Utilities/LockSet.cs deleted file mode 100644 index 65e188c..0000000 --- a/Storage/Storage/Internal_/Utilities/LockSet.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - public class LockSet { - private static readonly ConditionalWeakTable stableIds = - new ConditionalWeakTable(); - private static long nextStableId = 0; - - private readonly IEnumerable mutexes; - - public LockSet(IEnumerable mutexes) { - this.mutexes = (from mutex in mutexes - orderby GetStableId(mutex) - select mutex).ToList(); - } - - public void Enter() { - foreach (var mutex in mutexes) { - Monitor.Enter(mutex); - } - } - - public void Exit() { - foreach (var mutex in mutexes) { - Monitor.Exit(mutex); - } - } - - private static IComparable GetStableId(object mutex) { - lock (stableIds) { - return stableIds.GetValue(mutex, k => nextStableId++); - } - } - } -} diff --git a/Storage/Storage/Internal_/Utilities/ReflectionHelpers.cs b/Storage/Storage/Internal_/Utilities/ReflectionHelpers.cs deleted file mode 100644 index 55c4833..0000000 --- a/Storage/Storage/Internal_/Utilities/ReflectionHelpers.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using System.Reflection; -using System.Collections.Generic; -using System.Linq; - -namespace LeanCloud.Storage.Internal -{ - public static class ReflectionHelpers - { - public static IEnumerable GetProperties(Type type) - { -#if MONO || UNITY - return type.GetProperties(); -#else - return type.GetRuntimeProperties(); -#endif - } - - public static MethodInfo GetMethod(Type type, string name, Type[] parameters) - { -#if MONO || UNITY - return type.GetMethod(name, parameters); -#else - return type.GetRuntimeMethod(name, parameters); -#endif - } - - public static bool IsPrimitive(Type type) - { -#if MONO || UNITY - return type.IsPrimitive; -#else - return type.GetTypeInfo().IsPrimitive; -#endif - } - - public static IEnumerable GetInterfaces(Type type) - { -#if MONO || UNITY - return type.GetInterfaces(); -#else - return type.GetTypeInfo().ImplementedInterfaces; -#endif - } - - public static bool IsConstructedGenericType(Type type) - { -#if UNITY - return type.IsGenericType && !type.IsGenericTypeDefinition; -#else - return type.IsConstructedGenericType; -#endif - } - - public static IEnumerable GetConstructors(Type type) - { -#if UNITY - BindingFlags searchFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - return type.GetConstructors(searchFlags); -#else - return type.GetTypeInfo().DeclaredConstructors - .Where(c => (c.Attributes & MethodAttributes.Static) == 0); -#endif - } - - public static Type[] GetGenericTypeArguments(Type type) - { -#if UNITY - return type.GetGenericArguments(); -#else - return type.GenericTypeArguments; -#endif - } - - public static PropertyInfo GetProperty(Type type, string name) - { -#if MONO || UNITY - return type.GetProperty(name); -#else - return type.GetRuntimeProperty(name); -#endif - } - - /// - /// This method helps simplify the process of getting a constructor for a type. - /// A method like this exists in .NET but is not allowed in a Portable Class Library, - /// so we've built our own. - /// - /// - /// - /// - public static ConstructorInfo FindConstructor(this Type self, params Type[] parameterTypes) - { - var constructors = - from constructor in GetConstructors(self) - let parameters = constructor.GetParameters() - let types = from p in parameters select p.ParameterType - where types.SequenceEqual(parameterTypes) - select constructor; - return constructors.SingleOrDefault(); - } - - public static bool IsNullable(Type t) - { - bool isGeneric; -#if UNITY - isGeneric = t.IsGenericType && !t.IsGenericTypeDefinition; -#else - isGeneric = t.IsConstructedGenericType; -#endif - return isGeneric && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)); - } - - public static IEnumerable GetCustomAttributes(this Assembly assembly) where T : Attribute - { -#if UNITY - return assembly.GetCustomAttributes(typeof(T), false).Select(attr => attr as T); -#else - return CustomAttributeExtensions.GetCustomAttributes(assembly); -#endif - } - } -} diff --git a/Storage/Storage/Internal_/Utilities/SynchronizedEventHandler.cs b/Storage/Storage/Internal_/Utilities/SynchronizedEventHandler.cs deleted file mode 100644 index d94f12a..0000000 --- a/Storage/Storage/Internal_/Utilities/SynchronizedEventHandler.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - /// - /// Represents an event handler that calls back from the synchronization context - /// that subscribed. - /// Should look like an EventArgs, but may not inherit EventArgs if T is implemented by the Windows team. - /// - public class SynchronizedEventHandler { - private LinkedList> delegates = - new LinkedList>(); - public void Add(Delegate del) { - lock (delegates) { - TaskFactory factory; - if (SynchronizationContext.Current != null) { - factory = - new TaskFactory(CancellationToken.None, - TaskCreationOptions.None, - TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.FromCurrentSynchronizationContext()); - } else { - factory = Task.Factory; - } - foreach (var d in del.GetInvocationList()) { - delegates.AddLast(new Tuple(d, factory)); - } - } - } - - public void Remove(Delegate del) { - lock (delegates) { - if (delegates.Count == 0) { - return; - } - foreach (var d in del.GetInvocationList()) { - var node = delegates.First; - while (node != null) { - if (node.Value.Item1 == d) { - delegates.Remove(node); - break; - } - node = node.Next; - } - } - } - } - - public Task Invoke(object sender, T args) { - IEnumerable> toInvoke; - var toContinue = new[] { Task.FromResult(0) }; - lock (delegates) { - toInvoke = delegates.ToList(); - } - var invocations = toInvoke - .Select(p => p.Item2.ContinueWhenAll(toContinue, - _ => p.Item1.DynamicInvoke(sender, args))) - .ToList(); - return Task.WhenAll(invocations); - } - } -} diff --git a/Storage/Storage/Internal_/Utilities/TaskQueue.cs b/Storage/Storage/Internal_/Utilities/TaskQueue.cs deleted file mode 100644 index 4f1ee25..0000000 --- a/Storage/Storage/Internal_/Utilities/TaskQueue.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - /// - /// A helper class for enqueuing tasks - /// - public class TaskQueue { - /// - /// We only need to keep the tail of the queue. Cancelled tasks will - /// just complete normally/immediately when their turn arrives. - /// - private Task tail; - private readonly object mutex = new object(); - - /// - /// Gets a cancellable task that can be safely awaited and is dependent - /// on the current tail of the queue. This essentially gives us a proxy - /// for the tail end of the queue whose awaiting can be cancelled. - /// - /// A cancellation token that cancels - /// the task even if the task is still in the queue. This allows the - /// running task to return immediately without breaking the dependency - /// chain. It also ensures that errors do not propagate. - /// A new task that should be awaited by enqueued tasks. - private Task GetTaskToAwait(CancellationToken cancellationToken) { - lock (mutex) { - Task toAwait = tail ?? Task.FromResult(true); - return toAwait.ContinueWith(task => { }, cancellationToken); - } - } - - /// - /// Enqueues a task created by . If the task is - /// cancellable (or should be able to be cancelled while it is waiting in the - /// queue), pass a cancellationToken. - /// - /// The type of task. - /// A function given a task to await once state is - /// snapshotted (e.g. after capturing session tokens at the time of the save call). - /// Awaiting this task will wait for the created task's turn in the queue. - /// A cancellation token that can be used to - /// cancel waiting in the queue. - /// The task created by the taskStart function. - public T Enqueue(Func taskStart, CancellationToken cancellationToken) - where T : Task { - Task oldTail; - T task; - lock (mutex) { - oldTail = this.tail ?? Task.FromResult(true); - // The task created by taskStart is responsible for waiting the - // task passed to it before doing its work (this gives it an opportunity - // to do startup work or save state before waiting for its turn in the queue - task = taskStart(GetTaskToAwait(cancellationToken)); - - // The tail task should be dependent on the old tail as well as the newly-created - // task. This prevents cancellation of the new task from causing the queue to run - // out of order. - this.tail = Task.WhenAll(oldTail, task); - } - return task; - } - - public object Mutex { get { return mutex; } } - } -} diff --git a/Storage/Storage/Internal_/Utilities/XamarinAttributes.cs b/Storage/Storage/Internal_/Utilities/XamarinAttributes.cs deleted file mode 100644 index b4ee1c1..0000000 --- a/Storage/Storage/Internal_/Utilities/XamarinAttributes.cs +++ /dev/null @@ -1,426 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; - -namespace LeanCloud.Storage.Internal { - /// - /// A reimplementation of Xamarin's PreserveAttribute. - /// This allows us to support AOT and linking for Xamarin platforms. - /// - [AttributeUsage(AttributeTargets.All)] - internal class PreserveAttribute : Attribute { - public bool AllMembers; - public bool Conditional; - } - - [AttributeUsage(AttributeTargets.All)] - internal class LinkerSafeAttribute : Attribute { - public LinkerSafeAttribute() { } - } - - [Preserve(AllMembers = true)] - internal class PreserveWrapperTypes { - /// - /// Exists to ensure that generic types are AOT-compiled for the conversions we support. - /// Any new value types that we add support for will need to be registered here. - /// The method itself is never called, but by virtue of the Preserve attribute being set - /// on the class, these types will be AOT-compiled. - /// - /// This also applies to Unity. - /// - private static List CreateWrapperTypes() { - return new List { - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - - (Action)(() => AVCloud.CallFunctionAsync(null, null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null ,CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null,CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync(null, null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync>(null, null, CancellationToken.None)), - (Action)(() => AVCloud.CallFunctionAsync>(null, null, CancellationToken.None)), - - typeof(FlexibleListWrapper), - typeof(FlexibleListWrapper), - typeof(FlexibleDictionaryWrapper), - typeof(FlexibleDictionaryWrapper), - }; - } - } -} diff --git a/Storage/Storage/Public/AVACL.cs b/Storage/Storage/Public/AVACL.cs deleted file mode 100644 index 3d6d7f0..0000000 --- a/Storage/Storage/Public/AVACL.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using LeanCloud.Storage.Internal; - -namespace LeanCloud { - /// - /// A AVACL is used to control which users and roles can access or modify a particular object. Each - /// can have its own AVACL. You can grant read and write permissions - /// separately to specific users, to groups of users that belong to roles, or you can grant permissions - /// to "the public" so that, for example, any user could read a particular object but only a particular - /// set of users could write to that object. - /// - public class AVACL : IJsonConvertible { - private enum AccessKind { - Read, - Write - } - private const string publicName = "*"; - private readonly ICollection readers = new HashSet(); - private readonly ICollection writers = new HashSet(); - - internal AVACL(IDictionary jsonObject) { - readers = new HashSet(from pair in jsonObject - where ((IDictionary)pair.Value).ContainsKey("read") - select pair.Key); - writers = new HashSet(from pair in jsonObject - where ((IDictionary)pair.Value).ContainsKey("write") - select pair.Key); - } - - /// - /// Creates an ACL with no permissions granted. - /// - public AVACL() { - } - - /// - /// Creates an ACL where only the provided user has access. - /// - /// The only user that can read or write objects governed by this ACL. - public AVACL(AVUser owner) { - SetReadAccess(owner, true); - SetWriteAccess(owner, true); - } - - IDictionary IJsonConvertible.ToJSON() { - var result = new Dictionary(); - foreach (var user in readers.Union(writers)) { - var userPermissions = new Dictionary(); - if (readers.Contains(user)) { - userPermissions["read"] = true; - } - if (writers.Contains(user)) { - userPermissions["write"] = true; - } - result[user] = userPermissions; - } - return result; - } - - private void SetAccess(AccessKind kind, string userId, bool allowed) { - if (userId == null) { - throw new ArgumentException("Cannot set access for an unsaved user or role."); - } - ICollection target = null; - switch (kind) { - case AccessKind.Read: - target = readers; - break; - case AccessKind.Write: - target = writers; - break; - default: - throw new NotImplementedException("Unknown AccessKind"); - } - if (allowed) { - target.Add(userId); - } else { - target.Remove(userId); - } - } - - private bool GetAccess(AccessKind kind, string userId) { - if (userId == null) { - throw new ArgumentException("Cannot get access for an unsaved user or role."); - } - switch (kind) { - case AccessKind.Read: - return readers.Contains(userId); - case AccessKind.Write: - return writers.Contains(userId); - default: - throw new NotImplementedException("Unknown AccessKind"); - } - } - - /// - /// Gets or sets whether the public is allowed to read this object. - /// - public bool PublicReadAccess { - get { - return GetAccess(AccessKind.Read, publicName); - } - set { - SetAccess(AccessKind.Read, publicName, value); - } - } - - /// - /// Gets or sets whether the public is allowed to write this object. - /// - public bool PublicWriteAccess { - get { - return GetAccess(AccessKind.Write, publicName); - } - set { - SetAccess(AccessKind.Write, publicName, value); - } - } - - /// - /// Sets whether the given user id is allowed to read this object. - /// - /// The objectId of the user. - /// Whether the user has permission. - public void SetReadAccess(string userId, bool allowed) { - SetAccess(AccessKind.Read, userId, allowed); - } - - /// - /// Sets whether the given user is allowed to read this object. - /// - /// The user. - /// Whether the user has permission. - public void SetReadAccess(AVUser user, bool allowed) { - SetReadAccess(user.ObjectId, allowed); - } - - /// - /// Sets whether the given user id is allowed to write this object. - /// - /// The objectId of the user. - /// Whether the user has permission. - public void SetWriteAccess(string userId, bool allowed) { - SetAccess(AccessKind.Write, userId, allowed); - } - - /// - /// Sets whether the given user is allowed to write this object. - /// - /// The user. - /// Whether the user has permission. - public void SetWriteAccess(AVUser user, bool allowed) { - SetWriteAccess(user.ObjectId, allowed); - } - - /// - /// Gets whether the given user id is *explicitly* allowed to read this object. - /// Even if this returns false, the user may still be able to read it if - /// PublicReadAccess is true or a role that the user belongs to has read access. - /// - /// The user objectId to check. - /// Whether the user has access. - public bool GetReadAccess(string userId) { - return GetAccess(AccessKind.Read, userId); - } - - /// - /// Gets whether the given user is *explicitly* allowed to read this object. - /// Even if this returns false, the user may still be able to read it if - /// PublicReadAccess is true or a role that the user belongs to has read access. - /// - /// The user to check. - /// Whether the user has access. - public bool GetReadAccess(AVUser user) { - return GetReadAccess(user.ObjectId); - } - - /// - /// Gets whether the given user id is *explicitly* allowed to write this object. - /// Even if this returns false, the user may still be able to write it if - /// PublicReadAccess is true or a role that the user belongs to has write access. - /// - /// The user objectId to check. - /// Whether the user has access. - public bool GetWriteAccess(string userId) { - return GetAccess(AccessKind.Write, userId); - } - - /// - /// Gets whether the given user is *explicitly* allowed to write this object. - /// Even if this returns false, the user may still be able to write it if - /// PublicReadAccess is true or a role that the user belongs to has write access. - /// - /// The user to check. - /// Whether the user has access. - public bool GetWriteAccess(AVUser user) { - return GetWriteAccess(user.ObjectId); - } - - /// - /// Sets whether users belonging to the role with the given - /// are allowed to read this object. - /// - /// The name of the role. - /// Whether the role has access. - public void SetRoleReadAccess(string roleName, bool allowed) { - SetAccess(AccessKind.Read, "role:" + roleName, allowed); - } - - /// - /// Sets whether users belonging to the given role are allowed to read this object. - /// - /// The role. - /// Whether the role has access. - public void SetRoleReadAccess(AVRole role, bool allowed) { - SetRoleReadAccess(role.Name, allowed); - } - - /// - /// Gets whether users belonging to the role with the given - /// are allowed to read this object. Even if this returns false, the role may still be - /// able to read it if a parent role has read access. - /// - /// The name of the role. - /// Whether the role has access. - public bool GetRoleReadAccess(string roleName) { - return GetAccess(AccessKind.Read, "role:" + roleName); - } - - /// - /// Gets whether users belonging to the role are allowed to read this object. - /// Even if this returns false, the role may still be able to read it if a - /// parent role has read access. - /// - /// The name of the role. - /// Whether the role has access. - public bool GetRoleReadAccess(AVRole role) { - return GetRoleReadAccess(role.Name); - } - - /// - /// Sets whether users belonging to the role with the given - /// are allowed to write this object. - /// - /// The name of the role. - /// Whether the role has access. - public void SetRoleWriteAccess(string roleName, bool allowed) { - SetAccess(AccessKind.Write, "role:" + roleName, allowed); - } - - /// - /// Sets whether users belonging to the given role are allowed to write this object. - /// - /// The role. - /// Whether the role has access. - public void SetRoleWriteAccess(AVRole role, bool allowed) { - SetRoleWriteAccess(role.Name, allowed); - } - - /// - /// Gets whether users belonging to the role with the given - /// are allowed to write this object. Even if this returns false, the role may still be - /// able to write it if a parent role has write access. - /// - /// The name of the role. - /// Whether the role has access. - public bool GetRoleWriteAccess(string roleName) { - return GetAccess(AccessKind.Write, "role:" + roleName); - } - - /// - /// Gets whether users belonging to the role are allowed to write this object. - /// Even if this returns false, the role may still be able to write it if a - /// parent role has write access. - /// - /// The name of the role. - /// Whether the role has access. - public bool GetRoleWriteAccess(AVRole role) { - return GetRoleWriteAccess(role.Name); - } - } -} diff --git a/Storage/Storage/Public/AVClassNameAttribute.cs b/Storage/Storage/Public/AVClassNameAttribute.cs deleted file mode 100644 index aa82d0b..0000000 --- a/Storage/Storage/Public/AVClassNameAttribute.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LeanCloud -{ - /// - /// Defines the class name for a subclass of AVObject. - /// - [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] - public sealed class AVClassNameAttribute : Attribute - { - /// - /// Constructs a new AVClassName attribute. - /// - /// The class name to associate with the AVObject subclass. - public AVClassNameAttribute(string className) - { - this.ClassName = className; - } - - /// - /// Gets the class name to associate with the AVObject subclass. - /// - public string ClassName { get; private set; } - } -} diff --git a/Storage/Storage/Public/AVClient.cs b/Storage/Storage/Public/AVClient.cs deleted file mode 100644 index f08ff2a..0000000 --- a/Storage/Storage/Public/AVClient.cs +++ /dev/null @@ -1,198 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json; - -namespace LeanCloud { - /// - /// LeanCloud SDK 客户端类 - /// - public static class AVClient { - public static readonly string[] DateFormatStrings = { - // Official ISO format - "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'", - // It's possible that the string converter server-side may trim trailing zeroes, - // so these two formats cover ourselves from that. - "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'ff'Z'", - "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'f'Z'", - }; - - /// - /// LeanCloud SDK 配置 - /// - public class Configuration { - /// - /// App Id - /// - public string ApplicationId { get; set; } - - /// - /// App Key - /// - public string ApplicationKey { get; set; } - - /// - /// Master Key - /// - public string MasterKey { get; set; } - - /// - /// Gets or sets additional HTTP headers to be sent with network requests from the SDK. - /// - public IDictionary AdditionalHTTPHeaders { get; set; } - - /// - /// 存储服务器地址 - /// - /// The API server. - public string ApiServer { get; set; } - - /// - /// 云引擎服务器地址 - /// - /// The engine server uri. - public string EngineServer { get; set; } - - /// - /// 即时通信服务器地址 - /// - /// The RTMR outer. - public string RTMServer { get; set; } - - /// - /// 直连即时通信服务器地址 - /// - /// The realtime server. - public string RealtimeServer { get; set; } - - public Uri PushServer { get; set; } - - public Uri StatsServer { get; set; } - } - - private static readonly object mutex = new object(); - - static AVClient() { - } - - /// - /// LeanCloud SDK 当前配置 - /// - public static Configuration CurrentConfiguration { get; internal set; } - - internal static string APIVersion { - get { - return "1.1"; - } - } - - internal static string Name { - get { - return "LeanCloud-CSharp-SDK"; - } - } - - internal static string Version { - get { - return "0.1.0"; - } - } - - /// - /// 初始化 LeanCloud SDK - /// - /// App Id - /// App Key - public static void Initialize(string applicationId, string applicationKey) { - Initialize(new Configuration { - ApplicationId = applicationId, - ApplicationKey = applicationKey - }); - } - - public static void Initialize(string applicationId, string applicationKey, string serverUrl) { - Initialize(new Configuration { - ApplicationId = applicationId, - ApplicationKey = applicationKey, - ApiServer = serverUrl, - EngineServer = serverUrl, - RTMServer = serverUrl - }); - } - - internal static Action LogTracker { get; private set; } - - /// - /// 启动日志打印 - /// - /// - public static void HttpLog(Action trace) { - LogTracker = trace; - } - /// - /// 打印 HTTP 访问日志 - /// - /// - public static void PrintLog(string log) { - LogTracker?.Invoke(log); - } - - /// - /// 是否使用生产环境 - /// - public static bool UseProduction { - get; set; - } - - /// - /// 是否使用 MasterKey - /// - public static bool UseMasterKey { - get; set; - } - - /// - /// 初始化 LeanCloud - /// - /// 初始化配置 - public static void Initialize(Configuration configuration) { - CurrentConfiguration = configuration; - - AVObject.RegisterSubclass(); - AVObject.RegisterSubclass(); - AVObject.RegisterSubclass(); - } - - internal static void Clear() { - AVPlugins.Instance.AppRouterController.Clear(); - AVPlugins.Instance.Reset(); - } - - public static string BuildQueryString(IDictionary parameters) { - return string.Join("&", (from pair in parameters - let valueString = pair.Value as string - select string.Format("{0}={1}", - Uri.EscapeDataString(pair.Key), - Uri.EscapeDataString(string.IsNullOrEmpty(valueString) ? - JsonConvert.SerializeObject(pair.Value) : valueString))) - .ToArray()); - } - - internal static IDictionary DecodeQueryString(string queryString) { - var dict = new Dictionary(); - foreach (var pair in queryString.Split('&')) { - var parts = pair.Split(new char[] { '=' }, 2); - dict[parts[0]] = parts.Length == 2 ? Uri.UnescapeDataString(parts[1].Replace("+", " ")) : null; - } - return dict; - } - } -} diff --git a/Storage/Storage/Public/AVCloud.cs b/Storage/Storage/Public/AVCloud.cs deleted file mode 100644 index 6c6cf3a..0000000 --- a/Storage/Storage/Public/AVCloud.cs +++ /dev/null @@ -1,311 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using LeanCloud.Storage.Internal; -using System.Net.Http; - -namespace LeanCloud { - /// - /// AVCloud,提供与 LeanCloud 云函数交互的接口 - /// - public static class AVCloud { - internal static AVCloudCodeController CloudCodeController { - get { - return AVPlugins.Instance.CloudCodeController; - } - } - - /// - /// Calls a cloud function. - /// - /// The type of data you will receive from the cloud function. This - /// can be an IDictionary, string, IList, AVObject, or any other type supported by - /// AVObject. - /// The cloud function to call. - /// The parameters to send to the cloud function. This - /// dictionary can contain anything that could be passed into a AVObject except for - /// AVObjects themselves. - /// The cancellation token. - /// The result of the cloud call. - public static Task CallFunctionAsync(string name, IDictionary parameters = null, CancellationToken cancellationToken = default) { - return CloudCodeController.CallFunctionAsync(name, parameters, cancellationToken); - } - - /// - /// 远程调用云函数,返回结果会反序列化为 . - /// - /// - /// - /// - /// - /// - public static Task RPCFunctionAsync(string name, IDictionary parameters = null, CancellationToken cancellationToken = default) { - return CloudCodeController.RPCFunction(name, parameters, cancellationToken); - } - - /// - /// 请求发送验证码。 - /// - /// 是否发送成功。 - /// 手机号。 - /// 应用名称。 - /// 进行的操作名称。 - /// 验证码失效时间。 - /// Cancellation token。 - public static Task RequestSMSCodeAsync(string mobilePhoneNumber, string name, string op, int ttl = 10, CancellationToken cancellationToken = default) { - if (string.IsNullOrEmpty(mobilePhoneNumber)) { - throw new AVException(AVException.ErrorCode.MobilePhoneInvalid, "Moblie Phone number is invalid.", null); - } - - Dictionary strs = new Dictionary() - { - { "mobilePhoneNumber", mobilePhoneNumber }, - }; - if (!string.IsNullOrEmpty(name)) { - strs.Add("name", name); - } - if (!string.IsNullOrEmpty(op)) { - strs.Add("op", op); - } - if (ttl > 0) { - strs.Add("TTL", ttl); - } - var command = new EngineCommand { - Path = "requestSmsCode", - Method = HttpMethod.Post, - Content = strs - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - } - - /// - /// 请求发送验证码。 - /// - /// 是否发送成功。 - /// 手机号。 - public static Task RequestSMSCodeAsync(string mobilePhoneNumber) { - return RequestSMSCodeAsync(mobilePhoneNumber, CancellationToken.None); - } - - - /// - /// 请求发送验证码。 - /// - /// 是否发送成功。 - /// 手机号。 - /// Cancellation Token. - public static Task RequestSMSCodeAsync(string mobilePhoneNumber, CancellationToken cancellationToken) { - return RequestSMSCodeAsync(mobilePhoneNumber, null, null, 0, cancellationToken); - } - - /// - /// 发送手机短信,并指定模板以及传入模板所需的参数。 - /// - /// - /// - /// - /// - /// - /// - public static Task RequestSMSCodeAsync( - string mobilePhoneNumber, - string template, - IDictionary env, - string sign = "", - string validateToken = "") { - - if (string.IsNullOrEmpty(mobilePhoneNumber)) { - throw new AVException(AVException.ErrorCode.MobilePhoneInvalid, "Moblie Phone number is invalid.", null); - } - Dictionary strs = new Dictionary() - { - { "mobilePhoneNumber", mobilePhoneNumber }, - }; - strs.Add("template", template); - if (string.IsNullOrEmpty(sign)) { - strs.Add("sign", sign); - } - if (string.IsNullOrEmpty(validateToken)) { - strs.Add("validate_token", validateToken); - } - foreach (var key in env.Keys) { - strs.Add(key, env[key]); - } - var command = new EngineCommand { - Path = "requestSmsCode", - Method = HttpMethod.Post, - Content = strs - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - /// - /// - /// - /// - /// - public static Task RequestVoiceCodeAsync(string mobilePhoneNumber) { - if (string.IsNullOrEmpty(mobilePhoneNumber)) { - throw new AVException(AVException.ErrorCode.MobilePhoneInvalid, "Moblie Phone number is invalid.", null); - } - Dictionary body = new Dictionary() - { - { "mobilePhoneNumber", mobilePhoneNumber }, - { "smsType", "voice" }, - { "IDD", "+86" } - }; - - var command = new EngineCommand { - Path = "requestSmsCode", - Method = HttpMethod.Post, - Content = body - }; - - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - /// - /// 验证是否是有效短信验证码。 - /// - /// 是否验证通过。 - /// 手机号 - /// 验证码。 - public static Task VerifySmsCodeAsync(string code, string mobilePhoneNumber) { - return VerifySmsCodeAsync(code, mobilePhoneNumber, CancellationToken.None); - } - - /// - /// 验证是否是有效短信验证码。 - /// - /// 是否验证通过。 - /// 验证码。 - /// 手机号 - /// Cancellation token. - public static Task VerifySmsCodeAsync(string code, string mobilePhoneNumber, CancellationToken cancellationToken) { - var command = new AVCommand { - Path = $"verifySmsCode/{code.Trim()}?mobilePhoneNumber={mobilePhoneNumber.Trim()}", - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - } - - /// - /// Stands for a captcha result. - /// - public class Captcha { - /// - /// Used for captcha verify. - /// - public string Token { internal set; get; } - - /// - /// Captcha image URL. - /// - public string Url { internal set; get; } - - /// - /// Verify the user's input of catpcha. - /// - /// User's input of this captcha. - /// - public Task VerifyAsync(string code) { - return AVCloud.VerifyCaptchaAsync(code, Token); - } - } - - /// - /// Get a captcha image. - /// - /// captcha image width. - /// captcha image height. - /// CancellationToken. - /// an instance of Captcha. - public static async Task RequestCaptchaAsync(int width = 85, int height = 30, CancellationToken cancellationToken = default) { - var path = string.Format("requestCaptcha?width={0}&height={1}", width, height); - var command = new AVCommand { - Path = $"requestCaptcha?width={width}&height={height}", - Method = HttpMethod.Get - }; - var result = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - var decoded = AVDecoder.Instance.Decode(result.Item2) as IDictionary; - return new Captcha { - Token = decoded["captcha_token"] as string, - Url = decoded["captcha_url"] as string, - }; - } - - /// - /// Verify the user's input of catpcha. - /// - /// The captcha's token, from server. - /// User's input of this captcha. - /// CancellationToken. - /// - public static async Task VerifyCaptchaAsync(string code, string token, CancellationToken cancellationToken = default) { - var data = new Dictionary { - { "captcha_token", token }, - { "captcha_code", code }, - }; - var command = new AVCommand { - Path = "verifyCaptcha", - Method = HttpMethod.Post, - Content = data - }; - var result = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - if (result.Item2.TryGetValue("validate_token", out object tokenObj)) { - return tokenObj as string; - } - throw new KeyNotFoundException("validate_token"); - } - - /// - /// Get the custom cloud parameters, you can set them at console https://leancloud.cn/dashboard/devcomponent.html?appid={your_app_Id}#/component/custom_param - /// - /// - /// - public static async Task> GetCustomParametersAsync(CancellationToken cancellationToken = default) { - var command = new AVCommand { - Path = $"statistics/apps/{AVClient.CurrentConfiguration.ApplicationId}/sendPolicy", - Method = HttpMethod.Get - }; - var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - var settings = data.Item2; - var CloudParameters = settings["parameters"] as IDictionary; - return CloudParameters; - } - - public class RealtimeSignature { - public string Nonce { internal set; get; } - public long Timestamp { internal set; get; } - public string ClientId { internal set; get; } - public string Signature { internal set; get; } - } - - public static async Task RequestRealtimeSignatureAsync(CancellationToken cancellationToken = default) { - var command = new AVCommand { - Path = "rtm/sign", - Method = HttpMethod.Post, - Content = new Dictionary { - { "session_token", AVUser.CurrentUser?.SessionToken } - } - }; - var result = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - var body = result.Item2; - return new RealtimeSignature() { - Nonce = body["nonce"] as string, - Timestamp = (long)body["timestamp"], - ClientId = body["client_id"] as string, - Signature = body["signature"] as string, - }; - } - - /// - /// Gets the LeanEngine hosting URL for current app async. - /// - /// The lean engine hosting URL async. - public static Task GetLeanEngineHostingUrlAsync() { - return CallFunctionAsync("_internal_extensions_get_domain"); - } - - } -} diff --git a/Storage/Storage/Public/AVException.cs b/Storage/Storage/Public/AVException.cs deleted file mode 100644 index 125363f..0000000 --- a/Storage/Storage/Public/AVException.cs +++ /dev/null @@ -1,285 +0,0 @@ -using System; - -namespace LeanCloud -{ - /// - /// Exceptions that may occur when sending requests to LeanCloud. - /// - public class AVException : Exception - { - /// - /// Error codes that may be delivered in response to requests to LeanCloud. - /// - public enum ErrorCode - { - /// - /// Error code indicating that an unknown error or an error unrelated to LeanCloud - /// occurred. - /// - OtherCause = -1, - - /// - /// Error code indicating that something has gone wrong with the server. - /// If you get this error code, it is LeanCloud's fault. - /// - InternalServerError = 1, - - /// - /// Error code indicating the connection to the LeanCloud servers failed. - /// - ConnectionFailed = 100, - - /// - /// Error code indicating the specified object doesn't exist. - /// - ObjectNotFound = 101, - - /// - /// Error code indicating you tried to query with a datatype that doesn't - /// support it, like exact matching an array or object. - /// - InvalidQuery = 102, - - /// - /// Error code indicating a missing or invalid classname. Classnames are - /// case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the - /// only valid characters. - /// - InvalidClassName = 103, - - /// - /// Error code indicating an unspecified object id. - /// - MissingObjectId = 104, - - /// - /// Error code indicating an invalid key name. Keys are case-sensitive. They - /// must start with a letter, and a-zA-Z0-9_ are the only valid characters. - /// - InvalidKeyName = 105, - - /// - /// Error code indicating a malformed pointer. You should not see this unless - /// you have been mucking about changing internal LeanCloud code. - /// - InvalidPointer = 106, - - /// - /// Error code indicating that badly formed JSON was received upstream. This - /// either indicates you have done something unusual with modifying how - /// things encode to JSON, or the network is failing badly. - /// - InvalidJSON = 107, - - /// - /// Error code indicating that the feature you tried to access is only - /// available internally for testing purposes. - /// - CommandUnavailable = 108, - - /// - /// You must call LeanCloud.initialize before using the LeanCloud library. - /// - NotInitialized = 109, - - /// - /// Error code indicating that a field was set to an inconsistent type. - /// - IncorrectType = 111, - - /// - /// Error code indicating an invalid channel name. A channel name is either - /// an empty string (the broadcast channel) or contains only a-zA-Z0-9_ - /// characters and starts with a letter. - /// - InvalidChannelName = 112, - - /// - /// Error code indicating that push is misconfigured. - /// - PushMisconfigured = 115, - - /// - /// Error code indicating that the object is too large. - /// - ObjectTooLarge = 116, - - /// - /// Error code indicating that the operation isn't allowed for clients. - /// - OperationForbidden = 119, - - /// - /// Error code indicating the result was not found in the cache. - /// - CacheMiss = 120, - - /// - /// Error code indicating that an invalid key was used in a nested - /// JSONObject. - /// - InvalidNestedKey = 121, - - /// - /// Error code indicating that an invalid filename was used for AVFile. - /// A valid file name contains only a-zA-Z0-9_. characters and is between 1 - /// and 128 characters. - /// - InvalidFileName = 122, - - /// - /// Error code indicating an invalid ACL was provided. - /// - InvalidACL = 123, - - /// - /// Error code indicating that the request timed out on the server. Typically - /// this indicates that the request is too expensive to run. - /// - Timeout = 124, - - /// - /// Error code indicating that the email address was invalid. - /// - InvalidEmailAddress = 125, - - /// - /// Error code indicating that a unique field was given a value that is - /// already taken. - /// - DuplicateValue = 137, - - /// - /// Error code indicating that a role's name is invalid. - /// - InvalidRoleName = 139, - - /// - /// Error code indicating that an application quota was exceeded. Upgrade to - /// resolve. - /// - ExceededQuota = 140, - - /// - /// Error code indicating that a Cloud Code script failed. - /// - ScriptFailed = 141, - - /// - /// Error code indicating that a Cloud Code validation failed. - /// - ValidationFailed = 142, - - /// - /// Error code indicating that deleting a file failed. - /// - FileDeleteFailed = 153, - - /// - /// Error code indicating that the application has exceeded its request limit. - /// - RequestLimitExceeded = 155, - - /// - /// Error code indicating that the provided event name is invalid. - /// - InvalidEventName = 160, - - /// - /// Error code indicating that the username is missing or empty. - /// - UsernameMissing = 200, - - /// - /// Error code indicating that the password is missing or empty. - /// - PasswordMissing = 201, - - /// - /// Error code indicating that the username has already been taken. - /// - UsernameTaken = 202, - - /// - /// Error code indicating that the email has already been taken. - /// - EmailTaken = 203, - - /// - /// Error code indicating that the email is missing, but must be specified. - /// - EmailMissing = 204, - - /// - /// Error code indicating that a user with the specified email was not found. - /// - EmailNotFound = 205, - - /// - /// Error code indicating that a user object without a valid session could - /// not be altered. - /// - SessionMissing = 206, - - /// - /// Error code indicating that a user can only be created through signup. - /// - MustCreateUserThroughSignup = 207, - - /// - /// Error code indicating that an an account being linked is already linked - /// to another user. - /// - AccountAlreadyLinked = 208, - - /// - /// Error code indicating that the current session token is invalid. - /// - InvalidSessionToken = 209, - - /// - /// Error code indicating that a user cannot be linked to an account because - /// that account's id could not be found. - /// - LinkedIdMissing = 250, - - /// - /// Error code indicating that a user with a linked (e.g. Facebook) account - /// has an invalid session. - /// - InvalidLinkedSession = 251, - - /// - /// Error code indicating that a service being linked (e.g. Facebook or - /// Twitter) is unsupported. - /// - UnsupportedService = 252, - - /// - /// 手机号不合法 - /// - MobilePhoneInvalid = 253, - - /// - /// 按条件更新/删除失败 - /// - NoEffectOnUpdatingOrDeleting = 305, - - /// - /// 循环引用 - /// - CircleReference = 400, - } - - internal AVException(ErrorCode code, string message, Exception cause = null) - : base(message, cause) - { - this.Code = code; - } - - /// - /// The LeanCloud error code associated with the exception. - /// - public ErrorCode Code { get; private set; } - } -} diff --git a/Storage/Storage/Public/AVExtensions.cs b/Storage/Storage/Public/AVExtensions.cs deleted file mode 100644 index 5f00067..0000000 --- a/Storage/Storage/Public/AVExtensions.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using System.Linq; -using LeanCloud.Storage.Internal; - -namespace LeanCloud { - /// - /// Provides convenience extension methods for working with collections - /// of AVObjects so that you can easily save and fetch them in batches. - /// - public static class AVExtensions { - /// - /// Saves all of the AVObjects in the enumeration. Equivalent to - /// calling - /// . - /// - /// The objects to save. - /// The cancellation token. - public static Task SaveAllAsync(this IEnumerable objects, CancellationToken cancellationToken = default) - where T : AVObject { - return AVObject.SaveAllAsync(objects, cancellationToken); - } - - /// - /// Fetches all of the objects in the enumeration. Equivalent to - /// calling . - /// - /// The objects to save. - public static Task> FetchAllAsync(this IEnumerable objects) - where T : AVObject { - return AVObject.FetchAllAsync(objects); - } - - /// - /// Fetches all of the objects in the enumeration. Equivalent to - /// calling - /// . - /// - /// The objects to fetch. - /// The cancellation token. - public static Task> FetchAllAsync( - this IEnumerable objects, CancellationToken cancellationToken) - where T : AVObject { - return AVObject.FetchAllAsync(objects, cancellationToken); - } - - /// - /// Fetches all of the objects in the enumeration that don't already have - /// data. Equivalent to calling - /// . - /// - /// The objects to fetch. - public static Task> FetchAllIfNeededAsync( - this IEnumerable objects) - where T : AVObject { - return AVObject.FetchAllIfNeededAsync(objects); - } - - /// - /// Fetches all of the objects in the enumeration that don't already have - /// data. Equivalent to calling - /// . - /// - /// The objects to fetch. - /// The cancellation token. - public static Task> FetchAllIfNeededAsync( - this IEnumerable objects, CancellationToken cancellationToken) - where T : AVObject { - return AVObject.FetchAllIfNeededAsync(objects, cancellationToken); - } - - /// - /// Constructs a query that is the or of the given queries. - /// - /// The type of AVObject being queried. - /// An initial query to 'or' with additional queries. - /// The list of AVQueries to 'or' together. - /// A query that is the or of the given queries. - public static AVQuery Or(this AVQuery source, params AVQuery[] queries) - where T : AVObject { - return AVQuery.Or(queries.Concat(new[] { source })); - } - - public static Task FetchAsync(this T obj, - IEnumerable keys = null, IEnumerable includes = null, AVACL includeACL = null, - CancellationToken cancellationToken = default) where T : AVObject { - var queryString = new Dictionary(); - if (keys != null) { - var encode = string.Join(",", keys.ToArray()); - queryString.Add("keys", encode); - } - if (includes != null) { - var encode = string.Join(",", includes.ToArray()); - queryString.Add("include", encode); - } - if (includeACL != null) { - queryString.Add("returnACL", includeACL); - } - return obj.FetchAsyncInternal(queryString, cancellationToken).OnSuccess(t => (T)t.Result); - } - } -} diff --git a/Storage/Storage/Public/AVFieldNameAttribute.cs b/Storage/Storage/Public/AVFieldNameAttribute.cs deleted file mode 100644 index 7abfd2b..0000000 --- a/Storage/Storage/Public/AVFieldNameAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace LeanCloud { - /// - /// Specifies a field name for a property on a AVObject subclass. - /// - [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] - public sealed class AVFieldNameAttribute : Attribute { - /// - /// Constructs a new AVFieldName attribute. - /// - /// The name of the field on the AVObject that the - /// property represents. - public AVFieldNameAttribute(string fieldName) { - FieldName = fieldName; - } - - /// - /// Gets the name of the field represented by this property. - /// - public string FieldName { get; private set; } - } -} diff --git a/Storage/Storage/Public/AVFile.cs b/Storage/Storage/Public/AVFile.cs deleted file mode 100644 index b5026fa..0000000 --- a/Storage/Storage/Public/AVFile.cs +++ /dev/null @@ -1,565 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud { - /// - /// AVFile 存储于 LeanCloud 的文件类 - /// - [AVClassName("_File")] - public class AVFile : AVObject { - const string QCloud = "qcloud"; - const string AWS = "s3"; - - #region Properties - - /// - /// Gets the name of the file. Before save is called, this is the filename given by - /// the user. After save is called, that name gets prefixed with a unique identifier. - /// - [AVFieldName("name")] - public string Name { - get { - return GetProperty("Name"); - } - set { - SetProperty(value, "Name"); - } - } - - [AVFieldName("key")] - public string Key { - get { - return GetProperty("Key"); - } - set { - SetProperty(value, "Key"); - } - } - - /// - /// Gets the MIME type of the file. This is either passed in to the constructor or - /// inferred from the file extension. "unknown/unknown" will be used if neither is - /// available. - /// - [AVFieldName("mime_type")] - public string MimeType { - get { - return GetProperty("MimeType"); - } - set { - SetProperty(value, "MimeType"); - } - } - - /// - /// Gets the url of the file. It is only available after you save the file or after - /// you get the file from a . - /// - [AVFieldName("url")] - public string Url { - get { - return GetProperty("Url"); - } - set { - SetProperty(value, "Url"); - } - } - - /// - /// 文件的元数据。 - /// - [AVFieldName("metaData")] - public IDictionary MetaData { - get { - return GetProperty>("MetaData"); - } - set { - SetProperty(value, "MetaData"); - } - } - - #endregion - - private readonly Stream dataStream; - - #region Constructor - /// - /// 通过文件名,数据流,文件类型,文件源信息构建一个 AVFile - /// - /// 文件名 - /// 数据流 - /// 文件类型 - /// 文件源信息 - public AVFile(string name, Stream data, string mimeType = null, IDictionary metaData = null) { - Name = name; - MimeType = mimeType ?? GetMIMEType(name); - MetaData = metaData; - dataStream = data; - } - - /// - /// 根据文件名,文件 Byte 数组,以及文件类型构建 AVFile - /// - /// 文件名 - /// 文件 Byte 数组 - /// 文件类型 - public AVFile(string name, byte[] data, string mimeType = null) - : this(name, new MemoryStream(data), mimeType) { } - - /// - /// 根据文件名,文件流数据,文件类型构建 AVFile - /// - /// 文件名 - /// 文件流数据 - /// 文件类型 - public AVFile(string name, Stream data, string mimeType = null) - : this(name, data, mimeType, new Dictionary()) { - } - - /// - /// 根据 byte 数组以及文件名创建文件 - /// - /// 文件名 - /// 文件的 byte[] 数据 - public AVFile(string name, byte[] data) - : this(name, new MemoryStream(data), new Dictionary()) { - - } - - /// - /// 根据文件名,数据 byte[] 数组以及元数据创建文件 - /// - /// 文件名 - /// 文件的 byte[] 数据 - /// 元数据 - public AVFile(string name, byte[] data, IDictionary metaData) - : this(name, new MemoryStream(data), metaData) { - - } - - /// - /// 根据文件名,数据流以及元数据创建文件 - /// - /// 文件名 - /// 文件的数据流 - /// 元数据 - public AVFile(string name, Stream data, IDictionary metaData) - : this(name, data, GetMIMEType(name), metaData) { - } - - /// - /// 根据文件名,数据流以及元数据创建文件 - /// - /// 文件名 - /// 文件的数据流 - public AVFile(string name, Stream data) - : this(name, data, new Dictionary()) { - - } - - public AVFile() { - - } - - #endregion - - #region created by url or uri - - /// - /// 根据文件名,Uri,文件类型以及文件源信息 - /// - /// 文件名 - /// 文件Uri - /// 文件类型 - /// 文件源信息 - public AVFile(string name, Uri uri, string mimeType = null, IDictionary metaData = null) { - Name = name; - MimeType = mimeType ?? GetMIMEType(name); - IsExternal = true; - } - - /// - /// 根据文件名,文件 Url,文件类型,文件源信息构建 AVFile - /// - /// 文件名 - /// 文件 Url - /// 文件类型 - /// 文件源信息 - public AVFile(string name, string url, string mimeType = null, IDictionary metaData = null) - : this(name, new Uri(url), mimeType, metaData) { - - } - - /// - /// 根据文件名,文件 Url以及文件的源信息构建 AVFile - /// - /// 文件名 - /// 文件 Url - /// 文件源信息 - public AVFile(string name, string url, IDictionary metaData) - : this(name, url, null, metaData) { - } - - /// - /// 根据文件名,文件 Uri,以及文件类型构建 AVFile - /// - /// 文件名 - /// 文件 Uri - /// 文件类型 - public AVFile(string name, Uri uri, string mimeType = null) - : this(name, uri, mimeType, new Dictionary()) { - - } - - /// - /// 根据文件名以及文件 Uri 构建 AVFile - /// - /// 文件名 - /// 文件 Uri - public AVFile(string name, Uri uri) - : this(name, uri, null, new Dictionary()) { - - } - /// - /// 根据文件名和 Url 创建文件 - /// - /// 文件名 - /// 文件的 Url - public AVFile(string name, string url) { - Name = name; - Url = url; - IsExternal = true; - } - - #endregion - - - - /// - /// 获取缩略图 - /// - /// 宽 - /// 高 - /// 质量 1-100 - /// 等比缩放,是否裁剪 - /// 格式 - /// - public string GetThumbnailUrl(int width, int height, - int quality = 100, bool scaleToFit = true, string format = "png") { - int mode = scaleToFit ? 2 : 1; - return $"{Url}?imageView/{mode}/w/{width}/h/{height}/q/{quality}/format/{format}"; - } - - internal static AVFileController FileController { - get { - return AVPlugins.Instance.FileController; - } - } - - IDictionary ToJSON() { - if (IsDirty) { - throw new InvalidOperationException( - "AVFile must be saved before it can be serialized."); - } - return new Dictionary { - { "__type", "File"} , - { "id", ObjectId }, - { "name", Name }, - { "url", Url } - }; - } - - #region Save - - /// - /// 保存文件 - /// - /// The cancellation token. - public override async Task SaveAsync(bool fetchWhenSave = false, AVQuery query = null, CancellationToken cancellationToken = default) { - if (IsExternal) { - await base.SaveAsync(fetchWhenSave, query, cancellationToken); - } else { - // 需不需要判断数据的 dirty? - // 获取 FileToken 并创建对应记录 - Tuple> res = await AVPlugins.Instance.FileController.GetFileToken(Name, MetaData); - IObjectState serverState = AVObjectCoder.Instance.Decode(res.Item2, AVDecoder.Instance); - HandleSave(serverState); - IDictionary fileData = res.Item2; - AVClient.PrintLog(fileData.ToString()); - - var provider = fileData["provider"] as string; - switch (provider) { - case QCloud: { - await new QCloudUploader { - FileName = Key, - UploadUrl = fileData["upload_url"] as string, - Token = fileData["token"] as string, - Bucket = fileData["bucket"] as string, - Stream = dataStream - }.Upload(); - } - break; - case AWS: { - await new AWSUploader { - UploadUrl = fileData["upload_url"] as string, - MimeType = MimeType, - Stream = dataStream - }.Upload(); - } - break; - default: { - await new QiniuUploader { - Key = Key, - Token = fileData["token"] as string, - MimeType = MimeType, - MetaData = MetaData, - Stream = dataStream - }.Upload(); - } - break; - } - } - } - - #endregion - - #region Compatible - - /// - /// 文件是否为外链文件。 - /// - /// - /// - public bool IsExternal { - get; private set; - } - - internal static string GetUniqueName(string name) { - string key = Guid.NewGuid().ToString(); - string extension = Path.GetExtension(name); - key += extension; - return key; - } - - internal static string GetMIMEType(string fileName) { - try { - string str = Path.GetExtension(fileName).Remove(0, 1); - if (!MIMETypesDictionary.ContainsKey(str)) { - return "unknown/unknown"; - } - return MIMETypesDictionary[str]; - } catch { - return "unknown/unknown"; - } - } - - //public static AVFile CreateWithData(string objectId, string name, string url, IDictionary metaData) { - // var fileState = new FileState { - // Name = name, - // ObjectId = objectId, - // Url = new Uri(url), - // MetaData = metaData - // }; - // return CreateWithState(fileState); - //} - - #endregion - - private readonly static Dictionary MIMETypesDictionary = new Dictionary { - { "ai", "application/postscript" }, - { "aif", "audio/x-aiff" }, - { "aifc", "audio/x-aiff" }, - { "aiff", "audio/x-aiff" }, - { "asc", "text/plain" }, - { "atom", "application/atom+xml" }, - { "au", "audio/basic" }, - { "avi", "video/x-msvideo" }, - { "bcpio", "application/x-bcpio" }, - { "bin", "application/octet-stream" }, - { "bmp", "image/bmp" }, - { "cdf", "application/x-netcdf" }, - { "cgm", "image/cgm" }, - { "class", "application/octet-stream" }, - { "cpio", "application/x-cpio" }, - { "cpt", "application/mac-compactpro" }, - { "csh", "application/x-csh" }, - { "css", "text/css" }, - { "dcr", "application/x-director" }, - { "dif", "video/x-dv" }, - { "dir", "application/x-director" }, - { "djv", "image/vnd.djvu" }, - { "djvu", "image/vnd.djvu" }, - { "dll", "application/octet-stream" }, - { "dmg", "application/octet-stream" }, - { "dms", "application/octet-stream" }, - { "doc", "application/msword" }, - { "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }, - { "dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template" }, - { "docm", "application/vnd.ms-word.document.macroEnabled.12" }, - { "dotm", "application/vnd.ms-word.template.macroEnabled.12" }, - { "dtd", "application/xml-dtd" }, - { "dv", "video/x-dv" }, - { "dvi", "application/x-dvi" }, - { "dxr", "application/x-director" }, - { "eps", "application/postscript" }, - { "etx", "text/x-setext" }, - { "exe", "application/octet-stream" }, - { "ez", "application/andrew-inset" }, - { "gif", "image/gif" }, - { "gram", "application/srgs" }, - { "grxml", "application/srgs+xml" }, - { "gtar", "application/x-gtar" }, - { "hdf", "application/x-hdf" }, - { "hqx", "application/mac-binhex40" }, - { "htm", "text/html" }, - { "html", "text/html" }, - { "ice", "x-conference/x-cooltalk" }, - { "ico", "image/x-icon" }, - { "ics", "text/calendar" }, - { "ief", "image/ief" }, - { "ifb", "text/calendar" }, - { "iges", "model/iges" }, - { "igs", "model/iges" }, - { "jnlp", "application/x-java-jnlp-file" }, - { "jp2", "image/jp2" }, - { "jpe", "image/jpeg" }, - { "jpeg", "image/jpeg" }, - { "jpg", "image/jpeg" }, - { "js", "application/x-javascript" }, - { "kar", "audio/midi" }, - { "latex", "application/x-latex" }, - { "lha", "application/octet-stream" }, - { "lzh", "application/octet-stream" }, - { "m3u", "audio/x-mpegurl" }, - { "m4a", "audio/mp4a-latm" }, - { "m4b", "audio/mp4a-latm" }, - { "m4p", "audio/mp4a-latm" }, - { "m4u", "video/vnd.mpegurl" }, - { "m4v", "video/x-m4v" }, - { "mac", "image/x-macpaint" }, - { "man", "application/x-troff-man" }, - { "mathml", "application/mathml+xml" }, - { "me", "application/x-troff-me" }, - { "mesh", "model/mesh" }, - { "mid", "audio/midi" }, - { "midi", "audio/midi" }, - { "mif", "application/vnd.mif" }, - { "mov", "video/quicktime" }, - { "movie", "video/x-sgi-movie" }, - { "mp2", "audio/mpeg" }, - { "mp3", "audio/mpeg" }, - { "mp4", "video/mp4" }, - { "mpe", "video/mpeg" }, - { "mpeg", "video/mpeg" }, - { "mpg", "video/mpeg" }, - { "mpga", "audio/mpeg" }, - { "ms", "application/x-troff-ms" }, - { "msh", "model/mesh" }, - { "mxu", "video/vnd.mpegurl" }, - { "nc", "application/x-netcdf" }, - { "oda", "application/oda" }, - { "ogg", "application/ogg" }, - { "pbm", "image/x-portable-bitmap" }, - { "pct", "image/pict" }, - { "pdb", "chemical/x-pdb" }, - { "pdf", "application/pdf" }, - { "pgm", "image/x-portable-graymap" }, - { "pgn", "application/x-chess-pgn" }, - { "pic", "image/pict" }, - { "pict", "image/pict" }, - { "png", "image/png" }, - { "pnm", "image/x-portable-anymap" }, - { "pnt", "image/x-macpaint" }, - { "pntg", "image/x-macpaint" }, - { "ppm", "image/x-portable-pixmap" }, - { "ppt", "application/vnd.ms-powerpoint" }, - { "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" }, - { "potx", "application/vnd.openxmlformats-officedocument.presentationml.template" }, - { "ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow" }, - { "ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12" }, - { "pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12" }, - { "potm", "application/vnd.ms-powerpoint.template.macroEnabled.12" }, - { "ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12" }, - { "ps", "application/postscript" }, - { "qt", "video/quicktime" }, - { "qti", "image/x-quicktime" }, - { "qtif", "image/x-quicktime" }, - { "ra", "audio/x-pn-realaudio" }, - { "ram", "audio/x-pn-realaudio" }, - { "ras", "image/x-cmu-raster" }, - { "rdf", "application/rdf+xml" }, - { "rgb", "image/x-rgb" }, - { "rm", "application/vnd.rn-realmedia" }, - { "roff", "application/x-troff" }, - { "rtf", "text/rtf" }, - { "rtx", "text/richtext" }, - { "sgm", "text/sgml" }, - { "sgml", "text/sgml" }, - { "sh", "application/x-sh" }, - { "shar", "application/x-shar" }, - { "silo", "model/mesh" }, - { "sit", "application/x-stuffit" }, - { "skd", "application/x-koan" }, - { "skm", "application/x-koan" }, - { "skp", "application/x-koan" }, - { "skt", "application/x-koan" }, - { "smi", "application/smil" }, - { "smil", "application/smil" }, - { "snd", "audio/basic" }, - { "so", "application/octet-stream" }, - { "spl", "application/x-futuresplash" }, - { "src", "application/x-wais-Source" }, - { "sv4cpio", "application/x-sv4cpio" }, - { "sv4crc", "application/x-sv4crc" }, - { "svg", "image/svg+xml" }, - { "swf", "application/x-shockwave-flash" }, - { "t", "application/x-troff" }, - { "tar", "application/x-tar" }, - { "tcl", "application/x-tcl" }, - { "tex", "application/x-tex" }, - { "texi", "application/x-texinfo" }, - { "texinfo", "application/x-texinfo" }, - { "tif", "image/tiff" }, - { "tiff", "image/tiff" }, - { "tr", "application/x-troff" }, - { "tsv", "text/tab-separated-values" }, - { "txt", "text/plain" }, - { "ustar", "application/x-ustar" }, - { "vcd", "application/x-cdlink" }, - { "vrml", "model/vrml" }, - { "vxml", "application/voicexml+xml" }, - { "wav", "audio/x-wav" }, - { "wbmp", "image/vnd.wap.wbmp" }, - { "wbmxl", "application/vnd.wap.wbxml" }, - { "wml", "text/vnd.wap.wml" }, - { "wmlc", "application/vnd.wap.wmlc" }, - { "wmls", "text/vnd.wap.wmlscript" }, - { "wmlsc", "application/vnd.wap.wmlscriptc" }, - { "wrl", "model/vrml" }, - { "xbm", "image/x-xbitmap" }, - { "xht", "application/xhtml+xml" }, - { "xhtml", "application/xhtml+xml" }, - { "xls", "application/vnd.ms-excel" }, - { "xml", "application/xml" }, - { "xpm", "image/x-xpixmap" }, - { "xsl", "application/xml" }, - { "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }, - { "xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template" }, - { "xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12" }, - { "xltm", "application/vnd.ms-excel.template.macroEnabled.12" }, - { "xlam", "application/vnd.ms-excel.addin.macroEnabled.12" }, - { "xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12" }, - { "xslt", "application/xslt+xml" }, - { "xul", "application/vnd.mozilla.xul+xml" }, - { "xwd", "image/x-xwindowdump" }, - { "xyz", "chemical/x-xyz" }, - { "zip", "application/zip" } - }; - } -} diff --git a/Storage/Storage/Public/AVGeoDistance.cs b/Storage/Storage/Public/AVGeoDistance.cs deleted file mode 100644 index 7c8fdcf..0000000 --- a/Storage/Storage/Public/AVGeoDistance.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace LeanCloud { - /// - /// Represents a distance between two AVGeoPoints. - /// - public struct AVGeoDistance { - private const double EarthMeanRadiusKilometers = 6371.0; - private const double EarthMeanRadiusMiles = 3958.8; - - /// - /// Creates a AVGeoDistance. - /// - /// The distance in radians. - public AVGeoDistance(double radians) - : this() { - Radians = radians; - } - - /// - /// Gets the distance in radians. - /// - public double Radians { get; private set; } - - /// - /// Gets the distance in miles. - /// - public double Miles { - get { - return Radians * EarthMeanRadiusMiles; - } - } - - /// - /// Gets the distance in kilometers. - /// - public double Kilometers { - get { - return Radians * EarthMeanRadiusKilometers; - } - } - - /// - /// Gets a AVGeoDistance from a number of miles. - /// - /// The number of miles. - /// A AVGeoDistance for the given number of miles. - public static AVGeoDistance FromMiles(double miles) { - return new AVGeoDistance(miles / EarthMeanRadiusMiles); - } - - /// - /// Gets a AVGeoDistance from a number of kilometers. - /// - /// The number of kilometers. - /// A AVGeoDistance for the given number of kilometers. - public static AVGeoDistance FromKilometers(double kilometers) { - return new AVGeoDistance(kilometers / EarthMeanRadiusKilometers); - } - - /// - /// Gets a AVGeoDistance from a number of radians. - /// - /// The number of radians. - /// A AVGeoDistance for the given number of radians. - public static AVGeoDistance FromRadians(double radians) { - return new AVGeoDistance(radians); - } - } -} diff --git a/Storage/Storage/Public/AVGeoPoint.cs b/Storage/Storage/Public/AVGeoPoint.cs deleted file mode 100644 index cefb19a..0000000 --- a/Storage/Storage/Public/AVGeoPoint.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Generic; -using LeanCloud.Storage.Internal; - -namespace LeanCloud { - /// - /// AVGeoPoint represents a latitude / longitude point that may be associated - /// with a key in a AVObject or used as a reference point for geo queries. - /// This allows proximity-based queries on the key. - /// - /// Only one key in a class may contain a GeoPoint. - /// - public struct AVGeoPoint : IJsonConvertible { - /// - /// Constructs a AVGeoPoint with the specified latitude and longitude. - /// - /// The point's latitude. - /// The point's longitude. - public AVGeoPoint(double latitude, double longitude) - : this() { - Latitude = latitude; - Longitude = longitude; - } - - private double latitude; - /// - /// Gets or sets the latitude of the GeoPoint. Valid range is [-90, 90]. - /// Extremes should not be used. - /// - public double Latitude { - get { - return latitude; - } - set { - if (value > 90 || value < -90) { - throw new ArgumentOutOfRangeException(nameof(value), "Latitude must be within the range [-90, 90]"); - } - latitude = value; - } - } - - private double longitude; - /// - /// Gets or sets the longitude. Valid range is [-180, 180]. - /// Extremes should not be used. - /// - public double Longitude { - get { - return longitude; - } - set { - if (value > 180 || value < -180) { - throw new ArgumentOutOfRangeException(nameof(value), "Longitude must be within the range [-180, 180]"); - } - longitude = value; - } - } - - /// - /// Get the distance in radians between this point and another GeoPoint. This is the smallest angular - /// distance between the two points. - /// - /// GeoPoint describing the other point being measured against. - /// The distance in between the two points. - public AVGeoDistance DistanceTo(AVGeoPoint point) { - double d2r = Math.PI / 180; // radian conversion factor - double lat1rad = Latitude * d2r; - double long1rad = longitude * d2r; - double lat2rad = point.Latitude * d2r; - double long2rad = point.Longitude * d2r; - double deltaLat = lat1rad - lat2rad; - double deltaLong = long1rad - long2rad; - double sinDeltaLatDiv2 = Math.Sin(deltaLat / 2); - double sinDeltaLongDiv2 = Math.Sin(deltaLong / 2); - // Square of half the straight line chord distance between both points. - // [0.0, 1.0] - double a = sinDeltaLatDiv2 * sinDeltaLatDiv2 + - Math.Cos(lat1rad) * Math.Cos(lat2rad) * sinDeltaLongDiv2 * sinDeltaLongDiv2; - a = Math.Min(1.0, a); - return new AVGeoDistance(2 * Math.Asin(Math.Sqrt(a))); - } - - IDictionary IJsonConvertible.ToJSON() { - return new Dictionary { - { "__type", "GeoPoint" }, - { "latitude", Latitude }, - { "longitude", Longitude } - }; - } - } -} diff --git a/Storage/Storage/Public/AVObject.cs b/Storage/Storage/Public/AVObject.cs deleted file mode 100644 index 48d3c67..0000000 --- a/Storage/Storage/Public/AVObject.cs +++ /dev/null @@ -1,802 +0,0 @@ -using LeanCloud.Storage.Internal; -using LeanCloud.Utilities; -using System; -using System.Text; -using System.Collections.Generic; -using System.Net.Http; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections; - -namespace LeanCloud { - public class AVObject : IEnumerable> { - static readonly HashSet RESERVED_KEYS = new HashSet { - "objectId", "ACL", "createdAt", "updatedAt" - }; - - public string ClassName { - get { - return state.ClassName; - } - } - - [AVFieldName("objectId")] - public string ObjectId { - get { - return state.ObjectId; - } - set { - IsDirty = true; - MutateState(mutableClone => { - mutableClone.ObjectId = value; - }); - } - } - - internal AVACL acl; - - [AVFieldName("ACL")] - public AVACL ACL { - // 设置 IsDirty - get { - return acl; - } set { - acl = value; - IsDirty = true; - } - } - - [AVFieldName("createdAt")] - public DateTime? CreatedAt { - get { - return state.CreatedAt; - } - } - - [AVFieldName("updatedAt")] - public DateTime? UpdatedAt { - get { - return state.UpdatedAt; - } - } - - public ICollection Keys { - get { - return estimatedData.Keys; - } - } - - private static readonly string AutoClassName = "_Automatic"; - - internal readonly Dictionary operationDict = new Dictionary(); - private Dictionary serverData = new Dictionary(); - private Dictionary estimatedData = new Dictionary(); - - private bool dirty; - - private IObjectState state; - - internal void MutateState(Action func) { - state = state.MutatedClone(func); - RebuildEstimatedData(); - } - - public IObjectState State { - get { - return state; - } - } - - internal static ObjectSubclassingController SubclassingController { - get { - return AVPlugins.Instance.SubclassingController; - } - } - - public static string GetSubClassName() { - return SubclassingController.GetClassName(typeof(TAVObject)); - } - - #region AVObject Creation - - protected AVObject() - : this(AutoClassName) { - } - - public AVObject(string className) { - if (className == null) { - throw new ArgumentException("You must specify a LeanCloud class name when creating a new AVObject."); - } - if (AutoClassName.Equals(className)) { - className = SubclassingController.GetClassName(GetType()); - } - if (!SubclassingController.IsTypeValid(className, GetType())) { - throw new ArgumentException( - "You must create this type of AVObject using AVObject.Create() or the proper subclass."); - } - state = new MutableObjectState { - ClassName = className - }; - IsDirty = true; - } - - public static AVObject Create(string className) { - return SubclassingController.Instantiate(className); - } - - public static AVObject CreateWithoutData(string className, string objectId) { - var result = SubclassingController.Instantiate(className); - result.ObjectId = objectId; - result.IsDirty = false; - if (result.IsDirty) { - throw new InvalidOperationException("A AVObject subclass default constructor must not make changes to the object that cause it to be dirty."); - } - return result; - } - - public static T Create() where T : AVObject { - return (T)SubclassingController.Instantiate(SubclassingController.GetClassName(typeof(T))); - } - - public static T CreateWithoutData(string objectId) where T : AVObject { - return (T)CreateWithoutData(SubclassingController.GetClassName(typeof(T)), objectId); - } - - public static T FromState(IObjectState state, string defaultClassName) where T : AVObject { - string className = state.ClassName ?? defaultClassName; - - T obj = (T)CreateWithoutData(className, state.ObjectId); - obj.HandleFetchResult(state); - - return obj; - } - - #endregion - - public static IDictionary GetPropertyMappings(string className) { - return SubclassingController.GetPropertyMappings(className); - } - - private static string GetFieldForPropertyName(string className, string propertyName) { - if (SubclassingController.GetPropertyMappings(className).TryGetValue(propertyName, out string fieldName)) { - return fieldName; - } - return null; - } - - protected virtual void SetProperty(T value, string propertyName) { - this[GetFieldForPropertyName(ClassName, propertyName)] = value; - } - - protected AVRelation GetRelationProperty(string propertyName) where T : AVObject { - return GetRelation(GetFieldForPropertyName(ClassName, propertyName)); - } - - protected virtual T GetProperty(string propertyName) { - return GetProperty(default, propertyName); - } - - protected virtual T GetProperty(T defaultValue, string propertyName) { - if (TryGetValue(GetFieldForPropertyName(ClassName, propertyName), out T result)) { - return result; - } - return defaultValue; - } - - internal virtual void SetDefaultValues() { - } - - public static void RegisterSubclass() where T : AVObject, new() { - SubclassingController.RegisterSubclass(typeof(T)); - } - - internal static void UnregisterSubclass() where T : AVObject, new() { - SubclassingController.UnregisterSubclass(typeof(T)); - } - - public void Revert() { - if (operationDict.Any()) { - operationDict.Clear(); - RebuildEstimatedData(); - } - } - - internal virtual void HandleFetchResult(IObjectState serverState) { - MergeFromServer(serverState); - } - - internal virtual void HandleSave(IObjectState serverState) { - state = state.MutatedClone((objectState) => objectState.Apply(operationDict)); - MergeFromServer(serverState); - } - - public virtual void MergeFromServer(IObjectState serverState) { - // Make a new serverData with fetched values. - var newServerData = serverState.ToDictionary(t => t.Key, t => t.Value); - - // We cache the fetched object because subsequent Save operation might flush - // the fetched objects into Pointers. - //IDictionary fetchedObject = CollectFetchedObjects(); - - //foreach (var pair in serverState) { - // var value = pair.Value; - // if (value is AVObject) { - // // Resolve fetched object. - // var avObject = value as AVObject; - // if (fetchedObject.TryGetValue(avObject.ObjectId, out AVObject obj)) { - // value = obj; - // } - // } - // newServerData[pair.Key] = value; - //} - - this.serverData = serverData.Concat(this.serverData.Where(d => !serverData.ContainsKey(d.Key))) - .ToDictionary(x => x.Key, x => x.Value); - - IsDirty = false; - serverState = serverState.MutatedClone(mutableClone => { - mutableClone.ServerData = newServerData; - }); - MutateState(mutableClone => { - mutableClone.Apply(serverState); - }); - } - - internal void MergeFromObject(AVObject other) { - if (this == other) { - return; - } - - operationDict.Clear(); - foreach (KeyValuePair entry in other.operationDict) { - operationDict.Add(entry.Key, entry.Value); - } - state = other.State; - - RebuildEstimatedData(); - } - - internal IDictionary ToJSONObject() { - IDictionary result = new Dictionary(); - if (ACL != null) { - result["ACL"] = PointerOrLocalIdEncoder.Instance.Encode(ACL); - } - foreach (KeyValuePair kv in operationDict) { - result[kv.Key] = PointerOrLocalIdEncoder.Instance.Encode(kv.Value); - } - return result; - } - - public static IDictionary ToJSONObjectForSaving(IDictionary operations) { - var result = new Dictionary(); - foreach (var pair in operations) { - // AVRPCSerialize the data - var operation = pair.Value; - - result[pair.Key] = PointerOrLocalIdEncoder.Instance.Encode(operation); - } - return result; - } - - internal IDictionary EncodeForSaving(IDictionary data) { - var result = new Dictionary(); - foreach (var key in data.Keys) { - var value = data[key]; - result.Add(key, PointerOrLocalIdEncoder.Instance.Encode(value)); - } - - return result; - } - - - internal IDictionary ServerDataToJSONObjectForSerialization() { - return PointerOrLocalIdEncoder.Instance.Encode(state.ToDictionary(t => t.Key, t => t.Value)) - as IDictionary; - } - - internal virtual async Task FetchAsyncInternal(IDictionary queryString, CancellationToken cancellationToken = default) { - if (ObjectId == null) { - throw new InvalidOperationException("Cannot refresh an object that hasn't been saved to the server."); - } - if (queryString == null) { - queryString = new Dictionary(); - } - - var command = new AVCommand { - Path = $"classes/{Uri.EscapeDataString(state.ClassName)}/{Uri.EscapeDataString(state.ObjectId)}?{AVClient.BuildQueryString(queryString)}", - Method = HttpMethod.Get - }; - var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - IObjectState objectState = AVObjectCoder.Instance.Decode(data.Item2, AVDecoder.Instance); - - HandleFetchResult(objectState); - return this; - } - - #region Save - - public virtual async Task SaveAsync(bool fetchWhenSave = false, AVQuery query = null, CancellationToken cancellationToken = default) { - if (HasCircleReference(this, new HashSet())) { - throw new AVException(AVException.ErrorCode.CircleReference, "Found a circle dependency when save"); - } - Stack batches = BatchObjects(new List { this }, false); - await SaveBatches(batches, cancellationToken); - - IDictionary objectJSON = ToJSONObject(); - var command = new AVCommand { - Path = ObjectId == null ? $"classes/{Uri.EscapeDataString(ClassName)}" : - $"classes/{Uri.EscapeDataString(ClassName)}/{ObjectId}", - Method = ObjectId == null ? HttpMethod.Post : HttpMethod.Put, - Content = objectJSON - }; - Dictionary args = new Dictionary(); - if (fetchWhenSave) { - args.Add("fetchWhenSave", fetchWhenSave); - } - // 查询条件 - if (query != null) { - args.Add("where", query.BuildWhere()); - } - if (args.Count > 0) { - string encode = AVClient.BuildQueryString(args); - command.Path = $"{command.Path}?{encode}"; - } - var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - IObjectState serverState = AVObjectCoder.Instance.Decode(data.Item2, AVDecoder.Instance); - HandleSave(serverState); - } - - public static async Task SaveAllAsync(IEnumerable objects, CancellationToken cancellationToken = default) - where T : AVObject { - foreach (T obj in objects) { - if (HasCircleReference(obj, new HashSet())) { - throw new AVException(AVException.ErrorCode.CircleReference, "Found a circle dependency when save"); - } - } - Stack batches = BatchObjects(objects, true); - await SaveBatches(batches, cancellationToken); - } - - static async Task SaveBatches(Stack batches, CancellationToken cancellationToken = default) { - while (batches.Any()) { - Batch batch = batches.Pop(); - IList dirtyObjects = batch.Objects.Where(o => o.IsDirty).ToList(); - - List commandList = new List(); - foreach (AVObject avObj in dirtyObjects) { - AVCommand command = new AVCommand { - Path = avObj.ObjectId == null ? $"classes/{Uri.EscapeDataString(avObj.ClassName)}" : - $"classes/{Uri.EscapeDataString(avObj.ClassName)}/{Uri.EscapeDataString(avObj.ObjectId)}", - Method = avObj.ObjectId == null ? HttpMethod.Post : HttpMethod.Put, - Content = avObj.ToJSONObject() - }; - commandList.Add(command); - } - IList list = new List(); - var result = await AVPlugins.Instance.CommandRunner.ExecuteBatchRequests(commandList, cancellationToken); - foreach (var data in result) { - if (data.TryGetValue("success", out object val)) { - IObjectState obj = AVObjectCoder.Instance.Decode(val as IDictionary, AVDecoder.Instance); - list.Add(obj); - } - } - - try { - foreach (var pair in dirtyObjects.Zip(list, (item, state) => new { item, state })) { - pair.item.HandleSave(pair.state); - } - } catch (Exception e) { - throw e; - } - } - } - - #endregion - - private static Task> FetchAllInternalAsync( - IEnumerable objects, bool force, CancellationToken cancellationToken) where T : AVObject { - - if (objects.Any(obj => obj.state.ObjectId == null)) { - throw new InvalidOperationException("You cannot fetch objects that haven't already been saved."); - } - - // Do one Find for each class. - var findsByClass = - (from obj in objects - group obj.ObjectId by obj.ClassName into classGroup - where classGroup.Any() - select new { - ClassName = classGroup.Key, - FindTask = new AVQuery(classGroup.Key) - .WhereContainedIn("objectId", classGroup) - .FindAsync(cancellationToken) - }).ToDictionary(pair => pair.ClassName, pair => pair.FindTask); - - // Wait for all the Finds to complete. - return Task.WhenAll(findsByClass.Values.ToList()).OnSuccess(__ => { - if (cancellationToken.IsCancellationRequested) { - return objects; - } - - // Merge the data from the Finds into the input objects. - var pairs = from obj in objects - from result in findsByClass[obj.ClassName].Result - where result.ObjectId == obj.ObjectId - select new { obj, result }; - foreach (var pair in pairs) { - pair.obj.MergeFromObject(pair.result); - } - - return objects; - }); - } - - #region Fetch - - public static Task> FetchAllIfNeededAsync( - IEnumerable objects, CancellationToken cancellationToken = default) where T : AVObject { - return FetchAllInternalAsync(objects, false, cancellationToken); - } - - public static Task> FetchAllAsync(IEnumerable objects, CancellationToken cancellationToken = default) where T : AVObject { - return FetchAllInternalAsync(objects, true, cancellationToken); - } - - #endregion - - #region Delete - - public virtual async Task DeleteAsync(AVQuery query = null, CancellationToken cancellationToken = default) { - if (ObjectId == null) { - return; - } - var command = new AVCommand { - Path = $"classes/{state.ClassName}/{state.ObjectId}", - Method = HttpMethod.Delete - }; - if (query != null) { - Dictionary where = new Dictionary { - { "where", query.BuildWhere() } - }; - command.Path = $"{command.Path}?{AVClient.BuildQueryString(where)}"; - } - await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - IsDirty = true; - } - - public static async Task DeleteAllAsync(IEnumerable objects, CancellationToken cancellationToken = default) - where T : AVObject { - var uniqueObjects = new HashSet(objects.OfType().ToList(), - new IdentityEqualityComparer()); - - var states = uniqueObjects.Select(t => t.state); - var requests = states - .Where(item => item.ObjectId != null) - .Select(item => new AVCommand { - Path = $"classes/{Uri.EscapeDataString(item.ClassName)}/{Uri.EscapeDataString(item.ObjectId)}", - Method = HttpMethod.Delete - }) - .ToList(); - await AVPlugins.Instance.CommandRunner.ExecuteBatchRequests(requests, cancellationToken); - - foreach (var obj in uniqueObjects) { - obj.IsDirty = true; - } - } - - #endregion - - public virtual void Remove(string key) { - CheckKeyValid(key); - PerformOperation(key, AVDeleteOperation.Instance); - } - - virtual public object this[string key] { - get { - CheckKeyValid(key); - - if (!estimatedData.TryGetValue(key, out object value)) { - value = state[key]; - } - - if (value is AVRelationBase) { - var relation = value as AVRelationBase; - relation.EnsureParentAndKey(this, key); - } - - return value; - } - set { - CheckKeyValid(key); - Set(key, value); - } - } - - #region Atomic Increment - - public void Increment(string key) { - Increment(key, 1); - } - - public void Increment(string key, long amount) { - CheckKeyValid(key); - PerformOperation(key, new AVIncrementOperation(amount)); - } - - public void Increment(string key, double amount) { - CheckKeyValid(key); - PerformOperation(key, new AVIncrementOperation(amount)); - } - - #endregion - - #region List - - public void AddToList(string key, object value) { - CheckKeyValid(key); - AddRangeToList(key, new[] { value }); - } - - public void AddRangeToList(string key, IEnumerable values) { - CheckKeyValid(key); - PerformOperation(key, new AVAddOperation(values.Cast())); - } - - public void AddUniqueToList(string key, object value) { - CheckKeyValid(key); - AddRangeUniqueToList(key, new object[] { value }); - } - - public void AddRangeUniqueToList(string key, IEnumerable values) { - CheckKeyValid(key); - PerformOperation(key, new AVAddUniqueOperation(values.Cast())); - } - - public void RemoveAllFromList(string key, IEnumerable values) { - CheckKeyValid(key); - PerformOperation(key, new AVRemoveOperation(values.Cast())); - } - - #endregion - - - private IEnumerable ApplyOperations(IDictionary operations, IDictionary map) { - List appliedKeys = new List(); - foreach (var pair in operations) { - map.TryGetValue(pair.Key, out object oldValue); - var newValue = pair.Value.Apply(oldValue, pair.Key); - if (newValue != AVDeleteOperation.DeleteToken) { - map[pair.Key] = newValue; - } else { - map.Remove(pair.Key); - } - appliedKeys.Add(pair.Key); - } - return appliedKeys; - } - - internal void RebuildEstimatedData() { - estimatedData.Clear(); - foreach (KeyValuePair entry in serverData) { - estimatedData.Add(entry.Key, entry.Value); - } - ApplyOperations(operationDict, estimatedData); - } - - internal void PerformOperation(string key, IAVFieldOperation operation) { - estimatedData.TryGetValue(key, out object oldValue); - object newValue = operation.Apply(oldValue, key); - if (newValue != AVDeleteOperation.DeleteToken) { - estimatedData[key] = newValue; - } else { - estimatedData.Remove(key); - } - - if (operationDict.TryGetValue(key, out IAVFieldOperation oldOperation)) { - operation = operation.MergeWithPrevious(oldOperation); - } - operationDict[key] = operation; - } - - internal virtual void OnSettingValue(ref string key, ref object value) { - if (key == null) { - throw new ArgumentNullException(nameof(key)); - } - } - - internal void Set(string key, object value) { - OnSettingValue(ref key, ref value); - PerformOperation(key, new AVSetOperation(value)); - } - - void CheckKeyValid(string key) { - if (string.IsNullOrEmpty(key)) { - throw new ArgumentNullException(nameof(key)); - } - if (key.StartsWith("_", StringComparison.CurrentCulture)) { - throw new ArgumentException("key should not start with _"); - } - if (RESERVED_KEYS.Contains(key)) { - throw new ArgumentException($"key: {key} is reserved by LeanCloud"); - } - } - - - - public void Add(string key, object value) { - if (ContainsKey(key)) { - throw new ArgumentException("Key already exists", key); - } - this[key] = value; - } - - public bool ContainsKey(string key) { - return estimatedData.ContainsKey(key) || state.ContainsKey(key); - } - - public T Get(string key) { - return Conversion.To(this[key]); - } - - public AVRelation GetRelation(string key) where T : AVObject { - // All the sanity checking is done when add or remove is called. - TryGetValue(key, out AVRelation relation); - return relation ?? new AVRelation(this, key); - } - - public AVQuery GetRelationRevserseQuery(string parentClassName, string key) where T : AVObject { - if (string.IsNullOrEmpty(parentClassName)) { - throw new ArgumentNullException(nameof(parentClassName), "can not query a relation without parentClassName."); - } - if (string.IsNullOrEmpty(key)) { - throw new ArgumentNullException(nameof(key), "can not query a relation without key."); - } - return new AVQuery(parentClassName).WhereEqualTo(key, this); - } - - public virtual bool TryGetValue(string key, out T result) { - if (ContainsKey(key)) { - try { - var temp = Conversion.To(this[key]); - result = temp; - return true; - } catch (InvalidCastException) { - result = default; - return false; - } - } - result = default; - return false; - } - - public bool HasSameId(AVObject other) { - return other != null && - object.Equals(ClassName, other.ClassName) && - object.Equals(ObjectId, other.ObjectId); - } - - public bool IsDirty { - get { - return CheckIsDirty(); - } - internal set { - dirty = value; - } - } - - public bool IsKeyDirty(string key) { - return operationDict.ContainsKey(key); - } - - private bool CheckIsDirty() { - return dirty || operationDict.Count > 0; - } - - IEnumerator> IEnumerable>.GetEnumerator() { - return estimatedData.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() { - return ((IEnumerable>)this).GetEnumerator(); - } - - public static AVQuery GetQuery(string className) - where T : AVObject { - return new AVQuery(className); - } - - - static bool HasCircleReference(object obj, HashSet parents) { - if (parents.Contains(obj)) { - return true; - } - IEnumerable deps = null; - if (obj is IList) { - deps = obj as IList; - } else if (obj is IDictionary) { - deps = (obj as IDictionary).Values; - } else if (obj is AVObject) { - deps = (obj as AVObject).estimatedData.Values; - } - HashSet depParent = new HashSet(parents); - if (obj is AVObject) { - depParent.Add(obj as AVObject); - } - if (deps != null) { - foreach (object dep in deps) { - HashSet p = new HashSet(depParent); - if (HasCircleReference(dep, p)) { - return true; - } - } - } - return false; - } - - static Stack BatchObjects(IEnumerable avObjects, bool containsSelf) { - Stack batches = new Stack(); - if (containsSelf) { - batches.Push(new Batch(avObjects)); - } - - IEnumerable deps = avObjects.Select(avObj => avObj.estimatedData.Values); - do { - HashSet childSets = new HashSet(); - foreach (object dep in deps) { - IEnumerable children = null; - if (dep is IList) { - children = dep as IList; - } else if (dep is IDictionary) { - children = (dep as IDictionary).Values; - } else if (dep is AVObject && (dep as AVObject).ObjectId == null) { - // 如果依赖是 AVObject 类型并且还没有保存过,则应该遍历其依赖 - // 这里应该是从 Operation 中查找新增的对象 - children = (dep as AVObject).estimatedData.Values; - } - if (children != null) { - foreach (object child in children) { - childSets.Add(child); - } - } - } - IEnumerable depAVObjs = deps.OfType().Where(o => o.ObjectId == null); - if (depAVObjs.Any()) { - batches.Push(new Batch(depAVObjs)); - } - deps = childSets; - } while (deps != null && deps.Any()); - - return batches; - } - - /// - /// 保存 AVObject 时用到的辅助批次工具类 - /// - internal class Batch { - internal HashSet Objects { - get; set; - } - - public Batch() { - Objects = new HashSet(); - } - - public Batch(IEnumerable objects) : this() { - foreach (AVObject obj in objects) { - Objects.Add(obj); - } - } - - public override string ToString() { - StringBuilder sb = new StringBuilder(); - sb.AppendLine("----------------------------"); - foreach (AVObject obj in Objects) { - sb.AppendLine(obj.ClassName); - } - sb.AppendLine("----------------------------"); - return sb.ToString(); - } - } - } -} diff --git a/Storage/Storage/Public/AVQuery.cs b/Storage/Storage/Public/AVQuery.cs deleted file mode 100644 index a8bbd74..0000000 --- a/Storage/Storage/Public/AVQuery.cs +++ /dev/null @@ -1,347 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Net.Http; -using System.Text.RegularExpressions; -using Newtonsoft.Json; -using LeanCloud.Storage.Internal; - -namespace LeanCloud { - public class AVQuery where T : AVObject { - public string ClassName { - get; internal set; - } - - private string path; - public string Path { - get { - if (string.IsNullOrEmpty(path)) { - return $"classes/{Uri.EscapeDataString(ClassName)}"; - } - return path; - } - set { - path = value; - } - } - - /// - /// 根查询条件,默认是 and 查询,可以设置为 or 查询 - /// - QueryCombinedCondition condition; - - static AVQueryController QueryController { - get { - return AVPlugins.Instance.QueryController; - } - } - - public AVQuery() - : this(AVPlugins.Instance.SubclassingController.GetClassName(typeof(T))) { - } - - public AVQuery(string className) { - if (string.IsNullOrEmpty(className)) { - throw new ArgumentNullException(nameof(className)); - } - ClassName = className; - condition = new QueryCombinedCondition(); - } - - #region Combined Query - - public static AVQuery And(IEnumerable> queries) { - AVQuery composition = new AVQuery(); - string className = null; - if (queries != null) { - foreach (AVQuery query in queries) { - if (className != null && className != query.ClassName) { - throw new ArgumentException("All of the queries in an or query must be on the same class."); - } - composition.condition.AddCondition(query.condition); - className = query.ClassName; - } - } - composition.ClassName = className; - return composition; - } - - public static AVQuery Or(IEnumerable> queries) { - AVQuery composition = new AVQuery { - condition = new QueryCombinedCondition(QueryCombinedCondition.OR) - }; - string className = null; - if (queries != null) { - foreach (AVQuery query in queries) { - if (className != null && className != query.ClassName) { - throw new ArgumentException("All of the queries in an or query must be on the same class."); - } - composition.condition.AddCondition(query.condition); - className = query.ClassName; - } - } - composition.ClassName = className; - return composition; - } - - #endregion - - public virtual async Task> FindAsync(CancellationToken cancellationToken = default) { - IEnumerable states = await QueryController.FindAsync(this, cancellationToken); - return (from state in states - select AVObject.FromState(state, ClassName)); - } - - public virtual async Task FirstOrDefaultAsync(CancellationToken cancellationToken = default) { - IObjectState state = await QueryController.FirstAsync(this, cancellationToken); - return state == null ? default : AVObject.FromState(state, ClassName); - } - - public virtual async Task FirstAsync(CancellationToken cancellationToken = default) { - var result = await FirstOrDefaultAsync(cancellationToken); - if (result == null) { - throw new AVException(AVException.ErrorCode.ObjectNotFound, - "No results matched the query."); - } - return result; - } - - public virtual Task CountAsync(CancellationToken cancellationToken = default) { - return QueryController.CountAsync(this, cancellationToken); - } - - public virtual async Task GetAsync(string objectId, CancellationToken cancellationToken) { - WhereEqualTo("objectId", objectId); - Limit(1); - var result = await FindAsync(cancellationToken); - var first = result.FirstOrDefault(); - if (first == null) { - throw new AVException(AVException.ErrorCode.ObjectNotFound, - "Object with the given objectId not found."); - } - return first; - } - - #region CQL - - /// - /// 执行 CQL 查询 - /// - /// CQL 语句 - /// CancellationToken - /// 返回符合条件的对象集合 - public static Task> DoCloudQueryAsync(string cql, CancellationToken cancellationToken = default) { - var queryString = $"cloudQuery?cql={Uri.EscapeDataString(cql)}"; - return RebuildObjectFromCloudQueryResult(queryString); - } - - /// - /// 执行 CQL 查询 - /// - /// 带有占位符的模板 cql 语句 - /// 占位符对应的参数数组 - /// - public static Task> DoCloudQueryAsync(string cqlTeamplate, params object[] pvalues) { - string queryStringTemplate = "cloudQuery?cql={0}&pvalues={1}"; - string pSrting = JsonConvert.SerializeObject(pvalues); - string queryString = string.Format(queryStringTemplate, Uri.EscapeDataString(cqlTeamplate), Uri.EscapeDataString(pSrting)); - - return RebuildObjectFromCloudQueryResult(queryString); - } - - internal static async Task> RebuildObjectFromCloudQueryResult(string queryString) { - var command = new AVCommand { - Path = queryString, - Method = HttpMethod.Get - }; - var result = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, CancellationToken.None); - var items = result.Item2["results"] as IList; - var className = result.Item2["className"].ToString(); - - IEnumerable states = (from item in items - select AVObjectCoder.Instance.Decode(item as IDictionary, AVDecoder.Instance)); - - return from state in states - select AVObject.FromState(state, className); - } - - #endregion - - public AVQuery OrderBy(string key) { - condition.OrderBy(key); - return this; - } - - public AVQuery OrderByDescending(string key) { - condition.OrderByDescending(key); - return this; - } - - public AVQuery Include(string key) { - condition.Include(key); - return this; - } - - public AVQuery Select(string key) { - condition.Select(key); - return this; - } - - public AVQuery Skip(int count) { - condition.Skip(count); - return this; - } - - public AVQuery Limit(int count) { - condition.Limit(count); - return this; - } - - #region Where - - public AVQuery WhereContainedIn(string key, IEnumerable values) { - condition.WhereContainedIn(key, values); - return this; - } - - public AVQuery WhereContainsAll(string key, IEnumerable values) { - condition.WhereContainsAll(key, values); - return this; - } - - public AVQuery WhereContains(string key, string substring) { - condition.WhereContains(key, substring); - return this; - } - - public AVQuery WhereDoesNotExist(string key) { - condition.WhereDoesNotExist(key); - return this; - } - - public AVQuery WhereDoesNotMatchQuery(string key, AVQuery query) where TOther : AVObject { - condition.WhereDoesNotMatchQuery(key, query); - return this; - } - - public AVQuery WhereEndsWith(string key, string suffix) { - condition.WhereEndsWith(key, suffix); - return this; - } - - public AVQuery WhereEqualTo(string key, object value) { - condition.WhereEqualTo(key, value); - return this; - } - - public AVQuery WhereSizeEqualTo(string key, uint size) { - condition.WhereSizeEqualTo(key, size); - return this; - } - - public AVQuery WhereExists(string key) { - condition.WhereExists(key); - return this; - } - - public AVQuery WhereGreaterThan(string key, object value) { - condition.WhereGreaterThan(key, value); - return this; - } - - public AVQuery WhereGreaterThanOrEqualTo(string key, object value) { - condition.WhereGreaterThanOrEqualTo(key, value); - return this; - } - - public AVQuery WhereLessThan(string key, object value) { - condition.WhereLessThan(key, value); - return this; - } - - public AVQuery WhereLessThanOrEqualTo(string key, object value) { - condition.WhereLessThanOrEqualTo(key, value); - return this; - } - - public AVQuery WhereMatches(string key, Regex regex, string modifiers) { - condition.WhereMatches(key, regex, modifiers); - return this; - } - - public AVQuery WhereMatches(string key, Regex regex) { - return WhereMatches(key, regex, null); - } - - public AVQuery WhereMatches(string key, string pattern, string modifiers) { - return WhereMatches(key, new Regex(pattern, RegexOptions.ECMAScript), modifiers); - } - - public AVQuery WhereMatches(string key, string pattern) { - return WhereMatches(key, pattern, null); - } - - public AVQuery WhereMatchesKeyInQuery(string key, string keyInQuery, AVQuery query) where TOther : AVObject { - condition.WhereMatchesKeyInQuery(key, keyInQuery, query); - return this; - } - - public AVQuery WhereDoesNotMatchesKeyInQuery(string key, string keyInQuery, AVQuery query) where TOther : AVObject { - condition.WhereDoesNotMatchesKeyInQuery(key, keyInQuery, query); - return this; - } - - public AVQuery WhereMatchesQuery(string key, AVQuery query) where TOther : AVObject { - condition.WhereMatchesQuery(key, query); - return this; - } - - public AVQuery WhereNear(string key, AVGeoPoint point) { - condition.WhereNear(key, point); - return this; - } - - public AVQuery WhereNotContainedIn(string key, IEnumerable values) { - condition.WhereNotContainedIn(key, values); - return this; - } - - public AVQuery WhereNotEqualTo(string key, object value) { - condition.WhereNotEqualTo(key, value); - return this; - } - - public AVQuery WhereStartsWith(string key, string suffix) { - condition.WhereStartsWith(key, suffix); - return this; - } - - public AVQuery WhereWithinGeoBox(string key, AVGeoPoint southwest, AVGeoPoint northeast) { - condition.WhereWithinGeoBox(key, southwest, northeast); - return this; - } - - public AVQuery WhereWithinDistance(string key, AVGeoPoint point, AVGeoDistance maxDistance) { - condition.WhereWithinDistance(key, point, maxDistance); - return this; - } - - public AVQuery WhereRelatedTo(AVObject parent, string key) { - condition.WhereRelatedTo(parent, key); - return this; - } - - #endregion - - public IDictionary BuildParameters(string className = null) { - return condition.BuildParameters(className); - } - - public IDictionary BuildWhere() { - return condition.ToJSON(); - } - } -} diff --git a/Storage/Storage/Public/AVQueryExtensions.cs b/Storage/Storage/Public/AVQueryExtensions.cs deleted file mode 100644 index e15be08..0000000 --- a/Storage/Storage/Public/AVQueryExtensions.cs +++ /dev/null @@ -1,780 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; - -namespace LeanCloud -{ - /// - /// Provides extension methods for to support - /// Linq-style queries. - /// - public static class AVQueryExtensions - { - private static readonly MethodInfo getMethod; - private static readonly MethodInfo stringContains; - private static readonly MethodInfo stringStartsWith; - private static readonly MethodInfo stringEndsWith; - private static readonly MethodInfo containsMethod; - private static readonly MethodInfo notContainsMethod; - private static readonly MethodInfo containsKeyMethod; - private static readonly MethodInfo notContainsKeyMethod; - private static readonly Dictionary functionMappings; - static AVQueryExtensions() - { - getMethod = GetMethod(obj => obj.Get(null)).GetGenericMethodDefinition(); - stringContains = GetMethod(str => str.Contains(null)); - stringStartsWith = GetMethod(str => str.StartsWith(null)); - stringEndsWith = GetMethod(str => str.EndsWith(null)); - functionMappings = new Dictionary { - { - stringContains, - GetMethod>(q => q.WhereContains(null, null)) - }, - { - stringStartsWith, - GetMethod>(q => q.WhereStartsWith(null, null)) - }, - { - stringEndsWith, - GetMethod>(q => q.WhereEndsWith(null,null)) - }, - }; - containsMethod = GetMethod( - o => AVQueryExtensions.ContainsStub(null, null)).GetGenericMethodDefinition(); - notContainsMethod = GetMethod( - o => AVQueryExtensions.NotContainsStub(null, null)) - .GetGenericMethodDefinition(); - - containsKeyMethod = GetMethod(o => AVQueryExtensions.ContainsKeyStub(null, null)); - notContainsKeyMethod = GetMethod( - o => AVQueryExtensions.NotContainsKeyStub(null, null)); - } - - /// - /// Gets a MethodInfo for a top-level method call. - /// - private static MethodInfo GetMethod(Expression> expression) - { - return (expression.Body as MethodCallExpression).Method; - } - - /// - /// When a query is normalized, this is a placeholder to indicate we should - /// add a WhereContainedIn() clause. - /// - private static bool ContainsStub(object collection, T value) - { - throw new NotImplementedException( - "Exists only for expression translation as a placeholder."); - } - - /// - /// When a query is normalized, this is a placeholder to indicate we should - /// add a WhereNotContainedIn() clause. - /// - private static bool NotContainsStub(object collection, T value) - { - throw new NotImplementedException( - "Exists only for expression translation as a placeholder."); - } - - /// - /// When a query is normalized, this is a placeholder to indicate that we should - /// add a WhereExists() clause. - /// - private static bool ContainsKeyStub(AVObject obj, string key) - { - throw new NotImplementedException( - "Exists only for expression translation as a placeholder."); - } - - /// - /// When a query is normalized, this is a placeholder to indicate that we should - /// add a WhereDoesNotExist() clause. - /// - private static bool NotContainsKeyStub(AVObject obj, string key) - { - throw new NotImplementedException( - "Exists only for expression translation as a placeholder."); - } - - /// - /// Evaluates an expression and throws if the expression has components that can't be - /// evaluated (e.g. uses the parameter that's only represented by an object on the server). - /// - private static object GetValue(Expression exp) - { - try - { - return Expression.Lambda( - typeof(Func<>).MakeGenericType(exp.Type), exp).Compile().DynamicInvoke(); - } - catch (Exception e) - { - throw new InvalidOperationException("Unable to evaluate expression: " + exp, e); - } - } - - /// - /// Checks whether the MethodCallExpression is a call to AVObject.Get(), - /// which is the call we normalize all indexing into the AVObject to. - /// - private static bool IsAVObjectGet(MethodCallExpression node) - { - if (node == null || node.Object == null) - { - return false; - } - if (!typeof(AVObject).GetTypeInfo().IsAssignableFrom(node.Object.Type.GetTypeInfo())) - { - return false; - } - return node.Method.IsGenericMethod && node.Method.GetGenericMethodDefinition() == getMethod; - } - - - /// - /// Visits an Expression, converting AVObject.Get/AVObject[]/AVObject.Property, - /// and nested indices into a single call to AVObject.Get() with a "field path" like - /// "foo.bar.baz" - /// - private class ObjectNormalizer : ExpressionVisitor - { - protected override Expression VisitIndex(IndexExpression node) - { - var visitedObject = Visit(node.Object); - var indexer = visitedObject as MethodCallExpression; - if (IsAVObjectGet(indexer)) - { - var indexValue = GetValue(node.Arguments[0]) as string; - if (indexValue == null) - { - throw new InvalidOperationException("Index must be a string"); - } - var newPath = GetValue(indexer.Arguments[0]) + "." + indexValue; - return Expression.Call(indexer.Object, - getMethod.MakeGenericMethod(node.Type), - Expression.Constant(newPath, typeof(string))); - } - return base.VisitIndex(node); - } - - /// - /// Check for a AVFieldName attribute and use that as the path component, turning - /// properties like foo.ObjectId into foo.Get("objectId") - /// - protected override Expression VisitMember(MemberExpression node) - { - var fieldName = node.Member.GetCustomAttribute(); - if (fieldName != null && - typeof(AVObject).GetTypeInfo().IsAssignableFrom(node.Expression.Type.GetTypeInfo())) - { - var newPath = fieldName.FieldName; - return Expression.Call(node.Expression, - getMethod.MakeGenericMethod(node.Type), - Expression.Constant(newPath, typeof(string))); - } - return base.VisitMember(node); - } - - /// - /// If a AVObject.Get() call has been cast, just change the generic parameter. - /// - protected override Expression VisitUnary(UnaryExpression node) - { - var methodCall = Visit(node.Operand) as MethodCallExpression; - if ((node.NodeType == ExpressionType.Convert || - node.NodeType == ExpressionType.ConvertChecked) && - IsAVObjectGet(methodCall)) - { - return Expression.Call(methodCall.Object, - getMethod.MakeGenericMethod(node.Type), - methodCall.Arguments); - } - return base.VisitUnary(node); - } - - protected override Expression VisitMethodCall(MethodCallExpression node) - { - if (node.Method.Name == "get_Item" && node.Object is ParameterExpression) - { - var indexPath = GetValue(node.Arguments[0]) as string; - return Expression.Call(node.Object, - getMethod.MakeGenericMethod(typeof(object)), - Expression.Constant(indexPath, typeof(string))); - } - - if (node.Method.Name == "get_Item" || IsAVObjectGet(node)) - { - var visitedObject = Visit(node.Object); - var indexer = visitedObject as MethodCallExpression; - if (IsAVObjectGet(indexer)) - { - var indexValue = GetValue(node.Arguments[0]) as string; - if (indexValue == null) - { - throw new InvalidOperationException("Index must be a string"); - } - var newPath = GetValue(indexer.Arguments[0]) + "." + indexValue; - return Expression.Call(indexer.Object, - getMethod.MakeGenericMethod(node.Type), - Expression.Constant(newPath, typeof(string))); - } - } - return base.VisitMethodCall(node); - } - } - - /// - /// Normalizes Where expressions. - /// - private class WhereNormalizer : ExpressionVisitor - { - - /// - /// Normalizes binary operators. <, >, <=, >= !=, and == - /// This puts the AVObject.Get() on the left side of the operation - /// (reversing it if necessary), and normalizes the AVObject.Get() - /// - protected override Expression VisitBinary(BinaryExpression node) - { - var leftTransformed = new ObjectNormalizer().Visit(node.Left) as MethodCallExpression; - var rightTransformed = new ObjectNormalizer().Visit(node.Right) as MethodCallExpression; - - MethodCallExpression objectExpression; - Expression filterExpression; - bool inverted; - if (leftTransformed != null) - { - objectExpression = leftTransformed; - filterExpression = node.Right; - inverted = false; - } - else - { - objectExpression = rightTransformed; - filterExpression = node.Left; - inverted = true; - } - - try - { - switch (node.NodeType) - { - case ExpressionType.GreaterThan: - if (inverted) - { - return Expression.LessThan(objectExpression, filterExpression); - } - else - { - return Expression.GreaterThan(objectExpression, filterExpression); - } - case ExpressionType.GreaterThanOrEqual: - if (inverted) - { - return Expression.LessThanOrEqual(objectExpression, filterExpression); - } - else - { - return Expression.GreaterThanOrEqual(objectExpression, filterExpression); - } - case ExpressionType.LessThan: - if (inverted) - { - return Expression.GreaterThan(objectExpression, filterExpression); - } - else - { - return Expression.LessThan(objectExpression, filterExpression); - } - case ExpressionType.LessThanOrEqual: - if (inverted) - { - return Expression.GreaterThanOrEqual(objectExpression, filterExpression); - } - else - { - return Expression.LessThanOrEqual(objectExpression, filterExpression); - } - case ExpressionType.Equal: - return Expression.Equal(objectExpression, filterExpression); - case ExpressionType.NotEqual: - return Expression.NotEqual(objectExpression, filterExpression); - } - } - catch (ArgumentException) - { - throw new InvalidOperationException("Operation not supported: " + node); - } - return base.VisitBinary(node); - } - - /// - /// If a ! operator is used, this removes the ! and instead calls the equivalent - /// function (so e.g. == becomes !=, < becomes >=, Contains becomes NotContains) - /// - protected override Expression VisitUnary(UnaryExpression node) - { - // Normalizes inversion - if (node.NodeType == ExpressionType.Not) - { - var visitedOperand = Visit(node.Operand); - var binaryOperand = visitedOperand as BinaryExpression; - if (binaryOperand != null) - { - switch (binaryOperand.NodeType) - { - case ExpressionType.GreaterThan: - return Expression.LessThanOrEqual(binaryOperand.Left, binaryOperand.Right); - case ExpressionType.GreaterThanOrEqual: - return Expression.LessThan(binaryOperand.Left, binaryOperand.Right); - case ExpressionType.LessThan: - return Expression.GreaterThanOrEqual(binaryOperand.Left, binaryOperand.Right); - case ExpressionType.LessThanOrEqual: - return Expression.GreaterThan(binaryOperand.Left, binaryOperand.Right); - case ExpressionType.Equal: - return Expression.NotEqual(binaryOperand.Left, binaryOperand.Right); - case ExpressionType.NotEqual: - return Expression.Equal(binaryOperand.Left, binaryOperand.Right); - } - } - - var methodCallOperand = visitedOperand as MethodCallExpression; - if (methodCallOperand != null) - { - if (methodCallOperand.Method.IsGenericMethod) - { - if (methodCallOperand.Method.GetGenericMethodDefinition() == containsMethod) - { - var genericNotContains = notContainsMethod.MakeGenericMethod( - methodCallOperand.Method.GetGenericArguments()); - return Expression.Call(genericNotContains, methodCallOperand.Arguments.ToArray()); - } - if (methodCallOperand.Method.GetGenericMethodDefinition() == notContainsMethod) - { - var genericContains = containsMethod.MakeGenericMethod( - methodCallOperand.Method.GetGenericArguments()); - return Expression.Call(genericContains, methodCallOperand.Arguments.ToArray()); - } - } - if (methodCallOperand.Method == containsKeyMethod) - { - return Expression.Call(notContainsKeyMethod, methodCallOperand.Arguments.ToArray()); - } - if (methodCallOperand.Method == notContainsKeyMethod) - { - return Expression.Call(containsKeyMethod, methodCallOperand.Arguments.ToArray()); - } - } - } - return base.VisitUnary(node); - } - - /// - /// Normalizes .Equals into == and Contains() into the appropriate stub. - /// - protected override Expression VisitMethodCall(MethodCallExpression node) - { - // Convert .Equals() into == - if (node.Method.Name == "Equals" && - node.Method.ReturnType == typeof(bool) && - node.Method.GetParameters().Length == 1) - { - var obj = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; - var parameter = new ObjectNormalizer().Visit(node.Arguments[0]) as MethodCallExpression; - if ((IsAVObjectGet(obj) && (obj.Object is ParameterExpression)) || - (IsAVObjectGet(parameter) && (parameter.Object is ParameterExpression))) - { - return Expression.Equal(node.Object, node.Arguments[0]); - } - } - - // Convert the .Contains() into a ContainsStub - if (node.Method != stringContains && - node.Method.Name == "Contains" && - node.Method.ReturnType == typeof(bool) && - node.Method.GetParameters().Length <= 2) - { - var collection = node.Method.GetParameters().Length == 1 ? - node.Object : - node.Arguments[0]; - var parameterIndex = node.Method.GetParameters().Length - 1; - var parameter = new ObjectNormalizer().Visit(node.Arguments[parameterIndex]) - as MethodCallExpression; - if (IsAVObjectGet(parameter) && (parameter.Object is ParameterExpression)) - { - var genericContains = containsMethod.MakeGenericMethod(parameter.Type); - return Expression.Call(genericContains, collection, parameter); - } - var target = new ObjectNormalizer().Visit(collection) as MethodCallExpression; - var element = node.Arguments[parameterIndex]; - if (IsAVObjectGet(target) && (target.Object is ParameterExpression)) - { - var genericContains = containsMethod.MakeGenericMethod(element.Type); - return Expression.Call(genericContains, target, element); - } - } - - // Convert obj["foo.bar"].ContainsKey("baz") into obj.ContainsKey("foo.bar.baz") - if (node.Method.Name == "ContainsKey" && - node.Method.ReturnType == typeof(bool) && - node.Method.GetParameters().Length == 1) - { - var getter = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; - Expression target = null; - string path = null; - if (IsAVObjectGet(getter) && getter.Object is ParameterExpression) - { - target = getter.Object; - path = GetValue(getter.Arguments[0]) + "." + GetValue(node.Arguments[0]); - return Expression.Call(containsKeyMethod, target, Expression.Constant(path)); - } - else if (node.Object is ParameterExpression) - { - target = node.Object; - path = GetValue(node.Arguments[0]) as string; - } - if (target != null && path != null) - { - return Expression.Call(containsKeyMethod, target, Expression.Constant(path)); - } - } - return base.VisitMethodCall(node); - } - } - - /// - /// Converts a normalized method call expression into the appropriate AVQuery clause. - /// - private static AVQuery WhereMethodCall( - this AVQuery source, Expression> expression, MethodCallExpression node) - where T : AVObject - { - if (IsAVObjectGet(node) && (node.Type == typeof(bool) || node.Type == typeof(bool?))) - { - // This is a raw boolean field access like 'where obj.Get("foo")' - return source.WhereEqualTo(GetValue(node.Arguments[0]) as string, true); - } - - MethodInfo translatedMethod; - if (functionMappings.TryGetValue(node.Method, out translatedMethod)) - { - var objTransformed = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; - if (!(IsAVObjectGet(objTransformed) && - objTransformed.Object == expression.Parameters[0])) - { - throw new InvalidOperationException( - "The left-hand side of a supported function call must be a AVObject field access."); - } - var fieldPath = GetValue(objTransformed.Arguments[0]); - var containedIn = GetValue(node.Arguments[0]); - var queryType = translatedMethod.DeclaringType.GetGenericTypeDefinition() - .MakeGenericType(typeof(T)); - translatedMethod = ReflectionHelpers.GetMethod(queryType, - translatedMethod.Name, - translatedMethod.GetParameters().Select(p => p.ParameterType).ToArray()); - return translatedMethod.Invoke(source, new[] { fieldPath, containedIn }) as AVQuery; - } - - if (node.Arguments[0] == expression.Parameters[0]) - { - // obj.ContainsKey("foo") --> query.WhereExists("foo") - if (node.Method == containsKeyMethod) - { - return source.WhereExists(GetValue(node.Arguments[1]) as string); - } - // !obj.ContainsKey("foo") --> query.WhereDoesNotExist("foo") - if (node.Method == notContainsKeyMethod) - { - return source.WhereDoesNotExist(GetValue(node.Arguments[1]) as string); - } - } - - if (node.Method.IsGenericMethod) - { - if (node.Method.GetGenericMethodDefinition() == containsMethod) - { - // obj.Get>("path").Contains(someValue) - if (IsAVObjectGet(node.Arguments[0] as MethodCallExpression)) - { - return source.WhereEqualTo( - GetValue(((MethodCallExpression)node.Arguments[0]).Arguments[0]) as string, - GetValue(node.Arguments[1])); - } - // someList.Contains(obj.Get("path")) - if (IsAVObjectGet(node.Arguments[1] as MethodCallExpression)) - { - var collection = GetValue(node.Arguments[0]) as System.Collections.IEnumerable; - return source.WhereContainedIn( - GetValue(((MethodCallExpression)node.Arguments[1]).Arguments[0]) as string, - collection.Cast()); - } - } - - if (node.Method.GetGenericMethodDefinition() == notContainsMethod) - { - // !obj.Get>("path").Contains(someValue) - if (IsAVObjectGet(node.Arguments[0] as MethodCallExpression)) - { - return source.WhereNotEqualTo( - GetValue(((MethodCallExpression)node.Arguments[0]).Arguments[0]) as string, - GetValue(node.Arguments[1])); - } - // !someList.Contains(obj.Get("path")) - if (IsAVObjectGet(node.Arguments[1] as MethodCallExpression)) - { - var collection = GetValue(node.Arguments[0]) as System.Collections.IEnumerable; - return source.WhereNotContainedIn( - GetValue(((MethodCallExpression)node.Arguments[1]).Arguments[0]) as string, - collection.Cast()); - } - } - } - throw new InvalidOperationException(node.Method + " is not a supported method call in a where expression."); - } - - /// - /// Converts a normalized binary expression into the appropriate AVQuery clause. - /// - private static AVQuery WhereBinaryExpression( - this AVQuery source, Expression> expression, BinaryExpression node) - where T : AVObject - { - var leftTransformed = new ObjectNormalizer().Visit(node.Left) as MethodCallExpression; - - if (!(IsAVObjectGet(leftTransformed) && - leftTransformed.Object == expression.Parameters[0])) - { - throw new InvalidOperationException( - "Where expressions must have one side be a field operation on a AVObject."); - } - - var fieldPath = GetValue(leftTransformed.Arguments[0]) as string; - var filterValue = GetValue(node.Right); - - switch (node.NodeType) - { - case ExpressionType.GreaterThan: - return source.WhereGreaterThan(fieldPath, filterValue); - case ExpressionType.GreaterThanOrEqual: - return source.WhereGreaterThanOrEqualTo(fieldPath, filterValue); - case ExpressionType.LessThan: - return source.WhereLessThan(fieldPath, filterValue); - case ExpressionType.LessThanOrEqual: - return source.WhereLessThanOrEqualTo(fieldPath, filterValue); - case ExpressionType.Equal: - return source.WhereEqualTo(fieldPath, filterValue); - case ExpressionType.NotEqual: - return source.WhereNotEqualTo(fieldPath, filterValue); - default: - throw new InvalidOperationException( - "Where expressions do not support this operator."); - } - } - - /// - /// Filters a query based upon the predicate provided. - /// - /// The type of AVObject being queried for. - /// The base to which - /// the predicate will be added. - /// A function to test each AVObject for a condition. - /// The predicate must be able to be represented by one of the standard Where - /// functions on AVQuery - /// A new AVQuery whose results will match the given predicate as - /// well as the Source's filters. - public static AVQuery Where( - this AVQuery source, Expression> predicate) - where TSource : AVObject - { - // Handle top-level logic operators && and || - var binaryExpression = predicate.Body as BinaryExpression; - if (binaryExpression != null) - { - if (binaryExpression.NodeType == ExpressionType.AndAlso) - { - return source - .Where(Expression.Lambda>( - binaryExpression.Left, predicate.Parameters)) - .Where(Expression.Lambda>( - binaryExpression.Right, predicate.Parameters)); - } - if (binaryExpression.NodeType == ExpressionType.OrElse) - { - var left = source.Where(Expression.Lambda>( - binaryExpression.Left, predicate.Parameters)); - var right = source.Where(Expression.Lambda>( - binaryExpression.Right, predicate.Parameters)); - return left.Or(right); - } - } - - var normalized = new WhereNormalizer().Visit(predicate.Body); - - var methodCallExpr = normalized as MethodCallExpression; - if (methodCallExpr != null) - { - return source.WhereMethodCall(predicate, methodCallExpr); - } - - var binaryExpr = normalized as BinaryExpression; - if (binaryExpr != null) - { - return source.WhereBinaryExpression(predicate, binaryExpr); - } - - var unaryExpr = normalized as UnaryExpression; - if (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Not) - { - var node = unaryExpr.Operand as MethodCallExpression; - if (IsAVObjectGet(node) && (node.Type == typeof(bool) || node.Type == typeof(bool?))) - { - // This is a raw boolean field access like 'where !obj.Get("foo")' - return source.WhereNotEqualTo(GetValue(node.Arguments[0]) as string, true); - } - } - - throw new InvalidOperationException( - "Encountered an unsupported expression for AVQueries."); - } - - /// - /// Normalizes an OrderBy's keySelector expression and then extracts the path - /// from the AVObject.Get() call. - /// - private static string GetOrderByPath( - Expression> keySelector) - { - string result = null; - var normalized = new ObjectNormalizer().Visit(keySelector.Body); - var callExpr = normalized as MethodCallExpression; - if (IsAVObjectGet(callExpr) && callExpr.Object == keySelector.Parameters[0]) - { - // We're operating on the parameter - result = GetValue(callExpr.Arguments[0]) as string; - } - if (result == null) - { - throw new InvalidOperationException( - "OrderBy expression must be a field access on a AVObject."); - } - return result; - } - - /// - /// Orders a query based upon the key selector provided. - /// - /// The type of AVObject being queried for. - /// The type of key returned by keySelector. - /// The query to order. - /// A function to extract a key from the AVObject. - /// A new AVQuery based on Source whose results will be ordered by - /// the key specified in the keySelector. - public static AVQuery OrderBy( - this AVQuery source, Expression> keySelector) - where TSource : AVObject - { - return source.OrderBy(GetOrderByPath(keySelector)); - } - - /// - /// Orders a query based upon the key selector provided. - /// - /// The type of AVObject being queried for. - /// The type of key returned by keySelector. - /// The query to order. - /// A function to extract a key from the AVObject. - /// A new AVQuery based on Source whose results will be ordered by - /// the key specified in the keySelector. - public static AVQuery OrderByDescending( - this AVQuery source, Expression> keySelector) - where TSource : AVObject - { - return source.OrderByDescending(GetOrderByPath(keySelector)); - } - - /// - /// Correlates the elements of two queries based on matching keys. - /// - /// The type of AVObjects of the first query. - /// The type of AVObjects of the second query. - /// The type of the keys returned by the key selector - /// functions. - /// The type of the result. This must match either - /// TOuter or TInner - /// The first query to join. - /// The query to join to the first query. - /// A function to extract a join key from the results of - /// the first query. - /// A function to extract a join key from the results of - /// the second query. - /// A function to select either the outer or inner query - /// result to determine which query is the base query. - /// A new AVQuery with a WhereMatchesQuery or WhereMatchesKeyInQuery - /// clause based upon the query indicated in the . - public static AVQuery Join( - this AVQuery outer, - AVQuery inner, - Expression> outerKeySelector, - Expression> innerKeySelector, - Expression> resultSelector) - where TOuter : AVObject - where TInner : AVObject - where TResult : AVObject - { - // resultSelector must select either the inner object or the outer object. If it's the inner - // object, reverse the query. - if (resultSelector.Body == resultSelector.Parameters[1]) - { - // The inner object was selected. - return inner.Join( - outer, - innerKeySelector, - outerKeySelector, - (i, o) => i) as AVQuery; - } - if (resultSelector.Body != resultSelector.Parameters[0]) - { - throw new InvalidOperationException("Joins must select either the outer or inner object."); - } - - // Normalize both selectors - Expression outerNormalized = new ObjectNormalizer().Visit(outerKeySelector.Body); - Expression innerNormalized = new ObjectNormalizer().Visit(innerKeySelector.Body); - MethodCallExpression outerAsGet = outerNormalized as MethodCallExpression; - MethodCallExpression innerAsGet = innerNormalized as MethodCallExpression; - if (IsAVObjectGet(outerAsGet) && outerAsGet.Object == outerKeySelector.Parameters[0]) - { - var outerKey = GetValue(outerAsGet.Arguments[0]) as string; - - if (IsAVObjectGet(innerAsGet) && innerAsGet.Object == innerKeySelector.Parameters[0]) - { - // Both are key accesses, so treat this as a WhereMatchesKeyInQuery - var innerKey = GetValue(innerAsGet.Arguments[0]) as string; - return outer.WhereMatchesKeyInQuery(outerKey, innerKey, inner) as AVQuery; - } - - if (innerKeySelector.Body == innerKeySelector.Parameters[0]) - { - // The inner selector is on the result of the query itself, so treat this as a - // WhereMatchesQuery - return outer.WhereMatchesQuery(outerKey, inner) as AVQuery; - } - throw new InvalidOperationException( - "The key for the joined object must be a AVObject or a field access " + - "on the AVObject."); - } - - // TODO (hallucinogen): If we ever support "and" queries fully and/or support a "where this object - // matches some key in some other query" (as opposed to requiring a key on this query), we - // can add support for even more types of joins. - - throw new InvalidOperationException( - "The key for the selected object must be a field access on the AVObject."); - } - } -} diff --git a/Storage/Storage/Public/AVRelation.cs b/Storage/Storage/Public/AVRelation.cs deleted file mode 100644 index 1be6f4b..0000000 --- a/Storage/Storage/Public/AVRelation.cs +++ /dev/null @@ -1,147 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq.Expressions; - -namespace LeanCloud { - /// - /// A common base class for AVRelations. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public abstract class AVRelationBase : IJsonConvertible { - private AVObject parent; - private string key; - private string targetClassName; - - internal AVRelationBase(AVObject parent, string key) { - EnsureParentAndKey(parent, key); - } - - internal AVRelationBase(AVObject parent, string key, string targetClassName) - : this(parent, key) { - this.targetClassName = targetClassName; - } - - internal static ObjectSubclassingController SubclassingController { - get { - return AVPlugins.Instance.SubclassingController; - } - } - - internal void EnsureParentAndKey(AVObject parent, string key) { - this.parent = this.parent ?? parent; - this.key = this.key ?? key; - Debug.Assert(this.parent == parent, "Relation retrieved from two different objects"); - Debug.Assert(this.key == key, "Relation retrieved from two different keys"); - } - - internal void Add(AVObject obj) { - var change = new AVRelationOperation(new[] { obj }, null); - parent.PerformOperation(key, change); - targetClassName = change.TargetClassName; - } - - internal void Remove(AVObject obj) { - var change = new AVRelationOperation(null, new[] { obj }); - parent.PerformOperation(key, change); - targetClassName = change.TargetClassName; - } - - IDictionary IJsonConvertible.ToJSON() { - return new Dictionary { - { "__type", "Relation"}, - { "className", targetClassName} - }; - } - - internal AVQuery GetQuery() where T : AVObject { - if (targetClassName != null) { - return new AVQuery(targetClassName) - .WhereRelatedTo(parent, key); - } - - return new AVQuery(parent.ClassName) - .WhereRelatedTo(parent, key); - } - - internal AVQuery GetReverseQuery(T target) where T : AVObject { - if (target.ObjectId == null) { - throw new ArgumentNullException(nameof(target), "can not query a relation without target ObjectId."); - } - - return new AVQuery(parent.ClassName).WhereEqualTo(key, target); - } - - internal string TargetClassName { - get { - return targetClassName; - } - set { - targetClassName = value; - } - } - - /// - /// Produces the proper AVRelation<T> instance for the given classname. - /// - internal static AVRelationBase CreateRelation(AVObject parent, - string key, - string targetClassName) { - var targetType = SubclassingController.GetType(targetClassName) ?? typeof(AVObject); - - Expression>> createRelationExpr = - () => CreateRelation(parent, key, targetClassName); - var createRelationMethod = - ((MethodCallExpression)createRelationExpr.Body) - .Method - .GetGenericMethodDefinition() - .MakeGenericMethod(targetType); - return (AVRelationBase)createRelationMethod.Invoke(null, new object[] { parent, key, targetClassName }); - } - - private static AVRelation CreateRelation(AVObject parent, string key, string targetClassName) - where T : AVObject { - return new AVRelation(parent, key, targetClassName); - } - } - - /// - /// Provides access to all of the children of a many-to-many relationship. Each instance of - /// AVRelation is associated with a particular parent and key. - /// - /// The type of the child objects. - public sealed class AVRelation : AVRelationBase where T : AVObject { - - internal AVRelation(AVObject parent, string key) : base(parent, key) { } - - internal AVRelation(AVObject parent, string key, string targetClassName) - : base(parent, key, targetClassName) { } - - /// - /// Adds an object to this relation. The object must already have been saved. - /// - /// The object to add. - public void Add(T obj) { - base.Add(obj); - } - - /// - /// Removes an object from this relation. The object must already have been saved. - /// - /// The object to remove. - public void Remove(T obj) { - base.Remove(obj); - } - - /// - /// Gets a query that can be used to query the objects in this relation. - /// - public AVQuery Query { - get { - return base.GetQuery(); - } - } - } -} diff --git a/Storage/Storage/Public/AVRole.cs b/Storage/Storage/Public/AVRole.cs deleted file mode 100644 index df107c8..0000000 --- a/Storage/Storage/Public/AVRole.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Text.RegularExpressions; - -namespace LeanCloud { - /// - /// 角色类 - /// - [AVClassName("_Role")] - public class AVRole : AVObject { - private static readonly Regex namePattern = new Regex("^[0-9a-zA-Z_\\- ]+$"); - - /// - /// Constructs a new AVRole. You must assign a name and ACL to the role. - /// - public AVRole() { } - - /// - /// Constructs a new AVRole with the given name. - /// - /// The name of the role to create. - /// The ACL for this role. Roles must have an ACL. - public AVRole(string name, AVACL acl) - : this() { - Name = name; - ACL = acl; - } - - /// - /// Gets the name of the role. - /// - [AVFieldName("name")] - public string Name { - get { - return GetProperty("Name"); - } - set { - SetProperty(value, "Name"); - } - } - - /// - /// Gets the for the s that are - /// direct children of this role. These users are granted any privileges that - /// this role has been granted (e.g. read or write access through ACLs). You can - /// add or remove child users from the role through this relation. - /// - [AVFieldName("users")] - public AVRelation Users { - get { - return GetRelationProperty("Users"); - } - } - - /// - /// Gets the for the s that are - /// direct children of this role. These roles' users are granted any privileges that - /// this role has been granted (e.g. read or write access through ACLs). You can - /// add or remove child roles from the role through this relation. - /// - [AVFieldName("roles")] - public AVRelation Roles { - get { - return GetRelationProperty("Roles"); - } - } - - internal override void OnSettingValue(ref string key, ref object value) { - base.OnSettingValue(ref key, ref value); - if (key == "name") { - if (ObjectId != null) { - throw new InvalidOperationException( - "A role's name can only be set before it has been saved."); - } - if (!(value is string)) { - throw new ArgumentException("A role's name must be a string.", nameof(value)); - } - if (!namePattern.IsMatch((string)value)) { - throw new ArgumentException( - "A role's name can only contain alphanumeric characters, _, -, and spaces.", nameof(value)); - } - } - } - - /// - /// Gets a over the Role collection. - /// - public static AVQuery Query { - get { - return new AVQuery(); - } - } - } -} diff --git a/Storage/Storage/Public/AVUploadProgressEventArgs.cs b/Storage/Storage/Public/AVUploadProgressEventArgs.cs deleted file mode 100644 index 5134fab..0000000 --- a/Storage/Storage/Public/AVUploadProgressEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace LeanCloud { - /// - /// Represents upload progress. - /// - public class AVUploadProgressEventArgs : EventArgs { - - /// - /// Gets the progress (a number between 0.0 and 1.0) of an upload. - /// - public double Progress { get; set; } - } -} diff --git a/Storage/Storage/Public/AVUser.cs b/Storage/Storage/Public/AVUser.cs deleted file mode 100644 index 5521060..0000000 --- a/Storage/Storage/Public/AVUser.cs +++ /dev/null @@ -1,646 +0,0 @@ -using LeanCloud.Storage.Internal; -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud { - /// - /// 用户类 - /// - [AVClassName("_User")] - public class AVUser : AVObject { - internal static AVUserController UserController { - get { - return AVPlugins.Instance.UserController; - } - } - - /// - /// 获取当前用户 - /// - public static AVUser CurrentUser { - get; - internal set; - } - - /// - /// 创建一个 AVUser 查询对象 - /// - public static AVQuery Query { - get { - return new AVQuery(); - } - } - - - /// - /// 用户名 - /// - [AVFieldName("username")] - public string Username { - get { - return GetProperty(null, "Username"); - } - set { - SetProperty(value, "Username"); - } - } - - /// - /// 密码 - /// - [AVFieldName("password")] - public string Password { - private get { - return GetProperty(null, "Password"); - } - set { - SetProperty(value, "Password"); - } - } - - /// - /// Email - /// - [AVFieldName("email")] - public string Email { - get { - return GetProperty(null, "Email"); - } - set { - SetProperty(value, "Email"); - } - } - - /// - /// 手机号 - /// - [AVFieldName("mobilePhoneNumber")] - public string MobilePhoneNumber { - get { - return GetProperty(null, "MobilePhoneNumber"); - } - set { - SetProperty(value, "MobilePhoneNumber"); - } - } - - /// - /// 手机号是否已经验证 - /// - /// true if mobile phone verified; otherwise, false. - [AVFieldName("mobilePhoneVerified")] - public bool MobilePhoneVerified { - get { - return GetProperty(false, "MobilePhoneVerified"); - } - set { - SetProperty(value, "MobilePhoneVerified"); - } - } - - /// - /// 获取 Session Token - /// - public string SessionToken { - get { - if (State.ContainsKey("sessionToken")) { - return State["sessionToken"] as string; - } - return null; - } - } - - /// - /// 用户数据 - /// - internal IDictionary> AuthData { - get { - if (TryGetValue("authData", out IDictionary> authData)) { - return authData; - } - return null; - } - private set { - this["authData"] = value; - } - } - - /// - /// 判断用户是否为匿名用户 - /// - public bool IsAnonymous { - get { - return AuthData != null && AuthData.Keys.Contains("anonymous"); - } - } - - /// - /// 判断是否是当前用户 - /// - public bool IsCurrent { - get { - return CurrentUser == this; - } - } - - /// - /// 判断当前用户的 Session Token 是否有效 - /// - /// - public async Task IsAuthenticatedAsync() { - if (SessionToken == null || CurrentUser == null || CurrentUser.ObjectId != ObjectId) { - return false; - } - var command = new AVCommand { - Path = $"users/me?session_token={SessionToken}", - Method = HttpMethod.Get - }; - await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - return true; - } - - /// - /// 刷新用户的 Session Token,刷新后 Session Token 将会改变 - /// - /// - public async Task RefreshSessionTokenAsync() { - var serverState = await UserController.RefreshSessionTokenAsync(ObjectId); - HandleSave(serverState); - } - - #region 账号密码登陆 - - /// - /// 注册新用户,注册成功后将保存为当前用户。必须设置用户名和密码。 - /// - /// - public async Task SignUpAsync() { - if (AuthData == null) { - if (string.IsNullOrEmpty(Username)) { - throw new InvalidOperationException("Cannot sign up user with an empty name."); - } - if (string.IsNullOrEmpty(Password)) { - throw new InvalidOperationException("Cannot sign up user with an empty password."); - } - } - if (!string.IsNullOrEmpty(ObjectId)) { - throw new InvalidOperationException("Cannot sign up a user that already exists."); - } - - var serverState = await UserController.SignUpAsync(State, operationDict); - HandleSave(serverState); - CurrentUser = this; - } - - /// - /// 使用用户名和密码登陆。登陆成功后,将用户设置为当前用户 - /// - /// 用户名 - /// 密码 - /// - public static async Task LogInAsync(string username, string password) { - var ret = await UserController.LogInAsync(username, null, password); - AVUser user = FromState(ret, "_User"); - CurrentUser = user; - return user; - } - - /// - /// 用邮箱作和密码匹配登录 - /// - /// 邮箱 - /// 密码 - /// - public static async Task LogInWithEmailAsync(string email, string password) { - var ret = await UserController.LogInAsync(null, email, password); - AVUser user = FromState(ret, "_User"); - CurrentUser = user; - return CurrentUser; - } - - /// - /// 通过绑定的邮箱请求重置密码 - /// 邮件可以在 LeanCloud 站点安全的重置密码 - /// - /// 绑定的邮箱地址 - /// - public static Task RequestPasswordResetAsync(string email) { - return UserController.RequestPasswordResetAsync(email); - } - - /// - /// 更新用户的密码,需要用户的旧密码 - /// - /// 旧密码 - /// 新密码 - public async Task UpdatePasswordAsync(string oldPassword, string newPassword) { - IObjectState state = await UserController.UpdatePasswordAsync(ObjectId, oldPassword, newPassword); - HandleFetchResult(state); - } - - /// - /// 申请发送验证邮箱的邮件,一周之内有效 - /// 如果该邮箱已经验证通过,会直接返回 True,并不会真正发送邮件 - /// 注意,不能频繁的调用此接口,一天之内只允许向同一个邮箱发送验证邮件 3 次,超过调用次数,会直接返回错误 - /// - /// 邮箱地址 - /// - public static Task RequestEmailVerifyAsync(string email) { - Dictionary strs = new Dictionary { - { "email", email } - }; - var command = new AVCommand { - Path = "requestEmailVerify", - Method = HttpMethod.Post, - Content = strs - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - #endregion - - #region 手机号登录 - - /// - /// 用手机号和密码匹配登陆 - /// - /// 手机号 - /// 密码 - /// - public static Task LogInWithMobilePhoneNumberAsync(string mobilePhoneNumber, string password) { - Dictionary strs = new Dictionary { - { "mobilePhoneNumber", mobilePhoneNumber }, - { "password", password } - }; - return LogInWithParametersAsync(strs); - } - - /// - /// 用手机号和验证码登陆 - /// - /// 手机号 - /// 短信验证码 - /// - public static Task LogInWithMobilePhoneNumberSmsCodeAsync(string mobilePhoneNumber, string smsCode) { - Dictionary strs = new Dictionary { - { "mobilePhoneNumber", mobilePhoneNumber }, - { "smsCode", smsCode } - }; - return LogInWithParametersAsync(strs); - } - - /// - /// 请求登录短信验证码 - /// - /// 手机号 - /// 验证码 - /// - public static Task RequestLogInSmsCodeAsync(string mobilePhoneNumber, string validateToken) { - Dictionary strs = new Dictionary { - { "mobilePhoneNumber", mobilePhoneNumber }, - }; - if (string.IsNullOrEmpty(validateToken)) { - strs.Add("validate_token", validateToken); - } - var command = new AVCommand { - Path = "requestLoginSmsCode", - Method = HttpMethod.Post, - Content = strs - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - /// - /// 手机号注册和登录 - /// - /// 手机号 - /// 短信验证码 - /// - public static async Task SignUpOrLogInByMobilePhoneAsync(string mobilePhoneNumber, string smsCode) { - Dictionary strs = new Dictionary { - { "mobilePhoneNumber", mobilePhoneNumber }, - { "smsCode", smsCode } - }; - var ret = await UserController.LogInWithParametersAsync("usersByMobilePhone", strs); - var user = CreateWithoutData(null); - user.HandleFetchResult(ret); - CurrentUser = user; - return CurrentUser; - } - - #endregion - - #region 第三方登录 - - /// - /// 使用第三方数据注册;如果已经存在相同的 Auth Data,则执行登录 - /// - /// Auth Data - /// 平台 - /// - /// - public static Task LogInWithAuthDataAsync(IDictionary authData, string platform, AVUserAuthDataLogInOption options = null) { - if (options == null) { - options = new AVUserAuthDataLogInOption(); - } - return LogInWithAsync(platform, authData, options.FailOnNotExist); - } - - /// - /// 使用第三方数据注册; - /// - /// Auth data - /// 平台标识 - /// - /// - /// - public static Task LogInWithAuthDataAndUnionIdAsync(IDictionary authData, string platform, string unionId, - AVUserAuthDataLogInOption options = null) { - if (options == null) { - options = new AVUserAuthDataLogInOption(); - } - MergeAuthData(authData, unionId, options); - return LogInWithAsync(platform, authData, options.FailOnNotExist); - } - - /// - /// 绑定第三方登录 - /// - /// Auth data - /// 平台标识 - /// - public Task AssociateAuthDataAsync(IDictionary authData, string platform) { - return LinkWithAuthDataAsync(platform, authData); - } - - /// - /// 绑定第三方登录 - /// - /// Auth data - /// 平台标识 - /// - /// - /// - public Task AssociateAuthDataAndUnionIdAsync(IDictionary authData, string platform, string unionId, - AVUserAuthDataLogInOption options = null) { - if (options == null) { - options = new AVUserAuthDataLogInOption(); - } - MergeAuthData(authData, unionId, options); - return LinkWithAuthDataAsync(platform, authData); - } - - /// - /// 解绑第三方登录 - /// - /// 平台标识 - /// - public Task DisassociateWithAuthDataAsync(string platform) { - return LinkWithAuthDataAsync(platform, null); - } - - #endregion - - #region 重置密码 - - /// - /// 请求重置密码,需要传入注册时使用的手机号。 - /// - /// 注册时使用的手机号 - /// 图形验证码 - /// - public static Task RequestPasswordResetBySmsCode(string mobilePhoneNumber, string validateToken = null) { - Dictionary strs = new Dictionary { - { "mobilePhoneNumber", mobilePhoneNumber }, - }; - if (string.IsNullOrEmpty(validateToken)) { - strs.Add("validate_token", validateToken); - } - var command = new AVCommand { - Path = "requestPasswordResetBySmsCode", - Method = HttpMethod.Post, - Content = strs - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - /// - /// 通过验证码重置密码。 - /// - /// 新密码 - /// 6 位数验证码 - /// - public static Task ResetPasswordBySmsCodeAsync(string newPassword, string smsCode) { - Dictionary strs = new Dictionary { - { "password", newPassword } - }; - var command = new AVCommand { - Path = $"resetPasswordBySmsCode/{smsCode}", - Method = HttpMethod.Put, - Content = strs - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - /// - /// 发送验证码到用户绑定的手机上 - /// - /// 手机号 - /// 验证码 - /// - public static Task RequestMobilePhoneVerifyAsync(string mobilePhoneNumber, string validateToken = null) { - Dictionary strs = new Dictionary { - { "mobilePhoneNumber", mobilePhoneNumber } - }; - if (!string.IsNullOrEmpty(validateToken)) { - strs.Add("validate_token", validateToken); - } - var command = new AVCommand { - Path = "requestMobilePhoneVerify", - Method = HttpMethod.Post, - Content = strs - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - /// - /// 验证手机验证码是否为有效值 - /// - /// 手机收到的验证码 - /// - public static Task VerifyMobilePhoneAsync(string code) { - var command = new AVCommand { - Path = $"verifyMobilePhone/{code.Trim()}", - Method = HttpMethod.Post - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - #endregion - - /// - /// 匿名登录 - /// - /// - public static Task LogInAnonymouslyAsync() { - var data = new Dictionary { - { "id", Guid.NewGuid().ToString() } - }; - var options = new AVUserAuthDataLogInOption(); - return LogInWithAuthDataAsync(data, "anonymous", options); - } - - /// - /// 使用 Session Token 登录。 - /// - /// Session Token - /// - public static async Task BecomeAsync(string sessionToken) { - var ret = await UserController.GetUserAsync(sessionToken); - AVUser user = FromState(ret, "_User"); - CurrentUser = user; - return user; - } - - /// - /// 用户登出 - /// - public static void LogOut() { - CurrentUser = null; - } - - #region 事件流系统相关 API - - /// - /// 关注某个用户 - /// - /// 被关注的用户 - /// - public Task FollowAsync(string userObjectId) { - return FollowAsync(userObjectId, null); - } - - /// - /// 关注某个用户 - /// - /// 被关注的用户 Id - /// 关注的时候附加属性 - /// - public Task FollowAsync(string userObjectId, IDictionary data) { - if (data != null) { - data = EncodeForSaving(data); - } - var command = new AVCommand { - Path = $"users/{ObjectId}/friendship/{userObjectId}", - Method = HttpMethod.Post, - Content = data - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - /// - /// 取关某一个用户 - /// - /// 用户 Id - /// - public Task UnfollowAsync(string userObjectId) { - var command = new AVCommand { - Path = $"users/{ObjectId}/friendship/{userObjectId}", - Method = HttpMethod.Delete - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - /// - /// 获取当前用户的关注者的查询 - /// - /// - public AVQuery GetFollowerQuery() { - AVQuery query = new AVQuery { - Path = $"users/{ObjectId}/followers" - }; - return query; - } - - /// - /// 获取当前用户所关注的用户的查询 - /// - /// - public AVQuery GetFolloweeQuery() { - AVQuery query = new AVQuery { - Path = $"users/{ObjectId}/followees" - }; - return query; - } - - /// - /// 同时查询关注了当前用户的关注者和当前用户所关注的用户 - /// - /// - public AVQuery GetFollowersAndFolloweesQuery() { - AVQuery query = new AVQuery { - Path = $"users/{ObjectId}/followersAndFollowees" - }; - return query; - } - - /// - /// 获取当前用户的关注者 - /// - /// - public Task> GetFollowersAsync() { - return GetFollowerQuery().FindAsync(CancellationToken.None); - } - - /// - /// 获取当前用户所关注的用户 - /// - /// - public Task> GetFolloweesAsync() { - return GetFolloweeQuery().FindAsync(CancellationToken.None); - } - - #endregion - - public Task> GetRolesAsync() { - AVQuery query = new AVQuery(); - query.WhereEqualTo("users", this); - return query.FindAsync(); - } - - Task LinkWithAuthDataAsync(string authType, IDictionary data) { - AuthData = new Dictionary> { - [authType] = data - }; - return SaveAsync(); - } - - internal static async Task LogInWithAsync(string authType, IDictionary data, bool failOnNotExist) { - var ret = await UserController.LogInAsync(authType, data, failOnNotExist); - AVUser user = FromState(ret, "_User"); - user.AuthData = new Dictionary> { - [authType] = data - }; - CurrentUser = user; - return CurrentUser; - } - - internal static async Task LogInWithParametersAsync(Dictionary strs) { - IObjectState ret = await UserController.LogInWithParametersAsync("login", strs); - AVUser user = CreateWithoutData(null); - user.HandleFetchResult(ret); - CurrentUser = user; - return CurrentUser; - } - - /// 合并为支持 AuthData 的格式 - static void MergeAuthData(IDictionary authData, string unionId, AVUserAuthDataLogInOption options) { - authData["platform"] = options.UnionIdPlatform; - authData["main_account"] = options.AsMainAccount; - authData["unionid"] = unionId; - } - } -} diff --git a/Storage/Storage/Public/AVUserAuthDataLogInOption.cs b/Storage/Storage/Public/AVUserAuthDataLogInOption.cs deleted file mode 100644 index 1918c2b..0000000 --- a/Storage/Storage/Public/AVUserAuthDataLogInOption.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace LeanCloud { - /// - /// AuthData 登陆选项 - /// - public class AVUserAuthDataLogInOption { - - /// - /// unionId platform - /// - /// unionId platform. - public string UnionIdPlatform; - - /// - /// If true, the unionId will be associated with the user. - /// - /// true If true, the unionId will be associated with the user. false. - public bool AsMainAccount; - - /// - /// If true, the login request will fail when no user matches this authData exists. - /// - /// true If true, the login request will fail when no user matches this authData exists. false. - public bool FailOnNotExist; - - public AVUserAuthDataLogInOption() { - UnionIdPlatform = "weixin"; - AsMainAccount = false; - FailOnNotExist = false; - } - } -} diff --git a/Storage/Storage/Public/LeaderBoard/AVLeaderboard.cs b/Storage/Storage/Public/LeaderBoard/AVLeaderboard.cs deleted file mode 100644 index 3537846..0000000 --- a/Storage/Storage/Public/LeaderBoard/AVLeaderboard.cs +++ /dev/null @@ -1,498 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Collections.Generic; -using LeanCloud.Storage.Internal; -using System.Net.Http; - -namespace LeanCloud { - /// - /// 排行榜顺序 - /// - public enum AVLeaderboardOrder { - /// - /// 升序 - /// - ASCENDING, - /// - /// 降序 - /// - DESCENDING - } - - /// - /// 排行榜更新策略 - /// - public enum AVLeaderboardUpdateStrategy { - /// - /// 更好地 - /// - BETTER, - /// - /// 最近的 - /// - LAST, - /// - /// 总和 - /// - SUM, - } - - /// - /// 排行榜刷新频率 - /// - public enum AVLeaderboardVersionChangeInterval { - /// - /// 从不 - /// - NEVER, - /// - /// 每天 - /// - DAY, - /// - /// 每周 - /// - WEEK, - /// - /// 每月 - /// - MONTH - } - - /// - /// 排行榜类 - /// - public class AVLeaderboard { - /// - /// 成绩名字 - /// - /// The name of the statistic. - public string StatisticName { - get; private set; - } - - /// - /// 排行榜顺序 - /// - /// The order. - public AVLeaderboardOrder Order { - get; private set; - } - - /// - /// 排行榜更新策略 - /// - /// The update strategy. - public AVLeaderboardUpdateStrategy UpdateStrategy { - get; private set; - } - - /// - /// 排行榜版本更新频率 - /// - /// The version change intervak. - public AVLeaderboardVersionChangeInterval VersionChangeInterval { - get; private set; - } - - /// - /// 版本号 - /// - /// The version. - public int Version { - get; private set; - } - - /// - /// 下次重置时间 - /// - /// The next reset at. - public DateTime NextResetAt { - get; private set; - } - - /// - /// 创建时间 - /// - /// The created at. - public DateTime CreatedAt { - get; private set; - } - - /// - /// Leaderboard 构造方法 - /// - /// 成绩名称 - AVLeaderboard(string statisticName) { - StatisticName = statisticName; - } - - AVLeaderboard() { - } - - /// - /// 创建排行榜对象 - /// - /// 排行榜对象 - /// 名称 - /// 排序方式 - /// 版本更新频率 - /// 成绩更新策略 - public static async Task CreateLeaderboard(string statisticName, - AVLeaderboardOrder order = AVLeaderboardOrder.DESCENDING, - AVLeaderboardUpdateStrategy updateStrategy = AVLeaderboardUpdateStrategy.BETTER, - AVLeaderboardVersionChangeInterval versionChangeInterval = AVLeaderboardVersionChangeInterval.WEEK) { - - if (string.IsNullOrEmpty(statisticName)) { - throw new ArgumentNullException(nameof(statisticName)); - } - var data = new Dictionary { - { "statisticName", statisticName }, - { "order", order.ToString().ToLower() }, - { "versionChangeInterval", versionChangeInterval.ToString().ToLower() }, - { "updateStrategy", updateStrategy.ToString().ToLower() }, - }; - var command = new AVCommand { - Path = "leaderboard/leaderboards", - Method = HttpMethod.Post, - Content = data - }; - var result = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - try { - var leaderboard = Parse(result.Item2); - return leaderboard; - } catch (Exception e) { - throw new AVException(AVException.ErrorCode.InvalidJSON, e.Message); - } - } - - /// - /// 创建排行榜对象 - /// - /// 排行榜对象 - /// 名称 - public static AVLeaderboard CreateWithoutData(string statisticName) { - if (string.IsNullOrEmpty(statisticName)) { - throw new ArgumentNullException(nameof(statisticName)); - } - return new AVLeaderboard(statisticName); - } - - /// - /// 获取排行榜对象 - /// - /// 排行榜对象 - /// 名称 - public static Task GetLeaderboard(string statisticName) { - return CreateWithoutData(statisticName).Fetch(); - } - - /// - /// 更新用户成绩 - /// - /// 更新的成绩 - /// 用户 - /// 成绩 - /// 是否强行覆盖 - public static async Task> UpdateStatistics(AVUser user, Dictionary statistics, bool overwrite = false) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - if (statistics == null || statistics.Count == 0) { - throw new ArgumentNullException(nameof(statistics)); - } - var data = new List(); - foreach (var statistic in statistics) { - var kv = new Dictionary { - { "statisticName", statistic.Key }, - { "statisticValue", statistic.Value }, - }; - data.Add(kv); - } - var path = string.Format("leaderboard/users/{0}/statistics", user.ObjectId); - if (overwrite) { - path = string.Format("{0}?overwrite=1", path); - } - var command = new AVCommand { - Path = path, - Method = HttpMethod.Post, - Content = data - }; - var result = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - try { - List statisticList = new List(); - List list = result.Item2["results"] as List; - foreach (object obj in list) { - statisticList.Add(AVStatistic.Parse(obj as IDictionary)); - } - return statisticList; - } catch (Exception e) { - throw new AVException(AVException.ErrorCode.InvalidJSON, e.Message); - } - } - - /// - /// 获取用户成绩 - /// - /// 成绩列表 - /// 用户 - /// 名称列表 - public static async Task> GetStatistics(AVUser user, List statisticNames = null) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - var path = string.Format("leaderboard/users/{0}/statistics", user.ObjectId); - if (statisticNames != null && statisticNames.Count > 0) { - var names = string.Join(",", statisticNames.ToArray()); - path = string.Format("{0}?statistics={1}", path, names); - } - var command = new AVCommand { - Path = path, - Method = HttpMethod.Post - }; - var result = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - try { - List statisticList = new List(); - List list = result.Item2["results"] as List; - foreach (object obj in list) { - statisticList.Add(AVStatistic.Parse(obj as IDictionary)); - } - return statisticList; - } catch (Exception e) { - throw new AVException(AVException.ErrorCode.InvalidJSON, e.Message); - } - } - - /// - /// 删除用户成绩 - /// - /// 用户 - /// 名称列表 - public static async Task DeleteStatistics(AVUser user, List statisticNames) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - if (statisticNames == null || statisticNames.Count == 0) { - throw new ArgumentNullException(nameof(statisticNames)); - } - var path = string.Format("leaderboard/users/{0}/statistics", user.ObjectId); - var names = string.Join(",", statisticNames.ToArray()); - path = string.Format("{0}?statistics={1}", path, names); - var command = new AVCommand { - Path = path, - Method = HttpMethod.Delete, - }; - await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - /// - /// 获取排行榜历史数据 - /// - /// 排行榜归档列表 - /// 跳过数量 - /// 分页数量 - public async Task> GetArchives(int skip = 0, int limit = 10) { - var path = string.Format("leaderboard/leaderboards/{0}/archives", StatisticName); - path = string.Format("{0}?skip={1}&limit={2}", path, skip, limit); - var command = new AVCommand { - Path = path, - Method = HttpMethod.Get - }; - var result = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - List archives = new List(); - List list = result.Item2["results"] as List; - foreach (object obj in list) { - archives.Add(AVLeaderboardArchive.Parse(obj as IDictionary)); - } - return archives; - } - - /// - /// 获取排行榜结果 - /// - /// 排名列表 - public Task> GetResults(int version = -1, int skip = 0, int limit = 10, List selectUserKeys = null, - List includeStatistics = null) { - return GetResults(null, version, skip, limit, selectUserKeys, includeStatistics); - } - - /// - /// 获取用户及附近的排名 - /// - /// 排名列表 - /// 用户 - /// 版本号 - /// 跳过数量 - /// 分页数量 - /// 包含的玩家的字段列表 - /// 包含的其他排行榜名称 - public Task> GetResultsAroundUser(int version = -1, int skip = 0, int limit = 10, - List selectUserKeys = null, - List includeStatistics = null) { - return GetResults(AVUser.CurrentUser, version, skip, limit, selectUserKeys, includeStatistics); - } - - async Task> GetResults(AVUser user, - int version, int skip, int limit, - List selectUserKeys, - List includeStatistics) { - - var path = string.Format("leaderboard/leaderboards/{0}/ranks", StatisticName); - if (user != null) { - path = string.Format("{0}/{1}", path, user.ObjectId); - } - path = string.Format("{0}?skip={1}&limit={2}", path, skip, limit); - if (version != -1) { - path = string.Format("{0}&version={1}", path, version); - } - if (selectUserKeys != null) { - var keys = string.Join(",", selectUserKeys.ToArray()); - path = string.Format("{0}&includeUser={1}", path, keys); - } - if (includeStatistics != null) { - var statistics = string.Join(",", includeStatistics.ToArray()); - path = string.Format("{0}&includeStatistics={1}", path, statistics); - } - var command = new AVCommand { - Path = path, - Method = HttpMethod.Get - }; - var result = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - try { - List rankingList = new List(); - List list = result.Item2["results"] as List; - foreach (object obj in list) { - rankingList.Add(AVRanking.Parse(obj as IDictionary)); - } - return rankingList; - } catch (Exception e) { - throw new AVException(AVException.ErrorCode.InvalidJSON, e.Message); - } - } - - /// - /// 设置更新策略 - /// - /// 排行榜对象 - /// 更新策略 - public async Task UpdateUpdateStrategy(AVLeaderboardUpdateStrategy updateStrategy) { - var data = new Dictionary { - { "updateStrategy", updateStrategy.ToString().ToLower() } - }; - var result = await Update(data); - UpdateStrategy = (AVLeaderboardUpdateStrategy)Enum.Parse(typeof(AVLeaderboardUpdateStrategy), result["updateStrategy"].ToString().ToUpper()); - return this; - } - - /// - /// 设置版本更新频率 - /// - /// 排行榜对象 - /// 版本更新频率 - public async Task UpdateVersionChangeInterval(AVLeaderboardVersionChangeInterval versionChangeInterval) { - var data = new Dictionary { - { "versionChangeInterval", versionChangeInterval.ToString().ToLower() } - }; - var result = await Update(data); - VersionChangeInterval = (AVLeaderboardVersionChangeInterval)Enum.Parse(typeof(AVLeaderboardVersionChangeInterval), result["versionChangeInterval"].ToString().ToUpper()); - return this; - } - - async Task> Update(Dictionary data) { - var path = string.Format("leaderboard/leaderboards/{0}", StatisticName); - var command = new AVCommand { - Path = path, - Method = HttpMethod.Put, - Content = data - }; - var result = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - return result.Item2; - } - - /// - /// 拉取排行榜数据 - /// - /// 排行榜对象 - public async Task Fetch() { - var path = string.Format("leaderboard/leaderboards/{0}", StatisticName); - var command = new AVCommand { - Path = path, - Method = HttpMethod.Get - }; - var result = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - try { - // 反序列化 Leaderboard 对象 - var leaderboard = Parse(result.Item2); - return leaderboard; - } catch (Exception e) { - throw new AVException(AVException.ErrorCode.InvalidJSON, e.Message); - } - } - - /// - /// 重置排行榜 - /// - /// 排行榜对象 - public async Task Reset() { - var path = string.Format("leaderboard/leaderboards/{0}/incrementVersion", StatisticName); - var command = new AVCommand { - Path = path, - Method = HttpMethod.Put - }; - var result = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - try { - Init(result.Item2); - return this; - } catch (Exception e) { - throw new AVException(AVException.ErrorCode.InvalidJSON, e.Message); - } - } - - /// - /// 销毁排行榜 - /// - public Task Destroy() { - var path = string.Format("leaderboard/leaderboards/{0}", StatisticName); - var command = new AVCommand { - Path = path, - Method = HttpMethod.Delete - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - static AVLeaderboard Parse(IDictionary data) { - if (data == null) { - throw new ArgumentNullException(nameof(data)); - } - var leaderboard = new AVLeaderboard(); - leaderboard.Init(data); - return leaderboard; - } - - void Init(IDictionary data) { - if (data == null) { - throw new ArgumentNullException(nameof(data)); - } - object nameObj; - if (data.TryGetValue("statisticName", out nameObj)) { - StatisticName = nameObj.ToString(); - } - object orderObj; - if (data.TryGetValue("order", out orderObj)) { - Order = (AVLeaderboardOrder)Enum.Parse(typeof(AVLeaderboardOrder), orderObj.ToString().ToUpper()); - } - object strategyObj; - if (data.TryGetValue("updateStrategy", out strategyObj)) { - UpdateStrategy = (AVLeaderboardUpdateStrategy)Enum.Parse(typeof(AVLeaderboardUpdateStrategy), strategyObj.ToString().ToUpper()); - } - object intervalObj; - if (data.TryGetValue("versionChangeInterval", out intervalObj)) { - VersionChangeInterval = (AVLeaderboardVersionChangeInterval)Enum.Parse(typeof(AVLeaderboardVersionChangeInterval), intervalObj.ToString().ToUpper()); - } - object versionObj; - if (data.TryGetValue("version", out versionObj)) { - Version = int.Parse(versionObj.ToString()); - } - } - } -} diff --git a/Storage/Storage/Public/LeaderBoard/AVLeaderboardArchive.cs b/Storage/Storage/Public/LeaderBoard/AVLeaderboardArchive.cs deleted file mode 100644 index a9df41f..0000000 --- a/Storage/Storage/Public/LeaderBoard/AVLeaderboardArchive.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using LeanCloud.Storage.Internal; - -namespace LeanCloud { - /// - /// 归档的排行榜 - /// - public class AVLeaderboardArchive { - /// - /// 名称 - /// - /// The name of the statistic. - public string StatisticName { - get; internal set; - } - - /// - /// 版本号 - /// - /// The version. - public int Version { - get; internal set; - } - - /// - /// 状态 - /// - /// The status. - public string Status { - get; internal set; - } - - /// - /// 下载地址 - /// - /// The URL. - public string Url { - get; internal set; - } - - /// - /// 激活时间 - /// - /// The activated at. - public DateTime ActivatedAt { - get; internal set; - } - - /// - /// 归档时间 - /// - /// The deactivated at. - public DateTime DeactivatedAt { - get; internal set; - } - - AVLeaderboardArchive() { - } - - internal static AVLeaderboardArchive Parse(IDictionary data) { - if (data == null) { - throw new ArgumentNullException(nameof(data)); - } - AVLeaderboardArchive archive = new AVLeaderboardArchive { - StatisticName = data["statisticName"].ToString(), - Version = int.Parse(data["version"].ToString()), - Status = data["status"].ToString(), - Url = data["url"].ToString(), - ActivatedAt = (DateTime)AVDecoder.Instance.Decode(data["activatedAt"]), - DeactivatedAt = (DateTime)AVDecoder.Instance.Decode(data["activatedAt"]) - }; - return archive; - } - } -} diff --git a/Storage/Storage/Public/LeaderBoard/AVRanking.cs b/Storage/Storage/Public/LeaderBoard/AVRanking.cs deleted file mode 100644 index 6d5fa90..0000000 --- a/Storage/Storage/Public/LeaderBoard/AVRanking.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using LeanCloud.Storage.Internal; - -namespace LeanCloud { - /// - /// 排名类 - /// - public class AVRanking { - /// - /// 名次 - /// - /// The rank. - public int Rank { - get; private set; - } - - /// - /// 用户 - /// - /// The user. - public AVUser User { - get; private set; - } - - public string StatisticName { - get; private set; - } - - /// - /// 分数 - /// - /// The value. - public double Value { - get; private set; - } - - /// - /// 成绩 - /// - /// The included statistics. - public List IncludedStatistics { - get; private set; - } - - AVRanking() { - } - - internal static AVRanking Parse(IDictionary data) { - if (data == null) { - throw new ArgumentNullException(nameof(data)); - } - var ranking = new AVRanking { - Rank = int.Parse(data["rank"].ToString()), - User = AVDecoder.Instance.Decode(data["user"]) as AVUser, - StatisticName = data["statisticName"].ToString(), - Value = double.Parse(data["statisticValue"].ToString()) - }; - object statisticsObj; - if (data.TryGetValue("statistics", out statisticsObj)) { - ranking.IncludedStatistics = new List(); - var statisticsObjList = statisticsObj as List; - foreach (object statisticObj in statisticsObjList) { - var statistic = AVStatistic.Parse(statisticObj as IDictionary); - ranking.IncludedStatistics.Add(statistic); - } - } - - return ranking; - } - } -} diff --git a/Storage/Storage/Public/LeaderBoard/AVStatistic.cs b/Storage/Storage/Public/LeaderBoard/AVStatistic.cs deleted file mode 100644 index 6d4fdcb..0000000 --- a/Storage/Storage/Public/LeaderBoard/AVStatistic.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud { - /// - /// 成绩类 - /// - public class AVStatistic { - /// - /// 排行榜名称 - /// - /// The name. - public string Name { - get; private set; - } - - /// - /// 成绩值 - /// - /// The value. - public double Value { - get; private set; - } - - /// - /// 排行榜版本 - /// - /// The version. - public int Version { - get; internal set; - } - - public AVStatistic(string name, double value) { - Name = name; - Value = value; - } - - AVStatistic() { } - - internal static AVStatistic Parse(IDictionary data) { - if (data == null) { - throw new ArgumentNullException(nameof(data)); - } - AVStatistic statistic = new AVStatistic { - Name = data["statisticName"].ToString(), - Value = double.Parse(data["statisticValue"].ToString()), - Version = int.Parse(data["version"].ToString()) - }; - return statistic; - } - } -} diff --git a/Storage/Storage/Public/Utilities/Conversion.cs b/Storage/Storage/Public/Utilities/Conversion.cs deleted file mode 100644 index a3ace3b..0000000 --- a/Storage/Storage/Public/Utilities/Conversion.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Utilities -{ - /// - /// A set of utilities for converting generic types between each other. - /// - public static class Conversion - { - /// - /// Converts a value to the requested type -- coercing primitives to - /// the desired type, wrapping lists and dictionaries appropriately, - /// or else returning null. - /// - /// This should be used on any containers that might be coming from a - /// user to normalize the collection types. Collection types coming from - /// JSON deserialization can be safely assumed to be lists or dictionaries of - /// objects. - /// - public static T As(object value) where T : class - { - return ConvertTo(value) as T; - } - - /// - /// Converts a value to the requested type -- coercing primitives to - /// the desired type, wrapping lists and dictionaries appropriately, - /// or else throwing an exception. - /// - /// This should be used on any containers that might be coming from a - /// user to normalize the collection types. Collection types coming from - /// JSON deserialization can be safely assumed to be lists or dictionaries of - /// objects. - /// - public static T To(object value) - { - return (T)ConvertTo(value); - } - - /// - /// Converts a value to the requested type -- coercing primitives to - /// the desired type, wrapping lists and dictionaries appropriately, - /// or else passing the object along to the caller unchanged. - /// - /// This should be used on any containers that might be coming from a - /// user to normalize the collection types. Collection types coming from - /// JSON deserialization can be safely assumed to be lists or dictionaries of - /// objects. - /// - internal static object ConvertTo(object value) - { - if (value is T || value == null) - { - return value; - } - - if (ReflectionHelpers.IsPrimitive(typeof(T))) - { - return (T)Convert.ChangeType(value, typeof(T)); - } - - return value; - } - - /// - /// Holds a dictionary that maps a cache of interface types for related concrete types. - /// The lookup is slow the first time for each type because it has to enumerate all interface - /// on the object type, but made fast by the cache. - /// - /// The map is: - /// (object type, generic interface type) => constructed generic type - /// - private static readonly Dictionary, Type> interfaceLookupCache = - new Dictionary, Type>(); - private static Type GetInterfaceType(Type objType, Type genericInterfaceType) - { - // Side note: It so sucks to have to do this. What a piece of crap bit of code - // Unfortunately, .NET doesn't provide any of the right hooks to do this for you - // *sigh* - if (ReflectionHelpers.IsConstructedGenericType(genericInterfaceType)) - { - genericInterfaceType = genericInterfaceType.GetGenericTypeDefinition(); - } - var cacheKey = new Tuple(objType, genericInterfaceType); - if (interfaceLookupCache.ContainsKey(cacheKey)) - { - return interfaceLookupCache[cacheKey]; - } - foreach (var type in ReflectionHelpers.GetInterfaces(objType)) - { - if (ReflectionHelpers.IsConstructedGenericType(type) && - type.GetGenericTypeDefinition() == genericInterfaceType) - { - return interfaceLookupCache[cacheKey] = type; - } - } - return null; - } - } -} diff --git a/Test/Common.Test/AppRouterTest.cs b/Test/Common.Test/AppRouterTest.cs index ee9294a..acbc144 100644 --- a/Test/Common.Test/AppRouterTest.cs +++ b/Test/Common.Test/AppRouterTest.cs @@ -1,19 +1,19 @@ using System; using System.Threading.Tasks; using NUnit.Framework; -using LeanCloud; +using LeanCloud.Common; namespace Common.Test { public class AppRouterTest { - static void Print(LogLevel level, string info) { + static void Print(LeanCloud.LogLevel level, string info) { switch (level) { - case LogLevel.Debug: + case LeanCloud.LogLevel.Debug: TestContext.Out.WriteLine($"[DEBUG] {info}"); break; - case LogLevel.Warn: + case LeanCloud.LogLevel.Warn: TestContext.Out.WriteLine($"[WARNING] {info}"); break; - case LogLevel.Error: + case LeanCloud.LogLevel.Error: TestContext.Out.WriteLine($"[ERROR] {info}"); break; default: @@ -24,12 +24,12 @@ namespace Common.Test { [SetUp] public void SetUp() { - Logger.LogDelegate += Print; + LeanCloud.Logger.LogDelegate += Print; } [TearDown] public void TearDown() { - Logger.LogDelegate -= Print; + LeanCloud.Logger.LogDelegate -= Print; } [Test] diff --git a/Storage/Storage.Test/ACLTest.cs b/Test/Storage.Test/ACLTest.cs similarity index 100% rename from Storage/Storage.Test/ACLTest.cs rename to Test/Storage.Test/ACLTest.cs diff --git a/Storage/Storage.Test/CloudTest.cs b/Test/Storage.Test/CloudTest.cs similarity index 100% rename from Storage/Storage.Test/CloudTest.cs rename to Test/Storage.Test/CloudTest.cs diff --git a/Storage/Storage.Test/ExceptionTest.cs b/Test/Storage.Test/ExceptionTest.cs similarity index 100% rename from Storage/Storage.Test/ExceptionTest.cs rename to Test/Storage.Test/ExceptionTest.cs diff --git a/Storage/Storage.Test/FileTest.cs b/Test/Storage.Test/FileTest.cs similarity index 100% rename from Storage/Storage.Test/FileTest.cs rename to Test/Storage.Test/FileTest.cs diff --git a/Storage/Storage.Test/GeoTest.cs b/Test/Storage.Test/GeoTest.cs similarity index 100% rename from Storage/Storage.Test/GeoTest.cs rename to Test/Storage.Test/GeoTest.cs diff --git a/Storage/Storage.Test/ObjectTest.cs b/Test/Storage.Test/ObjectTest.cs similarity index 100% rename from Storage/Storage.Test/ObjectTest.cs rename to Test/Storage.Test/ObjectTest.cs diff --git a/Storage/Storage.Test/OperationTest.cs b/Test/Storage.Test/OperationTest.cs similarity index 100% rename from Storage/Storage.Test/OperationTest.cs rename to Test/Storage.Test/OperationTest.cs diff --git a/Storage/Storage.Test/QueryTest.cs b/Test/Storage.Test/QueryTest.cs similarity index 100% rename from Storage/Storage.Test/QueryTest.cs rename to Test/Storage.Test/QueryTest.cs diff --git a/Storage/Storage.Test/RelationTest.cs b/Test/Storage.Test/RelationTest.cs similarity index 100% rename from Storage/Storage.Test/RelationTest.cs rename to Test/Storage.Test/RelationTest.cs diff --git a/Storage/Storage.Test/RoleTest.cs b/Test/Storage.Test/RoleTest.cs similarity index 100% rename from Storage/Storage.Test/RoleTest.cs rename to Test/Storage.Test/RoleTest.cs diff --git a/Storage/Storage.Test/Storage.Test.csproj b/Test/Storage.Test/Storage.Test.csproj similarity index 89% rename from Storage/Storage.Test/Storage.Test.csproj rename to Test/Storage.Test/Storage.Test.csproj index 99a9d07..86429e9 100644 --- a/Storage/Storage.Test/Storage.Test.csproj +++ b/Test/Storage.Test/Storage.Test.csproj @@ -15,6 +15,6 @@ - + diff --git a/Storage/Storage.Test/SubClassTest.cs b/Test/Storage.Test/SubClassTest.cs similarity index 100% rename from Storage/Storage.Test/SubClassTest.cs rename to Test/Storage.Test/SubClassTest.cs diff --git a/Storage/Storage.Test/UserTest.cs b/Test/Storage.Test/UserTest.cs similarity index 100% rename from Storage/Storage.Test/UserTest.cs rename to Test/Storage.Test/UserTest.cs diff --git a/Test/Storage.Test/Utils.cs b/Test/Storage.Test/Utils.cs new file mode 100644 index 0000000..739b4aa --- /dev/null +++ b/Test/Storage.Test/Utils.cs @@ -0,0 +1,25 @@ +using System; +using LeanCloud; +using LeanCloud.Common; +using NUnit.Framework; + +namespace LeanCloud.Test { + public static class Utils { + internal static void Print(LogLevel level, string info) { + switch (level) { + case LogLevel.Debug: + TestContext.Out.WriteLine($"[DEBUG] {info}"); + break; + case LogLevel.Warn: + TestContext.Out.WriteLine($"[WARNING] {info}"); + break; + case LogLevel.Error: + TestContext.Out.WriteLine($"[ERROR] {info}"); + break; + default: + TestContext.Out.WriteLine(info); + break; + } + } + } +} diff --git a/Storage/Storage.Test/assets/hello.png b/Test/Storage.Test/assets/hello.png similarity index 100% rename from Storage/Storage.Test/assets/hello.png rename to Test/Storage.Test/assets/hello.png diff --git a/Storage/Storage.Test/assets/test.apk b/Test/Storage.Test/assets/test.apk similarity index 100% rename from Storage/Storage.Test/assets/test.apk rename to Test/Storage.Test/assets/test.apk diff --git a/csharp-sdk.sln b/csharp-sdk.sln index af711ed..9489670 100644 --- a/csharp-sdk.sln +++ b/csharp-sdk.sln @@ -1,35 +1,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Storage", "Storage", "{CD6B6669-1A56-437A-932E-BCE7F5D4CD18}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage.Test", "Test\Storage.Test\Storage.Test.csproj", "{BE05B492-78CD-47CA-9F48-C3E9B4813AFF}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RTM", "RTM", "{64D8F9A1-BA44-459C-817C-788B4EBC0B9F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LiveQuery", "LiveQuery", "{5B895B7A-1F6E-40A5-8081-43B334D2C076}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LeanEngine", "LeanEngine", "{CDC4E11B-592E-48C3-9105-CD49435424AA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage.PCL", "Storage\Storage.PCL\Storage.PCL.csproj", "{659D19F0-9A40-42C0-886C-555E64F16848}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage.Unity", "Storage\Storage.Unity\Storage.Unity.csproj", "{A0D50BCB-E50E-4AAE-8E7D-24BF5AE33DAC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RTM.PCL", "RTM\RTM.PCL\RTM.PCL.csproj", "{92B2B40E-A3CD-4672-AC84-2E894E1A6CE5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RTM.Unity", "RTM\RTM.Unity\RTM.Unity.csproj", "{1E608FCD-9039-4FF7-8EE7-BA8B00E15D1C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RTM.Test", "RTM\RTM.Test\RTM.Test.csproj", "{A1BBD0B5-41C6-4579-B9A3-5EF778BE7F95}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveQuery.PCL", "LiveQuery\LiveQuery.PCL\LiveQuery.PCL.csproj", "{EA1C601E-D853-41F7-B9EB-276CBF7D1FA5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveQuery.Unity", "LiveQuery\LiveQuery.Unity\LiveQuery.Unity.csproj", "{3251B4D8-D11A-4D90-8626-27FEE266B066}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveQuery.Test", "LiveQuery\LiveQuery.Test\LiveQuery.Test.csproj", "{F907012C-74DF-4575-AFE6-E8DAACC26D24}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage.Test", "Storage\Storage.Test\Storage.Test.csproj", "{BE05B492-78CD-47CA-9F48-C3E9B4813AFF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage", "Storage\Storage\Storage.csproj", "{59DA32A0-4CD3-424A-8584-D08B8D1E2B98}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RTM", "RTM\RTM\RTM.csproj", "{D4A30F70-AAED-415D-B940-023B3D7241EE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage", "Storage\Storage.csproj", "{59DA32A0-4CD3-424A-8584-D08B8D1E2B98}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{C827DA2F-6AB4-48D8-AB5B-6DAB925F8933}" EndProject @@ -43,38 +17,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {659D19F0-9A40-42C0-886C-555E64F16848}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {659D19F0-9A40-42C0-886C-555E64F16848}.Debug|Any CPU.Build.0 = Debug|Any CPU - {659D19F0-9A40-42C0-886C-555E64F16848}.Release|Any CPU.ActiveCfg = Release|Any CPU - {659D19F0-9A40-42C0-886C-555E64F16848}.Release|Any CPU.Build.0 = Release|Any CPU - {A0D50BCB-E50E-4AAE-8E7D-24BF5AE33DAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A0D50BCB-E50E-4AAE-8E7D-24BF5AE33DAC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A0D50BCB-E50E-4AAE-8E7D-24BF5AE33DAC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A0D50BCB-E50E-4AAE-8E7D-24BF5AE33DAC}.Release|Any CPU.Build.0 = Release|Any CPU - {92B2B40E-A3CD-4672-AC84-2E894E1A6CE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {92B2B40E-A3CD-4672-AC84-2E894E1A6CE5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {92B2B40E-A3CD-4672-AC84-2E894E1A6CE5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {92B2B40E-A3CD-4672-AC84-2E894E1A6CE5}.Release|Any CPU.Build.0 = Release|Any CPU - {1E608FCD-9039-4FF7-8EE7-BA8B00E15D1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1E608FCD-9039-4FF7-8EE7-BA8B00E15D1C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1E608FCD-9039-4FF7-8EE7-BA8B00E15D1C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E608FCD-9039-4FF7-8EE7-BA8B00E15D1C}.Release|Any CPU.Build.0 = Release|Any CPU - {A1BBD0B5-41C6-4579-B9A3-5EF778BE7F95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1BBD0B5-41C6-4579-B9A3-5EF778BE7F95}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1BBD0B5-41C6-4579-B9A3-5EF778BE7F95}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1BBD0B5-41C6-4579-B9A3-5EF778BE7F95}.Release|Any CPU.Build.0 = Release|Any CPU - {EA1C601E-D853-41F7-B9EB-276CBF7D1FA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EA1C601E-D853-41F7-B9EB-276CBF7D1FA5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EA1C601E-D853-41F7-B9EB-276CBF7D1FA5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EA1C601E-D853-41F7-B9EB-276CBF7D1FA5}.Release|Any CPU.Build.0 = Release|Any CPU - {3251B4D8-D11A-4D90-8626-27FEE266B066}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3251B4D8-D11A-4D90-8626-27FEE266B066}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3251B4D8-D11A-4D90-8626-27FEE266B066}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3251B4D8-D11A-4D90-8626-27FEE266B066}.Release|Any CPU.Build.0 = Release|Any CPU - {F907012C-74DF-4575-AFE6-E8DAACC26D24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F907012C-74DF-4575-AFE6-E8DAACC26D24}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F907012C-74DF-4575-AFE6-E8DAACC26D24}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F907012C-74DF-4575-AFE6-E8DAACC26D24}.Release|Any CPU.Build.0 = Release|Any CPU {BE05B492-78CD-47CA-9F48-C3E9B4813AFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BE05B492-78CD-47CA-9F48-C3E9B4813AFF}.Debug|Any CPU.Build.0 = Debug|Any CPU {BE05B492-78CD-47CA-9F48-C3E9B4813AFF}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -83,10 +25,6 @@ Global {59DA32A0-4CD3-424A-8584-D08B8D1E2B98}.Debug|Any CPU.Build.0 = Debug|Any CPU {59DA32A0-4CD3-424A-8584-D08B8D1E2B98}.Release|Any CPU.ActiveCfg = Release|Any CPU {59DA32A0-4CD3-424A-8584-D08B8D1E2B98}.Release|Any CPU.Build.0 = Release|Any CPU - {D4A30F70-AAED-415D-B940-023B3D7241EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D4A30F70-AAED-415D-B940-023B3D7241EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D4A30F70-AAED-415D-B940-023B3D7241EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D4A30F70-AAED-415D-B940-023B3D7241EE}.Release|Any CPU.Build.0 = Release|Any CPU {4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Debug|Any CPU.Build.0 = Debug|Any CPU {4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -97,14 +35,6 @@ Global {758DE75D-37D7-4392-B564-9484348B505C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution - {659D19F0-9A40-42C0-886C-555E64F16848} = {CD6B6669-1A56-437A-932E-BCE7F5D4CD18} - {A0D50BCB-E50E-4AAE-8E7D-24BF5AE33DAC} = {CD6B6669-1A56-437A-932E-BCE7F5D4CD18} - {92B2B40E-A3CD-4672-AC84-2E894E1A6CE5} = {64D8F9A1-BA44-459C-817C-788B4EBC0B9F} - {1E608FCD-9039-4FF7-8EE7-BA8B00E15D1C} = {64D8F9A1-BA44-459C-817C-788B4EBC0B9F} - {A1BBD0B5-41C6-4579-B9A3-5EF778BE7F95} = {64D8F9A1-BA44-459C-817C-788B4EBC0B9F} - {EA1C601E-D853-41F7-B9EB-276CBF7D1FA5} = {5B895B7A-1F6E-40A5-8081-43B334D2C076} - {3251B4D8-D11A-4D90-8626-27FEE266B066} = {5B895B7A-1F6E-40A5-8081-43B334D2C076} - {F907012C-74DF-4575-AFE6-E8DAACC26D24} = {5B895B7A-1F6E-40A5-8081-43B334D2C076} {BE05B492-78CD-47CA-9F48-C3E9B4813AFF} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933} {4DF4E0F4-1013-477F-ADA6-BFAFD9312335} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933} EndGlobalSection