From d6a268349cfa2a8024224727a81e361b02f5151c Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Sat, 21 Mar 2020 05:48:58 -0700 Subject: [PATCH 01/24] Organize file structure and add Parse.Core project for future use. --- .../Device}/IDeviceInfoController.cs | 1 - .../IObjectSubclassingController.cs | 3 - .../Management}/IParseCommandRunner.cs | 0 .../Management}/IParseCorePlugins.cs | 1 - .../IParseCurrentUserController.cs | 1 - .../Management}/IParseFileController.cs | 0 .../Management}/IParseObjectController.cs | 1 - .../IParseObjectCurrentController.cs | 1 - .../Management}/IParseUserController.cs | 1 - .../Management/Tracking}/IObjectState.cs | 0 .../Tracking}/IParseFieldOperation.cs | 0 .../Analytics}/IParseAnalyticsController.cs | 0 .../Analytics/IParseAnalyticsPlugins.cs | 4 +- .../IParseAuthenticationProvider.cs | 0 .../Code}/IParseCloudCodeController.cs | 1 - .../Configuration}/IParseConfigController.cs | 3 +- .../IParseCurrentConfigController.cs | 1 - .../Platform/Files/IParseFileController.cs | 18 + .../IInstallationIdController.cs | 1 - .../IParseCurrentInstallationController.cs | 1 - .../Installation}/IParseInstallationCoder.cs | 2 - .../IParsePushChannelsController.cs | 6 +- .../Notifications}/IParsePushController.cs | 3 +- .../Notifications}/IParsePushPlugins.cs | 1 - .../Platform/Notifications}/IPushState.cs | 0 .../Session}/IParseSessionController.cs | 1 - .../Query}/IParseQueryController.cs | 2 - .../Storage}/IStorageController.cs | 1 - .../ParseDownloadProgressEventArgs.cs | 0 .../ParsePushNotificationEventArgs.cs | 44 +++ .../ParseUploadProgressEventArgs.cs | 0 Parse.Core/Parse.Core.csproj | 8 + Parse.Test/ACLTests.cs | 7 +- Parse.Test/AnalyticsControllerTests.cs | 8 +- Parse.Test/AnalyticsTests.cs | 8 +- Parse.Test/CloudControllerTests.cs | 8 +- Parse.Test/CloudTests.cs | 6 +- Parse.Test/CommandTests.cs | 8 +- Parse.Test/ConfigTests.cs | 11 +- Parse.Test/ConversionTests.cs | 2 - Parse.Test/CurrentUserControllerTests.cs | 8 +- Parse.Test/DecoderTests.cs | 4 +- Parse.Test/EncoderTests.cs | 4 +- Parse.Test/FileControllerTests.cs | 6 +- Parse.Test/FileStateTests.cs | 3 +- Parse.Test/FileTests.cs | 6 +- Parse.Test/GeoPointTests.cs | 6 +- Parse.Test/InstallationIdControllerTests.cs | 7 +- Parse.Test/InstallationTests.cs | 8 +- Parse.Test/JsonTests.cs | 3 +- Parse.Test/MoqExtensions.cs | 4 +- Parse.Test/ObjectCoderTests.cs | 10 +- Parse.Test/ObjectControllerTests.cs | 9 +- Parse.Test/ObjectStateTests.cs | 3 +- Parse.Test/ObjectTests.cs | 40 +- Parse.Test/ProgressTests.cs | 4 +- Parse.Test/PushEncoderTests.cs | 4 +- Parse.Test/PushStateTests.cs | 3 +- Parse.Test/PushTests.cs | 10 +- Parse.Test/RelationTests.cs | 4 +- Parse.Test/SessionControllerTests.cs | 19 +- Parse.Test/SessionTests.cs | 7 +- Parse.Test/UserControllerTests.cs | 7 +- Parse.Test/UserTests.cs | 6 +- Parse.sln | 10 +- .../Device/IDeviceInfoController.cs | 24 ++ .../IObjectSubclassingController.cs | 21 + .../Management/IParseCommandRunner.cs | 26 ++ .../Management/IParseCorePlugins.cs | 26 ++ .../Management/IParseCurrentUserController.cs | 14 + .../Management/IParseObjectController.cs | 21 + .../IParseObjectCurrentController.cs | 54 +++ .../Management/IParseUserController.cs | 27 ++ .../Management/Tracking/IObjectState.cs | 21 + .../Tracking/IParseFieldOperation.cs | 46 +++ .../Analytics/IParseAnalyticsController.cs | 39 ++ .../Analytics/IParseAnalyticsPlugins.cs | 15 + .../IParseAuthenticationProvider.cs | 40 ++ .../Code/IParseCloudCodeController.cs | 16 + .../Configuration/IParseConfigController.cs | 24 ++ .../IParseCurrentConfigController.cs | 34 ++ .../Platform/Files/IParseFileController.cs | 18 + .../Installation/IInstallationIdController.cs | 27 ++ .../IParseCurrentInstallationController.cs | 10 + .../Installation/IParseInstallationCoder.cs | 12 + .../IParsePushChannelsController.cs | 14 + .../Notifications/IParsePushController.cs | 12 + .../Notifications/IParsePushPlugins.cs | 15 + .../Platform/Notifications/IPushState.cs | 20 + .../Session/IParseSessionController.cs | 18 + .../Query/IParseQueryController.cs | 17 + .../Storage/IStorageController.cs | 56 +++ .../DeviceInfoController.cs | 0 .../ParseDownloadProgressEventArgs.cs | 19 + Parse/{Public => Framework}/ParseException.cs | 5 +- .../ParsePushNotificationEventArgs.cs | 2 +- .../Framework/ParseUploadProgressEventArgs.cs | 19 + Parse/Internal/Command/ParseCommandRunner.cs | 141 ------- .../ParseCurrentConfigController.cs | 94 ----- .../Push/Controller/ParsePushController.cs | 36 -- .../Controller/ParseCurrentUserController.cs | 188 --------- .../Internal/Utilities/FlexibleListWrapper.cs | 102 ----- .../Internal/Utilities/InternalExtensions.cs | 133 ------- .../Utilities/ParseQueryExtensions.cs | 35 -- .../ObjectSubclassInfo.cs | 3 - .../ObjectSubclassingController.cs | 13 +- .../Command => Management}/ParseCommand.cs | 23 +- Parse/Management/ParseCommandRunner.cs | 138 +++++++ .../ParseCorePlugins.cs | 5 - .../Management/ParseCurrentUserController.cs | 171 +++++++++ .../ParseObjectController.cs | 67 ++-- .../ParseUserController.cs | 31 +- .../Tracking}/MutableObjectState.cs | 65 +--- .../Tracking}/ParseAddOperation.cs | 24 +- .../Tracking}/ParseAddUniqueOperation.cs | 40 +- .../Tracking}/ParseDeleteOperation.cs | 23 +- .../Tracking}/ParseFieldOperations.cs | 11 +- .../Tracking}/ParseIncrementOperation.cs | 31 +- .../Tracking}/ParseRelationOperation.cs | 30 +- .../Tracking}/ParseRemoveOperation.cs | 28 +- .../Tracking}/ParseSetOperation.cs | 20 +- Parse/{Internal => }/Modules/IParseModule.cs | 2 - .../Modules/ParseModuleAttribute.cs | 5 +- .../Modules/ParseModuleController.cs | 9 +- Parse/Parse.csproj | 8 +- Parse/{Public => }/ParseClient.cs | 5 +- Parse/{Public => }/ParseFile.cs | 80 +--- Parse/{Public => }/ParseGeoDistance.cs | 36 +- Parse/{Public => }/ParseGeoPoint.cs | 16 +- Parse/{Public => }/ParseInstallation.cs | 112 ++---- Parse/{Public => }/ParseObject.cs | 361 +++++++----------- Parse/{Public => }/ParsePush.cs | 283 ++++++-------- Parse/{Public => }/ParseQuery.cs | 47 ++- Parse/{Public => }/ParseRelation.cs | 68 +--- Parse/{Public => }/ParseRole.cs | 27 +- Parse/ParseSession.cs | 89 +++++ Parse/{Public => }/ParseUser.cs | 55 +-- .../Analytics}/ParseAnalytics.cs | 35 +- .../Analytics}/ParseAnalyticsController.cs | 0 .../Analytics/ParseAnalyticsPlugins.cs | 1 - Parse/{Public => Platform/Code}/ParseCloud.cs | 19 +- .../Code}/ParseCloudCodeController.cs | 11 +- .../Controller/ParseConfigController.cs | 5 +- .../ParseCurrentConfigController.cs | 80 ++++ .../Configuration}/ParseConfig.cs | 57 +-- .../State => Platform/Files}/FileState.cs | 0 .../Files}/ParseFileController.cs | 15 +- .../Installation}/InstallationIdController.cs | 20 +- .../ParseCurrentInstallationController.cs | 69 ++-- .../Installation}/ParseInstallationCoder.cs | 18 +- .../Notifications}/MutablePushState.cs | 44 +-- .../ParsePushChannelsController.cs | 3 +- .../Notifications/ParsePushController.cs | 31 ++ .../Notifications}/ParsePushEncoder.cs | 13 +- .../Notifications}/ParsePushModule.cs | 1 - .../Notifications}/ParsePushPlugins.cs | 0 .../{Public => Platform/Security}/ParseACL.cs | 2 +- .../Session}/ParseSessionController.cs | 17 +- Parse/Public/ParseSession.cs | 114 ------ .../ParseQueryController.cs | 7 +- .../StorageController.cs | 45 +-- Parse/{ => Utilities}/AssemblyLister.cs | 2 +- Parse/{Public => }/Utilities/Conversion.cs | 0 .../Encoding/NoObjectsEncoder.cs | 13 +- .../Encoding/ParseDecoder.cs | 32 +- .../Encoding/ParseEncoder.cs | 27 +- .../Encoding/ParseObjectCoder.cs | 0 .../Encoding/PointerOrLocalIdEncoder.cs | 8 +- .../Utilities/FlexibleDictionaryWrapper.cs | 89 +---- Parse/Utilities/FlexibleListWrapper.cs | 60 +++ .../HttpClient.Portable.cs | 17 +- .../HttpClient => Utilities}/HttpRequest.cs | 0 .../HttpClient => Utilities}/IHttpClient.cs | 0 .../Utilities/IJsonConvertible.cs | 1 - .../Utilities/IdentityEqualityComparer.cs | 10 +- Parse/Utilities/InternalExtensions.cs | 109 ++++++ Parse/{Internal => }/Utilities/Json.cs | 2 - Parse/{Internal => }/Utilities/LockSet.cs | 0 .../ParseClassNameAttribute.cs | 4 - .../Utilities/ParseConfigExtensions.cs | 6 +- .../{Public => Utilities}/ParseExtensions.cs | 58 +-- .../ParseFieldNameAttribute.cs | 9 +- .../Utilities/ParseFileExtensions.cs | 6 +- .../Utilities/ParseObjectExtensions.cs | 55 +-- .../ParseQueryExtensions.cs | 171 ++++----- .../Utilities/ParseRelationExtensions.cs | 18 +- .../Utilities/ParseSessionExtensions.cs | 12 +- .../Utilities/ParseUserExtensions.cs | 48 +-- .../Utilities/ReflectionHelpers.cs | 67 ++-- .../Utilities/StorageManager.cs | 0 .../Utilities/SynchronizedEventHandler.cs | 11 +- Parse/{Internal => }/Utilities/TaskQueue.cs | 1 - .../Utilities/ThreadingUtilities.cs | 2 - .../Utilities/XamarinAttributes.cs | 5 +- 194 files changed, 2436 insertions(+), 2740 deletions(-) rename {Parse/Internal/Push/DeviceInfo => Parse.Core/Abstractions/Device}/IDeviceInfoController.cs (98%) rename {Parse/Internal/Object/Subclassing => Parse.Core/Abstractions/Management}/IObjectSubclassingController.cs (88%) rename {Parse/Internal/Command => Parse.Core/Abstractions/Management}/IParseCommandRunner.cs (100%) rename {Parse/Internal => Parse.Core/Abstractions/Management}/IParseCorePlugins.cs (98%) rename {Parse/Internal/User/Controller => Parse.Core/Abstractions/Management}/IParseCurrentUserController.cs (97%) rename {Parse/Internal/File/Controller => Parse.Core/Abstractions/Management}/IParseFileController.cs (100%) rename {Parse/Internal/Object/Controller => Parse.Core/Abstractions/Management}/IParseObjectController.cs (98%) rename {Parse/Internal/Object/Controller => Parse.Core/Abstractions/Management}/IParseObjectCurrentController.cs (99%) rename {Parse/Internal/User/Controller => Parse.Core/Abstractions/Management}/IParseUserController.cs (98%) rename {Parse/Internal/Object/State => Parse.Core/Abstractions/Management/Tracking}/IObjectState.cs (100%) rename {Parse/Internal/Operation => Parse.Core/Abstractions/Management/Tracking}/IParseFieldOperation.cs (100%) rename {Parse/Internal/Analytics/Controller => Parse.Core/Abstractions/Platform/Analytics}/IParseAnalyticsController.cs (100%) rename {Parse/Internal => Parse.Core/Abstractions/Platform}/Analytics/IParseAnalyticsPlugins.cs (89%) rename {Parse/Internal => Parse.Core/Abstractions/Platform}/Authentication/IParseAuthenticationProvider.cs (100%) rename {Parse/Internal/Cloud/Controller => Parse.Core/Abstractions/Platform/Code}/IParseCloudCodeController.cs (97%) rename {Parse/Internal/Config/Controller => Parse.Core/Abstractions/Platform/Configuration}/IParseConfigController.cs (98%) rename {Parse/Internal/Config/Controller => Parse.Core/Abstractions/Platform/Configuration}/IParseCurrentConfigController.cs (98%) create mode 100644 Parse.Core/Abstractions/Platform/Files/IParseFileController.cs rename {Parse/Internal/InstallationId/Controller => Parse.Core/Abstractions/Platform/Installation}/IInstallationIdController.cs (97%) rename {Parse/Internal/Push/Installation/Controller => Parse.Core/Abstractions/Platform/Installation}/IParseCurrentInstallationController.cs (97%) rename {Parse/Internal/Push/Installation/Coder => Parse.Core/Abstractions/Platform/Installation}/IParseInstallationCoder.cs (93%) rename {Parse/Internal/Push/Controller => Parse.Core/Abstractions/Platform/Notifications}/IParsePushChannelsController.cs (94%) rename {Parse/Internal/Push/Controller => Parse.Core/Abstractions/Platform/Notifications}/IParsePushController.cs (97%) rename {Parse/Internal/Push => Parse.Core/Abstractions/Platform/Notifications}/IParsePushPlugins.cs (96%) rename {Parse/Internal/Push/State => Parse.Core/Abstractions/Platform/Notifications}/IPushState.cs (100%) rename {Parse/Internal/Session/Controller => Parse.Core/Abstractions/Platform/Session}/IParseSessionController.cs (98%) rename {Parse/Internal/Query/Controller => Parse.Core/Abstractions/Query}/IParseQueryController.cs (96%) rename {Parse/Internal/Storage/Controller => Parse.Core/Abstractions/Storage}/IStorageController.cs (99%) rename {Parse/Public => Parse.Core/Framework}/ParseDownloadProgressEventArgs.cs (100%) create mode 100644 Parse.Core/Framework/ParsePushNotificationEventArgs.cs rename {Parse/Public => Parse.Core/Framework}/ParseUploadProgressEventArgs.cs (100%) create mode 100644 Parse.Core/Parse.Core.csproj create mode 100644 Parse/Abstractions/Device/IDeviceInfoController.cs create mode 100644 Parse/Abstractions/Management/IObjectSubclassingController.cs create mode 100644 Parse/Abstractions/Management/IParseCommandRunner.cs create mode 100644 Parse/Abstractions/Management/IParseCorePlugins.cs create mode 100644 Parse/Abstractions/Management/IParseCurrentUserController.cs create mode 100644 Parse/Abstractions/Management/IParseObjectController.cs create mode 100644 Parse/Abstractions/Management/IParseObjectCurrentController.cs create mode 100644 Parse/Abstractions/Management/IParseUserController.cs create mode 100644 Parse/Abstractions/Management/Tracking/IObjectState.cs create mode 100644 Parse/Abstractions/Management/Tracking/IParseFieldOperation.cs create mode 100644 Parse/Abstractions/Platform/Analytics/IParseAnalyticsController.cs create mode 100644 Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs create mode 100644 Parse/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs create mode 100644 Parse/Abstractions/Platform/Code/IParseCloudCodeController.cs create mode 100644 Parse/Abstractions/Platform/Configuration/IParseConfigController.cs create mode 100644 Parse/Abstractions/Platform/Configuration/IParseCurrentConfigController.cs create mode 100644 Parse/Abstractions/Platform/Files/IParseFileController.cs create mode 100644 Parse/Abstractions/Platform/Installation/IInstallationIdController.cs create mode 100644 Parse/Abstractions/Platform/Installation/IParseCurrentInstallationController.cs create mode 100644 Parse/Abstractions/Platform/Installation/IParseInstallationCoder.cs create mode 100644 Parse/Abstractions/Platform/Notifications/IParsePushChannelsController.cs create mode 100644 Parse/Abstractions/Platform/Notifications/IParsePushController.cs create mode 100644 Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs create mode 100644 Parse/Abstractions/Platform/Notifications/IPushState.cs create mode 100644 Parse/Abstractions/Platform/Session/IParseSessionController.cs create mode 100644 Parse/Abstractions/Query/IParseQueryController.cs create mode 100644 Parse/Abstractions/Storage/IStorageController.cs rename Parse/{Internal/Push/DeviceInfo => Device}/DeviceInfoController.cs (100%) create mode 100644 Parse/Framework/ParseDownloadProgressEventArgs.cs rename Parse/{Public => Framework}/ParseException.cs (99%) rename Parse/{Public => Framework}/ParsePushNotificationEventArgs.cs (100%) create mode 100644 Parse/Framework/ParseUploadProgressEventArgs.cs delete mode 100644 Parse/Internal/Command/ParseCommandRunner.cs delete mode 100644 Parse/Internal/Config/Controller/ParseCurrentConfigController.cs delete mode 100644 Parse/Internal/Push/Controller/ParsePushController.cs delete mode 100644 Parse/Internal/User/Controller/ParseCurrentUserController.cs delete mode 100644 Parse/Internal/Utilities/FlexibleListWrapper.cs delete mode 100644 Parse/Internal/Utilities/InternalExtensions.cs delete mode 100644 Parse/Internal/Utilities/ParseQueryExtensions.cs rename Parse/{Internal/Object/Subclassing => Management}/ObjectSubclassInfo.cs (94%) rename Parse/{Internal/Object/Subclassing => Management}/ObjectSubclassingController.cs (92%) rename Parse/{Internal/Command => Management}/ParseCommand.cs (84%) create mode 100644 Parse/Management/ParseCommandRunner.cs rename Parse/{Internal => Management}/ParseCorePlugins.cs (98%) create mode 100644 Parse/Management/ParseCurrentUserController.cs rename Parse/{Internal/Object/Controller => Management}/ParseObjectController.cs (74%) rename Parse/{Internal/User/Controller => Management}/ParseUserController.cs (75%) rename Parse/{Internal/Object/State => Management/Tracking}/MutableObjectState.cs (61%) rename Parse/{Internal/Operation => Management/Tracking}/ParseAddOperation.cs (73%) rename Parse/{Internal/Operation => Management/Tracking}/ParseAddUniqueOperation.cs (66%) rename Parse/{Internal/Operation => Management/Tracking}/ParseDeleteOperation.cs (65%) rename Parse/{Internal/Operation => Management/Tracking}/ParseFieldOperations.cs (86%) rename Parse/{Internal/Operation => Management/Tracking}/ParseIncrementOperation.cs (93%) rename Parse/{Internal/Operation => Management/Tracking}/ParseRelationOperation.cs (81%) rename Parse/{Internal/Operation => Management/Tracking}/ParseRemoveOperation.cs (70%) rename Parse/{Internal/Operation => Management/Tracking}/ParseSetOperation.cs (57%) rename Parse/{Internal => }/Modules/IParseModule.cs (90%) rename Parse/{Internal => }/Modules/ParseModuleAttribute.cs (77%) rename Parse/{Internal => }/Modules/ParseModuleController.cs (90%) rename Parse/{Public => }/ParseClient.cs (99%) rename Parse/{Public => }/ParseFile.cs (79%) rename Parse/{Public => }/ParseGeoDistance.cs (73%) rename Parse/{Public => }/ParseGeoPoint.cs (92%) rename Parse/{Public => }/ParseInstallation.cs (81%) rename Parse/{Public => }/ParseObject.cs (77%) rename Parse/{Public => }/ParsePush.cs (73%) rename Parse/{Public => }/ParseQuery.cs (96%) rename Parse/{Public => }/ParseRelation.cs (75%) rename Parse/{Public => }/ParseRole.cs (84%) create mode 100644 Parse/ParseSession.cs rename Parse/{Public => }/ParseUser.cs (92%) rename Parse/{Public => Platform/Analytics}/ParseAnalytics.cs (86%) rename Parse/{Internal/Analytics/Controller => Platform/Analytics}/ParseAnalyticsController.cs (100%) rename Parse/{Internal => Platform}/Analytics/ParseAnalyticsPlugins.cs (99%) rename Parse/{Public => Platform/Code}/ParseCloud.cs (87%) rename Parse/{Internal/Cloud/Controller => Platform/Code}/ParseCloudCodeController.cs (82%) rename Parse/{Internal/Config => Platform/Configuration}/Controller/ParseConfigController.cs (96%) create mode 100644 Parse/Platform/Configuration/Controller/ParseCurrentConfigController.cs rename Parse/{Public => Platform/Configuration}/ParseConfig.cs (70%) rename Parse/{Internal/File/State => Platform/Files}/FileState.cs (100%) rename Parse/{Internal/File/Controller => Platform/Files}/ParseFileController.cs (82%) rename Parse/{Internal/InstallationId/Controller => Platform/Installation}/InstallationIdController.cs (84%) rename Parse/{Internal/Push/Installation/Controller => Platform/Installation}/ParseCurrentInstallationController.cs (58%) rename Parse/{Internal/Push/Installation/Coder => Platform/Installation}/ParseInstallationCoder.cs (69%) rename Parse/{Internal/Push/State => Platform/Notifications}/MutablePushState.cs (53%) rename Parse/{Internal/Push/Controller => Platform/Notifications}/ParsePushChannelsController.cs (98%) create mode 100644 Parse/Platform/Notifications/ParsePushController.cs rename Parse/{Internal/Push/Coder => Platform/Notifications}/ParsePushEncoder.cs (85%) rename Parse/{Internal/Push => Platform/Notifications}/ParsePushModule.cs (97%) rename Parse/{Internal/Push => Platform/Notifications}/ParsePushPlugins.cs (100%) rename Parse/{Public => Platform/Security}/ParseACL.cs (100%) rename Parse/{Internal/Session/Controller => Platform/Session}/ParseSessionController.cs (85%) delete mode 100644 Parse/Public/ParseSession.cs rename Parse/{Internal/Query/Controller => Query}/ParseQueryController.cs (94%) rename Parse/{Internal/Storage/Controller => Storage}/StorageController.cs (80%) rename Parse/{ => Utilities}/AssemblyLister.cs (94%) rename Parse/{Public => }/Utilities/Conversion.cs (100%) rename Parse/{Internal => Utilities}/Encoding/NoObjectsEncoder.cs (76%) rename Parse/{Internal => Utilities}/Encoding/ParseDecoder.cs (77%) rename Parse/{Internal => Utilities}/Encoding/ParseEncoder.cs (83%) rename Parse/{Internal => Utilities}/Encoding/ParseObjectCoder.cs (100%) rename Parse/{Internal => Utilities}/Encoding/PointerOrLocalIdEncoder.cs (90%) rename Parse/{Internal => }/Utilities/FlexibleDictionaryWrapper.cs (50%) create mode 100644 Parse/Utilities/FlexibleListWrapper.cs rename Parse/{Internal/HttpClient/Portable => Utilities}/HttpClient.Portable.cs (93%) rename Parse/{Internal/HttpClient => Utilities}/HttpRequest.cs (100%) rename Parse/{Internal/HttpClient => Utilities}/IHttpClient.cs (100%) rename Parse/{Internal => }/Utilities/IJsonConvertible.cs (98%) rename Parse/{Internal => }/Utilities/IdentityEqualityComparer.cs (77%) create mode 100644 Parse/Utilities/InternalExtensions.cs rename Parse/{Internal => }/Utilities/Json.cs (99%) rename Parse/{Internal => }/Utilities/LockSet.cs (100%) rename Parse/{Public => Utilities}/ParseClassNameAttribute.cs (91%) rename Parse/{Internal => }/Utilities/ParseConfigExtensions.cs (90%) rename Parse/{Public => Utilities}/ParseExtensions.cs (78%) rename Parse/{Public => Utilities}/ParseFieldNameAttribute.cs (82%) rename Parse/{Internal => }/Utilities/ParseFileExtensions.cs (88%) rename Parse/{Internal => }/Utilities/ParseObjectExtensions.cs (63%) rename Parse/{Public => Utilities}/ParseQueryExtensions.cs (85%) rename Parse/{Internal => }/Utilities/ParseRelationExtensions.cs (75%) rename Parse/{Internal => }/Utilities/ParseSessionExtensions.cs (75%) rename Parse/{Internal => }/Utilities/ParseUserExtensions.cs (64%) rename Parse/{Internal => }/Utilities/ReflectionHelpers.cs (74%) rename Parse/{Internal => }/Utilities/StorageManager.cs (100%) rename Parse/{Internal => }/Utilities/SynchronizedEventHandler.cs (88%) rename Parse/{Internal => }/Utilities/TaskQueue.cs (99%) rename Parse/{Internal => }/Utilities/ThreadingUtilities.cs (90%) rename Parse/{Internal => }/Utilities/XamarinAttributes.cs (99%) diff --git a/Parse/Internal/Push/DeviceInfo/IDeviceInfoController.cs b/Parse.Core/Abstractions/Device/IDeviceInfoController.cs similarity index 98% rename from Parse/Internal/Push/DeviceInfo/IDeviceInfoController.cs rename to Parse.Core/Abstractions/Device/IDeviceInfoController.cs index 2e5f4c7b..9d4d36ca 100644 --- a/Parse/Internal/Push/DeviceInfo/IDeviceInfoController.cs +++ b/Parse.Core/Abstractions/Device/IDeviceInfoController.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.Tasks; namespace Parse.Push.Internal diff --git a/Parse/Internal/Object/Subclassing/IObjectSubclassingController.cs b/Parse.Core/Abstractions/Management/IObjectSubclassingController.cs similarity index 88% rename from Parse/Internal/Object/Subclassing/IObjectSubclassingController.cs rename to Parse.Core/Abstractions/Management/IObjectSubclassingController.cs index ab4932d0..d899f917 100644 --- a/Parse/Internal/Object/Subclassing/IObjectSubclassingController.cs +++ b/Parse.Core/Abstractions/Management/IObjectSubclassingController.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Parse.Core.Internal { diff --git a/Parse/Internal/Command/IParseCommandRunner.cs b/Parse.Core/Abstractions/Management/IParseCommandRunner.cs similarity index 100% rename from Parse/Internal/Command/IParseCommandRunner.cs rename to Parse.Core/Abstractions/Management/IParseCommandRunner.cs diff --git a/Parse/Internal/IParseCorePlugins.cs b/Parse.Core/Abstractions/Management/IParseCorePlugins.cs similarity index 98% rename from Parse/Internal/IParseCorePlugins.cs rename to Parse.Core/Abstractions/Management/IParseCorePlugins.cs index 73d8dc76..76cc4bac 100644 --- a/Parse/Internal/IParseCorePlugins.cs +++ b/Parse.Core/Abstractions/Management/IParseCorePlugins.cs @@ -1,7 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using Parse.Common.Internal; -using System; namespace Parse.Core.Internal { diff --git a/Parse/Internal/User/Controller/IParseCurrentUserController.cs b/Parse.Core/Abstractions/Management/IParseCurrentUserController.cs similarity index 97% rename from Parse/Internal/User/Controller/IParseCurrentUserController.cs rename to Parse.Core/Abstractions/Management/IParseCurrentUserController.cs index d235c480..c61b74ce 100644 --- a/Parse/Internal/User/Controller/IParseCurrentUserController.cs +++ b/Parse.Core/Abstractions/Management/IParseCurrentUserController.cs @@ -1,6 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Threading; using System.Threading.Tasks; diff --git a/Parse/Internal/File/Controller/IParseFileController.cs b/Parse.Core/Abstractions/Management/IParseFileController.cs similarity index 100% rename from Parse/Internal/File/Controller/IParseFileController.cs rename to Parse.Core/Abstractions/Management/IParseFileController.cs diff --git a/Parse/Internal/Object/Controller/IParseObjectController.cs b/Parse.Core/Abstractions/Management/IParseObjectController.cs similarity index 98% rename from Parse/Internal/Object/Controller/IParseObjectController.cs rename to Parse.Core/Abstractions/Management/IParseObjectController.cs index 0dd2363d..e0f95415 100644 --- a/Parse/Internal/Object/Controller/IParseObjectController.cs +++ b/Parse.Core/Abstractions/Management/IParseObjectController.cs @@ -1,6 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; diff --git a/Parse/Internal/Object/Controller/IParseObjectCurrentController.cs b/Parse.Core/Abstractions/Management/IParseObjectCurrentController.cs similarity index 99% rename from Parse/Internal/Object/Controller/IParseObjectCurrentController.cs rename to Parse.Core/Abstractions/Management/IParseObjectCurrentController.cs index eb505108..016e54ba 100644 --- a/Parse/Internal/Object/Controller/IParseObjectCurrentController.cs +++ b/Parse.Core/Abstractions/Management/IParseObjectCurrentController.cs @@ -1,6 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Threading; using System.Threading.Tasks; diff --git a/Parse/Internal/User/Controller/IParseUserController.cs b/Parse.Core/Abstractions/Management/IParseUserController.cs similarity index 98% rename from Parse/Internal/User/Controller/IParseUserController.cs rename to Parse.Core/Abstractions/Management/IParseUserController.cs index ff90feb8..c3ccaff9 100644 --- a/Parse/Internal/User/Controller/IParseUserController.cs +++ b/Parse.Core/Abstractions/Management/IParseUserController.cs @@ -1,6 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; diff --git a/Parse/Internal/Object/State/IObjectState.cs b/Parse.Core/Abstractions/Management/Tracking/IObjectState.cs similarity index 100% rename from Parse/Internal/Object/State/IObjectState.cs rename to Parse.Core/Abstractions/Management/Tracking/IObjectState.cs diff --git a/Parse/Internal/Operation/IParseFieldOperation.cs b/Parse.Core/Abstractions/Management/Tracking/IParseFieldOperation.cs similarity index 100% rename from Parse/Internal/Operation/IParseFieldOperation.cs rename to Parse.Core/Abstractions/Management/Tracking/IParseFieldOperation.cs diff --git a/Parse/Internal/Analytics/Controller/IParseAnalyticsController.cs b/Parse.Core/Abstractions/Platform/Analytics/IParseAnalyticsController.cs similarity index 100% rename from Parse/Internal/Analytics/Controller/IParseAnalyticsController.cs rename to Parse.Core/Abstractions/Platform/Analytics/IParseAnalyticsController.cs diff --git a/Parse/Internal/Analytics/IParseAnalyticsPlugins.cs b/Parse.Core/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs similarity index 89% rename from Parse/Internal/Analytics/IParseAnalyticsPlugins.cs rename to Parse.Core/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs index 0e31876e..709b8e7c 100644 --- a/Parse/Internal/Analytics/IParseAnalyticsPlugins.cs +++ b/Parse.Core/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs @@ -1,9 +1,9 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. +using Parse.Analytics.Internal; using Parse.Core.Internal; -using System; -namespace Parse.Analytics.Internal +namespace Parse.Analytics { public interface IParseAnalyticsPlugins { diff --git a/Parse/Internal/Authentication/IParseAuthenticationProvider.cs b/Parse.Core/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs similarity index 100% rename from Parse/Internal/Authentication/IParseAuthenticationProvider.cs rename to Parse.Core/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs diff --git a/Parse/Internal/Cloud/Controller/IParseCloudCodeController.cs b/Parse.Core/Abstractions/Platform/Code/IParseCloudCodeController.cs similarity index 97% rename from Parse/Internal/Cloud/Controller/IParseCloudCodeController.cs rename to Parse.Core/Abstractions/Platform/Code/IParseCloudCodeController.cs index bc4d237d..834a8984 100644 --- a/Parse/Internal/Cloud/Controller/IParseCloudCodeController.cs +++ b/Parse.Core/Abstractions/Platform/Code/IParseCloudCodeController.cs @@ -1,6 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; diff --git a/Parse/Internal/Config/Controller/IParseConfigController.cs b/Parse.Core/Abstractions/Platform/Configuration/IParseConfigController.cs similarity index 98% rename from Parse/Internal/Config/Controller/IParseConfigController.cs rename to Parse.Core/Abstractions/Platform/Configuration/IParseConfigController.cs index f3051eb5..f3b8c07f 100644 --- a/Parse/Internal/Config/Controller/IParseConfigController.cs +++ b/Parse.Core/Abstractions/Platform/Configuration/IParseConfigController.cs @@ -1,8 +1,7 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; -using System.Threading.Tasks; using System.Threading; +using System.Threading.Tasks; namespace Parse.Core.Internal { diff --git a/Parse/Internal/Config/Controller/IParseCurrentConfigController.cs b/Parse.Core/Abstractions/Platform/Configuration/IParseCurrentConfigController.cs similarity index 98% rename from Parse/Internal/Config/Controller/IParseCurrentConfigController.cs rename to Parse.Core/Abstractions/Platform/Configuration/IParseCurrentConfigController.cs index 307e431e..35571e52 100644 --- a/Parse/Internal/Config/Controller/IParseCurrentConfigController.cs +++ b/Parse.Core/Abstractions/Platform/Configuration/IParseCurrentConfigController.cs @@ -1,6 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Threading.Tasks; namespace Parse.Core.Internal diff --git a/Parse.Core/Abstractions/Platform/Files/IParseFileController.cs b/Parse.Core/Abstractions/Platform/Files/IParseFileController.cs new file mode 100644 index 00000000..f2e1e6a2 --- /dev/null +++ b/Parse.Core/Abstractions/Platform/Files/IParseFileController.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Parse.Core.Internal +{ + public interface IParseFileController + { + Task SaveAsync(FileState state, + Stream dataStream, + string sessionToken, + IProgress progress, + CancellationToken cancellationToken); + } +} diff --git a/Parse/Internal/InstallationId/Controller/IInstallationIdController.cs b/Parse.Core/Abstractions/Platform/Installation/IInstallationIdController.cs similarity index 97% rename from Parse/Internal/InstallationId/Controller/IInstallationIdController.cs rename to Parse.Core/Abstractions/Platform/Installation/IInstallationIdController.cs index 1cdf27ea..a95cfb1f 100644 --- a/Parse/Internal/InstallationId/Controller/IInstallationIdController.cs +++ b/Parse.Core/Abstractions/Platform/Installation/IInstallationIdController.cs @@ -1,7 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; -using System.Threading; using System.Threading.Tasks; namespace Parse.Core.Internal diff --git a/Parse/Internal/Push/Installation/Controller/IParseCurrentInstallationController.cs b/Parse.Core/Abstractions/Platform/Installation/IParseCurrentInstallationController.cs similarity index 97% rename from Parse/Internal/Push/Installation/Controller/IParseCurrentInstallationController.cs rename to Parse.Core/Abstractions/Platform/Installation/IParseCurrentInstallationController.cs index c8d6fb0a..d8fd32fa 100644 --- a/Parse/Internal/Push/Installation/Controller/IParseCurrentInstallationController.cs +++ b/Parse.Core/Abstractions/Platform/Installation/IParseCurrentInstallationController.cs @@ -1,7 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using Parse.Core.Internal; -using System; namespace Parse.Push.Internal { diff --git a/Parse/Internal/Push/Installation/Coder/IParseInstallationCoder.cs b/Parse.Core/Abstractions/Platform/Installation/IParseInstallationCoder.cs similarity index 93% rename from Parse/Internal/Push/Installation/Coder/IParseInstallationCoder.cs rename to Parse.Core/Abstractions/Platform/Installation/IParseInstallationCoder.cs index a1166ded..0182b610 100644 --- a/Parse/Internal/Push/Installation/Coder/IParseInstallationCoder.cs +++ b/Parse.Core/Abstractions/Platform/Installation/IParseInstallationCoder.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Generic; -using Parse; namespace Parse.Push.Internal { diff --git a/Parse/Internal/Push/Controller/IParsePushChannelsController.cs b/Parse.Core/Abstractions/Platform/Notifications/IParsePushChannelsController.cs similarity index 94% rename from Parse/Internal/Push/Controller/IParsePushChannelsController.cs rename to Parse.Core/Abstractions/Platform/Notifications/IParsePushChannelsController.cs index 9ed55bdb..befa37a5 100644 --- a/Parse/Internal/Push/Controller/IParsePushChannelsController.cs +++ b/Parse.Core/Abstractions/Platform/Notifications/IParsePushChannelsController.cs @@ -1,10 +1,8 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; -using System.Threading.Tasks; -using System.Collections; -using System.Threading; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace Parse.Push.Internal { diff --git a/Parse/Internal/Push/Controller/IParsePushController.cs b/Parse.Core/Abstractions/Platform/Notifications/IParsePushController.cs similarity index 97% rename from Parse/Internal/Push/Controller/IParsePushController.cs rename to Parse.Core/Abstractions/Platform/Notifications/IParsePushController.cs index 14b1569e..f567df76 100644 --- a/Parse/Internal/Push/Controller/IParsePushController.cs +++ b/Parse.Core/Abstractions/Platform/Notifications/IParsePushController.cs @@ -1,8 +1,7 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; -using System.Threading.Tasks; using System.Threading; +using System.Threading.Tasks; namespace Parse.Push.Internal { diff --git a/Parse/Internal/Push/IParsePushPlugins.cs b/Parse.Core/Abstractions/Platform/Notifications/IParsePushPlugins.cs similarity index 96% rename from Parse/Internal/Push/IParsePushPlugins.cs rename to Parse.Core/Abstractions/Platform/Notifications/IParsePushPlugins.cs index 0b0ba3e6..695bc288 100644 --- a/Parse/Internal/Push/IParsePushPlugins.cs +++ b/Parse.Core/Abstractions/Platform/Notifications/IParsePushPlugins.cs @@ -1,4 +1,3 @@ -using System; using Parse.Core.Internal; namespace Parse.Push.Internal diff --git a/Parse/Internal/Push/State/IPushState.cs b/Parse.Core/Abstractions/Platform/Notifications/IPushState.cs similarity index 100% rename from Parse/Internal/Push/State/IPushState.cs rename to Parse.Core/Abstractions/Platform/Notifications/IPushState.cs diff --git a/Parse/Internal/Session/Controller/IParseSessionController.cs b/Parse.Core/Abstractions/Platform/Session/IParseSessionController.cs similarity index 98% rename from Parse/Internal/Session/Controller/IParseSessionController.cs rename to Parse.Core/Abstractions/Platform/Session/IParseSessionController.cs index a7f75b27..304b2c23 100644 --- a/Parse/Internal/Session/Controller/IParseSessionController.cs +++ b/Parse.Core/Abstractions/Platform/Session/IParseSessionController.cs @@ -1,6 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Threading; using System.Threading.Tasks; diff --git a/Parse/Internal/Query/Controller/IParseQueryController.cs b/Parse.Core/Abstractions/Query/IParseQueryController.cs similarity index 96% rename from Parse/Internal/Query/Controller/IParseQueryController.cs rename to Parse.Core/Abstractions/Query/IParseQueryController.cs index 7a490ac0..bc473785 100644 --- a/Parse/Internal/Query/Controller/IParseQueryController.cs +++ b/Parse.Core/Abstractions/Query/IParseQueryController.cs @@ -1,7 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; -using System.Linq; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; diff --git a/Parse/Internal/Storage/Controller/IStorageController.cs b/Parse.Core/Abstractions/Storage/IStorageController.cs similarity index 99% rename from Parse/Internal/Storage/Controller/IStorageController.cs rename to Parse.Core/Abstractions/Storage/IStorageController.cs index f76e4584..b0925a84 100644 --- a/Parse/Internal/Storage/Controller/IStorageController.cs +++ b/Parse.Core/Abstractions/Storage/IStorageController.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/Parse/Public/ParseDownloadProgressEventArgs.cs b/Parse.Core/Framework/ParseDownloadProgressEventArgs.cs similarity index 100% rename from Parse/Public/ParseDownloadProgressEventArgs.cs rename to Parse.Core/Framework/ParseDownloadProgressEventArgs.cs diff --git a/Parse.Core/Framework/ParsePushNotificationEventArgs.cs b/Parse.Core/Framework/ParsePushNotificationEventArgs.cs new file mode 100644 index 00000000..0df3b323 --- /dev/null +++ b/Parse.Core/Framework/ParsePushNotificationEventArgs.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.Collections.Generic; + +namespace Parse +{ + /// + /// A wrapper around Parse push notification payload. + /// + public class ParsePushNotificationEventArgs : EventArgs + { + internal ParsePushNotificationEventArgs(IDictionary payload) + { + Payload = payload; + +#if !IOS + StringPayload = Json.Encode(payload); +#endif + } + + // TODO: (richardross) investigate this. + // Obj-C type -> .NET type is impossible to do flawlessly (especially + // on NSNumber). We can't transform NSDictionary into string because of this reason. +#if !IOS + internal ParsePushNotificationEventArgs(string stringPayload) + { + StringPayload = stringPayload; + + Payload = Json.Parse(stringPayload) as IDictionary; + } +#endif + + /// + /// The payload of the push notification as IDictionary. + /// + public IDictionary Payload { get; internal set; } + + /// + /// The payload of the push notification as string. + /// + public string StringPayload { get; internal set; } + } +} diff --git a/Parse/Public/ParseUploadProgressEventArgs.cs b/Parse.Core/Framework/ParseUploadProgressEventArgs.cs similarity index 100% rename from Parse/Public/ParseUploadProgressEventArgs.cs rename to Parse.Core/Framework/ParseUploadProgressEventArgs.cs diff --git a/Parse.Core/Parse.Core.csproj b/Parse.Core/Parse.Core.csproj new file mode 100644 index 00000000..fddef9d2 --- /dev/null +++ b/Parse.Core/Parse.Core.csproj @@ -0,0 +1,8 @@ + + + + netstandard2.0 + latest + + + diff --git a/Parse.Test/ACLTests.cs b/Parse.Test/ACLTests.cs index 0524e558..c35b2c9c 100644 --- a/Parse.Test/ACLTests.cs +++ b/Parse.Test/ACLTests.cs @@ -1,11 +1,6 @@ +using System; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; using Parse.Core.Internal; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; namespace Parse.Test { diff --git a/Parse.Test/AnalyticsControllerTests.cs b/Parse.Test/AnalyticsControllerTests.cs index f92a6f96..925693b3 100644 --- a/Parse.Test/AnalyticsControllerTests.cs +++ b/Parse.Test/AnalyticsControllerTests.cs @@ -1,15 +1,13 @@ -using Moq; -using Parse; -using Parse.Analytics.Internal; -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.Net; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Analytics.Internal; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/AnalyticsTests.cs b/Parse.Test/AnalyticsTests.cs index ca4cd3d4..75f2d708 100644 --- a/Parse.Test/AnalyticsTests.cs +++ b/Parse.Test/AnalyticsTests.cs @@ -1,13 +1,11 @@ -using Moq; -using Parse; -using Parse.Analytics.Internal; -using Parse.Core.Internal; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Analytics.Internal; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/CloudControllerTests.cs b/Parse.Test/CloudControllerTests.cs index 3c76b121..5832b6c7 100644 --- a/Parse.Test/CloudControllerTests.cs +++ b/Parse.Test/CloudControllerTests.cs @@ -1,14 +1,12 @@ -using Moq; -using Parse; -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.Net; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Core.Internal; namespace Parse.Test { @@ -56,7 +54,7 @@ public Task TestCallFunction() [TestMethod] [AsyncStateMachine(typeof(CloudControllerTests))] - public Task TestCallFunctionWithWrongType() => new ParseCloudCodeController(this.CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary() { { "result", "gogo" } })).Object).CallFunctionAsync("someFunction", null, null, CancellationToken.None).ContinueWith(t => + public Task TestCallFunctionWithWrongType() => new ParseCloudCodeController(CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary() { { "result", "gogo" } })).Object).CallFunctionAsync("someFunction", null, null, CancellationToken.None).ContinueWith(t => { Assert.IsTrue(t.IsFaulted); Assert.IsFalse(t.IsCanceled); diff --git a/Parse.Test/CloudTests.cs b/Parse.Test/CloudTests.cs index de373103..eb043a30 100644 --- a/Parse.Test/CloudTests.cs +++ b/Parse.Test/CloudTests.cs @@ -1,12 +1,10 @@ -using Moq; -using Parse; -using Parse.Core.Internal; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/CommandTests.cs b/Parse.Test/CommandTests.cs index f46980bb..b9f134b6 100644 --- a/Parse.Test/CommandTests.cs +++ b/Parse.Test/CommandTests.cs @@ -1,7 +1,3 @@ -using Moq; -using Parse; -using Parse.Common.Internal; -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.Linq; @@ -9,8 +5,10 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Common.Internal; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/ConfigTests.cs b/Parse.Test/ConfigTests.cs index 172a4777..85fec09c 100644 --- a/Parse.Test/ConfigTests.cs +++ b/Parse.Test/ConfigTests.cs @@ -1,15 +1,12 @@ -using Moq; -using Parse; -using Parse.Common.Internal; -using Parse.Core.Internal; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Newtonsoft.Json; - using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; +using Moq; +using Newtonsoft.Json; +using Parse.Common.Internal; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/ConversionTests.cs b/Parse.Test/ConversionTests.cs index 053a3556..e65e3388 100644 --- a/Parse.Test/ConversionTests.cs +++ b/Parse.Test/ConversionTests.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using Parse.Utilities; diff --git a/Parse.Test/CurrentUserControllerTests.cs b/Parse.Test/CurrentUserControllerTests.cs index 8e37c946..65059a12 100644 --- a/Parse.Test/CurrentUserControllerTests.cs +++ b/Parse.Test/CurrentUserControllerTests.cs @@ -1,14 +1,12 @@ -using Moq; -using Parse; -using Parse.Common.Internal; -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Common.Internal; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/DecoderTests.cs b/Parse.Test/DecoderTests.cs index ebff90d1..1233d7e1 100644 --- a/Parse.Test/DecoderTests.cs +++ b/Parse.Test/DecoderTests.cs @@ -1,9 +1,7 @@ -using Parse; -using Parse.Core.Internal; using System; using System.Collections.Generic; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/EncoderTests.cs b/Parse.Test/EncoderTests.cs index e6b3624c..1b021da2 100644 --- a/Parse.Test/EncoderTests.cs +++ b/Parse.Test/EncoderTests.cs @@ -1,11 +1,9 @@ -using Parse; -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Parse.Core.Internal; // TODO (hallucinogen): mock ParseACL, ParseObject, ParseUser once we have their Interfaces namespace Parse.Test diff --git a/Parse.Test/FileControllerTests.cs b/Parse.Test/FileControllerTests.cs index 2d1ac926..88a51912 100644 --- a/Parse.Test/FileControllerTests.cs +++ b/Parse.Test/FileControllerTests.cs @@ -1,6 +1,3 @@ -using Moq; -using Parse; -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.IO; @@ -8,8 +5,9 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/FileStateTests.cs b/Parse.Test/FileStateTests.cs index 7b378d11..2ba4d991 100644 --- a/Parse.Test/FileStateTests.cs +++ b/Parse.Test/FileStateTests.cs @@ -1,7 +1,6 @@ -using Parse.Core.Internal; using System; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/FileTests.cs b/Parse.Test/FileTests.cs index 6e287cde..79a6ffbf 100644 --- a/Parse.Test/FileTests.cs +++ b/Parse.Test/FileTests.cs @@ -1,13 +1,11 @@ -using Moq; -using Parse; -using Parse.Core.Internal; using System; using System.IO; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/GeoPointTests.cs b/Parse.Test/GeoPointTests.cs index fef1b8df..4d99d60a 100644 --- a/Parse.Test/GeoPointTests.cs +++ b/Parse.Test/GeoPointTests.cs @@ -1,12 +1,10 @@ -using Parse; -using Parse.Common.Internal; -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.Globalization; using System.Threading; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Parse.Common.Internal; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/InstallationIdControllerTests.cs b/Parse.Test/InstallationIdControllerTests.cs index e467486e..b0d6ea83 100644 --- a/Parse.Test/InstallationIdControllerTests.cs +++ b/Parse.Test/InstallationIdControllerTests.cs @@ -1,11 +1,10 @@ -using Moq; -using Parse.Common.Internal; -using Parse.Core.Internal; using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Common.Internal; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/InstallationTests.cs b/Parse.Test/InstallationTests.cs index 9ef3c89a..326b0a2a 100644 --- a/Parse.Test/InstallationTests.cs +++ b/Parse.Test/InstallationTests.cs @@ -1,13 +1,11 @@ -using Moq; -using Parse; -using Parse.Core.Internal; -using Parse.Push.Internal; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Core.Internal; +using Parse.Push.Internal; namespace Parse.Test { diff --git a/Parse.Test/JsonTests.cs b/Parse.Test/JsonTests.cs index 1105a7f5..82ac2b6b 100644 --- a/Parse.Test/JsonTests.cs +++ b/Parse.Test/JsonTests.cs @@ -1,9 +1,8 @@ -using Parse.Common.Internal; using System; using System.Collections; using System.Collections.Generic; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Parse.Common.Internal; namespace Parse.Test { diff --git a/Parse.Test/MoqExtensions.cs b/Parse.Test/MoqExtensions.cs index f9569e79..f88e44cf 100644 --- a/Parse.Test/MoqExtensions.cs +++ b/Parse.Test/MoqExtensions.cs @@ -1,6 +1,6 @@ -using Moq.Language; +using System.Reflection; +using Moq.Language; using Moq.Language.Flow; -using System.Reflection; namespace Parse.Test { diff --git a/Parse.Test/ObjectCoderTests.cs b/Parse.Test/ObjectCoderTests.cs index 488f8216..5b1a3f2e 100644 --- a/Parse.Test/ObjectCoderTests.cs +++ b/Parse.Test/ObjectCoderTests.cs @@ -1,14 +1,6 @@ -using System.Xml.XPath; -using System.Linq; -using System.Diagnostics; -using Castle.DynamicProxy.Generators.Emitters; -using Parse.Core.Internal; -using System; using System.Collections.Generic; -using System.Text; -using Parse; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/ObjectControllerTests.cs b/Parse.Test/ObjectControllerTests.cs index 3ed4f4b9..8f5ed1f6 100644 --- a/Parse.Test/ObjectControllerTests.cs +++ b/Parse.Test/ObjectControllerTests.cs @@ -1,6 +1,3 @@ -using Moq; -using Parse; -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.Linq; @@ -8,8 +5,9 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Core.Internal; namespace Parse.Test { @@ -389,7 +387,8 @@ public Task TestDeleteAllFailSome() } }); } - else results.Add(new Dictionary { ["success"] = null }); + else + results.Add(new Dictionary { ["success"] = null }); } Dictionary responseDict = new Dictionary { ["results"] = results }; diff --git a/Parse.Test/ObjectStateTests.cs b/Parse.Test/ObjectStateTests.cs index 65d5cbb3..60942b32 100644 --- a/Parse.Test/ObjectStateTests.cs +++ b/Parse.Test/ObjectStateTests.cs @@ -1,9 +1,8 @@ -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.Linq; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/ObjectTests.cs b/Parse.Test/ObjectTests.cs index ac51ad45..afbb054a 100644 --- a/Parse.Test/ObjectTests.cs +++ b/Parse.Test/ObjectTests.cs @@ -1,12 +1,10 @@ -using Parse; -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Parse.Core.Internal; namespace Parse.Test { @@ -450,50 +448,38 @@ public void TestPropertyChanged() [TestMethod] [AsyncStateMachine(typeof(ObjectTests))] - public Task TestSave() - { + public Task TestSave() => // TODO (hallucinogen): do this - return Task.FromResult(0); - } + Task.FromResult(0); [TestMethod] [AsyncStateMachine(typeof(ObjectTests))] - public Task TestSaveAll() - { + public Task TestSaveAll() => // TODO (hallucinogen): do this - return Task.FromResult(0); - } + Task.FromResult(0); [TestMethod] [AsyncStateMachine(typeof(ObjectTests))] - public Task TestDelete() - { + public Task TestDelete() => // TODO (hallucinogen): do this - return Task.FromResult(0); - } + Task.FromResult(0); [TestMethod] [AsyncStateMachine(typeof(ObjectTests))] - public Task TestDeleteAll() - { + public Task TestDeleteAll() => // TODO (hallucinogen): do this - return Task.FromResult(0); - } + Task.FromResult(0); [TestMethod] [AsyncStateMachine(typeof(ObjectTests))] - public Task TestFetch() - { + public Task TestFetch() => // TODO (hallucinogen): do this - return Task.FromResult(0); - } + Task.FromResult(0); [TestMethod] [AsyncStateMachine(typeof(ObjectTests))] - public Task TestFetchAll() - { + public Task TestFetchAll() => // TODO (hallucinogen): do this - return Task.FromResult(0); - } + Task.FromResult(0); } } diff --git a/Parse.Test/ProgressTests.cs b/Parse.Test/ProgressTests.cs index 5c29f383..e12ddf43 100644 --- a/Parse.Test/ProgressTests.cs +++ b/Parse.Test/ProgressTests.cs @@ -1,8 +1,6 @@ -using Moq; -using Parse; using System; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; namespace Parse.Test { diff --git a/Parse.Test/PushEncoderTests.cs b/Parse.Test/PushEncoderTests.cs index b8268099..3ac10a0f 100644 --- a/Parse.Test/PushEncoderTests.cs +++ b/Parse.Test/PushEncoderTests.cs @@ -1,10 +1,8 @@ -using Parse.Push.Internal; using System; -using System.Linq; using System.Collections.Generic; - using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; +using Parse.Push.Internal; namespace Parse.Test { diff --git a/Parse.Test/PushStateTests.cs b/Parse.Test/PushStateTests.cs index 041d9ca3..e980e84c 100644 --- a/Parse.Test/PushStateTests.cs +++ b/Parse.Test/PushStateTests.cs @@ -1,6 +1,5 @@ -using Parse.Push.Internal; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Parse.Push.Internal; namespace Parse.Test { diff --git a/Parse.Test/PushTests.cs b/Parse.Test/PushTests.cs index 646b8429..45c55b22 100644 --- a/Parse.Test/PushTests.cs +++ b/Parse.Test/PushTests.cs @@ -1,14 +1,12 @@ -using Moq; -using Parse; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Push.Internal; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Common.Internal; +using Parse.Core.Internal; +using Parse.Push.Internal; namespace Parse.Test { diff --git a/Parse.Test/RelationTests.cs b/Parse.Test/RelationTests.cs index 9f4269d6..cebda342 100644 --- a/Parse.Test/RelationTests.cs +++ b/Parse.Test/RelationTests.cs @@ -1,8 +1,6 @@ -using Parse; -using Parse.Core.Internal; using System.Collections.Generic; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/SessionControllerTests.cs b/Parse.Test/SessionControllerTests.cs index 1a8e4bf0..be9d1d9e 100644 --- a/Parse.Test/SessionControllerTests.cs +++ b/Parse.Test/SessionControllerTests.cs @@ -1,6 +1,3 @@ -using Moq; -using Parse; -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.Linq; @@ -8,8 +5,9 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Core.Internal; namespace Parse.Test { @@ -21,14 +19,11 @@ public class SessionControllerTests [TestMethod] [AsyncStateMachine(typeof(SessionControllerTests))] - public Task TestGetSessionWithEmptyResult() - { - return new ParseSessionController(this.CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, null)).Object).GetSessionAsync("S0m3Se551on", CancellationToken.None).ContinueWith(t => - { - Assert.IsTrue(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - }); - } + public Task TestGetSessionWithEmptyResult() => new ParseSessionController(CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, null)).Object).GetSessionAsync("S0m3Se551on", CancellationToken.None).ContinueWith(t => + { + Assert.IsTrue(t.IsFaulted); + Assert.IsFalse(t.IsCanceled); + }); [TestMethod] [AsyncStateMachine(typeof(SessionControllerTests))] diff --git a/Parse.Test/SessionTests.cs b/Parse.Test/SessionTests.cs index a7ddb7fc..e83ccab4 100644 --- a/Parse.Test/SessionTests.cs +++ b/Parse.Test/SessionTests.cs @@ -1,11 +1,10 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using Parse; -using Parse.Core.Internal; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/UserControllerTests.cs b/Parse.Test/UserControllerTests.cs index 49d25d7c..81ddb355 100644 --- a/Parse.Test/UserControllerTests.cs +++ b/Parse.Test/UserControllerTests.cs @@ -1,13 +1,12 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using Parse; -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.Net; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.Test/UserTests.cs b/Parse.Test/UserTests.cs index 01b98caa..51c6722f 100644 --- a/Parse.Test/UserTests.cs +++ b/Parse.Test/UserTests.cs @@ -1,13 +1,11 @@ -using Moq; -using Parse; -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Core.Internal; namespace Parse.Test { diff --git a/Parse.sln b/Parse.sln index e7669198..c4b0fb52 100644 --- a/Parse.sln +++ b/Parse.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2036 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29905.134 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Parse", "Parse\Parse.csproj", "{297FE1CA-AEDF-47BB-964D-E82780EA0A1C}" EndProject @@ -17,6 +17,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Parse.Core", "Parse.Core\Parse.Core.csproj", "{F1AC4286-3E67-4C0B-AB46-B03208A996E7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,6 +33,10 @@ Global {F54F1CF4-89AB-48E9-99C5-6CCD88B3EDBB}.Debug|Any CPU.Build.0 = Debug|Any CPU {F54F1CF4-89AB-48E9-99C5-6CCD88B3EDBB}.Release|Any CPU.ActiveCfg = Release|Any CPU {F54F1CF4-89AB-48E9-99C5-6CCD88B3EDBB}.Release|Any CPU.Build.0 = Release|Any CPU + {F1AC4286-3E67-4C0B-AB46-B03208A996E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1AC4286-3E67-4C0B-AB46-B03208A996E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1AC4286-3E67-4C0B-AB46-B03208A996E7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1AC4286-3E67-4C0B-AB46-B03208A996E7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Parse/Abstractions/Device/IDeviceInfoController.cs b/Parse/Abstractions/Device/IDeviceInfoController.cs new file mode 100644 index 00000000..9d4d36ca --- /dev/null +++ b/Parse/Abstractions/Device/IDeviceInfoController.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; + +namespace Parse.Push.Internal +{ + public interface IDeviceInfoController + { + string DeviceType { get; } + string DeviceTimeZone { get; } + string AppBuildVersion { get; } + string AppIdentifier { get; } + string AppName { get; } + + + /// + /// Executes platform specific hook that mutate the installation based on + /// the device platforms. + /// + /// Installation to be mutated. + /// + Task ExecuteParseInstallationSaveHookAsync(ParseInstallation installation); + + void Initialize(); + } +} diff --git a/Parse/Abstractions/Management/IObjectSubclassingController.cs b/Parse/Abstractions/Management/IObjectSubclassingController.cs new file mode 100644 index 00000000..d899f917 --- /dev/null +++ b/Parse/Abstractions/Management/IObjectSubclassingController.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace Parse.Core.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); + + ParseObject Instantiate(string className); + IDictionary GetPropertyMappings(string className); + } +} diff --git a/Parse/Abstractions/Management/IParseCommandRunner.cs b/Parse/Abstractions/Management/IParseCommandRunner.cs new file mode 100644 index 00000000..0739a45d --- /dev/null +++ b/Parse/Abstractions/Management/IParseCommandRunner.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace Parse.Core.Internal +{ + public interface IParseCommandRunner + { + /// + /// 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(ParseCommand command, + IProgress uploadProgress = null, + IProgress downloadProgress = null, + CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/Parse/Abstractions/Management/IParseCorePlugins.cs b/Parse/Abstractions/Management/IParseCorePlugins.cs new file mode 100644 index 00000000..76cc4bac --- /dev/null +++ b/Parse/Abstractions/Management/IParseCorePlugins.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using Parse.Common.Internal; + +namespace Parse.Core.Internal +{ + public interface IParseCorePlugins + { + void Reset(); + + IHttpClient HttpClient { get; } + IParseCommandRunner CommandRunner { get; } + IStorageController StorageController { get; } + + IParseCloudCodeController CloudCodeController { get; } + IParseConfigController ConfigController { get; } + IParseFileController FileController { get; } + IParseObjectController ObjectController { get; } + IParseQueryController QueryController { get; } + IParseSessionController SessionController { get; } + IParseUserController UserController { get; } + IObjectSubclassingController SubclassingController { get; } + IParseCurrentUserController CurrentUserController { get; } + IInstallationIdController InstallationIdController { get; } + } +} \ No newline at end of file diff --git a/Parse/Abstractions/Management/IParseCurrentUserController.cs b/Parse/Abstractions/Management/IParseCurrentUserController.cs new file mode 100644 index 00000000..c61b74ce --- /dev/null +++ b/Parse/Abstractions/Management/IParseCurrentUserController.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Threading; +using System.Threading.Tasks; + +namespace Parse.Core.Internal +{ + public interface IParseCurrentUserController : IParseObjectCurrentController + { + Task GetCurrentSessionTokenAsync(CancellationToken cancellationToken); + + Task LogOutAsync(CancellationToken cancellationToken); + } +} diff --git a/Parse/Abstractions/Management/IParseObjectController.cs b/Parse/Abstractions/Management/IParseObjectController.cs new file mode 100644 index 00000000..e0f95415 --- /dev/null +++ b/Parse/Abstractions/Management/IParseObjectController.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Parse.Core.Internal +{ + public interface IParseObjectController + { + Task FetchAsync(IObjectState state, 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/Parse/Abstractions/Management/IParseObjectCurrentController.cs b/Parse/Abstractions/Management/IParseObjectCurrentController.cs new file mode 100644 index 00000000..016e54ba --- /dev/null +++ b/Parse/Abstractions/Management/IParseObjectCurrentController.cs @@ -0,0 +1,54 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Threading; +using System.Threading.Tasks; + +namespace Parse.Core.Internal +{ + /// + /// IParseObjectCurrentController controls the single-instance + /// persistence used throughout the code-base. Sample usages are and + /// . + /// + /// Type of object being persisted. + public interface IParseObjectCurrentController where T : ParseObject + { + /// + /// 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/Parse/Abstractions/Management/IParseUserController.cs b/Parse/Abstractions/Management/IParseUserController.cs new file mode 100644 index 00000000..c3ccaff9 --- /dev/null +++ b/Parse/Abstractions/Management/IParseUserController.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Parse.Core.Internal +{ + public interface IParseUserController + { + Task SignUpAsync(IObjectState state, + IDictionary operations, + CancellationToken cancellationToken); + + Task LogInAsync(string username, + string password, + CancellationToken cancellationToken); + + Task LogInAsync(string authType, + IDictionary data, + CancellationToken cancellationToken); + + Task GetUserAsync(string sessionToken, CancellationToken cancellationToken); + + Task RequestPasswordResetAsync(string email, CancellationToken cancellationToken); + } +} diff --git a/Parse/Abstractions/Management/Tracking/IObjectState.cs b/Parse/Abstractions/Management/Tracking/IObjectState.cs new file mode 100644 index 00000000..473f4428 --- /dev/null +++ b/Parse/Abstractions/Management/Tracking/IObjectState.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.Collections.Generic; + +namespace Parse.Core.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/Parse/Abstractions/Management/Tracking/IParseFieldOperation.cs b/Parse/Abstractions/Management/Tracking/IParseFieldOperation.cs new file mode 100644 index 00000000..1dd018c2 --- /dev/null +++ b/Parse/Abstractions/Management/Tracking/IParseFieldOperation.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +namespace Parse.Core.Internal +{ + /// + /// A ParseFieldOperation represents a modification to a value in a ParseObject. + /// For example, setting, deleting, or incrementing a value are all different kinds of + /// ParseFieldOperations. ParseFieldOperations themselves can be considered to be + /// immutable. + /// + public interface IParseFieldOperation + { + /// + /// Converts the ParseFieldOperation to a data structure that can be converted to JSON and sent to + /// Parse 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 ParseFieldOperation or this. + IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous); + + /// + /// Returns a new estimated value based on a previous value and this operation. This + /// value is not intended to be sent to Parse, 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 ParseRelation 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/Parse/Abstractions/Platform/Analytics/IParseAnalyticsController.cs b/Parse/Abstractions/Platform/Analytics/IParseAnalyticsController.cs new file mode 100644 index 00000000..334abad3 --- /dev/null +++ b/Parse/Abstractions/Platform/Analytics/IParseAnalyticsController.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + + +namespace Parse.Analytics.Internal +{ + /// + /// The interface for the Parse Analytics API controller. + /// + public interface IParseAnalyticsController + { + /// + /// Tracks an event matching the specified details. + /// + /// The name of the event. + /// The parameters of the event. + /// The session token for the event. + /// The asynchonous cancellation token. + /// A that will complete successfully once the event has been set to be tracked. + Task TrackEventAsync(string name, + IDictionary dimensions, + string sessionToken, + CancellationToken cancellationToken); + + /// + /// Tracks an app open for the specified event. + /// + /// The hash for the target push notification. + /// The token of the current session. + /// The asynchronous cancellation token. + /// A the will complete successfully once app openings for the target push notification have been set to be tracked. + Task TrackAppOpenedAsync(string pushHash, + string sessionToken, + CancellationToken cancellationToken); + } +} diff --git a/Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs b/Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs new file mode 100644 index 00000000..709b8e7c --- /dev/null +++ b/Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using Parse.Analytics.Internal; +using Parse.Core.Internal; + +namespace Parse.Analytics +{ + public interface IParseAnalyticsPlugins + { + void Reset(); + + IParseCorePlugins CorePlugins { get; } + IParseAnalyticsController AnalyticsController { get; } + } +} \ No newline at end of file diff --git a/Parse/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs b/Parse/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs new file mode 100644 index 00000000..96e4145f --- /dev/null +++ b/Parse/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Parse.Core.Internal +{ + public interface IParseAuthenticationProvider + { + /// + /// 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/Parse/Abstractions/Platform/Code/IParseCloudCodeController.cs b/Parse/Abstractions/Platform/Code/IParseCloudCodeController.cs new file mode 100644 index 00000000..834a8984 --- /dev/null +++ b/Parse/Abstractions/Platform/Code/IParseCloudCodeController.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Parse.Core.Internal +{ + public interface IParseCloudCodeController + { + Task CallFunctionAsync(string name, + IDictionary parameters, + string sessionToken, + CancellationToken cancellationToken); + } +} diff --git a/Parse/Abstractions/Platform/Configuration/IParseConfigController.cs b/Parse/Abstractions/Platform/Configuration/IParseConfigController.cs new file mode 100644 index 00000000..f3b8c07f --- /dev/null +++ b/Parse/Abstractions/Platform/Configuration/IParseConfigController.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Threading; +using System.Threading.Tasks; + +namespace Parse.Core.Internal +{ + public interface IParseConfigController + { + /// + /// Gets the current config controller. + /// + /// The current config controller. + IParseCurrentConfigController 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/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigController.cs b/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigController.cs new file mode 100644 index 00000000..35571e52 --- /dev/null +++ b/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigController.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Threading.Tasks; + +namespace Parse.Core.Internal +{ + public interface IParseCurrentConfigController + { + /// + /// Gets the current config async. + /// + /// The current config async. + Task GetCurrentConfigAsync(); + + /// + /// Sets the current config async. + /// + /// The current config async. + /// Config. + Task SetCurrentConfigAsync(ParseConfig 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/Parse/Abstractions/Platform/Files/IParseFileController.cs b/Parse/Abstractions/Platform/Files/IParseFileController.cs new file mode 100644 index 00000000..f2e1e6a2 --- /dev/null +++ b/Parse/Abstractions/Platform/Files/IParseFileController.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Parse.Core.Internal +{ + public interface IParseFileController + { + Task SaveAsync(FileState state, + Stream dataStream, + string sessionToken, + IProgress progress, + CancellationToken cancellationToken); + } +} diff --git a/Parse/Abstractions/Platform/Installation/IInstallationIdController.cs b/Parse/Abstractions/Platform/Installation/IInstallationIdController.cs new file mode 100644 index 00000000..a95cfb1f --- /dev/null +++ b/Parse/Abstractions/Platform/Installation/IInstallationIdController.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.Threading.Tasks; + +namespace Parse.Core.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/Parse/Abstractions/Platform/Installation/IParseCurrentInstallationController.cs b/Parse/Abstractions/Platform/Installation/IParseCurrentInstallationController.cs new file mode 100644 index 00000000..d8fd32fa --- /dev/null +++ b/Parse/Abstractions/Platform/Installation/IParseCurrentInstallationController.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using Parse.Core.Internal; + +namespace Parse.Push.Internal +{ + public interface IParseCurrentInstallationController : IParseObjectCurrentController + { + } +} diff --git a/Parse/Abstractions/Platform/Installation/IParseInstallationCoder.cs b/Parse/Abstractions/Platform/Installation/IParseInstallationCoder.cs new file mode 100644 index 00000000..0182b610 --- /dev/null +++ b/Parse/Abstractions/Platform/Installation/IParseInstallationCoder.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Parse.Push.Internal +{ + // TODO: (richardross) once coder is refactored, make this extend IParseObjectCoder. + public interface IParseInstallationCoder + { + IDictionary Encode(ParseInstallation installation); + + ParseInstallation Decode(IDictionary data); + } +} \ No newline at end of file diff --git a/Parse/Abstractions/Platform/Notifications/IParsePushChannelsController.cs b/Parse/Abstractions/Platform/Notifications/IParsePushChannelsController.cs new file mode 100644 index 00000000..befa37a5 --- /dev/null +++ b/Parse/Abstractions/Platform/Notifications/IParsePushChannelsController.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Parse.Push.Internal +{ + public interface IParsePushChannelsController + { + Task SubscribeAsync(IEnumerable channels, CancellationToken cancellationToken); + Task UnsubscribeAsync(IEnumerable channels, CancellationToken cancellationToken); + } +} diff --git a/Parse/Abstractions/Platform/Notifications/IParsePushController.cs b/Parse/Abstractions/Platform/Notifications/IParsePushController.cs new file mode 100644 index 00000000..f567df76 --- /dev/null +++ b/Parse/Abstractions/Platform/Notifications/IParsePushController.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Threading; +using System.Threading.Tasks; + +namespace Parse.Push.Internal +{ + public interface IParsePushController + { + Task SendPushNotificationAsync(IPushState state, CancellationToken cancellationToken); + } +} diff --git a/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs b/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs new file mode 100644 index 00000000..695bc288 --- /dev/null +++ b/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs @@ -0,0 +1,15 @@ +using Parse.Core.Internal; + +namespace Parse.Push.Internal +{ + public interface IParsePushPlugins + { + void Reset(); + + IParseCorePlugins CorePlugins { get; } + IParsePushChannelsController PushChannelsController { get; } + IParsePushController PushController { get; } + IParseCurrentInstallationController CurrentInstallationController { get; } + IDeviceInfoController DeviceInfoController { get; } + } +} \ No newline at end of file diff --git a/Parse/Abstractions/Platform/Notifications/IPushState.cs b/Parse/Abstractions/Platform/Notifications/IPushState.cs new file mode 100644 index 00000000..4b8fd9b4 --- /dev/null +++ b/Parse/Abstractions/Platform/Notifications/IPushState.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.Collections.Generic; + +namespace Parse.Push.Internal +{ + public interface IPushState + { + ParseQuery Query { get; } + IEnumerable Channels { get; } + DateTime? Expiration { get; } + TimeSpan? ExpirationInterval { get; } + DateTime? PushTime { get; } + IDictionary Data { get; } + string Alert { get; } + + IPushState MutatedClone(Action func); + } +} diff --git a/Parse/Abstractions/Platform/Session/IParseSessionController.cs b/Parse/Abstractions/Platform/Session/IParseSessionController.cs new file mode 100644 index 00000000..304b2c23 --- /dev/null +++ b/Parse/Abstractions/Platform/Session/IParseSessionController.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Threading; +using System.Threading.Tasks; + +namespace Parse.Core.Internal +{ + public interface IParseSessionController + { + 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/Parse/Abstractions/Query/IParseQueryController.cs b/Parse/Abstractions/Query/IParseQueryController.cs new file mode 100644 index 00000000..bc473785 --- /dev/null +++ b/Parse/Abstractions/Query/IParseQueryController.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Parse.Core.Internal +{ + public interface IParseQueryController + { + Task> FindAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject; + + Task CountAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject; + + Task FirstAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject; + } +} diff --git a/Parse/Abstractions/Storage/IStorageController.cs b/Parse/Abstractions/Storage/IStorageController.cs new file mode 100644 index 00000000..b0925a84 --- /dev/null +++ b/Parse/Abstractions/Storage/IStorageController.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Parse.Common.Internal +{ + /// + /// An abstraction for accessing persistent storage in the Parse 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/Parse/Internal/Push/DeviceInfo/DeviceInfoController.cs b/Parse/Device/DeviceInfoController.cs similarity index 100% rename from Parse/Internal/Push/DeviceInfo/DeviceInfoController.cs rename to Parse/Device/DeviceInfoController.cs diff --git a/Parse/Framework/ParseDownloadProgressEventArgs.cs b/Parse/Framework/ParseDownloadProgressEventArgs.cs new file mode 100644 index 00000000..b9da12ec --- /dev/null +++ b/Parse/Framework/ParseDownloadProgressEventArgs.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; + +namespace Parse +{ + /// + /// Represents download progress. + /// + public class ParseDownloadProgressEventArgs : EventArgs + { + public ParseDownloadProgressEventArgs() { } + + /// + /// Gets the progress (a number between 0.0 and 1.0) of a download. + /// + public double Progress { get; set; } + } +} diff --git a/Parse/Public/ParseException.cs b/Parse/Framework/ParseException.cs similarity index 99% rename from Parse/Public/ParseException.cs rename to Parse/Framework/ParseException.cs index 3e3f49ce..355e2643 100644 --- a/Parse/Public/ParseException.cs +++ b/Parse/Framework/ParseException.cs @@ -260,10 +260,7 @@ public enum ErrorCode } internal ParseException(ErrorCode code, string message, Exception cause = null) - : base(message, cause) - { - this.Code = code; - } + : base(message, cause) => Code = code; /// /// The Parse error code associated with the exception. diff --git a/Parse/Public/ParsePushNotificationEventArgs.cs b/Parse/Framework/ParsePushNotificationEventArgs.cs similarity index 100% rename from Parse/Public/ParsePushNotificationEventArgs.cs rename to Parse/Framework/ParsePushNotificationEventArgs.cs index a4fdd065..a13f16d7 100644 --- a/Parse/Public/ParsePushNotificationEventArgs.cs +++ b/Parse/Framework/ParsePushNotificationEventArgs.cs @@ -1,8 +1,8 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Common.Internal; using System; using System.Collections.Generic; +using Parse.Common.Internal; namespace Parse { diff --git a/Parse/Framework/ParseUploadProgressEventArgs.cs b/Parse/Framework/ParseUploadProgressEventArgs.cs new file mode 100644 index 00000000..35df390c --- /dev/null +++ b/Parse/Framework/ParseUploadProgressEventArgs.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; + +namespace Parse +{ + /// + /// Represents upload progress. + /// + public class ParseUploadProgressEventArgs : EventArgs + { + public ParseUploadProgressEventArgs() { } + + /// + /// Gets the progress (a number between 0.0 and 1.0) of an upload. + /// + public double Progress { get; set; } + } +} diff --git a/Parse/Internal/Command/ParseCommandRunner.cs b/Parse/Internal/Command/ParseCommandRunner.cs deleted file mode 100644 index ed307b65..00000000 --- a/Parse/Internal/Command/ParseCommandRunner.cs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Parse.Common.Internal; - -namespace Parse.Core.Internal -{ - /// - /// The command runner for all SDK operations that need to interact with the targeted deployment of Parse Server. - /// - public class ParseCommandRunner : IParseCommandRunner - { - private readonly IHttpClient httpClient; - private readonly IInstallationIdController installationIdController; - - /// - /// Creates a new Parse SDK command runner. - /// - /// The implementation instance to use. - /// The implementation instance to use. - public ParseCommandRunner(IHttpClient httpClient, IInstallationIdController installationIdController) - { - this.httpClient = httpClient; - this.installationIdController = installationIdController; - } - - /// - /// Runs a specified . - /// - /// The to run. - /// An instance to push upload progress data to. - /// An instance to push download progress data to. - /// An asynchronous operation cancellation token that dictates if and when the operation should be cancelled. - /// - public Task>> RunCommandAsync(ParseCommand command, IProgress uploadProgress = null, IProgress downloadProgress = null, CancellationToken cancellationToken = default) - { - return PrepareCommand(command).ContinueWith(commandTask => - { - return httpClient.ExecuteAsync(commandTask.Result, uploadProgress, downloadProgress, cancellationToken).OnSuccess(t => - { - cancellationToken.ThrowIfCancellationRequested(); - - Tuple response = t.Result; - string contentString = response.Item2; - int responseCode = (int) response.Item1; - if (responseCode >= 500) - { - // Server error, return InternalServerError. - throw new ParseException(ParseException.ErrorCode.InternalServerError, response.Item2); - } - else if (contentString != null) - { - IDictionary contentJson = null; - try - { - // TODO: Newer versions of Parse Server send the failure results back as HTML. - contentJson = contentString.StartsWith("[") - ? new Dictionary { ["results"] = Json.Parse(contentString) } - : Json.Parse(contentString) as IDictionary; - } - catch (Exception e) - { - throw new ParseException(ParseException.ErrorCode.OtherCause, "Invalid or alternatively-formatted response recieved from server.", e); - } - if (responseCode < 200 || responseCode > 299) - { - int code = (int) (contentJson.ContainsKey("code") ? (long) contentJson["code"] : (int) ParseException.ErrorCode.OtherCause); - string error = contentJson.ContainsKey("error") ? - contentJson["error"] as string : - contentString; - throw new ParseException((ParseException.ErrorCode) code, error); - } - return new Tuple>(response.Item1, contentJson); - } - return new Tuple>(response.Item1, null); - }); - }).Unwrap(); - } - - private const string revocableSessionTokentrueValue = "1"; - private Task PrepareCommand(ParseCommand command) - { - ParseCommand newCommand = new ParseCommand(command); - - Task installationIdTask = installationIdController.GetAsync().ContinueWith(t => - { - newCommand.Headers.Add(new KeyValuePair("X-Parse-Installation-Id", t.Result.ToString())); - return newCommand; - }); - - // TODO (richardross): Inject configuration instead of using shared static here. - ParseClient.Configuration configuration = ParseClient.CurrentConfiguration; - newCommand.Headers.Add(new KeyValuePair("X-Parse-Application-Id", configuration.ApplicationID)); - newCommand.Headers.Add(new KeyValuePair("X-Parse-Client-Version", ParseClient.VersionString)); - - if (configuration.AuxiliaryHeaders != null) - { - foreach (KeyValuePair header in configuration.AuxiliaryHeaders) - { - newCommand.Headers.Add(header); - } - } - - if (!String.IsNullOrEmpty(configuration.VersionInfo.BuildVersion)) - { - newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Build-Version", configuration.VersionInfo.BuildVersion)); - } - if (!String.IsNullOrEmpty(configuration.VersionInfo.DisplayVersion)) - { - newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Display-Version", configuration.VersionInfo.DisplayVersion)); - } - if (!String.IsNullOrEmpty(configuration.VersionInfo.OSVersion)) - { - newCommand.Headers.Add(new KeyValuePair("X-Parse-OS-Version", configuration.VersionInfo.OSVersion)); - } - - // TODO (richardross): I hate the idea of having this super tightly coupled static variable in here. - // Lets eventually get rid of it. - if (!String.IsNullOrEmpty(ParseClient.MasterKey)) - { - newCommand.Headers.Add(new KeyValuePair("X-Parse-Master-Key", ParseClient.MasterKey)); - } - else - { - newCommand.Headers.Add(new KeyValuePair("X-Parse-Windows-Key", configuration.Key)); - } - - // TODO (richardross): Inject this instead of using static here. - if (ParseUser.IsRevocableSessionEnabled) - { - newCommand.Headers.Add(new KeyValuePair("X-Parse-Revocable-Session", revocableSessionTokentrueValue)); - } - - return installationIdTask; - } - } -} diff --git a/Parse/Internal/Config/Controller/ParseCurrentConfigController.cs b/Parse/Internal/Config/Controller/ParseCurrentConfigController.cs deleted file mode 100644 index c0d432ed..00000000 --- a/Parse/Internal/Config/Controller/ParseCurrentConfigController.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.Threading.Tasks; -using System.Threading; -using System.Collections.Generic; -using Parse.Common.Internal; - -namespace Parse.Core.Internal -{ - /// - /// Parse current config controller. - /// - internal class ParseCurrentConfigController : IParseCurrentConfigController - { - private const string CurrentConfigKey = "CurrentConfig"; - - private readonly TaskQueue taskQueue; - private ParseConfig currentConfig; - - private IStorageController storageController; - - /// - /// Initializes a new instance of the class. - /// - public ParseCurrentConfigController(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 = ParseClient.DeserializeJsonString(propertiesString); - currentConfig = new ParseConfig(dictionary); - } - else - { - currentConfig = new ParseConfig(); - } - - return currentConfig; - }); - } - - return Task.FromResult(currentConfig); - }), CancellationToken.None).Unwrap(); - } - - public Task SetCurrentConfigAsync(ParseConfig config) - { - return taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => - { - currentConfig = config; - - var jsonObject = ((IJsonConvertible) config).ToJSON(); - var jsonString = ParseClient.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/Parse/Internal/Push/Controller/ParsePushController.cs b/Parse/Internal/Push/Controller/ParsePushController.cs deleted file mode 100644 index 6d8da23a..00000000 --- a/Parse/Internal/Push/Controller/ParsePushController.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.Threading.Tasks; -using System.Threading; -using System.Collections.Generic; -using Parse.Common.Internal; -using Parse.Core.Internal; - -namespace Parse.Push.Internal -{ - internal class ParsePushController : IParsePushController - { - private readonly IParseCommandRunner commandRunner; - private readonly IParseCurrentUserController currentUserController; - - public ParsePushController(IParseCommandRunner commandRunner, IParseCurrentUserController currentUserController) - { - this.commandRunner = commandRunner; - this.currentUserController = currentUserController; - } - - public Task SendPushNotificationAsync(IPushState state, CancellationToken cancellationToken) - { - return currentUserController.GetCurrentSessionTokenAsync(cancellationToken).OnSuccess(sessionTokenTask => - { - var command = new ParseCommand("push", - method: "POST", - sessionToken: sessionTokenTask.Result, - data: ParsePushEncoder.Instance.Encode(state)); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken); - }).Unwrap(); - } - } -} diff --git a/Parse/Internal/User/Controller/ParseCurrentUserController.cs b/Parse/Internal/User/Controller/ParseCurrentUserController.cs deleted file mode 100644 index f0c92109..00000000 --- a/Parse/Internal/User/Controller/ParseCurrentUserController.cs +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Globalization; -using System.Threading; -using System.Threading.Tasks; -using Parse.Common.Internal; - -namespace Parse.Core.Internal -{ - public class ParseCurrentUserController : IParseCurrentUserController - { - private readonly object mutex = new object(); - private readonly TaskQueue taskQueue = new TaskQueue(); - - private IStorageController storageController; - - public ParseCurrentUserController(IStorageController storageController) - { - this.storageController = storageController; - } - - private ParseUser currentUser; - public ParseUser CurrentUser - { - get - { - lock (mutex) - { - return currentUser; - } - } - set - { - lock (mutex) - { - currentUser = value; - } - } - } - - public Task SetAsync(ParseUser user, CancellationToken cancellationToken) - { - return taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - Task saveTask = null; - if (user == null) - { - saveTask = storageController - .LoadAsync() - .OnSuccess(t => t.Result.RemoveAsync("CurrentUser")) - .Unwrap(); - } - else - { - // TODO (hallucinogen): we need to use ParseCurrentCoder instead of this janky encoding - var data = user.ServerDataToJSONObjectForSerialization(); - data["objectId"] = user.ObjectId; - if (user.CreatedAt != null) - { - data["createdAt"] = user.CreatedAt.Value.ToString(ParseClient.DateFormatStrings.First(), - CultureInfo.InvariantCulture); - } - if (user.UpdatedAt != null) - { - data["updatedAt"] = user.UpdatedAt.Value.ToString(ParseClient.DateFormatStrings.First(), - CultureInfo.InvariantCulture); - } - - saveTask = storageController - .LoadAsync() - .OnSuccess(t => t.Result.AddAsync("CurrentUser", Json.Encode(data))) - .Unwrap(); - } - CurrentUser = user; - - return saveTask; - }).Unwrap(); - }, cancellationToken); - } - - public Task GetAsync(CancellationToken cancellationToken) - { - ParseUser cachedCurrent; - - lock (mutex) - { - cachedCurrent = CurrentUser; - } - - if (cachedCurrent != null) - { - return Task.FromResult(cachedCurrent); - } - - return taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - return storageController.LoadAsync().OnSuccess(t => - { - object temp; - t.Result.TryGetValue("CurrentUser", out temp); - var userDataString = temp as string; - ParseUser user = null; - if (userDataString != null) - { - var userData = Json.Parse(userDataString) as IDictionary; - var state = ParseObjectCoder.Instance.Decode(userData, ParseDecoder.Instance); - user = ParseObject.FromState(state, "_User"); - } - - CurrentUser = user; - return user; - }); - }).Unwrap(); - }, cancellationToken); - } - - public Task ExistsAsync(CancellationToken cancellationToken) - { - if (CurrentUser != null) - { - return Task.FromResult(true); - } - - return taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - storageController.LoadAsync().OnSuccess(t => t.Result.ContainsKey("CurrentUser")) - ).Unwrap(); - }, cancellationToken); - } - - public bool IsCurrent(ParseUser user) - { - lock (mutex) - { - return CurrentUser == user; - } - } - - public void ClearFromMemory() - { - CurrentUser = null; - } - - public void ClearFromDisk() - { - lock (mutex) - { - ClearFromMemory(); - - taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - return storageController.LoadAsync().OnSuccess(t => t.Result.RemoveAsync("CurrentUser")); - }).Unwrap().Unwrap(); - }, CancellationToken.None); - } - } - - 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 taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => GetAsync(cancellationToken)).Unwrap().OnSuccess(t => - { - ClearFromDisk(); - }); - }, cancellationToken); - } - } -} diff --git a/Parse/Internal/Utilities/FlexibleListWrapper.cs b/Parse/Internal/Utilities/FlexibleListWrapper.cs deleted file mode 100644 index 8fc5afef..00000000 --- a/Parse/Internal/Utilities/FlexibleListWrapper.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Parse.Utilities; - -namespace Parse.Common.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/Parse/Internal/Utilities/InternalExtensions.cs b/Parse/Internal/Utilities/InternalExtensions.cs deleted file mode 100644 index 02466211..00000000 --- a/Parse/Internal/Utilities/InternalExtensions.cs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.ExceptionServices; -using System.Text; -using System.Threading.Tasks; - -namespace Parse.Common.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/Parse/Internal/Utilities/ParseQueryExtensions.cs b/Parse/Internal/Utilities/ParseQueryExtensions.cs deleted file mode 100644 index 112c1dd8..00000000 --- a/Parse/Internal/Utilities/ParseQueryExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.Collections.Generic; - -namespace Parse.Core.Internal -{ - /// - /// So here's the deal. We have a lot of internal APIs for ParseObject, ParseUser, 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 ParseQueryExtensions - { - public static string GetClassName(this ParseQuery query) where T : ParseObject - { - return query.ClassName; - } - - public static IDictionary BuildParameters(this ParseQuery query) where T : ParseObject - { - return query.BuildParameters(false); - } - - public static object GetConstraint(this ParseQuery query, string key) where T : ParseObject - { - return query.GetConstraint(key); - } - } -} diff --git a/Parse/Internal/Object/Subclassing/ObjectSubclassInfo.cs b/Parse/Management/ObjectSubclassInfo.cs similarity index 94% rename from Parse/Internal/Object/Subclassing/ObjectSubclassInfo.cs rename to Parse/Management/ObjectSubclassInfo.cs index e117ece7..d1f1c819 100644 --- a/Parse/Internal/Object/Subclassing/ObjectSubclassInfo.cs +++ b/Parse/Management/ObjectSubclassInfo.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using System.Reflection; using Parse.Common.Internal; diff --git a/Parse/Internal/Object/Subclassing/ObjectSubclassingController.cs b/Parse/Management/ObjectSubclassingController.cs similarity index 92% rename from Parse/Internal/Object/Subclassing/ObjectSubclassingController.cs rename to Parse/Management/ObjectSubclassingController.cs index af1047d0..0f325412 100644 --- a/Parse/Internal/Object/Subclassing/ObjectSubclassingController.cs +++ b/Parse/Management/ObjectSubclassingController.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Threading; using Parse.Common.Internal; @@ -65,8 +64,7 @@ public void RegisterSubclass(Type type) // TOCTTOU bug. mutex.EnterWriteLock(); - ObjectSubclassInfo previousInfo = null; - if (registeredSubclasses.TryGetValue(className, out previousInfo)) + if (registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo previousInfo)) { if (typeInfo.IsAssignableFrom(previousInfo.TypeInfo)) { @@ -102,10 +100,9 @@ public void RegisterSubclass(Type type) mutex.ExitWriteLock(); } - Action toPerform; mutex.EnterReadLock(); - registerActions.TryGetValue(className, out toPerform); + registerActions.TryGetValue(className, out Action toPerform); mutex.ExitReadLock(); toPerform?.Invoke(); @@ -127,10 +124,9 @@ public void AddRegisterHook(Type t, Action action) public ParseObject Instantiate(string className) { - ObjectSubclassInfo info = null; mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out info); + registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo info); mutex.ExitReadLock(); return info != null @@ -140,9 +136,8 @@ public ParseObject Instantiate(string className) public IDictionary GetPropertyMappings(string className) { - ObjectSubclassInfo info = null; mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out info); + registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo info); if (info == null) { registeredSubclasses.TryGetValue(parseObjectClassName, out info); diff --git a/Parse/Internal/Command/ParseCommand.cs b/Parse/Management/ParseCommand.cs similarity index 84% rename from Parse/Internal/Command/ParseCommand.cs rename to Parse/Management/ParseCommand.cs index d63eb448..948fac70 100644 --- a/Parse/Internal/Command/ParseCommand.cs +++ b/Parse/Management/ParseCommand.cs @@ -3,9 +3,9 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using Parse.Common.Internal; -using System.Linq; namespace Parse.Core.Internal { @@ -29,7 +29,7 @@ public override Stream Data ? new MemoryStream(Encoding.UTF8.GetBytes(Json.Encode(DataObject))) : null); } - set { base.Data = value; } + set => base.Data = value; } public ParseCommand(string relativeUri, @@ -41,10 +41,7 @@ public ParseCommand(string relativeUri, sessionToken: sessionToken, headers: headers, stream: null, - contentType: data != null ? "application/json" : null) - { - DataObject = data; - } + contentType: data != null ? "application/json" : null) => DataObject = data; public ParseCommand(string relativeUri, string method, @@ -58,11 +55,11 @@ public ParseCommand(string relativeUri, Data = stream; Headers = new List>(headers ?? Enumerable.Empty>()); - if (!string.IsNullOrEmpty(sessionToken)) + if (!String.IsNullOrEmpty(sessionToken)) { Headers.Add(new KeyValuePair("X-Parse-Session-Token", sessionToken)); } - if (!string.IsNullOrEmpty(contentType)) + if (!String.IsNullOrEmpty(contentType)) { Headers.Add(new KeyValuePair("Content-Type", contentType)); } @@ -70,11 +67,11 @@ public ParseCommand(string relativeUri, public ParseCommand(ParseCommand other) { - this.Uri = other.Uri; - this.Method = other.Method; - this.DataObject = other.DataObject; - this.Headers = new List>(other.Headers); - this.Data = other.Data; + Uri = other.Uri; + Method = other.Method; + DataObject = other.DataObject; + Headers = new List>(other.Headers); + Data = other.Data; } } } diff --git a/Parse/Management/ParseCommandRunner.cs b/Parse/Management/ParseCommandRunner.cs new file mode 100644 index 00000000..8925b0bc --- /dev/null +++ b/Parse/Management/ParseCommandRunner.cs @@ -0,0 +1,138 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Parse.Common.Internal; + +namespace Parse.Core.Internal +{ + /// + /// The command runner for all SDK operations that need to interact with the targeted deployment of Parse Server. + /// + public class ParseCommandRunner : IParseCommandRunner + { + private readonly IHttpClient httpClient; + private readonly IInstallationIdController installationIdController; + + /// + /// Creates a new Parse SDK command runner. + /// + /// The implementation instance to use. + /// The implementation instance to use. + public ParseCommandRunner(IHttpClient httpClient, IInstallationIdController installationIdController) + { + this.httpClient = httpClient; + this.installationIdController = installationIdController; + } + + /// + /// Runs a specified . + /// + /// The to run. + /// An instance to push upload progress data to. + /// An instance to push download progress data to. + /// An asynchronous operation cancellation token that dictates if and when the operation should be cancelled. + /// + public Task>> RunCommandAsync(ParseCommand command, IProgress uploadProgress = null, IProgress downloadProgress = null, CancellationToken cancellationToken = default) => PrepareCommand(command).ContinueWith(commandTask => + { + return httpClient.ExecuteAsync(commandTask.Result, uploadProgress, downloadProgress, cancellationToken).OnSuccess(t => + { + cancellationToken.ThrowIfCancellationRequested(); + + Tuple response = t.Result; + string contentString = response.Item2; + int responseCode = (int) response.Item1; + if (responseCode >= 500) + { + // Server error, return InternalServerError. + throw new ParseException(ParseException.ErrorCode.InternalServerError, response.Item2); + } + else if (contentString != null) + { + IDictionary contentJson = null; + try + { + // TODO: Newer versions of Parse Server send the failure results back as HTML. + contentJson = contentString.StartsWith("[") + ? new Dictionary { ["results"] = Json.Parse(contentString) } + : Json.Parse(contentString) as IDictionary; + } + catch (Exception e) + { + throw new ParseException(ParseException.ErrorCode.OtherCause, "Invalid or alternatively-formatted response recieved from server.", e); + } + if (responseCode < 200 || responseCode > 299) + { + int code = (int) (contentJson.ContainsKey("code") ? (long) contentJson["code"] : (int) ParseException.ErrorCode.OtherCause); + string error = contentJson.ContainsKey("error") ? + contentJson["error"] as string : + contentString; + throw new ParseException((ParseException.ErrorCode) code, error); + } + return new Tuple>(response.Item1, contentJson); + } + return new Tuple>(response.Item1, null); + }); + }).Unwrap(); + + private const string revocableSessionTokentrueValue = "1"; + private Task PrepareCommand(ParseCommand command) + { + ParseCommand newCommand = new ParseCommand(command); + + Task installationIdTask = installationIdController.GetAsync().ContinueWith(t => + { + newCommand.Headers.Add(new KeyValuePair("X-Parse-Installation-Id", t.Result.ToString())); + return newCommand; + }); + + // TODO (richardross): Inject configuration instead of using shared static here. + ParseClient.Configuration configuration = ParseClient.CurrentConfiguration; + newCommand.Headers.Add(new KeyValuePair("X-Parse-Application-Id", configuration.ApplicationID)); + newCommand.Headers.Add(new KeyValuePair("X-Parse-Client-Version", ParseClient.VersionString)); + + if (configuration.AuxiliaryHeaders != null) + { + foreach (KeyValuePair header in configuration.AuxiliaryHeaders) + { + newCommand.Headers.Add(header); + } + } + + if (!String.IsNullOrEmpty(configuration.VersionInfo.BuildVersion)) + { + newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Build-Version", configuration.VersionInfo.BuildVersion)); + } + if (!String.IsNullOrEmpty(configuration.VersionInfo.DisplayVersion)) + { + newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Display-Version", configuration.VersionInfo.DisplayVersion)); + } + if (!String.IsNullOrEmpty(configuration.VersionInfo.OSVersion)) + { + newCommand.Headers.Add(new KeyValuePair("X-Parse-OS-Version", configuration.VersionInfo.OSVersion)); + } + + // TODO (richardross): I hate the idea of having this super tightly coupled static variable in here. + // Lets eventually get rid of it. + if (!String.IsNullOrEmpty(ParseClient.MasterKey)) + { + newCommand.Headers.Add(new KeyValuePair("X-Parse-Master-Key", ParseClient.MasterKey)); + } + else + { + newCommand.Headers.Add(new KeyValuePair("X-Parse-Windows-Key", configuration.Key)); + } + + // TODO (richardross): Inject this instead of using static here. + if (ParseUser.IsRevocableSessionEnabled) + { + newCommand.Headers.Add(new KeyValuePair("X-Parse-Revocable-Session", revocableSessionTokentrueValue)); + } + + return installationIdTask; + } + } +} diff --git a/Parse/Internal/ParseCorePlugins.cs b/Parse/Management/ParseCorePlugins.cs similarity index 98% rename from Parse/Internal/ParseCorePlugins.cs rename to Parse/Management/ParseCorePlugins.cs index 49980780..603d64e8 100644 --- a/Parse/Internal/ParseCorePlugins.cs +++ b/Parse/Management/ParseCorePlugins.cs @@ -1,11 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using Parse.Common.Internal; -using Parse.Core.Internal; #if DEBUG [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Parse.Test")] diff --git a/Parse/Management/ParseCurrentUserController.cs b/Parse/Management/ParseCurrentUserController.cs new file mode 100644 index 00000000..e1c83160 --- /dev/null +++ b/Parse/Management/ParseCurrentUserController.cs @@ -0,0 +1,171 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Parse.Common.Internal; + +namespace Parse.Core.Internal +{ + public class ParseCurrentUserController : IParseCurrentUserController + { + private readonly object mutex = new object(); + private readonly TaskQueue taskQueue = new TaskQueue(); + + private IStorageController storageController; + + public ParseCurrentUserController(IStorageController storageController) => this.storageController = storageController; + + private ParseUser currentUser; + public ParseUser CurrentUser + { + get + { + lock (mutex) + { + return currentUser; + } + } + set + { + lock (mutex) + { + currentUser = value; + } + } + } + + public Task SetAsync(ParseUser user, CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => + { + return toAwait.ContinueWith(_ => + { + Task saveTask = null; + if (user == null) + { + saveTask = storageController + .LoadAsync() + .OnSuccess(t => t.Result.RemoveAsync("CurrentUser")) + .Unwrap(); + } + else + { + // TODO (hallucinogen): we need to use ParseCurrentCoder instead of this janky encoding + IDictionary data = user.ServerDataToJSONObjectForSerialization(); + data["objectId"] = user.ObjectId; + if (user.CreatedAt != null) + { + data["createdAt"] = user.CreatedAt.Value.ToString(ParseClient.DateFormatStrings.First(), + CultureInfo.InvariantCulture); + } + if (user.UpdatedAt != null) + { + data["updatedAt"] = user.UpdatedAt.Value.ToString(ParseClient.DateFormatStrings.First(), + CultureInfo.InvariantCulture); + } + + saveTask = storageController + .LoadAsync() + .OnSuccess(t => t.Result.AddAsync("CurrentUser", Json.Encode(data))) + .Unwrap(); + } + CurrentUser = user; + + return saveTask; + }).Unwrap(); + }, cancellationToken); + + public Task GetAsync(CancellationToken cancellationToken) + { + ParseUser cachedCurrent; + + lock (mutex) + { + cachedCurrent = CurrentUser; + } + + if (cachedCurrent != null) + { + return Task.FromResult(cachedCurrent); + } + + return taskQueue.Enqueue(toAwait => + { + return toAwait.ContinueWith(_ => + { + return storageController.LoadAsync().OnSuccess(t => + { + t.Result.TryGetValue("CurrentUser", out object temp); + string userDataString = temp as string; + ParseUser user = null; + if (userDataString != null) + { + IDictionary userData = Json.Parse(userDataString) as IDictionary; + IObjectState state = ParseObjectCoder.Instance.Decode(userData, ParseDecoder.Instance); + user = ParseObject.FromState(state, "_User"); + } + + CurrentUser = user; + return user; + }); + }).Unwrap(); + }, cancellationToken); + } + + public Task ExistsAsync(CancellationToken cancellationToken) + { + if (CurrentUser != null) + { + return Task.FromResult(true); + } + + return taskQueue.Enqueue(toAwait => + { + return toAwait.ContinueWith(_ => + storageController.LoadAsync().OnSuccess(t => t.Result.ContainsKey("CurrentUser")) + ).Unwrap(); + }, cancellationToken); + } + + public bool IsCurrent(ParseUser user) + { + lock (mutex) + { + return CurrentUser == user; + } + } + + public void ClearFromMemory() => CurrentUser = null; + + public void ClearFromDisk() + { + lock (mutex) + { + ClearFromMemory(); + + taskQueue.Enqueue(toAwait => + { + return toAwait.ContinueWith(_ => + { + return storageController.LoadAsync().OnSuccess(t => t.Result.RemoveAsync("CurrentUser")); + }).Unwrap().Unwrap(); + }, CancellationToken.None); + } + } + + public Task GetCurrentSessionTokenAsync(CancellationToken cancellationToken) => GetAsync(cancellationToken).OnSuccess(t => + { + ParseUser user = t.Result; + return user == null ? null : user.SessionToken; + }); + + public Task LogOutAsync(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => + { + return toAwait.ContinueWith(_ => GetAsync(cancellationToken)).Unwrap().OnSuccess(t => + { + ClearFromDisk(); + }); + }, cancellationToken); + } +} diff --git a/Parse/Internal/Object/Controller/ParseObjectController.cs b/Parse/Management/ParseObjectController.cs similarity index 74% rename from Parse/Internal/Object/Controller/ParseObjectController.cs rename to Parse/Management/ParseObjectController.cs index 40356da3..f083a86f 100644 --- a/Parse/Internal/Object/Controller/ParseObjectController.cs +++ b/Parse/Management/ParseObjectController.cs @@ -1,12 +1,12 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; -using System.Linq; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using Parse.Utilities; using Parse.Common.Internal; +using Parse.Utilities; namespace Parse.Core.Internal { @@ -14,16 +14,13 @@ public class ParseObjectController : IParseObjectController { private readonly IParseCommandRunner commandRunner; - public ParseObjectController(IParseCommandRunner commandRunner) - { - this.commandRunner = commandRunner; - } + public ParseObjectController(IParseCommandRunner commandRunner) => this.commandRunner = commandRunner; public Task FetchAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand(string.Format("classes/{0}/{1}", + ParseCommand command = new ParseCommand(String.Format("classes/{0}/{1}", Uri.EscapeDataString(state.ClassName), Uri.EscapeDataString(state.ObjectId)), method: "GET", @@ -41,18 +38,18 @@ public Task SaveAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken) { - var objectJSON = ParseObject.ToJSONObjectForSaving(operations); + IDictionary objectJSON = ParseObject.ToJSONObjectForSaving(operations); - var command = new ParseCommand((state.ObjectId == null ? - string.Format("classes/{0}", Uri.EscapeDataString(state.ClassName)) : - string.Format("classes/{0}/{1}", Uri.EscapeDataString(state.ClassName), state.ObjectId)), + ParseCommand command = new ParseCommand((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 = ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); + IObjectState serverState = ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); serverState = serverState.MutatedClone(mutableClone => { mutableClone.IsNew = t.Result.Item1 == System.Net.HttpStatusCode.Created; @@ -67,18 +64,18 @@ public IList> SaveAllAsync(IList states, CancellationToken cancellationToken) { - var requests = states + List requests = states .Zip(operationsList, (item, ops) => new ParseCommand( item.ObjectId == null - ? string.Format("classes/{0}", Uri.EscapeDataString(item.ClassName)) - : string.Format("classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), Uri.EscapeDataString(item.ObjectId)), + ? 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: ParseObject.ToJSONObjectForSaving(ops))) .ToList(); - var batchTasks = ExecuteBatchRequests(requests, sessionToken, cancellationToken); - var stateTasks = new List>(); - foreach (var task in batchTasks) + IList>> batchTasks = ExecuteBatchRequests(requests, sessionToken, cancellationToken); + List> stateTasks = new List>(); + foreach (Task> task in batchTasks) { stateTasks.Add(task.OnSuccess(t => { @@ -93,7 +90,7 @@ public Task DeleteAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand(string.Format("classes/{0}/{1}", + ParseCommand command = new ParseCommand(String.Format("classes/{0}/{1}", state.ClassName, state.ObjectId), method: "DELETE", sessionToken: sessionToken, @@ -106,10 +103,10 @@ public IList DeleteAllAsync(IList states, string sessionToken, CancellationToken cancellationToken) { - var requests = states + List requests = states .Where(item => item.ObjectId != null) .Select(item => new ParseCommand( - string.Format("classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), Uri.EscapeDataString(item.ObjectId)), + String.Format("classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), Uri.EscapeDataString(item.ObjectId)), method: "DELETE", data: null)) .ToList(); @@ -122,13 +119,13 @@ internal IList>> ExecuteBatchRequests(IList>>(); + List>> tasks = new List>>(); int batchSize = requests.Count; IEnumerable remaining = requests; while (batchSize > MaximumBatchSize) { - var process = remaining.Take(MaximumBatchSize).ToList(); + List process = remaining.Take(MaximumBatchSize).ToList(); remaining = remaining.Skip(MaximumBatchSize); tasks.AddRange(ExecuteBatchRequest(process, sessionToken, cancellationToken)); @@ -144,19 +141,19 @@ private IList>> ExecuteBatchRequest(IList>>(); + List>> tasks = new List>>(); int batchSize = requests.Count; - var tcss = new List>>(); + List>> tcss = new List>>(); for (int i = 0; i < batchSize; ++i) { - var tcs = new TaskCompletionSource>(); + TaskCompletionSource> tcs = new TaskCompletionSource>(); tcss.Add(tcs); tasks.Add(tcs.Task); } - var encodedRequests = requests.Select(r => + List encodedRequests = requests.Select(r => { - var results = new Dictionary { + Dictionary results = new Dictionary { { "method", r.Method }, { "path", r.Uri.AbsolutePath }, }; @@ -167,7 +164,7 @@ private IList>> ExecuteBatchRequest(IList().ToList(); - var command = new ParseCommand("batch", + ParseCommand command = new ParseCommand("batch", method: "POST", sessionToken: sessionToken, data: new Dictionary { { "requests", encodedRequests } }); @@ -176,7 +173,7 @@ private IList>> ExecuteBatchRequest(IList> tcs in tcss) { if (t.IsFaulted) { @@ -190,11 +187,11 @@ private IList>> ExecuteBatchRequest(IList>(t.Result.Item2["results"]); + IList resultsArray = Conversion.As>(t.Result.Item2["results"]); int resultLength = resultsArray.Count; if (resultLength != batchSize) { - foreach (var tcs in tcss) + foreach (TaskCompletionSource> tcs in tcss) { tcs.TrySetException(new InvalidOperationException( "Batch command result count expected: " + batchSize + " but was: " + resultLength + ".")); @@ -204,8 +201,8 @@ private IList>> ExecuteBatchRequest(IList; - var tcs = tcss[i]; + Dictionary result = resultsArray[i] as Dictionary; + TaskCompletionSource> tcs = tcss[i]; if (result.ContainsKey("success")) { @@ -213,7 +210,7 @@ private IList>> ExecuteBatchRequest(IList; + IDictionary error = result["error"] as IDictionary; long errorCode = (long) error["code"]; tcs.TrySetException(new ParseException((ParseException.ErrorCode) errorCode, error["error"] as string)); } diff --git a/Parse/Internal/User/Controller/ParseUserController.cs b/Parse/Management/ParseUserController.cs similarity index 75% rename from Parse/Internal/User/Controller/ParseUserController.cs rename to Parse/Management/ParseUserController.cs index 3451b63d..7b3c5c4d 100644 --- a/Parse/Internal/User/Controller/ParseUserController.cs +++ b/Parse/Management/ParseUserController.cs @@ -12,24 +12,21 @@ public class ParseUserController : IParseUserController { private readonly IParseCommandRunner commandRunner; - public ParseUserController(IParseCommandRunner commandRunner) - { - this.commandRunner = commandRunner; - } + public ParseUserController(IParseCommandRunner commandRunner) => this.commandRunner = commandRunner; public Task SignUpAsync(IObjectState state, IDictionary operations, CancellationToken cancellationToken) { - var objectJSON = ParseObject.ToJSONObjectForSaving(operations); + IDictionary objectJSON = ParseObject.ToJSONObjectForSaving(operations); - var command = new ParseCommand("classes/_User", + ParseCommand command = new ParseCommand("classes/_User", method: "POST", data: objectJSON); return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => { - var serverState = ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); + IObjectState serverState = ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); serverState = serverState.MutatedClone(mutableClone => { mutableClone.IsNew = true; @@ -42,18 +39,18 @@ public Task LogInAsync(string username, string password, CancellationToken cancellationToken) { - var data = new Dictionary{ + Dictionary data = new Dictionary{ {"username", username}, {"password", password} }; - var command = new ParseCommand(string.Format("login?{0}", ParseClient.BuildQueryString(data)), + ParseCommand command = new ParseCommand(String.Format("login?{0}", ParseClient.BuildQueryString(data)), method: "GET", data: null); return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => { - var serverState = ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); + IObjectState serverState = ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); serverState = serverState.MutatedClone(mutableClone => { mutableClone.IsNew = t.Result.Item1 == System.Net.HttpStatusCode.Created; @@ -66,10 +63,12 @@ public Task LogInAsync(string authType, IDictionary data, CancellationToken cancellationToken) { - var authData = new Dictionary(); - authData[authType] = data; + Dictionary authData = new Dictionary + { + [authType] = data + }; - var command = new ParseCommand("users", + ParseCommand command = new ParseCommand("users", method: "POST", data: new Dictionary { {"authData", authData} @@ -77,7 +76,7 @@ public Task LogInAsync(string authType, return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => { - var serverState = ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); + IObjectState serverState = ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); serverState = serverState.MutatedClone(mutableClone => { mutableClone.IsNew = t.Result.Item1 == System.Net.HttpStatusCode.Created; @@ -88,7 +87,7 @@ public Task LogInAsync(string authType, public Task GetUserAsync(string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand("users/me", + ParseCommand command = new ParseCommand("users/me", method: "GET", sessionToken: sessionToken, data: null); @@ -101,7 +100,7 @@ public Task GetUserAsync(string sessionToken, CancellationToken ca public Task RequestPasswordResetAsync(string email, CancellationToken cancellationToken) { - var command = new ParseCommand("requestPasswordReset", + ParseCommand command = new ParseCommand("requestPasswordReset", method: "POST", data: new Dictionary { {"email", email} diff --git a/Parse/Internal/Object/State/MutableObjectState.cs b/Parse/Management/Tracking/MutableObjectState.cs similarity index 61% rename from Parse/Internal/Object/State/MutableObjectState.cs rename to Parse/Management/Tracking/MutableObjectState.cs index 4a771803..16623628 100644 --- a/Parse/Internal/Object/State/MutableObjectState.cs +++ b/Parse/Management/Tracking/MutableObjectState.cs @@ -1,8 +1,8 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; -using System.Linq; using System.Collections.Generic; +using System.Linq; namespace Parse.Core.Internal { @@ -18,38 +18,22 @@ public class MutableObjectState : IObjectState private IDictionary serverData = new Dictionary(); public IDictionary ServerData { - get - { - return serverData; - } + get => serverData; - set - { - serverData = value; - } + set => serverData = value; } - public object this[string key] - { - get - { - return ServerData[key]; - } - } + public object this[string key] => ServerData[key]; - public bool ContainsKey(string key) - { - return ServerData.ContainsKey(key); - } + public bool ContainsKey(string key) => ServerData.ContainsKey(key); public void Apply(IDictionary operationSet) { // Apply operationSet - foreach (var pair in operationSet) + foreach (KeyValuePair pair in operationSet) { - object oldValue; - ServerData.TryGetValue(pair.Key, out oldValue); - var newValue = pair.Value.Apply(oldValue, pair.Key); + ServerData.TryGetValue(pair.Key, out object oldValue); + object newValue = pair.Value.Apply(oldValue, pair.Key); if (newValue != ParseDeleteOperation.DeleteToken) { ServerData[pair.Key] = newValue; @@ -77,7 +61,7 @@ public void Apply(IObjectState other) CreatedAt = other.CreatedAt; } - foreach (var pair in other) + foreach (KeyValuePair pair in other) { ServerData[pair.Key] = pair.Value; } @@ -85,32 +69,23 @@ public void Apply(IObjectState other) public IObjectState MutatedClone(Action func) { - var clone = MutableClone(); + MutableObjectState clone = MutableClone(); func(clone); return clone; } - protected virtual MutableObjectState MutableClone() + protected virtual MutableObjectState MutableClone() => new MutableObjectState { - return new MutableObjectState - { - IsNew = IsNew, - ClassName = ClassName, - ObjectId = ObjectId, - CreatedAt = CreatedAt, - UpdatedAt = UpdatedAt, - ServerData = this.ToDictionary(t => t.Key, t => t.Value) - }; - } + 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(); - } + IEnumerator> IEnumerable>.GetEnumerator() => ServerData.GetEnumerator(); - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return ((IEnumerable>) this).GetEnumerator(); - } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => ((IEnumerable>) this).GetEnumerator(); } } diff --git a/Parse/Internal/Operation/ParseAddOperation.cs b/Parse/Management/Tracking/ParseAddOperation.cs similarity index 73% rename from Parse/Internal/Operation/ParseAddOperation.cs rename to Parse/Management/Tracking/ParseAddOperation.cs index efa2ae63..e6f2d39f 100644 --- a/Parse/Internal/Operation/ParseAddOperation.cs +++ b/Parse/Management/Tracking/ParseAddOperation.cs @@ -11,18 +11,12 @@ namespace Parse.Core.Internal public class ParseAddOperation : IParseFieldOperation { private ReadOnlyCollection objects; - public ParseAddOperation(IEnumerable objects) - { - this.objects = new ReadOnlyCollection(objects.ToList()); - } + public ParseAddOperation(IEnumerable objects) => this.objects = new ReadOnlyCollection(objects.ToList()); - public object Encode() - { - return new Dictionary { + public object Encode() => new Dictionary { {"__op", "Add"}, {"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)} }; - } public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) { @@ -36,8 +30,8 @@ public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) } if (previous is ParseSetOperation) { - var setOp = (ParseSetOperation) previous; - var oldList = Conversion.To>(setOp.Value); + ParseSetOperation setOp = (ParseSetOperation) previous; + IList oldList = Conversion.To>(setOp.Value); return new ParseSetOperation(oldList.Concat(objects).ToList()); } if (previous is ParseAddOperation) @@ -53,16 +47,10 @@ public object Apply(object oldValue, string key) { return objects.ToList(); } - var oldList = Conversion.To>(oldValue); + IList oldList = Conversion.To>(oldValue); return oldList.Concat(objects).ToList(); } - public IEnumerable Objects - { - get - { - return objects; - } - } + public IEnumerable Objects => objects; } } diff --git a/Parse/Internal/Operation/ParseAddUniqueOperation.cs b/Parse/Management/Tracking/ParseAddUniqueOperation.cs similarity index 66% rename from Parse/Internal/Operation/ParseAddUniqueOperation.cs rename to Parse/Management/Tracking/ParseAddUniqueOperation.cs index 2e269945..eb1c58a9 100644 --- a/Parse/Internal/Operation/ParseAddUniqueOperation.cs +++ b/Parse/Management/Tracking/ParseAddUniqueOperation.cs @@ -11,18 +11,12 @@ namespace Parse.Core.Internal public class ParseAddUniqueOperation : IParseFieldOperation { private ReadOnlyCollection objects; - public ParseAddUniqueOperation(IEnumerable objects) - { - this.objects = new ReadOnlyCollection(objects.Distinct().ToList()); - } + public ParseAddUniqueOperation(IEnumerable objects) => this.objects = new ReadOnlyCollection(objects.Distinct().ToList()); - public object Encode() - { - return new Dictionary { + public object Encode() => new Dictionary { {"__op", "AddUnique"}, {"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)} }; - } public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) { @@ -36,15 +30,15 @@ public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) } if (previous is ParseSetOperation) { - var setOp = (ParseSetOperation) previous; - var oldList = Conversion.To>(setOp.Value); - var result = this.Apply(oldList, null); + ParseSetOperation setOp = (ParseSetOperation) previous; + IList oldList = Conversion.To>(setOp.Value); + object result = Apply(oldList, null); return new ParseSetOperation(result); } if (previous is ParseAddUniqueOperation) { - var oldList = ((ParseAddUniqueOperation) previous).Objects; - return new ParseAddUniqueOperation((IList) this.Apply(oldList, null)); + IEnumerable oldList = ((ParseAddUniqueOperation) previous).Objects; + return new ParseAddUniqueOperation((IList) Apply(oldList, null)); } throw new InvalidOperationException("Operation is invalid after previous operation."); } @@ -55,24 +49,24 @@ public object Apply(object oldValue, string key) { return objects.ToList(); } - var newList = Conversion.To>(oldValue).ToList(); - var comparer = ParseFieldOperations.ParseObjectComparer; - foreach (var objToAdd in objects) + List newList = Conversion.To>(oldValue).ToList(); + IEqualityComparer comparer = ParseFieldOperations.ParseObjectComparer; + foreach (object objToAdd in objects) { if (objToAdd is ParseObject) { - var matchedObj = newList.FirstOrDefault(listObj => comparer.Equals(objToAdd, listObj)); + object matchedObj = newList.FirstOrDefault(listObj => comparer.Equals(objToAdd, listObj)); if (matchedObj == null) { newList.Add(objToAdd); } else { - var index = newList.IndexOf(matchedObj); + int index = newList.IndexOf(matchedObj); newList[index] = objToAdd; } } - else if (!newList.Contains(objToAdd, comparer)) + else if (!newList.Contains(objToAdd, comparer)) { newList.Add(objToAdd); } @@ -80,12 +74,6 @@ public object Apply(object oldValue, string key) return newList; } - public IEnumerable Objects - { - get - { - return objects; - } - } + public IEnumerable Objects => objects; } } diff --git a/Parse/Internal/Operation/ParseDeleteOperation.cs b/Parse/Management/Tracking/ParseDeleteOperation.cs similarity index 65% rename from Parse/Internal/Operation/ParseDeleteOperation.cs rename to Parse/Management/Tracking/ParseDeleteOperation.cs index f3d9fa15..4bc4cc3d 100644 --- a/Parse/Internal/Operation/ParseDeleteOperation.cs +++ b/Parse/Management/Tracking/ParseDeleteOperation.cs @@ -11,30 +11,15 @@ public class ParseDeleteOperation : IParseFieldOperation { internal static readonly object DeleteToken = new object(); private static ParseDeleteOperation _Instance = new ParseDeleteOperation(); - public static ParseDeleteOperation Instance - { - get - { - return _Instance; - } - } + public static ParseDeleteOperation Instance => _Instance; private ParseDeleteOperation() { } - public object Encode() - { - return new Dictionary { + public object Encode() => new Dictionary { {"__op", "Delete"} }; - } - public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) - { - return this; - } + public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) => this; - public object Apply(object oldValue, string key) - { - return DeleteToken; - } + public object Apply(object oldValue, string key) => DeleteToken; } } diff --git a/Parse/Internal/Operation/ParseFieldOperations.cs b/Parse/Management/Tracking/ParseFieldOperations.cs similarity index 86% rename from Parse/Internal/Operation/ParseFieldOperations.cs rename to Parse/Management/Tracking/ParseFieldOperations.cs index e5638978..f410fb5e 100644 --- a/Parse/Internal/Operation/ParseFieldOperations.cs +++ b/Parse/Management/Tracking/ParseFieldOperations.cs @@ -9,8 +9,8 @@ public class ParseObjectIdComparer : IEqualityComparer { bool IEqualityComparer.Equals(object p1, object p2) { - var parseObj1 = p1 as ParseObject; - var parseObj2 = p2 as ParseObject; + ParseObject parseObj1 = p1 as ParseObject; + ParseObject parseObj2 = p2 as ParseObject; if (parseObj1 != null && parseObj2 != null) { return Equals(parseObj1.ObjectId, parseObj2.ObjectId); @@ -20,7 +20,7 @@ bool IEqualityComparer.Equals(object p1, object p2) public int GetHashCode(object p) { - var parseObject = p as ParseObject; + ParseObject parseObject = p as ParseObject; if (parseObject != null) { return parseObject.ObjectId.GetHashCode(); @@ -33,10 +33,7 @@ static class ParseFieldOperations { private static ParseObjectIdComparer comparer; - public static IParseFieldOperation Decode(IDictionary json) - { - throw new NotImplementedException(); - } + public static IParseFieldOperation Decode(IDictionary json) => throw new NotImplementedException(); public static IEqualityComparer ParseObjectComparer { diff --git a/Parse/Internal/Operation/ParseIncrementOperation.cs b/Parse/Management/Tracking/ParseIncrementOperation.cs similarity index 93% rename from Parse/Internal/Operation/ParseIncrementOperation.cs rename to Parse/Management/Tracking/ParseIncrementOperation.cs index 68e59228..72af8e3f 100644 --- a/Parse/Internal/Operation/ParseIncrementOperation.cs +++ b/Parse/Management/Tracking/ParseIncrementOperation.cs @@ -79,37 +79,30 @@ static ParseIncrementOperation() {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()) + foreach (Tuple pair in adders.Keys.ToList()) { if (pair.Item1.Equals(pair.Item2)) { continue; } - var reversePair = new Tuple(pair.Item2, pair.Item1); - var func = adders[pair]; + Tuple reversePair = new Tuple(pair.Item2, pair.Item1); + Func func = adders[pair]; adders[reversePair] = (left, right) => func(right, left); } } private object amount; - public ParseIncrementOperation(object amount) - { - this.amount = amount; - } + public ParseIncrementOperation(object amount) => this.amount = amount; - public object Encode() - { - return new Dictionary { + public object Encode() => 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)) + if (adders.TryGetValue(new Tuple(obj1.GetType(), obj2.GetType()), out Func adder)) { return adder(obj1, obj2); } @@ -128,12 +121,12 @@ public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) } if (previous is ParseSetOperation) { - var otherAmount = ((ParseSetOperation) previous).Value; + object otherAmount = ((ParseSetOperation) previous).Value; if (otherAmount is string) { throw new InvalidOperationException("Cannot increment a non-number type."); } - var myAmount = amount; + object myAmount = amount; return new ParseSetOperation(Add(otherAmount, myAmount)); } if (previous is ParseIncrementOperation) @@ -156,12 +149,6 @@ public object Apply(object oldValue, string key) return Add(otherAmount, myAmount); } - public object Amount - { - get - { - return amount; - } - } + public object Amount => amount; } } diff --git a/Parse/Internal/Operation/ParseRelationOperation.cs b/Parse/Management/Tracking/ParseRelationOperation.cs similarity index 81% rename from Parse/Internal/Operation/ParseRelationOperation.cs rename to Parse/Management/Tracking/ParseRelationOperation.cs index 4c5a16b6..ce0eae2f 100644 --- a/Parse/Internal/Operation/ParseRelationOperation.cs +++ b/Parse/Management/Tracking/ParseRelationOperation.cs @@ -4,8 +4,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Parse.Core.Internal { @@ -29,26 +27,26 @@ public ParseRelationOperation(IEnumerable adds, { adds = adds ?? new ParseObject[0]; removes = removes ?? new ParseObject[0]; - this.targetClassName = adds.Concat(removes).Select(o => o.ClassName).FirstOrDefault(); + 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 + List adds = this.adds .Select(id => PointerOrLocalIdEncoder.Instance.Encode( ParseObject.CreateWithoutData(targetClassName, id))) .ToList(); - var removes = this.removes + List removes = this.removes .Select(id => PointerOrLocalIdEncoder.Instance.Encode( ParseObject.CreateWithoutData(targetClassName, id))) .ToList(); - var addDict = adds.Count == 0 ? null : new Dictionary { + Dictionary addDict = adds.Count == 0 ? null : new Dictionary { {"__op", "AddRelation"}, {"objects", adds} }; - var removeDict = removes.Count == 0 ? null : new Dictionary { + Dictionary removeDict = removes.Count == 0 ? null : new Dictionary { {"__op", "RemoveRelation"}, {"objects", removes} }; @@ -73,18 +71,18 @@ public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) { throw new InvalidOperationException("You can't modify a relation after deleting it."); } - var other = previous as ParseRelationOperation; + ParseRelationOperation other = previous as ParseRelationOperation; if (other != null) { if (other.TargetClassName != TargetClassName) { throw new InvalidOperationException( - string.Format("Related object must be of class {0}, but {1} was passed in.", + 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(); + List newAdd = adds.Union(other.adds.Except(removes)).ToList(); + List newRemove = removes.Union(other.removes.Except(adds)).ToList(); return new ParseRelationOperation(newAdd, newRemove, TargetClassName); } throw new InvalidOperationException("Operation is invalid after previous operation."); @@ -102,8 +100,8 @@ public object Apply(object oldValue, string key) } if (oldValue is ParseRelationBase) { - var oldRelation = (ParseRelationBase) oldValue; - var oldClassName = oldRelation.TargetClassName; + ParseRelationBase oldRelation = (ParseRelationBase) oldValue; + string oldClassName = oldRelation.TargetClassName; if (oldClassName != null && oldClassName != targetClassName) { throw new InvalidOperationException("Related object must be a " + oldClassName @@ -115,11 +113,11 @@ public object Apply(object oldValue, string key) throw new InvalidOperationException("Operation is invalid after previous operation."); } - public string TargetClassName { get { return targetClassName; } } + public string TargetClassName => targetClassName; private IEnumerable IdsFromObjects(IEnumerable objects) { - foreach (var obj in objects) + foreach (ParseObject obj in objects) { if (obj.ObjectId == null) { @@ -128,7 +126,7 @@ private IEnumerable IdsFromObjects(IEnumerable objects) } if (obj.ClassName != targetClassName) { - throw new ArgumentException(string.Format( + throw new ArgumentException(String.Format( "Tried to create a ParseRelation with 2 different types: {0} and {1}", targetClassName, obj.ClassName)); diff --git a/Parse/Internal/Operation/ParseRemoveOperation.cs b/Parse/Management/Tracking/ParseRemoveOperation.cs similarity index 70% rename from Parse/Internal/Operation/ParseRemoveOperation.cs rename to Parse/Management/Tracking/ParseRemoveOperation.cs index fdead1c6..bdb4b583 100644 --- a/Parse/Internal/Operation/ParseRemoveOperation.cs +++ b/Parse/Management/Tracking/ParseRemoveOperation.cs @@ -12,18 +12,12 @@ namespace Parse.Core.Internal public class ParseRemoveOperation : IParseFieldOperation { private ReadOnlyCollection objects; - public ParseRemoveOperation(IEnumerable objects) - { - this.objects = new ReadOnlyCollection(objects.Distinct().ToList()); - } + public ParseRemoveOperation(IEnumerable objects) => this.objects = new ReadOnlyCollection(objects.Distinct().ToList()); - public object Encode() - { - return new Dictionary { + public object Encode() => new Dictionary { {"__op", "Remove"}, {"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)} }; - } public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) { @@ -37,13 +31,13 @@ public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) } if (previous is ParseSetOperation) { - var setOp = (ParseSetOperation) previous; - var oldList = Conversion.As>(setOp.Value); - return new ParseSetOperation(this.Apply(oldList, null)); + ParseSetOperation setOp = (ParseSetOperation) previous; + IList oldList = Conversion.As>(setOp.Value); + return new ParseSetOperation(Apply(oldList, null)); } if (previous is ParseRemoveOperation) { - var oldOp = (ParseRemoveOperation) previous; + ParseRemoveOperation oldOp = (ParseRemoveOperation) previous; return new ParseRemoveOperation(oldOp.Objects.Concat(objects)); } throw new InvalidOperationException("Operation is invalid after previous operation."); @@ -55,16 +49,10 @@ public object Apply(object oldValue, string key) { return new List(); } - var oldList = Conversion.As>(oldValue); + IList oldList = Conversion.As>(oldValue); return oldList.Except(objects, ParseFieldOperations.ParseObjectComparer).ToList(); } - public IEnumerable Objects - { - get - { - return objects; - } - } + public IEnumerable Objects => objects; } } diff --git a/Parse/Internal/Operation/ParseSetOperation.cs b/Parse/Management/Tracking/ParseSetOperation.cs similarity index 57% rename from Parse/Internal/Operation/ParseSetOperation.cs rename to Parse/Management/Tracking/ParseSetOperation.cs index ebefc190..eaceac44 100644 --- a/Parse/Internal/Operation/ParseSetOperation.cs +++ b/Parse/Management/Tracking/ParseSetOperation.cs @@ -4,25 +4,13 @@ namespace Parse.Core.Internal { public class ParseSetOperation : IParseFieldOperation { - public ParseSetOperation(object value) - { - Value = value; - } + public ParseSetOperation(object value) => Value = value; - public object Encode() - { - return PointerOrLocalIdEncoder.Instance.Encode(Value); - } + public object Encode() => PointerOrLocalIdEncoder.Instance.Encode(Value); - public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) - { - return this; - } + public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) => this; - public object Apply(object oldValue, string key) - { - return Value; - } + public object Apply(object oldValue, string key) => Value; public object Value { get; private set; } } diff --git a/Parse/Internal/Modules/IParseModule.cs b/Parse/Modules/IParseModule.cs similarity index 90% rename from Parse/Internal/Modules/IParseModule.cs rename to Parse/Modules/IParseModule.cs index 00be08d5..d2a84da1 100644 --- a/Parse/Internal/Modules/IParseModule.cs +++ b/Parse/Modules/IParseModule.cs @@ -1,5 +1,3 @@ -using System; - namespace Parse.Common.Internal { public interface IParseModule diff --git a/Parse/Internal/Modules/ParseModuleAttribute.cs b/Parse/Modules/ParseModuleAttribute.cs similarity index 77% rename from Parse/Internal/Modules/ParseModuleAttribute.cs rename to Parse/Modules/ParseModuleAttribute.cs index 0a9c93d6..87d1a1c0 100644 --- a/Parse/Internal/Modules/ParseModuleAttribute.cs +++ b/Parse/Modules/ParseModuleAttribute.cs @@ -9,10 +9,7 @@ public class ParseModuleAttribute : Attribute /// Instantiates a new ParseModuleAttribute. /// /// The type to which this module is applied. - public ParseModuleAttribute(Type ModuleType) - { - this.ModuleType = ModuleType; - } + public ParseModuleAttribute(Type ModuleType) => this.ModuleType = ModuleType; public Type ModuleType { get; private set; } } diff --git a/Parse/Internal/Modules/ParseModuleController.cs b/Parse/Modules/ParseModuleController.cs similarity index 90% rename from Parse/Internal/Modules/ParseModuleController.cs rename to Parse/Modules/ParseModuleController.cs index 841c7403..4412b9f5 100644 --- a/Parse/Internal/Modules/ParseModuleController.cs +++ b/Parse/Modules/ParseModuleController.cs @@ -12,10 +12,7 @@ namespace Parse.Common.Internal public class ParseModuleController { private static readonly ParseModuleController instance = new ParseModuleController(); - public static ParseModuleController Instance - { - get { return instance; } - } + public static ParseModuleController Instance => instance; private readonly object mutex = new object(); private readonly List modules = new List(); @@ -43,7 +40,7 @@ public void RegisterModule(IParseModule module) public void ScanForModules() { - var moduleTypes = Lister.AllAssemblies + IEnumerable moduleTypes = Lister.AllAssemblies .SelectMany(asm => asm.GetCustomAttributes()) .Select(attr => attr.ModuleType) .Where(type => type != null && type.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IParseModule))); @@ -57,7 +54,7 @@ public void ScanForModules() ConstructorInfo constructor = moduleType.FindConstructor(); if (constructor != null) { - var module = constructor.Invoke(new object[] { }) as IParseModule; + IParseModule module = constructor.Invoke(new object[] { }) as IParseModule; RegisterModule(module); } } diff --git a/Parse/Parse.csproj b/Parse/Parse.csproj index 08e95b3c..489050c4 100644 --- a/Parse/Parse.csproj +++ b/Parse/Parse.csproj @@ -3,7 +3,7 @@ netstandard2.0 bin\Release\netstandard2.0\Parse.xml - 2.0.0-develop + 2.0.0-develop-0001 2.0.0 latest @@ -13,7 +13,7 @@ https://raw.githubusercontent.com/parse-community/parse-community.github.io/master/img/favicon/favicon-194x194.png GitHub This is the official package for the Parse .NET Standard SDK. Add a cloud backend to any platform supporting .NET Standard 2.0 with this simple-to-use SDK. - Copyright © Parse 2018. All rights reserved. + Copyright © Parse 2020. All rights reserved. Parse;netstandard2.0;parse-platform;backend;sdk;netstandard;app false @@ -24,4 +24,8 @@ + + + + diff --git a/Parse/Public/ParseClient.cs b/Parse/ParseClient.cs similarity index 99% rename from Parse/Public/ParseClient.cs rename to Parse/ParseClient.cs index 5cbca097..56f4ddc1 100644 --- a/Parse/Public/ParseClient.cs +++ b/Parse/ParseClient.cs @@ -98,7 +98,8 @@ public string RelativeStorageFilePath get { FileInfo file = default; - while ((file = StorageManager.GetWrapperForRelativePersistentStorageFilePath(GeneratePath())).Exists && IsFallback); + while ((file = StorageManager.GetWrapperForRelativePersistentStorageFilePath(GeneratePath())).Exists && IsFallback) + ; return file.FullName; } @@ -205,9 +206,11 @@ public string MasterKey /// The current configuration that parse has been initialized with. /// public static Configuration CurrentConfiguration { get; internal set; } + internal static string MasterKey { get; set; } internal static Version Version => new AssemblyName(typeof(ParseClient).GetTypeInfo().Assembly.FullName).Version; + internal static string VersionString { get; } /// diff --git a/Parse/Public/ParseFile.cs b/Parse/ParseFile.cs similarity index 79% rename from Parse/Public/ParseFile.cs rename to Parse/ParseFile.cs index 7928ba5f..1eb66cdf 100644 --- a/Parse/Public/ParseFile.cs +++ b/Parse/ParseFile.cs @@ -1,13 +1,12 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.IO; -using System.Net; using System.Threading; using System.Threading.Tasks; using Parse.Common.Internal; +using Parse.Core.Internal; namespace Parse { @@ -35,15 +34,12 @@ public class ParseFile : IJsonConvertible #region Constructor - internal ParseFile(string name, Uri uri, string mimeType = null) + internal ParseFile(string name, Uri uri, string mimeType = null) => state = new FileState { - state = new FileState - { - Name = name, - Url = uri, - MimeType = mimeType - }; - } + Name = name, + Url = uri, + MimeType = mimeType + }; /// /// Creates a new file from a byte array and a name. @@ -73,7 +69,7 @@ public ParseFile(string name, Stream data, string mimeType = null) Name = name, MimeType = mimeType }; - this.dataStream = data; + dataStream = data; } #endregion @@ -83,66 +79,36 @@ public ParseFile(string name, Stream data, string mimeType = null) /// /// Gets whether the file still needs to be saved. /// - public bool IsDirty - { - get - { - return state.Url == null; - } - } + public bool IsDirty => 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. /// [ParseFieldName("name")] - public string Name - { - get - { - return state.Name; - } - } + public string Name => 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; - } - } + public string MimeType => 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 . /// [ParseFieldName("url")] - public Uri Url - { - get - { - return state.SecureUrl; - } - } + public Uri Url => state.SecureUrl; - internal static IParseFileController FileController - { - get - { - return ParseCorePlugins.Instance.FileController; - } - } + internal static IParseFileController FileController => ParseCorePlugins.Instance.FileController; #endregion IDictionary IJsonConvertible.ToJSON() { - if (this.IsDirty) + if (IsDirty) { throw new InvalidOperationException( "ParseFile must be saved before it can be serialized."); @@ -159,28 +125,19 @@ IDictionary IJsonConvertible.ToJSON() /// /// Saves the file to the Parse cloud. /// - public Task SaveAsync() - { - return SaveAsync(null, CancellationToken.None); - } + public Task SaveAsync() => SaveAsync(null, CancellationToken.None); /// /// Saves the file to the Parse cloud. /// /// The cancellation token. - public Task SaveAsync(CancellationToken cancellationToken) - { - return SaveAsync(null, cancellationToken); - } + public Task SaveAsync(CancellationToken cancellationToken) => SaveAsync(null, cancellationToken); /// /// Saves the file to the Parse cloud. /// /// The progress callback. - public Task SaveAsync(IProgress progress) - { - return SaveAsync(progress, CancellationToken.None); - } + public Task SaveAsync(IProgress progress) => SaveAsync(progress, CancellationToken.None); /// /// Saves the file to the Parse cloud. @@ -188,15 +145,12 @@ public Task SaveAsync(IProgress progress) /// The progress callback. /// The cancellation token. public Task SaveAsync(IProgress progress, - CancellationToken cancellationToken) - { - return taskQueue.Enqueue( + CancellationToken cancellationToken) => taskQueue.Enqueue( toAwait => FileController.SaveAsync(state, dataStream, ParseUser.CurrentSessionToken, progress, cancellationToken), cancellationToken) .OnSuccess(t => { state = t.Result; }); - } #endregion } diff --git a/Parse/Public/ParseGeoDistance.cs b/Parse/ParseGeoDistance.cs similarity index 73% rename from Parse/Public/ParseGeoDistance.cs rename to Parse/ParseGeoDistance.cs index 75b55bfd..33f31fc4 100644 --- a/Parse/Public/ParseGeoDistance.cs +++ b/Parse/ParseGeoDistance.cs @@ -15,10 +15,7 @@ public struct ParseGeoDistance /// /// The distance in radians. public ParseGeoDistance(double radians) - : this() - { - Radians = radians; - } + : this() => Radians = radians; /// /// Gets the distance in radians. @@ -28,53 +25,32 @@ public ParseGeoDistance(double radians) /// /// Gets the distance in miles. /// - public double Miles - { - get - { - return Radians * EarthMeanRadiusMiles; - } - } + public double Miles => Radians * EarthMeanRadiusMiles; /// /// Gets the distance in kilometers. /// - public double Kilometers - { - get - { - return Radians * EarthMeanRadiusKilometers; - } - } + public double Kilometers => Radians * EarthMeanRadiusKilometers; /// /// Gets a ParseGeoDistance from a number of miles. /// /// The number of miles. /// A ParseGeoDistance for the given number of miles. - public static ParseGeoDistance FromMiles(double miles) - { - return new ParseGeoDistance(miles / EarthMeanRadiusMiles); - } + public static ParseGeoDistance FromMiles(double miles) => new ParseGeoDistance(miles / EarthMeanRadiusMiles); /// /// Gets a ParseGeoDistance from a number of kilometers. /// /// The number of kilometers. /// A ParseGeoDistance for the given number of kilometers. - public static ParseGeoDistance FromKilometers(double kilometers) - { - return new ParseGeoDistance(kilometers / EarthMeanRadiusKilometers); - } + public static ParseGeoDistance FromKilometers(double kilometers) => new ParseGeoDistance(kilometers / EarthMeanRadiusKilometers); /// /// Gets a ParseGeoDistance from a number of radians. /// /// The number of radians. /// A ParseGeoDistance for the given number of radians. - public static ParseGeoDistance FromRadians(double radians) - { - return new ParseGeoDistance(radians); - } + public static ParseGeoDistance FromRadians(double radians) => new ParseGeoDistance(radians); } } diff --git a/Parse/Public/ParseGeoPoint.cs b/Parse/ParseGeoPoint.cs similarity index 92% rename from Parse/Public/ParseGeoPoint.cs rename to Parse/ParseGeoPoint.cs index 65f59555..93f741ea 100644 --- a/Parse/Public/ParseGeoPoint.cs +++ b/Parse/ParseGeoPoint.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using Parse.Core.Internal; using Parse.Common.Internal; namespace Parse @@ -35,10 +34,7 @@ public ParseGeoPoint(double latitude, double longitude) /// public double Latitude { - get - { - return latitude; - } + get => latitude; set { if (value > 90 || value < -90) @@ -57,10 +53,7 @@ public double Latitude /// public double Longitude { - get - { - return longitude; - } + get => longitude; set { if (value > 180 || value < -180) @@ -97,13 +90,10 @@ public ParseGeoDistance DistanceTo(ParseGeoPoint point) return new ParseGeoDistance(2 * Math.Asin(Math.Sqrt(a))); } - IDictionary IJsonConvertible.ToJSON() - { - return new Dictionary { + IDictionary IJsonConvertible.ToJSON() => new Dictionary { {"__type", "GeoPoint"}, {"latitude", Latitude}, {"longitude", Longitude} }; - } } } diff --git a/Parse/Public/ParseInstallation.cs b/Parse/ParseInstallation.cs similarity index 81% rename from Parse/Public/ParseInstallation.cs rename to Parse/ParseInstallation.cs index 4622e7c2..ab25682f 100644 --- a/Parse/Public/ParseInstallation.cs +++ b/Parse/ParseInstallation.cs @@ -1,17 +1,13 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Push.Internal; using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Reflection; -using System.Text; using System.Threading; using System.Threading.Tasks; +using Parse.Common.Internal; +using Parse.Push.Internal; namespace Parse { @@ -24,34 +20,17 @@ namespace Parse [ParseClassName("_Installation")] public partial class ParseInstallation : ParseObject { - private static readonly HashSet readOnlyKeys = new HashSet { - "deviceType", "deviceUris", "installationId", "timeZone", "localeIdentifier", "parseVersion", "appName", "appIdentifier", "appVersion", "pushType" - }; + static readonly HashSet readOnlyKeys = new HashSet { "deviceType", "deviceUris", "installationId", "timeZone", "localeIdentifier", "parseVersion", "appName", "appIdentifier", "appVersion", "pushType" }; - internal static IParseCurrentInstallationController CurrentInstallationController - { - get - { - return ParsePushPlugins.Instance.CurrentInstallationController; - } - } + internal static IParseCurrentInstallationController CurrentInstallationController => ParsePushPlugins.Instance.CurrentInstallationController; - internal static IDeviceInfoController DeviceInfoController - { - get - { - return ParsePushPlugins.Instance.DeviceInfoController; - } - } + internal static IDeviceInfoController DeviceInfoController => ParsePushPlugins.Instance.DeviceInfoController; /// /// Constructs a new ParseInstallation. Generally, you should not need to construct /// ParseInstallations yourself. Instead use . /// - public ParseInstallation() - : base() - { - } + public ParseInstallation() : base() { } /// /// Gets the ParseInstallation representing this app on this device. @@ -60,17 +39,14 @@ public static ParseInstallation CurrentInstallation { get { - var task = CurrentInstallationController.GetAsync(CancellationToken.None); + Task task = CurrentInstallationController.GetAsync(CancellationToken.None); // TODO (hallucinogen): this will absolutely break on Unity, but how should we resolve this? task.Wait(); return task.Result; } } - internal static void ClearInMemoryInstallation() - { - CurrentInstallationController.ClearFromMemory(); - } + internal static void ClearInMemoryInstallation() => CurrentInstallationController.ClearFromMemory(); /// /// Constructs a for ParseInstallations. @@ -87,13 +63,7 @@ internal static void ClearInMemoryInstallation() /// You can add additional query conditions, but one of the above must appear as a top-level AND /// clause in the query. /// - public static ParseQuery Query - { - get - { - return new ParseQuery(); - } - } + public static ParseQuery Query => new ParseQuery(); /// /// A GUID that uniquely names this app installed on this device. @@ -119,7 +89,7 @@ public Guid InstallationId internal set { Guid installationId = value; - SetProperty(installationId.ToString(), "InstallationId"); + SetProperty(installationId.ToString(), "InstallationId"); } } @@ -129,8 +99,8 @@ internal set [ParseFieldName("deviceType")] public string DeviceType { - get { return GetProperty("DeviceType"); } - internal set { SetProperty(value, "DeviceType"); } + get => GetProperty("DeviceType"); + internal set => SetProperty(value, "DeviceType"); } /// @@ -139,8 +109,8 @@ public string DeviceType [ParseFieldName("appName")] public string AppName { - get { return GetProperty("AppName"); } - internal set { SetProperty(value, "AppName"); } + get => GetProperty("AppName"); + internal set => SetProperty(value, "AppName"); } /// @@ -149,8 +119,8 @@ public string AppName [ParseFieldName("appVersion")] public string AppVersion { - get { return GetProperty("AppVersion"); } - internal set { SetProperty(value, "AppVersion"); } + get => GetProperty("AppVersion"); + internal set => SetProperty(value, "AppVersion"); } /// @@ -161,8 +131,8 @@ public string AppVersion [ParseFieldName("appIdentifier")] public string AppIdentifier { - get { return GetProperty("AppIdentifier"); } - internal set { SetProperty(value, "AppIdentifier"); } + get => GetProperty("AppIdentifier"); + internal set => SetProperty(value, "AppIdentifier"); } /// @@ -175,8 +145,8 @@ public string AppIdentifier [ParseFieldName("timeZone")] public string TimeZone { - get { return GetProperty("TimeZone"); } - private set { SetProperty(value, "TimeZone"); } + get => GetProperty("TimeZone"); + private set => SetProperty(value, "TimeZone"); } /// @@ -186,8 +156,8 @@ public string TimeZone [ParseFieldName("localeIdentifier")] public string LocaleIdentifier { - get { return GetProperty("LocaleIdentifier"); } - private set { SetProperty(value, "LocaleIdentifier"); } + get => GetProperty("LocaleIdentifier"); + private set => SetProperty(value, "LocaleIdentifier"); } /// @@ -206,13 +176,13 @@ private string GetLocaleIdentifier() { countryCode = RegionInfo.CurrentRegion.TwoLetterISORegionName; } - if (string.IsNullOrEmpty(countryCode)) + if (String.IsNullOrEmpty(countryCode)) { return languageCode; } else { - return string.Format("{0}-{1}", languageCode, countryCode); + return String.Format("{0}-{1}", languageCode, countryCode); } } @@ -224,7 +194,7 @@ public Version ParseVersion { get { - var versionString = GetProperty("ParseVersion"); + string versionString = GetProperty("ParseVersion"); Version version = null; try { @@ -240,7 +210,7 @@ public Version ParseVersion private set { Version version = value; - SetProperty(version.ToString(), "ParseVersion"); + SetProperty(version.ToString(), "ParseVersion"); } } @@ -251,30 +221,27 @@ private set [ParseFieldName("channels")] public IList Channels { - get { return GetProperty>("Channels"); } - set { SetProperty(value, "Channels"); } + get => GetProperty>("Channels"); + set => SetProperty(value, "Channels"); } - protected override bool IsKeyMutable(string key) - { - return !readOnlyKeys.Contains(key); - } + protected override bool IsKeyMutable(string key) => !readOnlyKeys.Contains(key); protected override Task SaveAsync(Task toAwait, CancellationToken cancellationToken) { Task platformHookTask = null; if (CurrentInstallationController.IsCurrent(this)) { - var configuration = ParseClient.CurrentConfiguration; + ParseClient.Configuration configuration = ParseClient.CurrentConfiguration; // 'this' is required in order for the extension method to be used. - this.SetIfDifferent("deviceType", DeviceInfoController.DeviceType); - this.SetIfDifferent("timeZone", DeviceInfoController.DeviceTimeZone); - this.SetIfDifferent("localeIdentifier", GetLocaleIdentifier()); - this.SetIfDifferent("parseVersion", GetParseVersion().ToString()); - this.SetIfDifferent("appVersion", configuration.VersionInfo.BuildVersion ?? DeviceInfoController.AppBuildVersion); - this.SetIfDifferent("appIdentifier", DeviceInfoController.AppIdentifier); - this.SetIfDifferent("appName", DeviceInfoController.AppName); + SetIfDifferent("deviceType", DeviceInfoController.DeviceType); + SetIfDifferent("timeZone", DeviceInfoController.DeviceTimeZone); + SetIfDifferent("localeIdentifier", GetLocaleIdentifier()); + SetIfDifferent("parseVersion", GetParseVersion().ToString()); + SetIfDifferent("appVersion", configuration.VersionInfo.BuildVersion ?? DeviceInfoController.AppBuildVersion); + SetIfDifferent("appIdentifier", DeviceInfoController.AppIdentifier); + SetIfDifferent("appName", DeviceInfoController.AppName); platformHookTask = DeviceInfoController.ExecuteParseInstallationSaveHookAsync(this); } @@ -292,10 +259,7 @@ protected override Task SaveAsync(Task toAwait, CancellationToken cancellationTo }).Unwrap(); } - private Version GetParseVersion() - { - return new AssemblyName(typeof(ParseInstallation).GetTypeInfo().Assembly.FullName).Version; - } + private Version GetParseVersion() => new AssemblyName(typeof(ParseInstallation).GetTypeInfo().Assembly.FullName).Version; /// /// This mapping of Windows names to a standard everyone else uses is maintained diff --git a/Parse/Public/ParseObject.cs b/Parse/ParseObject.cs similarity index 77% rename from Parse/Public/ParseObject.cs rename to Parse/ParseObject.cs index 0c7fde6a..870d81b4 100644 --- a/Parse/Public/ParseObject.cs +++ b/Parse/ParseObject.cs @@ -1,17 +1,15 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Core.Internal; -using Parse.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 Parse.Common.Internal; +using Parse.Core.Internal; +using Parse.Utilities; namespace Parse { @@ -87,7 +85,7 @@ public ParseObject(string className) // 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; + bool isPointer = isCreatingPointer.Value; isCreatingPointer.Value = false; if (className == null) @@ -124,7 +122,7 @@ public ParseObject(string className) /// /// Creates a reference to an existing ParseObject for use in creating associations between - /// ParseObjects. Calling on this object will return + /// ParseObjects. Calling on this object will return /// false until has been called. /// No network request will be made. /// @@ -152,7 +150,7 @@ public static ParseObject CreateWithoutData(string className, string objectId) /// /// Creates a reference to an existing ParseObject for use in creating associations between - /// ParseObjects. Calling on this object will return + /// ParseObjects. Calling on this object will return /// false until has been called. /// No network request will be made. /// @@ -260,14 +258,14 @@ internal void HandleFailedSave(IDictionary operati { lock (mutex) { - var opNode = operationSetQueue.Find(operationsBeforeSave); - var nextOperations = opNode.Next.Value; + LinkedListNode> opNode = operationSetQueue.Find(operationsBeforeSave); + IDictionary 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) + foreach (KeyValuePair pair in operationsBeforeSave) { - var operation1 = pair.Value; + IParseFieldOperation operation1 = pair.Value; nextOperations.TryGetValue(pair.Key, out IParseFieldOperation operation2); if (operation2 != null) operation2 = operation2.MergeWithPrevious(operation1); @@ -284,7 +282,7 @@ internal virtual void HandleSave(IObjectState serverState) { lock (mutex) { - var operationsBeforeSave = operationSetQueue.First.Value; + IDictionary operationsBeforeSave = operationSetQueue.First.Value; operationSetQueue.RemoveFirst(); // Merge the data from the save and the data from the server into serverData. @@ -296,7 +294,7 @@ internal virtual void HandleSave(IObjectState serverState) internal virtual void MergeFromServer(IObjectState serverState) { // Make a new serverData with fetched values. - var newServerData = serverState.ToDictionary(t => t.Key, t => t.Value); + Dictionary newServerData = serverState.ToDictionary(t => t.Key, t => t.Value); lock (mutex) { @@ -316,13 +314,13 @@ internal virtual void MergeFromServer(IObjectState serverState) // the fetched objects into Pointers. IDictionary fetchedObject = CollectFetchedObjects(); - foreach (var pair in serverState) + foreach (KeyValuePair pair in serverState) { - var value = pair.Value; + object value = pair.Value; if (value is ParseObject) { // Resolve fetched object. - var parseObject = value as ParseObject; + ParseObject parseObject = value as ParseObject; if (fetchedObject.ContainsKey(parseObject.ObjectId)) value = fetchedObject[parseObject.ObjectId]; } @@ -346,7 +344,7 @@ internal void MergeFromObject(ParseObject other) if (operationSetQueue.Count != 1) throw new InvalidOperationException("Attempt to MergeFromObject during save."); operationSetQueue.Clear(); - foreach (var operationSet in other.operationSetQueue) + foreach (IDictionary operationSet in other.operationSetQueue) operationSetQueue.AddLast(operationSet.ToDictionary(entry => entry.Key, entry => entry.Value)); lock (mutex) @@ -373,7 +371,7 @@ private bool HasDirtyChildren /// internal static IEnumerable DeepTraversal(object root, bool traverseParseObjects = false, bool yieldRoot = false) { - var items = DeepTraversalInternal(root, traverseParseObjects, new HashSet(new IdentityEqualityComparer())); + IEnumerable items = DeepTraversalInternal(root, traverseParseObjects, new HashSet(new IdentityEqualityComparer())); if (yieldRoot) return new[] { root }.Concat(items); else @@ -383,15 +381,15 @@ internal static IEnumerable DeepTraversal(object root, bool traversePars private static IEnumerable DeepTraversalInternal(object root, bool traverseParseObjects, ICollection seen) { seen.Add(root); - var itemsToVisit = isCompiledByIL2CPP ? (System.Collections.IEnumerable) null : (IEnumerable) null; - var dict = Conversion.As>(root); + System.Collections.IEnumerable itemsToVisit = isCompiledByIL2CPP ? null : (IEnumerable) null; + IDictionary dict = Conversion.As>(root); if (dict != null) { itemsToVisit = dict.Values; } else { - var list = Conversion.As>(root); + IList list = Conversion.As>(root); if (list != null) { itemsToVisit = list; @@ -406,13 +404,13 @@ private static IEnumerable DeepTraversalInternal(object root, bool trave } if (itemsToVisit != null) { - foreach (var i in itemsToVisit) + foreach (object i in itemsToVisit) { if (!seen.Contains(i)) { yield return i; - var children = DeepTraversalInternal(i, traverseParseObjects, seen); - foreach (var child in children) + IEnumerable children = DeepTraversalInternal(i, traverseParseObjects, seen); + foreach (object child in children) { yield return child; } @@ -433,8 +431,8 @@ private static IEnumerable DeepTraversalInternal(object root, bool trave internal static IDictionary ToJSONObjectForSaving(IDictionary operations) { - var result = new Dictionary(); - foreach (var pair in operations) + Dictionary result = new Dictionary(); + foreach (KeyValuePair pair in operations) result[pair.Key] = PointerOrLocalIdEncoder.Instance.Encode(pair.Value); return result; } @@ -450,7 +448,7 @@ internal IDictionary StartSave() { lock (mutex) { - var currentOperations = CurrentOperations; + IDictionary currentOperations = CurrentOperations; operationSetQueue.AddLast(new Dictionary()); OnPropertyChanged("IsDirty"); return currentOperations; @@ -504,12 +502,12 @@ protected virtual Task SaveAsync(Task toAwait, CancellationToken cancellationTok private static Task DeepSaveAsync(object obj, string sessionToken, CancellationToken cancellationToken) { - var objects = new List(); + List objects = new List(); CollectDirtyChildren(obj, objects); - var uniqueObjects = new HashSet(objects, new IdentityEqualityComparer()); + HashSet uniqueObjects = new HashSet(objects, new IdentityEqualityComparer()); - var saveDirtyFileTasks = DeepTraversal(obj, true).OfType().Where(f => f.IsDirty).Select(f => f.SaveAsync(cancellationToken)).ToList(); + List saveDirtyFileTasks = DeepTraversal(obj, true).OfType().Where(f => f.IsDirty).Select(f => f.SaveAsync(cancellationToken)).ToList(); return Task.WhenAll(saveDirtyFileTasks).OnSuccess(_ => { @@ -518,8 +516,8 @@ private static Task DeepSaveAsync(object obj, string sessionToken, CancellationT { // 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(); + List current = (from item in remaining where item.CanBeSerialized select item).ToList(); + List nextBatch = (from item in remaining where !item.CanBeSerialized select item).ToList(); remaining = nextBatch; if (current.Count == 0) @@ -536,12 +534,12 @@ private static Task DeepSaveAsync(object obj, string sessionToken, CancellationT { return toAwait.OnSuccess(__ => { - var states = (from item in current - select item.state).ToList(); - var operationsList = (from item in current - select item.StartSave()).ToList(); + List states = (from item in current + select item.state).ToList(); + List> operationsList = (from item in current + select item.StartSave()).ToList(); - var saveTasks = ObjectController.SaveAllAsync(states, + IList> saveTasks = ObjectController.SaveAllAsync(states, operationsList, sessionToken, cancellationToken); @@ -557,7 +555,7 @@ private static Task DeepSaveAsync(object obj, string sessionToken, CancellationT } else { - var serverStates = t.Result; + IObjectState[] serverStates = t.Result; foreach (var pair in current.Zip(serverStates, (item, state) => new { item, state })) { pair.item.HandleSave(pair.state); @@ -584,10 +582,7 @@ private static Task DeepSaveAsync(object obj, string sessionToken, CancellationT /// The objects to save. /// The cancellation token. public static Task SaveAllAsync( - IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject - { - return DeepSaveAsync(objects.ToList(), ParseUser.CurrentSessionToken, cancellationToken); - } + IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject => DeepSaveAsync(objects.ToList(), ParseUser.CurrentSessionToken, cancellationToken); #endregion @@ -597,11 +592,8 @@ public static Task SaveAllAsync( /// Fetches this object with the data from the server. /// /// The cancellation token. - internal Task FetchAsyncInternal(CancellationToken cancellationToken) - { - return taskQueue.Enqueue(toAwait => FetchAsyncInternal(toAwait, cancellationToken), + internal Task FetchAsyncInternal(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => FetchAsyncInternal(toAwait, cancellationToken), cancellationToken); - } internal Task FetchIfNeededAsyncInternal( Task toAwait, CancellationToken cancellationToken) @@ -618,21 +610,15 @@ internal Task FetchIfNeededAsyncInternal( /// 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), + internal Task FetchIfNeededAsyncInternal(CancellationToken cancellationToken) => 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 : ParseObject - { - return FetchAllIfNeededAsync(objects, CancellationToken.None); - } + IEnumerable objects) where T : ParseObject => FetchAllIfNeededAsync(objects, CancellationToken.None); /// /// Fetches all of the objects that don't have data in the provided list. @@ -641,13 +627,10 @@ public static Task> FetchAllIfNeededAsync( /// The cancellation token. /// The list passed in for convenience. public static Task> FetchAllIfNeededAsync( - IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject - { - return ParseObject.EnqueueForAll(objects.Cast(), (Task toAwait) => - { - return FetchAllInternalAsync(objects, false, toAwait, cancellationToken); - }, cancellationToken); - } + IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject => ParseObject.EnqueueForAll(objects.Cast(), (Task toAwait) => + { + return FetchAllInternalAsync(objects, false, toAwait, cancellationToken); + }, cancellationToken); /// /// Fetches all of the objects in the provided list. @@ -655,10 +638,7 @@ public static Task> FetchAllIfNeededAsync( /// The objects to fetch. /// The list passed in for convenience. public static Task> FetchAllAsync( - IEnumerable objects) where T : ParseObject - { - return FetchAllAsync(objects, CancellationToken.None); - } + IEnumerable objects) where T : ParseObject => FetchAllAsync(objects, CancellationToken.None); /// /// Fetches all of the objects in the provided list. @@ -667,13 +647,10 @@ public static Task> FetchAllAsync( /// The cancellation token. /// The list passed in for convenience. public static Task> FetchAllAsync( - IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject - { - return ParseObject.EnqueueForAll(objects.Cast(), (Task toAwait) => - { - return FetchAllInternalAsync(objects, true, toAwait, cancellationToken); - }, cancellationToken); - } + IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject => ParseObject.EnqueueForAll(objects.Cast(), (Task toAwait) => + { + return FetchAllInternalAsync(objects, true, toAwait, cancellationToken); + }, cancellationToken); /// /// Fetches all of the objects in the list. @@ -684,60 +661,57 @@ public static Task> FetchAllAsync( /// The cancellation token. /// The list passed in for convenience. private static Task> FetchAllInternalAsync( - IEnumerable objects, bool force, Task toAwait, CancellationToken cancellationToken) where T : ParseObject - { - 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 ParseQuery(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(); - } + IEnumerable objects, bool force, Task toAwait, CancellationToken cancellationToken) where T : ParseObject => toAwait.OnSuccess(_ => + { + if (objects.Any(obj => { return obj.state.ObjectId == null; })) + { + throw new InvalidOperationException("You cannot fetch objects that haven't already been saved."); + } + + List 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. + Dictionary>> findsByClass = + (from obj in objectsToFetch + group obj.ObjectId by obj.ClassName into classGroup + where classGroup.Count() > 0 + select new + { + ClassName = classGroup.Key, + FindTask = new ParseQuery(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 @@ -761,29 +735,20 @@ internal Task DeleteAsync(Task toAwait, CancellationToken cancellationToken) /// /// Deletes this object on the server. /// - public Task DeleteAsync() - { - return DeleteAsync(CancellationToken.None); - } + public Task DeleteAsync() => DeleteAsync(CancellationToken.None); /// /// Deletes this object on the server. /// /// The cancellation token. - public Task DeleteAsync(CancellationToken cancellationToken) - { - return taskQueue.Enqueue(toAwait => DeleteAsync(toAwait, cancellationToken), + public Task DeleteAsync(CancellationToken cancellationToken) => 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 : ParseObject - { - return DeleteAllAsync(objects, CancellationToken.None); - } + public static Task DeleteAllAsync(IEnumerable objects) where T : ParseObject => DeleteAllAsync(objects, CancellationToken.None); /// /// Deletes each object in the provided list. @@ -793,15 +758,15 @@ public static Task DeleteAllAsync(IEnumerable objects) where T : ParseObje public static Task DeleteAllAsync( IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject { - var uniqueObjects = new HashSet(objects.OfType().ToList(), + HashSet uniqueObjects = new HashSet(objects.OfType().ToList(), new IdentityEqualityComparer()); return ParseObject.EnqueueForAll(uniqueObjects, toAwait => { - var states = uniqueObjects.Select(t => t.state).ToList(); + List states = uniqueObjects.Select(t => t.state).ToList(); return toAwait.OnSuccess(_ => { - var deleteTasks = ObjectController.DeleteAllAsync(states, + IList deleteTasks = ObjectController.DeleteAllAsync(states, ParseUser.CurrentSessionToken, cancellationToken); @@ -809,7 +774,7 @@ public static Task DeleteAllAsync( }).Unwrap().OnSuccess(t => { // Dirty all objects in memory. - foreach (var obj in uniqueObjects) + foreach (ParseObject obj in uniqueObjects) { obj.IsDirty = true; } @@ -826,7 +791,7 @@ private static void CollectDirtyChildren(object node, ICollection seen, ICollection seenNew) { - foreach (var obj in DeepTraversal(node).OfType()) + foreach (ParseObject obj in DeepTraversal(node).OfType()) { ICollection scopedSeenNew; // Check for cycles of new objects. Any such cycle means it will be impossible to save @@ -868,24 +833,18 @@ private static void CollectDirtyChildren(object node, /// 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, + 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 ParseObject. /// - private static bool CanBeSerializedAsValue(object value) - { - return DeepTraversal(value, yieldRoot: true) + private static bool CanBeSerializedAsValue(object value) => DeepTraversal(value, yieldRoot: true) .OfType() .All(o => o.ObjectId != null); - } private bool CanBeSerialized { @@ -909,14 +868,14 @@ 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(); + TaskCompletionSource 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 lockSet = new LockSet(objects.Select(o => o.taskQueue.Mutex)); lockSet.Enter(); try @@ -927,7 +886,7 @@ private static Task EnqueueForAll(IEnumerable objects, Task fullTask = taskStart(readyToStart.Task); // Add fullTask to each of the objects' queues. - var childTasks = new List(); + List childTasks = new List(); foreach (ParseObject obj in objects) { obj.taskQueue.Enqueue((Task task) => @@ -972,10 +931,10 @@ private void ApplyOperations(IDictionary pair in operations) { map.TryGetValue(pair.Key, out object oldValue); - var newValue = pair.Value.Apply(oldValue, pair.Key); + object newValue = pair.Value.Apply(oldValue, pair.Key); if (newValue != ParseDeleteOperation.DeleteToken) { map[pair.Key] = newValue; @@ -996,11 +955,11 @@ internal void RebuildEstimatedData() lock (mutex) { estimatedData.Clear(); - foreach (var item in state) + foreach (KeyValuePair item in state) { estimatedData.Add(item); } - foreach (var operations in operationSetQueue) + foreach (IDictionary operations in operationSetQueue) { ApplyOperations(operations, estimatedData); } @@ -1032,7 +991,7 @@ internal void PerformOperation(string key, IParseFieldOperation operation) bool wasDirty = CurrentOperations.Count > 0; CurrentOperations.TryGetValue(key, out IParseFieldOperation oldOperation); - var newOperation = operation.MergeWithPrevious(oldOperation); + IParseFieldOperation newOperation = operation.MergeWithPrevious(oldOperation); CurrentOperations[key] = newOperation; if (!wasDirty) { @@ -1061,7 +1020,7 @@ internal virtual void OnSettingValue(ref string key, ref object value) /// /// The key for the object. Keys must be alphanumeric plus underscore /// and start with a letter. - /// The property is + /// The property is /// retrieved and is not found. /// The value for the key. virtual public object this[string key] @@ -1072,7 +1031,7 @@ virtual public object this[string key] { CheckGetAccess(key); - var value = estimatedData[key]; + object value = estimatedData[key]; // A relation may be deserialized without a parent or key. Either way, // make sure it's consistent. @@ -1117,7 +1076,7 @@ internal void Set(string key, object value) internal void SetIfDifferent(string key, T value) { - bool hasCurrent = TryGetValue(key, out T current); + bool hasCurrent = TryGetValue(key, out T current); if (value == null) { if (hasCurrent) @@ -1138,10 +1097,7 @@ internal void SetIfDifferent(string key, T value) /// Atomically increments the given key by 1. /// /// The key to increment. - public void Increment(string key) - { - Increment(key, 1); - } + public void Increment(string key) => Increment(key, 1); /// /// Atomically increments the given key by the given number. @@ -1180,10 +1136,7 @@ public void Increment(string key, double amount) /// /// The key. /// The object to add. - public void AddToList(string key, object value) - { - AddRangeToList(key, new[] { value }); - } + 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. @@ -1207,10 +1160,7 @@ public void AddRangeToList(string key, IEnumerable values) /// /// The key. /// The object to add. - public void AddUniqueToList(string key, object value) - { - AddRangeUniqueToList(key, new object[] { value }); - } + 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, @@ -1263,13 +1213,10 @@ public bool ContainsKey(string key) /// ParseObject and its descendents, Parse types such as ParseRelation and ParseGeopoint, /// primitive types,IList<T>, IDictionary<string, T>, and strings. /// The key of the element to get. - /// The property is + /// The property is /// retrieved and is not found. /// - public T Get(string key) - { - return Conversion.To(this[key]); - } + public T Get(string key) => Conversion.To(this[key]); /// /// Access or create a Relation value for a key. @@ -1301,7 +1248,7 @@ public bool TryGetValue(string key, out T result) { try { - var temp = Conversion.To(this[key]); + T temp = Conversion.To(this[key]); result = temp; return true; } @@ -1359,10 +1306,7 @@ private void CheckKeyIsMutable(string key) } } - protected virtual bool IsKeyMutable(string key) - { - return true; - } + protected virtual bool IsKeyMutable(string key) => true; /// /// A helper function for checking whether two ParseObjects point to @@ -1410,8 +1354,8 @@ public ICollection Keys [ParseFieldName("ACL")] public ParseACL ACL { - get { return GetProperty(null, "ACL"); } - set { SetProperty(value, "ACL"); } + get => GetProperty(null, "ACL"); + set => SetProperty(value, "ACL"); } /// @@ -1426,10 +1370,7 @@ public ParseACL ACL #endif bool IsNew { - get - { - return state.IsNew; - } + get => state.IsNew; #if !UNITY internal #endif @@ -1450,13 +1391,7 @@ bool IsNew /// changed locally. /// [ParseFieldName("updatedAt")] - public DateTime? UpdatedAt - { - get - { - return state.UpdatedAt; - } - } + public DateTime? UpdatedAt => state.UpdatedAt; /// /// Gets the first time this object was saved as the server sees it, so that if you create a @@ -1465,13 +1400,7 @@ public DateTime? UpdatedAt /// the time the object was created locally. /// [ParseFieldName("createdAt")] - public DateTime? CreatedAt - { - get - { - return state.CreatedAt; - } - } + public DateTime? CreatedAt => state.CreatedAt; /// /// Indicates whether this ParseObject has unsaved changes. @@ -1523,10 +1452,7 @@ private bool CheckIsDirty(bool considerChildren) [ParseFieldName("objectId")] public string ObjectId { - get - { - return state.ObjectId; - } + get => state.ObjectId; set { IsDirty = true; @@ -1552,13 +1478,7 @@ private void SetObjectIdInternal(string objectId) /// /// Gets the class name for the ParseObject. /// - public string ClassName - { - get - { - return state.ClassName; - } - } + public string ClassName => state.ClassName; /// /// Adds a value for the given key, throwing an Exception if the key @@ -1582,7 +1502,7 @@ public void Add(string key, object value) { lock (mutex) { - if (this.ContainsKey(key)) + if (ContainsKey(key)) { throw new ArgumentException("Key already exists", key); } @@ -1635,7 +1555,7 @@ public static ParseQuery GetQuery(string className) /// protected void OnFieldsChanged(IEnumerable fieldNames) { - var mappings = SubclassingController.GetPropertyMappings(ClassName); + IDictionary mappings = SubclassingController.GetPropertyMappings(ClassName); IEnumerable properties; if (fieldNames != null && mappings != null) @@ -1653,7 +1573,7 @@ join f in fieldNames on m.Value equals f properties = Enumerable.Empty(); } - foreach (var property in properties) + foreach (string property in properties) { OnPropertyChanged(property); } @@ -1672,10 +1592,7 @@ protected void OnPropertyChanged( #else string propertyName #endif -) - { - propertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } +) => propertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); private SynchronizedEventHandler propertyChanged = new SynchronizedEventHandler(); diff --git a/Parse/Public/ParsePush.cs b/Parse/ParsePush.cs similarity index 73% rename from Parse/Public/ParsePush.cs rename to Parse/ParsePush.cs index 7b97da1c..ab410621 100644 --- a/Parse/Public/ParsePush.cs +++ b/Parse/ParsePush.cs @@ -4,9 +4,8 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Push.Internal; -using Parse.Core.Internal; using Parse.Common.Internal; +using Parse.Push.Internal; namespace Parse { @@ -40,18 +39,15 @@ public ParsePush() /// public ParseQuery Query { - get { return state.Query; } - set - { - MutateState(s => - { - if (s.Channels != null && value != null && value.GetConstraint("channels") != null) - { - throw new InvalidOperationException("A push may not have both Channels and a Query with a channels constraint"); - } - s.Query = value; - }); - } + get => state.Query; + set => MutateState(s => + { + if (s.Channels != null && value != null && value.GetConstraint("channels") != null) + { + throw new InvalidOperationException("A push may not have both Channels and a Query with a channels constraint"); + } + s.Query = value; + }); } /// @@ -67,18 +63,15 @@ public ParseQuery Query /// public IEnumerable Channels { - get { return state.Channels; } - set - { - MutateState(s => - { - if (value != null && s.Query != null && s.Query.GetConstraint("channels") != null) - { - throw new InvalidOperationException("A push may not have both Channels and a Query with a channels constraint"); - } - s.Channels = value; - }); - } + get => state.Channels; + set => MutateState(s => + { + if (value != null && s.Query != null && s.Query.GetConstraint("channels") != null) + { + throw new InvalidOperationException("A push may not have both Channels and a Query with a channels constraint"); + } + s.Channels = value; + }); } /// @@ -86,18 +79,15 @@ public IEnumerable Channels /// public DateTime? Expiration { - get { return state.Expiration; } - set - { - MutateState(s => - { - if (s.ExpirationInterval != null) - { - throw new InvalidOperationException("Cannot set Expiration after setting ExpirationInterval"); - } - s.Expiration = value; - }); - } + get => state.Expiration; + set => MutateState(s => + { + if (s.ExpirationInterval != null) + { + throw new InvalidOperationException("Cannot set Expiration after setting ExpirationInterval"); + } + s.Expiration = value; + }); } /// @@ -105,19 +95,16 @@ public DateTime? Expiration /// public DateTime? PushTime { - get { return state.PushTime; } - set - { - MutateState(s => - { - DateTime now = DateTime.Now; - if (value < now || value > now.AddDays(14)) - { - throw new InvalidOperationException("Cannot set PushTime in the past or more than two weeks later than now"); - } - s.PushTime = value; - }); - } + get => state.PushTime; + set => MutateState(s => + { + DateTime now = DateTime.Now; + if (value < now || value > now.AddDays(14)) + { + throw new InvalidOperationException("Cannot set PushTime in the past or more than two weeks later than now"); + } + s.PushTime = value; + }); } /// @@ -125,18 +112,15 @@ public DateTime? PushTime /// public TimeSpan? ExpirationInterval { - get { return state.ExpirationInterval; } - set - { - MutateState(s => - { - if (s.Expiration != null) - { - throw new InvalidOperationException("Cannot set ExpirationInterval after setting Expiration"); - } - s.ExpirationInterval = value; - }); - } + get => state.ExpirationInterval; + set => MutateState(s => + { + if (s.Expiration != null) + { + throw new InvalidOperationException("Cannot set ExpirationInterval after setting Expiration"); + } + s.ExpirationInterval = value; + }); } /// @@ -152,18 +136,15 @@ public TimeSpan? ExpirationInterval /// public IDictionary Data { - get { return state.Data; } - set - { - MutateState(s => - { - if (s.Alert != null && value != null) - { - throw new InvalidOperationException("A push may not have both an Alert and Data"); - } - s.Data = value; - }); - } + get => state.Data; + set => MutateState(s => + { + if (s.Alert != null && value != null) + { + throw new InvalidOperationException("A push may not have both an Alert and Data"); + } + s.Data = value; + }); } /// @@ -177,26 +158,20 @@ public IDictionary Data /// public string Alert { - get { return state.Alert; } - set - { - MutateState(s => - { - if (s.Data != null && value != null) - { - throw new InvalidOperationException("A push may not have both an Alert and Data"); - } - s.Alert = value; - }); - } + get => state.Alert; + set => MutateState(s => + { + if (s.Data != null && value != null) + { + throw new InvalidOperationException("A push may not have both an Alert and Data"); + } + s.Alert = value; + }); } #endregion - internal IDictionary Encode() - { - return ParsePushEncoder.Instance.Encode(state); - } + internal IDictionary Encode() => ParsePushEncoder.Instance.Encode(state); private void MutateState(Action func) { @@ -206,21 +181,9 @@ private void MutateState(Action func) } } - private static IParsePushController PushController - { - get - { - return ParsePushPlugins.Instance.PushController; - } - } + private static IParsePushController PushController => ParsePushPlugins.Instance.PushController; - private static IParsePushChannelsController PushChannelsController - { - get - { - return ParsePushPlugins.Instance.PushChannelsController; - } - } + private static IParsePushChannelsController PushChannelsController => ParsePushPlugins.Instance.PushChannelsController; #region Sending Push @@ -231,10 +194,7 @@ private static IParsePushChannelsController PushChannelsController /// console on http://parse.com /// /// A Task for continuation. - public Task SendAsync() - { - return SendAsync(CancellationToken.None); - } + public Task SendAsync() => SendAsync(CancellationToken.None); /// /// Request a push to be sent. When this task completes, Parse has successfully acknowledged a request @@ -243,10 +203,7 @@ public Task SendAsync() /// console on http://parse.com /// /// CancellationToken to cancel the current operation. - public Task SendAsync(CancellationToken cancellationToken) - { - return PushController.SendPushNotificationAsync(state, cancellationToken); - } + public Task SendAsync(CancellationToken cancellationToken) => PushController.SendPushNotificationAsync(state, cancellationToken); /// /// Pushes a simple message to every device. This is shorthand for: @@ -260,8 +217,10 @@ public Task SendAsync(CancellationToken cancellationToken) /// The alert message to send. public static Task SendAlertAsync(string alert) { - var push = new ParsePush(); - push.Alert = alert; + ParsePush push = new ParsePush + { + Alert = alert + }; return push.SendAsync(); } @@ -279,9 +238,11 @@ public static Task SendAlertAsync(string alert) /// An Installation must be subscribed to channel to receive this Push Notification. public static Task SendAlertAsync(string alert, string channel) { - var push = new ParsePush(); - push.Channels = new List { channel }; - push.Alert = alert; + ParsePush push = new ParsePush + { + Channels = new List { channel }, + Alert = alert + }; return push.SendAsync(); } @@ -299,9 +260,11 @@ public static Task SendAlertAsync(string alert, string channel) /// An Installation must be subscribed to any of channels to receive this Push Notification. public static Task SendAlertAsync(string alert, IEnumerable channels) { - var push = new ParsePush(); - push.Channels = channels; - push.Alert = alert; + ParsePush push = new ParsePush + { + Channels = channels, + Alert = alert + }; return push.SendAsync(); } @@ -319,9 +282,11 @@ public static Task SendAlertAsync(string alert, IEnumerable channels) /// A query filtering the devices which should receive this Push Notification. public static Task SendAlertAsync(string alert, ParseQuery query) { - var push = new ParsePush(); - push.Query = query; - push.Alert = alert; + ParsePush push = new ParsePush + { + Query = query, + Alert = alert + }; return push.SendAsync(); } @@ -337,8 +302,10 @@ public static Task SendAlertAsync(string alert, ParseQuery qu /// A push payload. See the ParsePush.Data property for more information. public static Task SendDataAsync(IDictionary data) { - var push = new ParsePush(); - push.Data = data; + ParsePush push = new ParsePush + { + Data = data + }; return push.SendAsync(); } @@ -356,9 +323,11 @@ public static Task SendDataAsync(IDictionary data) /// An Installation must be subscribed to channel to receive this Push Notification. public static Task SendDataAsync(IDictionary data, string channel) { - var push = new ParsePush(); - push.Channels = new List { channel }; - push.Data = data; + ParsePush push = new ParsePush + { + Channels = new List { channel }, + Data = data + }; return push.SendAsync(); } @@ -376,9 +345,11 @@ public static Task SendDataAsync(IDictionary data, string channe /// An Installation must be subscribed to any of channels to receive this Push Notification. public static Task SendDataAsync(IDictionary data, IEnumerable channels) { - var push = new ParsePush(); - push.Channels = channels; - push.Data = data; + ParsePush push = new ParsePush + { + Channels = channels, + Data = data + }; return push.SendAsync(); } @@ -396,9 +367,11 @@ public static Task SendDataAsync(IDictionary data, IEnumerableA query filtering the devices which should receive this Push Notification. public static Task SendDataAsync(IDictionary data, ParseQuery query) { - var push = new ParsePush(); - push.Query = query; - push.Data = data; + ParsePush push = new ParsePush + { + Query = query, + Data = data + }; return push.SendAsync(); } @@ -437,10 +410,7 @@ public static event EventHandler ParsePushNotifi /// /// /// The channel to which this installation should subscribe. - public static Task SubscribeAsync(string channel) - { - return SubscribeAsync(new List { channel }, CancellationToken.None); - } + public static Task SubscribeAsync(string channel) => SubscribeAsync(new List { channel }, CancellationToken.None); /// /// Subscribe the current installation to this channel. This is shorthand for: @@ -453,10 +423,7 @@ public static Task SubscribeAsync(string channel) /// /// The channel to which this installation should subscribe. /// CancellationToken to cancel the current operation. - public static Task SubscribeAsync(string channel, CancellationToken cancellationToken) - { - return SubscribeAsync(new List { channel }, cancellationToken); - } + public static Task SubscribeAsync(string channel, CancellationToken cancellationToken) => SubscribeAsync(new List { channel }, cancellationToken); /// /// Subscribe the current installation to these channels. This is shorthand for: @@ -468,10 +435,7 @@ public static Task SubscribeAsync(string channel, CancellationToken cancellation /// /// /// The channels to which this installation should subscribe. - public static Task SubscribeAsync(IEnumerable channels) - { - return SubscribeAsync(channels, CancellationToken.None); - } + public static Task SubscribeAsync(IEnumerable channels) => SubscribeAsync(channels, CancellationToken.None); /// /// Subscribe the current installation to these channels. This is shorthand for: @@ -484,10 +448,7 @@ public static Task SubscribeAsync(IEnumerable channels) /// /// The channels to which this installation should subscribe. /// CancellationToken to cancel the current operation. - public static Task SubscribeAsync(IEnumerable channels, CancellationToken cancellationToken) - { - return PushChannelsController.SubscribeAsync(channels, cancellationToken); - } + public static Task SubscribeAsync(IEnumerable channels, CancellationToken cancellationToken) => PushChannelsController.SubscribeAsync(channels, cancellationToken); /// /// Unsubscribe the current installation from this channel. This is shorthand for: @@ -499,10 +460,7 @@ public static Task SubscribeAsync(IEnumerable channels, CancellationToke /// /// /// The channel from which this installation should unsubscribe. - public static Task UnsubscribeAsync(string channel) - { - return UnsubscribeAsync(new List { channel }, CancellationToken.None); - } + public static Task UnsubscribeAsync(string channel) => UnsubscribeAsync(new List { channel }, CancellationToken.None); /// /// Unsubscribe the current installation from this channel. This is shorthand for: @@ -515,10 +473,7 @@ public static Task UnsubscribeAsync(string channel) /// /// The channel from which this installation should unsubscribe. /// CancellationToken to cancel the current operation. - public static Task UnsubscribeAsync(string channel, CancellationToken cancellationToken) - { - return UnsubscribeAsync(new List { channel }, cancellationToken); - } + public static Task UnsubscribeAsync(string channel, CancellationToken cancellationToken) => UnsubscribeAsync(new List { channel }, cancellationToken); /// /// Unsubscribe the current installation from these channels. This is shorthand for: @@ -530,10 +485,7 @@ public static Task UnsubscribeAsync(string channel, CancellationToken cancellati /// /// /// The channels from which this installation should unsubscribe. - public static Task UnsubscribeAsync(IEnumerable channels) - { - return UnsubscribeAsync(channels, CancellationToken.None); - } + public static Task UnsubscribeAsync(IEnumerable channels) => UnsubscribeAsync(channels, CancellationToken.None); /// /// Unsubscribe the current installation from these channels. This is shorthand for: @@ -546,10 +498,7 @@ public static Task UnsubscribeAsync(IEnumerable channels) /// /// The channels from which this installation should unsubscribe. /// CancellationToken to cancel the current operation. - public static Task UnsubscribeAsync(IEnumerable channels, CancellationToken cancellationToken) - { - return PushChannelsController.UnsubscribeAsync(channels, cancellationToken); - } + public static Task UnsubscribeAsync(IEnumerable channels, CancellationToken cancellationToken) => PushChannelsController.UnsubscribeAsync(channels, cancellationToken); #endregion } diff --git a/Parse/Public/ParseQuery.cs b/Parse/ParseQuery.cs similarity index 96% rename from Parse/Public/ParseQuery.cs rename to Parse/ParseQuery.cs index 7ea296a1..760d595e 100644 --- a/Parse/Public/ParseQuery.cs +++ b/Parse/ParseQuery.cs @@ -8,9 +8,8 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using Parse.Core.Internal; using Parse.Common.Internal; -using System.Net.WebSockets; +using Parse.Core.Internal; namespace Parse { @@ -29,7 +28,7 @@ namespace Parse /// /// /// A ParseQuery can also be used to retrieve a single object whose id is known, - /// through the method. For example, this sample code + /// through the method. For example, this sample code /// fetches an object of class "MyClass" and id myId. /// /// @@ -89,7 +88,7 @@ private ParseQuery(ParseQuery source, IDictionary where = nul newOrderBy.AddRange(thenBy); orderBy = new ReadOnlyCollection(newOrderBy); } - + // Remove duplicates. if (orderBy != null) orderBy = new ReadOnlyCollection(new HashSet(orderBy).ToList()); @@ -128,17 +127,17 @@ private IDictionary MergeWhereClauses(IDictionary(this.where); - foreach (var pair in where) + Dictionary newWhere = new Dictionary(this.where); + foreach (KeyValuePair pair in where) { - var condition = pair.Value as IDictionary; + IDictionary condition = pair.Value as IDictionary; if (newWhere.ContainsKey(pair.Key)) { - var oldCondition = newWhere[pair.Key] as IDictionary; + IDictionary 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) + Dictionary newCondition = new Dictionary(oldCondition); + foreach (KeyValuePair conditionPair in condition) { if (newCondition.ContainsKey(conditionPair.Key)) throw new ArgumentException("More than one condition for the given key provided."); @@ -172,16 +171,16 @@ public ParseQuery() : this(SubclassingController.GetClassName(typeof(T))) { } public static ParseQuery Or(IEnumerable> queries) { string className = null; - var orValue = new List>(); + List> 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) + IEnumerable nonGenericQueries = queries; + foreach (object obj in nonGenericQueries) { - var q = obj as ParseQuery; + ParseQuery q = obj as ParseQuery; 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(); + IDictionary parameters = q.BuildParameters(); if (parameters.Count == 0) continue; if (!parameters.TryGetValue("where", out object where) || parameters.Count > 1) @@ -609,15 +608,15 @@ internal IDictionary BuildParameters(bool includeClassName = fal if (where != null) result["where"] = PointerOrLocalIdEncoder.Instance.Encode(where); if (orderBy != null) - result["order"] = string.Join(",", orderBy.ToArray()); + 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()); + result["include"] = String.Join(",", includes.ToArray()); if (selectedKeys != null) - result["keys"] = string.Join(",", selectedKeys.ToArray()); + result["keys"] = String.Join(",", selectedKeys.ToArray()); if (includeClassName) result["className"] = className; if (redirectClassNameForKey != null) @@ -639,9 +638,9 @@ private string GetRegexOptions(Regex regex, string modifiers) private IDictionary EncodeRegex(Regex regex, string modifiers) { - var options = GetRegexOptions(regex, modifiers); - var dict = new Dictionary { ["$regex"] = regex.ToString() }; - if (!string.IsNullOrEmpty(options)) + string options = GetRegexOptions(regex, modifiers); + Dictionary dict = new Dictionary { ["$regex"] = regex.ToString() }; + if (!String.IsNullOrEmpty(options)) dict["$options"] = options; return dict; } @@ -664,10 +663,8 @@ private void EnsureNotInstallationQuery() /// Serves as the default hash function. /// /// A hash code for the current object. - public override int GetHashCode() - { + public override int GetHashCode() => // TODO (richardross): Implement this. - return 0; - } + 0; } } diff --git a/Parse/Public/ParseRelation.cs b/Parse/ParseRelation.cs similarity index 75% rename from Parse/Public/ParseRelation.cs rename to Parse/ParseRelation.cs index 0f8b6da8..b4516b83 100644 --- a/Parse/Public/ParseRelation.cs +++ b/Parse/ParseRelation.cs @@ -1,15 +1,13 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Core.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; using Parse.Common.Internal; +using Parse.Core.Internal; namespace Parse { @@ -23,24 +21,12 @@ public abstract class ParseRelationBase : IJsonConvertible private string key; private string targetClassName; - internal ParseRelationBase(ParseObject parent, string key) - { - EnsureParentAndKey(parent, key); - } + internal ParseRelationBase(ParseObject parent, string key) => EnsureParentAndKey(parent, key); internal ParseRelationBase(ParseObject parent, string key, string targetClassName) - : this(parent, key) - { - this.targetClassName = targetClassName; - } + : this(parent, key) => this.targetClassName = targetClassName; - internal static IObjectSubclassingController SubclassingController - { - get - { - return ParseCorePlugins.Instance.SubclassingController; - } - } + internal static IObjectSubclassingController SubclassingController => ParseCorePlugins.Instance.SubclassingController; internal void EnsureParentAndKey(ParseObject parent, string key) { @@ -52,25 +38,22 @@ internal void EnsureParentAndKey(ParseObject parent, string key) internal void Add(ParseObject obj) { - var change = new ParseRelationOperation(new[] { obj }, null); + ParseRelationOperation change = new ParseRelationOperation(new[] { obj }, null); parent.PerformOperation(key, change); targetClassName = change.TargetClassName; } internal void Remove(ParseObject obj) { - var change = new ParseRelationOperation(null, new[] { obj }); + ParseRelationOperation change = new ParseRelationOperation(null, new[] { obj }); parent.PerformOperation(key, change); targetClassName = change.TargetClassName; } - IDictionary IJsonConvertible.ToJSON() - { - return new Dictionary { + IDictionary IJsonConvertible.ToJSON() => new Dictionary { {"__type", "Relation"}, {"className", targetClassName} }; - } internal ParseQuery GetQuery() where T : ParseObject { @@ -87,14 +70,8 @@ internal ParseQuery GetQuery() where T : ParseObject internal string TargetClassName { - get - { - return targetClassName; - } - set - { - targetClassName = value; - } + get => targetClassName; + set => targetClassName = value; } /// @@ -104,11 +81,11 @@ internal static ParseRelationBase CreateRelation(ParseObject parent, string key, string targetClassName) { - var targetType = SubclassingController.GetType(targetClassName) ?? typeof(ParseObject); + Type targetType = SubclassingController.GetType(targetClassName) ?? typeof(ParseObject); Expression>> createRelationExpr = () => CreateRelation(parent, key, targetClassName); - var createRelationMethod = + MethodInfo createRelationMethod = ((MethodCallExpression) createRelationExpr.Body) .Method .GetGenericMethodDefinition() @@ -117,10 +94,7 @@ internal static ParseRelationBase CreateRelation(ParseObject parent, } private static ParseRelation CreateRelation(ParseObject parent, string key, string targetClassName) - where T : ParseObject - { - return new ParseRelation(parent, key, targetClassName); - } + where T : ParseObject => new ParseRelation(parent, key, targetClassName); } /// @@ -140,29 +114,17 @@ internal ParseRelation(ParseObject parent, string key, string 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); - } + 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); - } + public void Remove(T obj) => base.Remove(obj); /// /// Gets a query that can be used to query the objects in this relation. /// - public ParseQuery Query - { - get - { - return base.GetQuery(); - } - } + public ParseQuery Query => base.GetQuery(); } } diff --git a/Parse/Public/ParseRole.cs b/Parse/ParseRole.cs similarity index 84% rename from Parse/Public/ParseRole.cs rename to Parse/ParseRole.cs index 28d2c79a..ea745f59 100644 --- a/Parse/Public/ParseRole.cs +++ b/Parse/ParseRole.cs @@ -1,12 +1,7 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Core.Internal; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace Parse { @@ -48,8 +43,8 @@ public ParseRole(string name, ParseACL acl) [ParseFieldName("name")] public string Name { - get { return GetProperty("Name"); } - set { SetProperty(value, "Name"); } + get => GetProperty("Name"); + set => SetProperty(value, "Name"); } /// @@ -59,10 +54,7 @@ public string Name /// add or remove child users from the role through this relation. /// [ParseFieldName("users")] - public ParseRelation Users - { - get { return GetRelationProperty("Users"); } - } + public ParseRelation Users => GetRelationProperty("Users"); /// /// Gets the for the s that are @@ -71,10 +63,7 @@ public ParseRelation Users /// add or remove child roles from the role through this relation. /// [ParseFieldName("roles")] - public ParseRelation Roles - { - get { return GetRelationProperty("Roles"); } - } + public ParseRelation Roles => GetRelationProperty("Roles"); internal override void OnSettingValue(ref string key, ref object value) { @@ -102,12 +91,6 @@ internal override void OnSettingValue(ref string key, ref object value) /// /// Gets a over the Role collection. /// - public static ParseQuery Query - { - get - { - return new ParseQuery(); - } - } + public static ParseQuery Query => new ParseQuery(); } } diff --git a/Parse/ParseSession.cs b/Parse/ParseSession.cs new file mode 100644 index 00000000..fd01a509 --- /dev/null +++ b/Parse/ParseSession.cs @@ -0,0 +1,89 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Parse.Common.Internal; +using Parse.Core.Internal; + +namespace Parse +{ + /// + /// Represents a session of a user for a Parse application. + /// + [ParseClassName("_Session")] + public class ParseSession : ParseObject + { + private static readonly HashSet readOnlyKeys = new HashSet { + "sessionToken", "createdWith", "restricted", "user", "expiresAt", "installationId" + }; + + protected override bool IsKeyMutable(string key) => !readOnlyKeys.Contains(key); + + /// + /// Gets the session token for a user, if they are logged in. + /// + [ParseFieldName("sessionToken")] + public string SessionToken => GetProperty(null, "SessionToken"); + + /// + /// Constructs a for ParseSession. + /// + public static ParseQuery Query => new ParseQuery(); + + internal static IParseSessionController SessionController => ParseCorePlugins.Instance.SessionController; + + /// + /// Gets the current object related to the current user. + /// + public static Task GetCurrentSessionAsync() => GetCurrentSessionAsync(CancellationToken.None); + + /// + /// Gets the current object related to the current user. + /// + /// The cancellation token + public static Task GetCurrentSessionAsync(CancellationToken cancellationToken) => ParseUser.GetCurrentUserAsync().OnSuccess(t1 => + { + ParseUser user = t1.Result; + if (user == null) + { + return Task.FromResult((ParseSession) null); + } + + string sessionToken = user.SessionToken; + if (sessionToken == null) + { + return Task.FromResult((ParseSession) null); + } + + return SessionController.GetSessionAsync(sessionToken, cancellationToken).OnSuccess(t => + { + ParseSession session = ParseObject.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 => + { + ParseSession session = ParseObject.FromState(t.Result, "_Session"); + return session.SessionToken; + }); + } + } +} diff --git a/Parse/Public/ParseUser.cs b/Parse/ParseUser.cs similarity index 92% rename from Parse/Public/ParseUser.cs rename to Parse/ParseUser.cs index 78c8318f..157550f6 100644 --- a/Parse/Public/ParseUser.cs +++ b/Parse/ParseUser.cs @@ -1,14 +1,11 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Core.Internal; using System; using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; using System.Threading; using System.Threading.Tasks; using Parse.Common.Internal; +using Parse.Core.Internal; namespace Parse { @@ -45,7 +42,7 @@ public bool IsAuthenticated /// Removes a key from the object's data if it exists. /// /// The key to remove. - /// Cannot remove the username key. + /// Cannot remove the username key. public override void Remove(string key) { if (key == "username") @@ -268,21 +265,18 @@ protected override Task SaveAsync(Task toAwait, CancellationToken cancellationTo /// 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(); + public static Task LogOutAsync(CancellationToken cancellationToken) => GetCurrentUserAsync().OnSuccess(t => + { + LogOutWithProviders(); - ParseUser user = t.Result; - if (user == null) - { - return Task.FromResult(0); - } + ParseUser user = t.Result; + if (user == null) + { + return Task.FromResult(0); + } - return user.taskQueue.Enqueue(toAwait => user.LogOutAsync(toAwait, cancellationToken), cancellationToken); - }).Unwrap(); - } + return user.taskQueue.Enqueue(toAwait => user.LogOutAsync(toAwait, cancellationToken), cancellationToken); + }).Unwrap(); internal Task LogOutAsync(Task toAwait, CancellationToken cancellationToken) { @@ -297,13 +291,13 @@ internal Task LogOutAsync(Task toAwait, CancellationToken cancellationToken) { mutableClone.ServerData.Remove("sessionToken"); }); - var revokeSessionTask = ParseSession.RevokeAsync(oldSessionToken, cancellationToken); + Task revokeSessionTask = ParseSession.RevokeAsync(oldSessionToken, cancellationToken); return Task.WhenAll(revokeSessionTask, CurrentUserController.LogOutAsync(cancellationToken)); } private static void LogOutWithProviders() { - foreach (var provider in authProviders.Values) + foreach (IParseAuthenticationProvider provider in authProviders.Values) { provider.Deauthenticate(); } @@ -317,7 +311,7 @@ public static ParseUser CurrentUser { get { - var userTask = GetCurrentUserAsync(); + Task userTask = GetCurrentUserAsync(); // TODO (hallucinogen): this will without a doubt fail in Unity. How should we fix it? userTask.Wait(); return userTask.Result; @@ -358,10 +352,7 @@ public static ParseUser CurrentUser /// migrate the sessionToken on disk to revocable session. /// /// The Task that upgrades the session. - public static Task EnableRevocableSessionAsync() - { - return EnableRevocableSessionAsync(CancellationToken.None); - } + public static Task EnableRevocableSessionAsync() => EnableRevocableSessionAsync(CancellationToken.None); /// /// Tells server to use revocable session on LogIn and SignUp, even when App's Settings @@ -378,7 +369,7 @@ public static Task EnableRevocableSessionAsync(CancellationToken cancellationTok return GetCurrentUserAsync(cancellationToken).OnSuccess(t => { - var user = t.Result; + ParseUser user = t.Result; return user.UpgradeToRevocableSessionAsync(cancellationToken); }); } @@ -402,16 +393,10 @@ internal static bool IsRevocableSessionEnabled } } - internal Task UpgradeToRevocableSessionAsync() - { - return UpgradeToRevocableSessionAsync(CancellationToken.None); - } + internal Task UpgradeToRevocableSessionAsync() => UpgradeToRevocableSessionAsync(CancellationToken.None); - internal Task UpgradeToRevocableSessionAsync(CancellationToken cancellationToken) - { - return taskQueue.Enqueue(toAwait => UpgradeToRevocableSessionAsync(toAwait, cancellationToken), + internal Task UpgradeToRevocableSessionAsync(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => UpgradeToRevocableSessionAsync(toAwait, cancellationToken), cancellationToken); - } internal Task UpgradeToRevocableSessionAsync(Task toAwait, CancellationToken cancellationToken) { @@ -496,7 +481,7 @@ private void SynchronizeAllAuthData() return; } - foreach (var pair in authData) + foreach (KeyValuePair> pair in authData) { SynchronizeAuthData(GetProvider(pair.Key)); } diff --git a/Parse/Public/ParseAnalytics.cs b/Parse/Platform/Analytics/ParseAnalytics.cs similarity index 86% rename from Parse/Public/ParseAnalytics.cs rename to Parse/Platform/Analytics/ParseAnalytics.cs index 9c08cf39..f1218890 100644 --- a/Parse/Public/ParseAnalytics.cs +++ b/Parse/Platform/Analytics/ParseAnalytics.cs @@ -2,10 +2,10 @@ using System; using System.Collections.Generic; -using System.Threading.Tasks; using System.Threading; -using Parse.Common.Internal; +using System.Threading.Tasks; using Parse.Analytics.Internal; +using Parse.Common.Internal; using Parse.Core.Internal; namespace Parse @@ -18,30 +18,15 @@ namespace Parse /// public partial class ParseAnalytics { - internal static IParseAnalyticsController AnalyticsController - { - get - { - return ParseAnalyticsPlugins.Instance.AnalyticsController; - } - } + internal static IParseAnalyticsController AnalyticsController => ParseAnalyticsPlugins.Instance.AnalyticsController; - internal static IParseCurrentUserController CurrentUserController - { - get - { - return ParseAnalyticsPlugins.Instance.CorePlugins.CurrentUserController; - } - } + internal static IParseCurrentUserController CurrentUserController => ParseAnalyticsPlugins.Instance.CorePlugins.CurrentUserController; /// /// Tracks this application being launched. /// /// An Async Task that can be waited on or ignored. - public static Task TrackAppOpenedAsync() - { - return ParseAnalytics.TrackAppOpenedWithPushHashAsync(); - } + public static Task TrackAppOpenedAsync() => ParseAnalytics.TrackAppOpenedWithPushHashAsync(); /// /// Tracks the occurrence of a custom event with additional dimensions. @@ -67,10 +52,7 @@ public static Task TrackAppOpenedAsync() /// The name of the custom event to report to ParseClient /// as having happened. /// An Async Task that can be waited on or ignored. - public static Task TrackEventAsync(string name) - { - return TrackEventAsync(name, null); - } + public static Task TrackEventAsync(string name) => TrackEventAsync(name, null); /// /// Tracks the occurrence of a custom event with additional dimensions. @@ -122,15 +104,12 @@ public static Task TrackEventAsync(string name, IDictionary dime /// An identifying hash for a given push notification, /// passed down from the server. /// An Async Task that can be waited on or ignored. - private static Task TrackAppOpenedWithPushHashAsync(string pushHash = null) - { - return CurrentUserController.GetCurrentSessionTokenAsync(CancellationToken.None) + private static Task TrackAppOpenedWithPushHashAsync(string pushHash = null) => CurrentUserController.GetCurrentSessionTokenAsync(CancellationToken.None) .OnSuccess(t => { return AnalyticsController.TrackAppOpenedAsync(pushHash, t.Result, CancellationToken.None); }).Unwrap(); - } } } diff --git a/Parse/Internal/Analytics/Controller/ParseAnalyticsController.cs b/Parse/Platform/Analytics/ParseAnalyticsController.cs similarity index 100% rename from Parse/Internal/Analytics/Controller/ParseAnalyticsController.cs rename to Parse/Platform/Analytics/ParseAnalyticsController.cs diff --git a/Parse/Internal/Analytics/ParseAnalyticsPlugins.cs b/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs similarity index 99% rename from Parse/Internal/Analytics/ParseAnalyticsPlugins.cs rename to Parse/Platform/Analytics/ParseAnalyticsPlugins.cs index a700efb7..42f7b19f 100644 --- a/Parse/Internal/Analytics/ParseAnalyticsPlugins.cs +++ b/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs @@ -1,6 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using Parse.Core.Internal; namespace Parse.Analytics.Internal diff --git a/Parse/Public/ParseCloud.cs b/Parse/Platform/Code/ParseCloud.cs similarity index 87% rename from Parse/Public/ParseCloud.cs rename to Parse/Platform/Code/ParseCloud.cs index 7c533a2b..45b334e9 100644 --- a/Parse/Public/ParseCloud.cs +++ b/Parse/Platform/Code/ParseCloud.cs @@ -1,6 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -23,13 +22,7 @@ namespace Parse /// public static class ParseCloud { - internal static IParseCloudCodeController CloudCodeController - { - get - { - return ParseCorePlugins.Instance.CloudCodeController; - } - } + internal static IParseCloudCodeController CloudCodeController => ParseCorePlugins.Instance.CloudCodeController; /// /// Calls a cloud function. @@ -42,10 +35,7 @@ internal static IParseCloudCodeController CloudCodeController /// dictionary can contain anything that could be passed into a ParseObject except for /// ParseObjects themselves. /// The result of the cloud call. - public static Task CallFunctionAsync(string name, IDictionary parameters) - { - return CallFunctionAsync(name, parameters, CancellationToken.None); - } + public static Task CallFunctionAsync(string name, IDictionary parameters) => CallFunctionAsync(name, parameters, CancellationToken.None); /// /// Calls a cloud function. @@ -60,12 +50,9 @@ public static Task CallFunctionAsync(string name, IDictionaryThe cancellation token. /// The result of the cloud call. public static Task CallFunctionAsync(string name, - IDictionary parameters, CancellationToken cancellationToken) - { - return CloudCodeController.CallFunctionAsync(name, + IDictionary parameters, CancellationToken cancellationToken) => CloudCodeController.CallFunctionAsync(name, parameters, ParseUser.CurrentSessionToken, cancellationToken); - } } } diff --git a/Parse/Internal/Cloud/Controller/ParseCloudCodeController.cs b/Parse/Platform/Code/ParseCloudCodeController.cs similarity index 82% rename from Parse/Internal/Cloud/Controller/ParseCloudCodeController.cs rename to Parse/Platform/Code/ParseCloudCodeController.cs index 5248750e..6b7d2912 100644 --- a/Parse/Internal/Cloud/Controller/ParseCloudCodeController.cs +++ b/Parse/Platform/Code/ParseCloudCodeController.cs @@ -4,8 +4,8 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Utilities; using Parse.Common.Internal; +using Parse.Utilities; namespace Parse.Core.Internal { @@ -13,24 +13,21 @@ public class ParseCloudCodeController : IParseCloudCodeController { private readonly IParseCommandRunner commandRunner; - public ParseCloudCodeController(IParseCommandRunner commandRunner) - { - this.commandRunner = commandRunner; - } + public ParseCloudCodeController(IParseCommandRunner commandRunner) => this.commandRunner = commandRunner; public Task CallFunctionAsync(string name, IDictionary parameters, string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand(string.Format("functions/{0}", Uri.EscapeUriString(name)), + ParseCommand command = new ParseCommand(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 = ParseDecoder.Instance.Decode(t.Result.Item2) as IDictionary; + IDictionary decoded = ParseDecoder.Instance.Decode(t.Result.Item2) as IDictionary; if (!decoded.ContainsKey("result")) { return default(T); diff --git a/Parse/Internal/Config/Controller/ParseConfigController.cs b/Parse/Platform/Configuration/Controller/ParseConfigController.cs similarity index 96% rename from Parse/Internal/Config/Controller/ParseConfigController.cs rename to Parse/Platform/Configuration/Controller/ParseConfigController.cs index 44018fed..4ad365b4 100644 --- a/Parse/Internal/Config/Controller/ParseConfigController.cs +++ b/Parse/Platform/Configuration/Controller/ParseConfigController.cs @@ -1,8 +1,7 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; -using System.Threading.Tasks; using System.Threading; +using System.Threading.Tasks; using Parse.Common.Internal; namespace Parse.Core.Internal @@ -28,7 +27,7 @@ public ParseConfigController(IParseCommandRunner commandRunner, IStorageControll public Task FetchConfigAsync(string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand("config", + ParseCommand command = new ParseCommand("config", method: "GET", sessionToken: sessionToken, data: null); diff --git a/Parse/Platform/Configuration/Controller/ParseCurrentConfigController.cs b/Parse/Platform/Configuration/Controller/ParseCurrentConfigController.cs new file mode 100644 index 00000000..53226c47 --- /dev/null +++ b/Parse/Platform/Configuration/Controller/ParseCurrentConfigController.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Parse.Common.Internal; + +namespace Parse.Core.Internal +{ + /// + /// Parse current config controller. + /// + internal class ParseCurrentConfigController : IParseCurrentConfigController + { + private const string CurrentConfigKey = "CurrentConfig"; + + private readonly TaskQueue taskQueue; + private ParseConfig currentConfig; + + private IStorageController storageController; + + /// + /// Initializes a new instance of the class. + /// + public ParseCurrentConfigController(IStorageController storageController) + { + this.storageController = storageController; + + taskQueue = new TaskQueue(); + } + + public Task GetCurrentConfigAsync() => taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => + { + if (currentConfig == null) + { + return storageController.LoadAsync().OnSuccess(t => + { + t.Result.TryGetValue(CurrentConfigKey, out object tmp); + + string propertiesString = tmp as string; + if (propertiesString != null) + { + IDictionary dictionary = ParseClient.DeserializeJsonString(propertiesString); + currentConfig = new ParseConfig(dictionary); + } + else + { + currentConfig = new ParseConfig(); + } + + return currentConfig; + }); + } + + return Task.FromResult(currentConfig); + }), CancellationToken.None).Unwrap(); + + public Task SetCurrentConfigAsync(ParseConfig config) => taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => + { + currentConfig = config; + + IDictionary jsonObject = ((IJsonConvertible) config).ToJSON(); + string jsonString = ParseClient.SerializeJsonString(jsonObject); + + return storageController.LoadAsync().OnSuccess(t => t.Result.AddAsync(CurrentConfigKey, jsonString)); + }).Unwrap().Unwrap(), CancellationToken.None); + + public Task ClearCurrentConfigAsync() => taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => + { + currentConfig = null; + + return storageController.LoadAsync().OnSuccess(t => t.Result.RemoveAsync(CurrentConfigKey)); + }).Unwrap().Unwrap(), CancellationToken.None); + + public Task ClearCurrentConfigInMemoryAsync() => taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => + { + currentConfig = null; + }), CancellationToken.None); + } +} diff --git a/Parse/Public/ParseConfig.cs b/Parse/Platform/Configuration/ParseConfig.cs similarity index 70% rename from Parse/Public/ParseConfig.cs rename to Parse/Platform/Configuration/ParseConfig.cs index 7756074c..e461f1e3 100644 --- a/Parse/Public/ParseConfig.cs +++ b/Parse/Platform/Configuration/ParseConfig.cs @@ -1,13 +1,11 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Collections.Generic; -using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Parse.Common.Internal; using Parse.Core.Internal; using Parse.Utilities; -using Parse.Common.Internal; namespace Parse { @@ -33,20 +31,11 @@ public static ParseConfig CurrentConfig } } - internal static void ClearCurrentConfig() - { - ConfigController.CurrentConfigController.ClearCurrentConfigAsync().Wait(); - } + internal static void ClearCurrentConfig() => ConfigController.CurrentConfigController.ClearCurrentConfigAsync().Wait(); - internal static void ClearCurrentConfigInMemory() - { - ConfigController.CurrentConfigController.ClearCurrentConfigInMemoryAsync().Wait(); - } + internal static void ClearCurrentConfigInMemory() => ConfigController.CurrentConfigController.ClearCurrentConfigInMemoryAsync().Wait(); - private static IParseConfigController ConfigController - { - get { return ParseCorePlugins.Instance.ConfigController; } - } + private static IParseConfigController ConfigController => ParseCorePlugins.Instance.ConfigController; internal ParseConfig() : base() @@ -55,7 +44,7 @@ internal ParseConfig() internal ParseConfig(IDictionary fetchedConfig) { - var props = ParseDecoder.Instance.Decode(fetchedConfig["params"]) as IDictionary; + IDictionary props = ParseDecoder.Instance.Decode(fetchedConfig["params"]) as IDictionary; properties = props; } @@ -63,20 +52,14 @@ internal ParseConfig(IDictionary fetchedConfig) /// Retrieves the ParseConfig asynchronously from the server. /// /// ParseConfig object that was fetched - public static Task GetAsync() - { - return GetAsync(CancellationToken.None); - } + public static Task GetAsync() => GetAsync(CancellationToken.None); /// /// Retrieves the ParseConfig asynchronously from the server. /// /// The cancellation token. /// ParseConfig object that was fetched - public static Task GetAsync(CancellationToken cancellationToken) - { - return ConfigController.FetchConfigAsync(ParseUser.CurrentSessionToken, cancellationToken); - } + public static Task GetAsync(CancellationToken cancellationToken) => ConfigController.FetchConfigAsync(ParseUser.CurrentSessionToken, cancellationToken); /// /// Gets a value for the key of a particular type. @@ -85,14 +68,11 @@ public static Task GetAsync(CancellationToken cancellationToken) /// ParseObject and its descendents, Parse types such as ParseRelation and ParseGeopoint, /// primitive types,IList<T>, IDictionary<string, T> and strings. /// The key of the element to get. - /// The property is retrieved + /// 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]); - } + public T Get(string key) => Conversion.To(properties[key]); /// /// Populates result with the value for the key, if possible. @@ -104,11 +84,11 @@ public T Get(string key) /// true if the lookup and conversion succeeded, otherwise false. public bool TryGetValue(string key, out T result) { - if (this.properties.ContainsKey(key)) + if (properties.ContainsKey(key)) { try { - var temp = Conversion.To(this.properties[key]); + T temp = Conversion.To(properties[key]); result = temp; return true; } @@ -125,22 +105,13 @@ public bool TryGetValue(string key, out T result) /// Gets a value on the config. /// /// The key for the parameter. - /// The property is + /// The property is /// retrieved and is not found. /// The value for the key. - virtual public object this[string key] - { - get - { - return this.properties[key]; - } - } + virtual public object this[string key] => properties[key]; - IDictionary IJsonConvertible.ToJSON() - { - return new Dictionary { + IDictionary IJsonConvertible.ToJSON() => new Dictionary { { "params", NoObjectsEncoder.Instance.Encode(properties) } }; - } } } diff --git a/Parse/Internal/File/State/FileState.cs b/Parse/Platform/Files/FileState.cs similarity index 100% rename from Parse/Internal/File/State/FileState.cs rename to Parse/Platform/Files/FileState.cs diff --git a/Parse/Internal/File/Controller/ParseFileController.cs b/Parse/Platform/Files/ParseFileController.cs similarity index 82% rename from Parse/Internal/File/Controller/ParseFileController.cs rename to Parse/Platform/Files/ParseFileController.cs index 48bebd7e..3f18d1cf 100644 --- a/Parse/Internal/File/Controller/ParseFileController.cs +++ b/Parse/Platform/Files/ParseFileController.cs @@ -12,10 +12,7 @@ public class ParseFileController : IParseFileController { private readonly IParseCommandRunner commandRunner; - public ParseFileController(IParseCommandRunner commandRunner) - { - this.commandRunner = commandRunner; - } + public ParseFileController(IParseCommandRunner commandRunner) => this.commandRunner = commandRunner; public Task SaveAsync(FileState state, Stream dataStream, @@ -31,13 +28,13 @@ public Task SaveAsync(FileState state, if (cancellationToken.IsCancellationRequested) { - var tcs = new TaskCompletionSource(); + TaskCompletionSource tcs = new TaskCompletionSource(); tcs.TrySetCanceled(); return tcs.Task; } - var oldPosition = dataStream.Position; - var command = new ParseCommand("files/" + state.Name, + long oldPosition = dataStream.Position; + ParseCommand command = new ParseCommand("files/" + state.Name, method: "POST", sessionToken: sessionToken, contentType: state.MimeType, @@ -47,8 +44,8 @@ public Task SaveAsync(FileState state, uploadProgress: progress, cancellationToken: cancellationToken).OnSuccess(uploadTask => { - var result = uploadTask.Result; - var jsonData = result.Item2; + Tuple> result = uploadTask.Result; + System.Collections.Generic.IDictionary jsonData = result.Item2; cancellationToken.ThrowIfCancellationRequested(); return new FileState diff --git a/Parse/Internal/InstallationId/Controller/InstallationIdController.cs b/Parse/Platform/Installation/InstallationIdController.cs similarity index 84% rename from Parse/Internal/InstallationId/Controller/InstallationIdController.cs rename to Parse/Platform/Installation/InstallationIdController.cs index 8e033c0d..56272b19 100644 --- a/Parse/Internal/InstallationId/Controller/InstallationIdController.cs +++ b/Parse/Platform/Installation/InstallationIdController.cs @@ -1,9 +1,8 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Common.Internal; using System; -using System.Threading; using System.Threading.Tasks; +using Parse.Common.Internal; namespace Parse.Core.Internal { @@ -14,10 +13,7 @@ public class InstallationIdController : IInstallationIdController private Guid? installationId; private readonly IStorageController storageController; - public InstallationIdController(IStorageController storageController) - { - this.storageController = storageController; - } + public InstallationIdController(IStorageController storageController) => this.storageController = storageController; public Task SetAsync(Guid? installationId) { @@ -56,10 +52,9 @@ public Task SetAsync(Guid? installationId) return storageController .LoadAsync() - .OnSuccess, Task>(s => + .OnSuccess(s => { - object id; - s.Result.TryGetValue(InstallationIdKey, out id); + s.Result.TryGetValue(InstallationIdKey, out object id); try { lock (mutex) @@ -70,16 +65,13 @@ public Task SetAsync(Guid? installationId) } catch (Exception) { - var newInstallationId = Guid.NewGuid(); + Guid newInstallationId = Guid.NewGuid(); return SetAsync(newInstallationId).OnSuccess(_ => newInstallationId); } }) .Unwrap(); } - public Task ClearAsync() - { - return SetAsync(null); - } + public Task ClearAsync() => SetAsync(null); } } diff --git a/Parse/Internal/Push/Installation/Controller/ParseCurrentInstallationController.cs b/Parse/Platform/Installation/ParseCurrentInstallationController.cs similarity index 58% rename from Parse/Internal/Push/Installation/Controller/ParseCurrentInstallationController.cs rename to Parse/Platform/Installation/ParseCurrentInstallationController.cs index aab0c16e..637f2c26 100644 --- a/Parse/Internal/Push/Installation/Controller/ParseCurrentInstallationController.cs +++ b/Parse/Platform/Installation/ParseCurrentInstallationController.cs @@ -1,13 +1,10 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Common.Internal; -using Parse.Core.Internal; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; +using Parse.Common.Internal; +using Parse.Core.Internal; namespace Parse.Push.Internal { @@ -47,30 +44,27 @@ internal ParseInstallation CurrentInstallation } } - public Task SetAsync(ParseInstallation installation, CancellationToken cancellationToken) - { - return taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - Task saveTask = storageController.LoadAsync().OnSuccess(storage => - { - if (installation == null) - { - return storage.Result.RemoveAsync(ParseInstallationKey); - } - else - { - var data = installationCoder.Encode(installation); - return storage.Result.AddAsync(ParseInstallationKey, Json.Encode(data)); - } - }).Unwrap(); - - CurrentInstallation = installation; - return saveTask; - }).Unwrap(); - }, cancellationToken); - } + public Task SetAsync(ParseInstallation installation, CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => + { + return toAwait.ContinueWith(_ => + { + Task saveTask = storageController.LoadAsync().OnSuccess(storage => + { + if (installation == null) + { + return storage.Result.RemoveAsync(ParseInstallationKey); + } + else + { + IDictionary data = installationCoder.Encode(installation); + return storage.Result.AddAsync(ParseInstallationKey, Json.Encode(data)); + } + }).Unwrap(); + + CurrentInstallation = installation; + return saveTask; + }).Unwrap(); + }, cancellationToken); public Task GetAsync(CancellationToken cancellationToken) { @@ -89,13 +83,12 @@ public Task GetAsync(CancellationToken cancellationToken) return storageController.LoadAsync().OnSuccess(stroage => { Task fetchTask; - object temp; - stroage.Result.TryGetValue(ParseInstallationKey, out temp); - var installationDataString = temp as string; + stroage.Result.TryGetValue(ParseInstallationKey, out object temp); + string installationDataString = temp as string; ParseInstallation installation = null; if (installationDataString != null) { - var installationData = Json.Parse(installationDataString) as IDictionary; + IDictionary installationData = Json.Parse(installationDataString) as IDictionary; installation = installationCoder.Decode(installationData); fetchTask = Task.FromResult(null); @@ -132,15 +125,9 @@ public Task ExistsAsync(CancellationToken cancellationToken) }, cancellationToken); } - public bool IsCurrent(ParseInstallation installation) - { - return CurrentInstallation == installation; - } + public bool IsCurrent(ParseInstallation installation) => CurrentInstallation == installation; - public void ClearFromMemory() - { - CurrentInstallation = null; - } + public void ClearFromMemory() => CurrentInstallation = null; public void ClearFromDisk() { diff --git a/Parse/Internal/Push/Installation/Coder/ParseInstallationCoder.cs b/Parse/Platform/Installation/ParseInstallationCoder.cs similarity index 69% rename from Parse/Internal/Push/Installation/Coder/ParseInstallationCoder.cs rename to Parse/Platform/Installation/ParseInstallationCoder.cs index c0024012..a302c6e9 100644 --- a/Parse/Internal/Push/Installation/Coder/ParseInstallationCoder.cs +++ b/Parse/Platform/Installation/ParseInstallationCoder.cs @@ -1,7 +1,5 @@ -using System; -using System.Linq; using System.Collections.Generic; -using Parse; +using System.Linq; using Parse.Core.Internal; namespace Parse.Push.Internal @@ -9,19 +7,13 @@ namespace Parse.Push.Internal public class ParseInstallationCoder : IParseInstallationCoder { private static readonly ParseInstallationCoder instance = new ParseInstallationCoder(); - public static ParseInstallationCoder Instance - { - get - { - return instance; - } - } + public static ParseInstallationCoder Instance => instance; private const string ISO8601Format = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'"; public IDictionary Encode(ParseInstallation installation) { - var state = installation.GetState(); - var data = PointerOrLocalIdEncoder.Instance.Encode(state.ToDictionary(x => x.Key, x => x.Value)) as IDictionary; + IObjectState state = installation.GetState(); + IDictionary data = PointerOrLocalIdEncoder.Instance.Encode(state.ToDictionary(x => x.Key, x => x.Value)) as IDictionary; data["objectId"] = state.ObjectId; if (state.CreatedAt != null) { @@ -36,7 +28,7 @@ public IDictionary Encode(ParseInstallation installation) public ParseInstallation Decode(IDictionary data) { - var state = ParseObjectCoder.Instance.Decode(data, ParseDecoder.Instance); + IObjectState state = ParseObjectCoder.Instance.Decode(data, ParseDecoder.Instance); return ParseObjectExtensions.FromState(state, "_Installation"); } } diff --git a/Parse/Internal/Push/State/MutablePushState.cs b/Parse/Platform/Notifications/MutablePushState.cs similarity index 53% rename from Parse/Internal/Push/State/MutablePushState.cs rename to Parse/Platform/Notifications/MutablePushState.cs index 9ffe47b1..aa2a3abd 100644 --- a/Parse/Internal/Push/State/MutablePushState.cs +++ b/Parse/Platform/Notifications/MutablePushState.cs @@ -1,7 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; -using System.Linq; using System.Collections.Generic; using Parse.Common.Internal; @@ -24,19 +23,16 @@ public IPushState MutatedClone(Action func) return clone; } - protected virtual MutablePushState MutableClone() + protected virtual MutablePushState MutableClone() => new MutablePushState { - return new MutablePushState - { - Query = Query, - Channels = Channels == null ? null : new List(Channels), - Expiration = Expiration, - ExpirationInterval = ExpirationInterval, - PushTime = PushTime, - Data = Data == null ? null : new Dictionary(Data), - Alert = Alert - }; - } + Query = Query, + Channels = Channels == null ? null : new List(Channels), + Expiration = Expiration, + ExpirationInterval = ExpirationInterval, + PushTime = PushTime, + Data = Data == null ? null : new Dictionary(Data), + Alert = Alert + }; public override bool Equals(object obj) { @@ -45,20 +41,18 @@ public override bool Equals(object obj) return false; } - var other = obj as MutablePushState; - return Object.Equals(this.Query, other.Query) && - this.Channels.CollectionsEqual(other.Channels) && - Object.Equals(this.Expiration, other.Expiration) && - Object.Equals(this.ExpirationInterval, other.ExpirationInterval) && - Object.Equals(this.PushTime, other.PushTime) && - this.Data.CollectionsEqual(other.Data) && - Object.Equals(this.Alert, other.Alert); + MutablePushState other = obj as MutablePushState; + return Object.Equals(Query, other.Query) && + Channels.CollectionsEqual(other.Channels) && + Object.Equals(Expiration, other.Expiration) && + Object.Equals(ExpirationInterval, other.ExpirationInterval) && + Object.Equals(PushTime, other.PushTime) && + Data.CollectionsEqual(other.Data) && + Object.Equals(Alert, other.Alert); } - public override int GetHashCode() - { + public override int GetHashCode() => // TODO (richardross): Implement this. - return 0; - } + 0; } } diff --git a/Parse/Internal/Push/Controller/ParsePushChannelsController.cs b/Parse/Platform/Notifications/ParsePushChannelsController.cs similarity index 98% rename from Parse/Internal/Push/Controller/ParsePushChannelsController.cs rename to Parse/Platform/Notifications/ParsePushChannelsController.cs index c002d254..f8c9ea20 100644 --- a/Parse/Internal/Push/Controller/ParsePushChannelsController.cs +++ b/Parse/Platform/Notifications/ParsePushChannelsController.cs @@ -1,9 +1,8 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; -using System.Threading.Tasks; using System.Collections.Generic; using System.Threading; +using System.Threading.Tasks; namespace Parse.Push.Internal { diff --git a/Parse/Platform/Notifications/ParsePushController.cs b/Parse/Platform/Notifications/ParsePushController.cs new file mode 100644 index 00000000..d64d59b2 --- /dev/null +++ b/Parse/Platform/Notifications/ParsePushController.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Threading; +using System.Threading.Tasks; +using Parse.Common.Internal; +using Parse.Core.Internal; + +namespace Parse.Push.Internal +{ + internal class ParsePushController : IParsePushController + { + private readonly IParseCommandRunner commandRunner; + private readonly IParseCurrentUserController currentUserController; + + public ParsePushController(IParseCommandRunner commandRunner, IParseCurrentUserController currentUserController) + { + this.commandRunner = commandRunner; + this.currentUserController = currentUserController; + } + + public Task SendPushNotificationAsync(IPushState state, CancellationToken cancellationToken) => currentUserController.GetCurrentSessionTokenAsync(cancellationToken).OnSuccess(sessionTokenTask => + { + ParseCommand command = new ParseCommand("push", + method: "POST", + sessionToken: sessionTokenTask.Result, + data: ParsePushEncoder.Instance.Encode(state)); + + return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken); + }).Unwrap(); + } +} diff --git a/Parse/Internal/Push/Coder/ParsePushEncoder.cs b/Parse/Platform/Notifications/ParsePushEncoder.cs similarity index 85% rename from Parse/Internal/Push/Coder/ParsePushEncoder.cs rename to Parse/Platform/Notifications/ParsePushEncoder.cs index f010ae41..c3d46ef2 100644 --- a/Parse/Internal/Push/Coder/ParsePushEncoder.cs +++ b/Parse/Platform/Notifications/ParsePushEncoder.cs @@ -1,7 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; -using System.Linq; using System.Collections.Generic; using Parse.Common.Internal; using Parse.Core.Internal; @@ -11,13 +10,7 @@ namespace Parse.Push.Internal public class ParsePushEncoder { private static readonly ParsePushEncoder instance = new ParsePushEncoder(); - public static ParsePushEncoder Instance - { - get - { - return instance; - } - } + public static ParsePushEncoder Instance => instance; private ParsePushEncoder() { } @@ -32,13 +25,13 @@ public IDictionary Encode(IPushState state) throw new InvalidOperationException("A push must have either Channels or a Query"); } - var data = state.Data ?? new Dictionary { { "alert", state.Alert } }; + IDictionary data = state.Data ?? new Dictionary { { "alert", state.Alert } }; var query = state.Query ?? ParseInstallation.Query; if (state.Channels != null) { query = query.WhereContainedIn("channels", state.Channels); } - var payload = new Dictionary { + Dictionary payload = new Dictionary { { "data", data }, { "where", query.BuildParameters().GetOrDefault("where", new Dictionary()) }, }; diff --git a/Parse/Internal/Push/ParsePushModule.cs b/Parse/Platform/Notifications/ParsePushModule.cs similarity index 97% rename from Parse/Internal/Push/ParsePushModule.cs rename to Parse/Platform/Notifications/ParsePushModule.cs index fd107f4c..5d77a7a1 100644 --- a/Parse/Internal/Push/ParsePushModule.cs +++ b/Parse/Platform/Notifications/ParsePushModule.cs @@ -1,6 +1,5 @@ using Parse.Common.Internal; using Parse.Core.Internal; -using System; namespace Parse.Push.Internal { diff --git a/Parse/Internal/Push/ParsePushPlugins.cs b/Parse/Platform/Notifications/ParsePushPlugins.cs similarity index 100% rename from Parse/Internal/Push/ParsePushPlugins.cs rename to Parse/Platform/Notifications/ParsePushPlugins.cs diff --git a/Parse/Public/ParseACL.cs b/Parse/Platform/Security/ParseACL.cs similarity index 100% rename from Parse/Public/ParseACL.cs rename to Parse/Platform/Security/ParseACL.cs index 88c98a4d..dbfc3163 100644 --- a/Parse/Public/ParseACL.cs +++ b/Parse/Platform/Security/ParseACL.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; using System.Linq; -using Parse.Core.Internal; using Parse.Common.Internal; +using Parse.Core.Internal; namespace Parse { diff --git a/Parse/Internal/Session/Controller/ParseSessionController.cs b/Parse/Platform/Session/ParseSessionController.cs similarity index 85% rename from Parse/Internal/Session/Controller/ParseSessionController.cs rename to Parse/Platform/Session/ParseSessionController.cs index 25b48917..bd3bf726 100644 --- a/Parse/Internal/Session/Controller/ParseSessionController.cs +++ b/Parse/Platform/Session/ParseSessionController.cs @@ -1,6 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -12,14 +11,11 @@ public class ParseSessionController : IParseSessionController { private readonly IParseCommandRunner commandRunner; - public ParseSessionController(IParseCommandRunner commandRunner) - { - this.commandRunner = commandRunner; - } + public ParseSessionController(IParseCommandRunner commandRunner) => this.commandRunner = commandRunner; public Task GetSessionAsync(string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand("sessions/me", + ParseCommand command = new ParseCommand("sessions/me", method: "GET", sessionToken: sessionToken, data: null); @@ -32,7 +28,7 @@ public Task GetSessionAsync(string sessionToken, CancellationToken public Task RevokeAsync(string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand("logout", + ParseCommand command = new ParseCommand("logout", method: "POST", sessionToken: sessionToken, data: new Dictionary()); @@ -42,7 +38,7 @@ public Task RevokeAsync(string sessionToken, CancellationToken cancellationToken public Task UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand("upgradeToRevocableSession", + ParseCommand command = new ParseCommand("upgradeToRevocableSession", method: "POST", sessionToken: sessionToken, data: new Dictionary()); @@ -53,9 +49,6 @@ public Task UpgradeToRevocableSessionAsync(string sessionToken, Ca }); } - public bool IsRevocableSessionToken(string sessionToken) - { - return sessionToken.Contains("r:"); - } + public bool IsRevocableSessionToken(string sessionToken) => sessionToken.Contains("r:"); } } diff --git a/Parse/Public/ParseSession.cs b/Parse/Public/ParseSession.cs deleted file mode 100644 index 6f438c7c..00000000 --- a/Parse/Public/ParseSession.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using Parse.Core.Internal; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Parse.Common.Internal; - -namespace Parse -{ - /// - /// Represents a session of a user for a Parse application. - /// - [ParseClassName("_Session")] - public class ParseSession : ParseObject - { - 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. - /// - [ParseFieldName("sessionToken")] - public string SessionToken - { - get { return GetProperty(null, "SessionToken"); } - } - - /// - /// Constructs a for ParseSession. - /// - public static ParseQuery Query - { - get - { - return new ParseQuery(); - } - } - - internal static IParseSessionController SessionController - { - get - { - return ParseCorePlugins.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 ParseUser.GetCurrentUserAsync().OnSuccess(t1 => - { - ParseUser user = t1.Result; - if (user == null) - { - return Task.FromResult((ParseSession) null); - } - - string sessionToken = user.SessionToken; - if (sessionToken == null) - { - return Task.FromResult((ParseSession) null); - } - - return SessionController.GetSessionAsync(sessionToken, cancellationToken).OnSuccess(t => - { - ParseSession session = ParseObject.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 => - { - ParseSession session = ParseObject.FromState(t.Result, "_Session"); - return session.SessionToken; - }); - } - } -} diff --git a/Parse/Internal/Query/Controller/ParseQueryController.cs b/Parse/Query/ParseQueryController.cs similarity index 94% rename from Parse/Internal/Query/Controller/ParseQueryController.cs rename to Parse/Query/ParseQueryController.cs index b8ffd10f..cf518f5b 100644 --- a/Parse/Internal/Query/Controller/ParseQueryController.cs +++ b/Parse/Query/ParseQueryController.cs @@ -1,9 +1,8 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; -using System.Linq; using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Parse.Common.Internal; @@ -20,7 +19,7 @@ internal class ParseQueryController : IParseQueryController public Task CountAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject { - var parameters = query.BuildParameters(); + IDictionary parameters = query.BuildParameters(); parameters["limit"] = 0; parameters["count"] = 1; @@ -29,7 +28,7 @@ public Task CountAsync(ParseQuery query, ParseUser user, Cancellation public Task FirstAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject { - var parameters = query.BuildParameters(); + IDictionary parameters = query.BuildParameters(); parameters["limit"] = 1; return FindAsync(query.ClassName, parameters, user?.SessionToken, cancellationToken).OnSuccess(t => (t.Result["results"] as IList).FirstOrDefault() as IDictionary is Dictionary item && item != null ? ParseObjectCoder.Instance.Decode(item, ParseDecoder.Instance) : null); diff --git a/Parse/Internal/Storage/Controller/StorageController.cs b/Parse/Storage/StorageController.cs similarity index 80% rename from Parse/Internal/Storage/Controller/StorageController.cs rename to Parse/Storage/StorageController.cs index 9a159d8b..ab826931 100644 --- a/Parse/Internal/Storage/Controller/StorageController.cs +++ b/Parse/Storage/StorageController.cs @@ -1,9 +1,9 @@ using System; -using System.Threading.Tasks; -using System.Linq; using System.Collections.Generic; -using System.Threading; using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Parse.Internal.Utilities; namespace Parse.Common.Internal @@ -36,27 +36,24 @@ internal Task SaveAsync() return file.WriteToAsync(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 Task LoadAsync() => 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) { diff --git a/Parse/AssemblyLister.cs b/Parse/Utilities/AssemblyLister.cs similarity index 94% rename from Parse/AssemblyLister.cs rename to Parse/Utilities/AssemblyLister.cs index 5ed14813..12344c8a 100644 --- a/Parse/AssemblyLister.cs +++ b/Parse/Utilities/AssemblyLister.cs @@ -34,7 +34,7 @@ private static IEnumerable DeepWalkReferences(this Assembly assembly, List assemblies = new List { assembly }; - foreach (var reference in assembly.GetReferencedAssemblies()) + foreach (AssemblyName reference in assembly.GetReferencedAssemblies()) { if (seen.Contains(reference.FullName)) continue; diff --git a/Parse/Public/Utilities/Conversion.cs b/Parse/Utilities/Conversion.cs similarity index 100% rename from Parse/Public/Utilities/Conversion.cs rename to Parse/Utilities/Conversion.cs diff --git a/Parse/Internal/Encoding/NoObjectsEncoder.cs b/Parse/Utilities/Encoding/NoObjectsEncoder.cs similarity index 76% rename from Parse/Internal/Encoding/NoObjectsEncoder.cs rename to Parse/Utilities/Encoding/NoObjectsEncoder.cs index 92d1662e..61bb8415 100644 --- a/Parse/Internal/Encoding/NoObjectsEncoder.cs +++ b/Parse/Utilities/Encoding/NoObjectsEncoder.cs @@ -14,17 +14,8 @@ public class NoObjectsEncoder : ParseEncoder // 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; - } - } + public static NoObjectsEncoder Instance => instance; - protected override IDictionary EncodeParseObject(ParseObject value) - { - throw new ArgumentException("ParseObjects not allowed here."); - } + protected override IDictionary EncodeParseObject(ParseObject value) => throw new ArgumentException("ParseObjects not allowed here."); } } diff --git a/Parse/Internal/Encoding/ParseDecoder.cs b/Parse/Utilities/Encoding/ParseDecoder.cs similarity index 77% rename from Parse/Internal/Encoding/ParseDecoder.cs rename to Parse/Utilities/Encoding/ParseDecoder.cs index 9128e15a..81238d99 100644 --- a/Parse/Internal/Encoding/ParseDecoder.cs +++ b/Parse/Utilities/Encoding/ParseDecoder.cs @@ -1,9 +1,9 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; -using System.Linq; using System.Collections.Generic; using System.Globalization; +using System.Linq; using Parse.Utilities; namespace Parse.Core.Internal @@ -23,7 +23,7 @@ public object Decode(object data) return null; } - var dict = data as IDictionary; + IDictionary dict = data as IDictionary; if (dict != null) { if (dict.ContainsKey("__op")) @@ -31,14 +31,13 @@ public object Decode(object data) return ParseFieldOperations.Decode(dict); } - object type; - dict.TryGetValue("__type", out type); - var typeString = type as string; + dict.TryGetValue("__type", out object type); + string typeString = type as string; if (typeString == null) { - var newDict = new Dictionary(); - foreach (var pair in dict) + Dictionary newDict = new Dictionary(); + foreach (KeyValuePair pair in dict) { newDict[pair.Key] = Decode(pair.Value); } @@ -73,7 +72,7 @@ public object Decode(object data) if (typeString == "Object") { - var state = ParseObjectCoder.Instance.Decode(dict, this); + IObjectState state = ParseObjectCoder.Instance.Decode(dict, this); return ParseObject.FromState(state, dict["className"] as string); } @@ -82,15 +81,15 @@ public object Decode(object data) return ParseRelationBase.CreateRelation(null, null, dict["className"] as string); } - var converted = new Dictionary(); - foreach (var pair in dict) + Dictionary converted = new Dictionary(); + foreach (KeyValuePair pair in dict) { converted[pair.Key] = Decode(pair.Value); } return converted; } - var list = data as IList; + IList list = data as IList; if (list != null) { return (from item in list @@ -100,16 +99,11 @@ public object Decode(object data) return data; } - protected virtual object DecodePointer(string className, string objectId) - { - return ParseObject.CreateWithoutData(className, objectId); - } + protected virtual object DecodePointer(string className, string objectId) => ParseObject.CreateWithoutData(className, objectId); - public static DateTime ParseDate(string input) - { + public static DateTime ParseDate(string input) => // TODO(hallucinogen): Figure out if we should be more flexible with the date formats // we accept. - return System.DateTime.ParseExact(input, ParseClient.DateFormatStrings, CultureInfo.InvariantCulture, DateTimeStyles.None); - } + System.DateTime.ParseExact(input, ParseClient.DateFormatStrings, CultureInfo.InvariantCulture, DateTimeStyles.None); } } diff --git a/Parse/Internal/Encoding/ParseEncoder.cs b/Parse/Utilities/Encoding/ParseEncoder.cs similarity index 83% rename from Parse/Internal/Encoding/ParseEncoder.cs rename to Parse/Utilities/Encoding/ParseEncoder.cs index e414de20..37dbaff3 100644 --- a/Parse/Internal/Encoding/ParseEncoder.cs +++ b/Parse/Utilities/Encoding/ParseEncoder.cs @@ -4,8 +4,8 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using Parse.Utilities; using Parse.Common.Internal; +using Parse.Utilities; namespace Parse.Core.Internal { @@ -22,9 +22,7 @@ public abstract class ParseEncoder private static readonly bool isCompiledByIL2CPP = false; #endif - public static bool IsValidType(object value) - { - return value == null || + public static bool IsValidType(object value) => value == null || ReflectionHelpers.IsPrimitive(value.GetType()) || value is string || value is ParseObject || @@ -36,7 +34,6 @@ value is DateTime || value is byte[] || Conversion.As>(value) != null || Conversion.As>(value) != null; - } public object Encode(object value) { @@ -50,7 +47,7 @@ public object Encode(object value) }; } - var bytes = value as byte[]; + byte[] bytes = value as byte[]; if (bytes != null) { return new Dictionary { @@ -59,37 +56,37 @@ public object Encode(object value) }; } - var obj = value as ParseObject; + ParseObject obj = value as ParseObject; if (obj != null) { return EncodeParseObject(obj); } - var jsonConvertible = value as IJsonConvertible; + IJsonConvertible jsonConvertible = value as IJsonConvertible; if (jsonConvertible != null) { return jsonConvertible.ToJSON(); } - var dict = Conversion.As>(value); + IDictionary dict = Conversion.As>(value); if (dict != null) { - var json = new Dictionary(); - foreach (var pair in dict) + Dictionary json = new Dictionary(); + foreach (KeyValuePair pair in dict) { json[pair.Key] = Encode(pair.Value); } return json; } - var list = Conversion.As>(value); + IList list = Conversion.As>(value); if (list != null) { return EncodeList(list); } // TODO (hallucinogen): convert IParseFieldOperation to IJsonConvertible - var operation = value as IParseFieldOperation; + IParseFieldOperation operation = value as IParseFieldOperation; if (operation != null) { return operation.Encode(); @@ -102,14 +99,14 @@ public object Encode(object value) private object EncodeList(IList list) { - var newArray = new List(); + List 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) + foreach (object item in list) { if (!IsValidType(item)) { diff --git a/Parse/Internal/Encoding/ParseObjectCoder.cs b/Parse/Utilities/Encoding/ParseObjectCoder.cs similarity index 100% rename from Parse/Internal/Encoding/ParseObjectCoder.cs rename to Parse/Utilities/Encoding/ParseObjectCoder.cs diff --git a/Parse/Internal/Encoding/PointerOrLocalIdEncoder.cs b/Parse/Utilities/Encoding/PointerOrLocalIdEncoder.cs similarity index 90% rename from Parse/Internal/Encoding/PointerOrLocalIdEncoder.cs rename to Parse/Utilities/Encoding/PointerOrLocalIdEncoder.cs index d6386203..b42a05e0 100644 --- a/Parse/Internal/Encoding/PointerOrLocalIdEncoder.cs +++ b/Parse/Utilities/Encoding/PointerOrLocalIdEncoder.cs @@ -14,13 +14,7 @@ public class PointerOrLocalIdEncoder : ParseEncoder // 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; - } - } + public static PointerOrLocalIdEncoder Instance => instance; protected override IDictionary EncodeParseObject(ParseObject value) { diff --git a/Parse/Internal/Utilities/FlexibleDictionaryWrapper.cs b/Parse/Utilities/FlexibleDictionaryWrapper.cs similarity index 50% rename from Parse/Internal/Utilities/FlexibleDictionaryWrapper.cs rename to Parse/Utilities/FlexibleDictionaryWrapper.cs index 0a155c76..284aabe4 100644 --- a/Parse/Internal/Utilities/FlexibleDictionaryWrapper.cs +++ b/Parse/Utilities/FlexibleDictionaryWrapper.cs @@ -17,113 +17,64 @@ namespace Parse.Common.Internal public class FlexibleDictionaryWrapper : IDictionary { private readonly IDictionary toWrap; - public FlexibleDictionaryWrapper(IDictionary toWrap) - { - this.toWrap = toWrap; - } + public FlexibleDictionaryWrapper(IDictionary toWrap) => this.toWrap = toWrap; - public void Add(string key, TOut value) - { - toWrap.Add(key, (TIn) Conversion.ConvertTo(value)); - } + public void Add(string key, TOut value) => toWrap.Add(key, (TIn) Conversion.ConvertTo(value)); - public bool ContainsKey(string key) - { - return toWrap.ContainsKey(key); - } + public bool ContainsKey(string key) => toWrap.ContainsKey(key); - public ICollection Keys - { - get { return toWrap.Keys; } - } + public ICollection Keys => toWrap.Keys; - public bool Remove(string key) - { - return toWrap.Remove(key); - } + public bool Remove(string key) => toWrap.Remove(key); public bool TryGetValue(string key, out TOut value) { - TIn outValue; - bool result = toWrap.TryGetValue(key, out outValue); + bool result = toWrap.TryGetValue(key, out TIn outValue); value = (TOut) Conversion.ConvertTo(outValue); return result; } - public ICollection Values - { - get - { - return toWrap.Values + public ICollection Values => 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); - } + get => (TOut) Conversion.ConvertTo(toWrap[key]); + set => toWrap[key] = (TIn) Conversion.ConvertTo(value); } - public void Add(KeyValuePair item) - { - toWrap.Add(new KeyValuePair(item.Key, + public void Add(KeyValuePair item) => toWrap.Add(new KeyValuePair(item.Key, (TIn) Conversion.ConvertTo(item.Value))); - } - public void Clear() - { - toWrap.Clear(); - } + public void Clear() => toWrap.Clear(); - public bool Contains(KeyValuePair item) - { - return toWrap.Contains(new KeyValuePair(item.Key, + public bool Contains(KeyValuePair item) => 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)); + IEnumerable> 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 int Count => toWrap.Count; - public bool IsReadOnly - { - get { return toWrap.IsReadOnly; } - } + public bool IsReadOnly => toWrap.IsReadOnly; - public bool Remove(KeyValuePair item) - { - return toWrap.Remove(new KeyValuePair(item.Key, + public bool Remove(KeyValuePair item) => toWrap.Remove(new KeyValuePair(item.Key, (TIn) Conversion.ConvertTo(item.Value))); - } public IEnumerator> GetEnumerator() { - foreach (var pair in toWrap) + foreach (KeyValuePair pair in toWrap) { yield return new KeyValuePair(pair.Key, (TOut) Conversion.ConvertTo(pair.Value)); } } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); } } diff --git a/Parse/Utilities/FlexibleListWrapper.cs b/Parse/Utilities/FlexibleListWrapper.cs new file mode 100644 index 00000000..800fb652 --- /dev/null +++ b/Parse/Utilities/FlexibleListWrapper.cs @@ -0,0 +1,60 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Parse.Utilities; + +namespace Parse.Common.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) => 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 => (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) => 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 => toWrap.Count; + + public bool IsReadOnly => toWrap.IsReadOnly; + + public bool Remove(TOut item) => toWrap.Remove((TIn) Conversion.ConvertTo(item)); + + public IEnumerator GetEnumerator() + { + foreach (object item in (IEnumerable) toWrap) + { + yield return (TOut) Conversion.ConvertTo(item); + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/Parse/Internal/HttpClient/Portable/HttpClient.Portable.cs b/Parse/Utilities/HttpClient.Portable.cs similarity index 93% rename from Parse/Internal/HttpClient/Portable/HttpClient.Portable.cs rename to Parse/Utilities/HttpClient.Portable.cs index aa984811..85834c49 100644 --- a/Parse/Internal/HttpClient/Portable/HttpClient.Portable.cs +++ b/Parse/Utilities/HttpClient.Portable.cs @@ -1,17 +1,14 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; +using System.Collections.Generic; using System.IO; using System.Net; +using System.Net.Http; 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 Parse.Common.Internal { @@ -39,7 +36,7 @@ public Task> ExecuteAsync(HttpRequest httpRequest, if (httpRequest.Headers != null) { - foreach (var header in httpRequest.Headers) + foreach (KeyValuePair header in httpRequest.Headers) { if (HttpContentHeaders.Contains(header.Key)) message.Content.Headers.Add(header.Key, header.Value); @@ -57,13 +54,13 @@ public Task> ExecuteAsync(HttpRequest httpRequest, return client.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ContinueWith(httpMessageTask => { - var response = httpMessageTask.Result; + HttpResponseMessage response = httpMessageTask.Result; uploadProgress.Report(new ParseUploadProgressEventArgs { Progress = 1 }); return response.Content.ReadAsStreamAsync().ContinueWith(streamTask => { - var resultStream = new MemoryStream(); - var responseStream = streamTask.Result; + MemoryStream resultStream = new MemoryStream(); + Stream responseStream = streamTask.Result; int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; @@ -96,7 +93,7 @@ public Task> ExecuteAsync(HttpRequest httpRequest, // If getting stream size is not supported, then report download only once. if (totalLength == -1) downloadProgress.Report(new ParseDownloadProgressEventArgs { Progress = 1.0 }); - var resultAsArray = resultStream.ToArray(); + byte[] resultAsArray = resultStream.ToArray(); resultStream.Dispose(); // Assume UTF-8 encoding. return new Tuple(response.StatusCode, Encoding.UTF8.GetString(resultAsArray, 0, resultAsArray.Length)); diff --git a/Parse/Internal/HttpClient/HttpRequest.cs b/Parse/Utilities/HttpRequest.cs similarity index 100% rename from Parse/Internal/HttpClient/HttpRequest.cs rename to Parse/Utilities/HttpRequest.cs diff --git a/Parse/Internal/HttpClient/IHttpClient.cs b/Parse/Utilities/IHttpClient.cs similarity index 100% rename from Parse/Internal/HttpClient/IHttpClient.cs rename to Parse/Utilities/IHttpClient.cs diff --git a/Parse/Internal/Utilities/IJsonConvertible.cs b/Parse/Utilities/IJsonConvertible.cs similarity index 98% rename from Parse/Internal/Utilities/IJsonConvertible.cs rename to Parse/Utilities/IJsonConvertible.cs index 0a9f1aff..081df368 100644 --- a/Parse/Internal/Utilities/IJsonConvertible.cs +++ b/Parse/Utilities/IJsonConvertible.cs @@ -1,6 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Collections.Generic; namespace Parse.Common.Internal diff --git a/Parse/Internal/Utilities/IdentityEqualityComparer.cs b/Parse/Utilities/IdentityEqualityComparer.cs similarity index 77% rename from Parse/Internal/Utilities/IdentityEqualityComparer.cs rename to Parse/Utilities/IdentityEqualityComparer.cs index 5517cbae..58e433b3 100644 --- a/Parse/Internal/Utilities/IdentityEqualityComparer.cs +++ b/Parse/Utilities/IdentityEqualityComparer.cs @@ -12,14 +12,8 @@ namespace Parse.Common.Internal /// public class IdentityEqualityComparer : IEqualityComparer { - public bool Equals(T x, T y) - { - return ReferenceEquals(x, y); - } + public bool Equals(T x, T y) => ReferenceEquals(x, y); - public int GetHashCode(T obj) - { - return RuntimeHelpers.GetHashCode(obj); - } + public int GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj); } } diff --git a/Parse/Utilities/InternalExtensions.cs b/Parse/Utilities/InternalExtensions.cs new file mode 100644 index 00000000..a37dfc75 --- /dev/null +++ b/Parse/Utilities/InternalExtensions.cs @@ -0,0 +1,109 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.ExceptionServices; +using System.Threading.Tasks; + +namespace Parse.Common.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) => task ?? Task.FromResult(default(T)); + + /// + /// Ensures a task (even null) is awaitable. + /// + /// + /// + public static Task Safe(this Task task) => task ?? Task.FromResult(null); + + public delegate void PartialAccessor(ref T arg); + + public static TValue GetOrDefault(this IDictionary self, + TKey key, + TValue defaultValue) + { + if (self.TryGetValue(key, out TValue value)) + { + return value; + } + return defaultValue; + } + + public static bool CollectionsEqual(this IEnumerable a, IEnumerable b) => Object.Equals(a, b) || + (a != null && b != null && + a.SequenceEqual(b)); + + public static Task OnSuccess(this Task task, + Func, TResult> continuation) => ((Task) task).OnSuccess(t => continuation((Task) t)); + + public static Task OnSuccess(this Task task, Action> continuation) => task.OnSuccess((Func, object>) (t => + { + continuation(t); + return null; + })); + + public static Task OnSuccess(this Task task, + Func continuation) => task.ContinueWith(t => + { + if (t.IsFaulted) + { + AggregateException 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) + { + TaskCompletionSource tcs = new TaskCompletionSource(); + tcs.SetCanceled(); + return tcs.Task; + } + else + { + return Task.FromResult(continuation(t)); + } + }).Unwrap(); + + public static Task OnSuccess(this Task task, Action continuation) => 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/Parse/Internal/Utilities/Json.cs b/Parse/Utilities/Json.cs similarity index 99% rename from Parse/Internal/Utilities/Json.cs rename to Parse/Utilities/Json.cs index 2005a14b..e35badad 100644 --- a/Parse/Internal/Utilities/Json.cs +++ b/Parse/Utilities/Json.cs @@ -3,11 +3,9 @@ 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 Parse.Common.Internal { diff --git a/Parse/Internal/Utilities/LockSet.cs b/Parse/Utilities/LockSet.cs similarity index 100% rename from Parse/Internal/Utilities/LockSet.cs rename to Parse/Utilities/LockSet.cs diff --git a/Parse/Public/ParseClassNameAttribute.cs b/Parse/Utilities/ParseClassNameAttribute.cs similarity index 91% rename from Parse/Public/ParseClassNameAttribute.cs rename to Parse/Utilities/ParseClassNameAttribute.cs index c4c39726..af769056 100644 --- a/Parse/Public/ParseClassNameAttribute.cs +++ b/Parse/Utilities/ParseClassNameAttribute.cs @@ -1,10 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Parse { diff --git a/Parse/Internal/Utilities/ParseConfigExtensions.cs b/Parse/Utilities/ParseConfigExtensions.cs similarity index 90% rename from Parse/Internal/Utilities/ParseConfigExtensions.cs rename to Parse/Utilities/ParseConfigExtensions.cs index 8e1a3072..e5a927cb 100644 --- a/Parse/Internal/Utilities/ParseConfigExtensions.cs +++ b/Parse/Utilities/ParseConfigExtensions.cs @@ -1,6 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Collections.Generic; namespace Parse.Core.Internal @@ -17,9 +16,6 @@ namespace Parse.Core.Internal /// public static class ParseConfigExtensions { - public static ParseConfig Create(IDictionary fetchedConfig) - { - return new ParseConfig(fetchedConfig); - } + public static ParseConfig Create(IDictionary fetchedConfig) => new ParseConfig(fetchedConfig); } } diff --git a/Parse/Public/ParseExtensions.cs b/Parse/Utilities/ParseExtensions.cs similarity index 78% rename from Parse/Public/ParseExtensions.cs rename to Parse/Utilities/ParseExtensions.cs index 5dae9417..b89860af 100644 --- a/Parse/Public/ParseExtensions.cs +++ b/Parse/Utilities/ParseExtensions.cs @@ -1,10 +1,9 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Linq; -using Parse.Core.Internal; using Parse.Common.Internal; namespace Parse @@ -20,10 +19,7 @@ public static class ParseExtensions /// calling . /// /// The objects to save. - public static Task SaveAllAsync(this IEnumerable objects) where T : ParseObject - { - return ParseObject.SaveAllAsync(objects); - } + public static Task SaveAllAsync(this IEnumerable objects) where T : ParseObject => ParseObject.SaveAllAsync(objects); /// /// Saves all of the ParseObjects in the enumeration. Equivalent to @@ -33,10 +29,7 @@ public static Task SaveAllAsync(this IEnumerable objects) where T : ParseO /// The objects to save. /// The cancellation token. public static Task SaveAllAsync( - this IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject - { - return ParseObject.SaveAllAsync(objects, cancellationToken); - } + this IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject => ParseObject.SaveAllAsync(objects, cancellationToken); /// /// Fetches all of the objects in the enumeration. Equivalent to @@ -44,10 +37,7 @@ public static Task SaveAllAsync( /// /// The objects to save. public static Task> FetchAllAsync(this IEnumerable objects) - where T : ParseObject - { - return ParseObject.FetchAllAsync(objects); - } + where T : ParseObject => ParseObject.FetchAllAsync(objects); /// /// Fetches all of the objects in the enumeration. Equivalent to @@ -58,10 +48,7 @@ public static Task> FetchAllAsync(this IEnumerable objects) /// The cancellation token. public static Task> FetchAllAsync( this IEnumerable objects, CancellationToken cancellationToken) - where T : ParseObject - { - return ParseObject.FetchAllAsync(objects, cancellationToken); - } + where T : ParseObject => ParseObject.FetchAllAsync(objects, cancellationToken); /// /// Fetches all of the objects in the enumeration that don't already have @@ -71,10 +58,7 @@ public static Task> FetchAllAsync( /// The objects to fetch. public static Task> FetchAllIfNeededAsync( this IEnumerable objects) - where T : ParseObject - { - return ParseObject.FetchAllIfNeededAsync(objects); - } + where T : ParseObject => ParseObject.FetchAllIfNeededAsync(objects); /// /// Fetches all of the objects in the enumeration that don't already have @@ -85,10 +69,7 @@ public static Task> FetchAllIfNeededAsync( /// The cancellation token. public static Task> FetchAllIfNeededAsync( this IEnumerable objects, CancellationToken cancellationToken) - where T : ParseObject - { - return ParseObject.FetchAllIfNeededAsync(objects, cancellationToken); - } + where T : ParseObject => ParseObject.FetchAllIfNeededAsync(objects, cancellationToken); /// /// Constructs a query that is the or of the given queries. @@ -98,18 +79,12 @@ public static Task> FetchAllIfNeededAsync( /// The list of ParseQueries to 'or' together. /// A query that is the or of the given queries. public static ParseQuery Or(this ParseQuery source, params ParseQuery[] queries) - where T : ParseObject - { - return ParseQuery.Or(queries.Concat(new[] { source })); - } + where T : ParseObject => ParseQuery.Or(queries.Concat(new[] { source })); /// /// Fetches this object with the data from the server. /// - public static Task FetchAsync(this T obj) where T : ParseObject - { - return obj.FetchAsyncInternal(CancellationToken.None).OnSuccess(t => (T) t.Result); - } + public static Task FetchAsync(this T obj) where T : ParseObject => obj.FetchAsyncInternal(CancellationToken.None).OnSuccess(t => (T) t.Result); /// /// Fetches this object with the data from the server. @@ -117,20 +92,14 @@ public static Task FetchAsync(this T obj) where T : ParseObject /// The ParseObject to fetch. /// The cancellation token. public static Task FetchAsync(this T obj, CancellationToken cancellationToken) - where T : ParseObject - { - return obj.FetchAsyncInternal(cancellationToken).OnSuccess(t => (T) t.Result); - } + where T : ParseObject => obj.FetchAsyncInternal(cancellationToken).OnSuccess(t => (T) t.Result); /// /// If this ParseObject has not been fetched (i.e. returns /// false), fetches this object with the data from the server. /// /// The ParseObject to fetch. - public static Task FetchIfNeededAsync(this T obj) where T : ParseObject - { - return obj.FetchIfNeededAsyncInternal(CancellationToken.None).OnSuccess(t => (T) t.Result); - } + public static Task FetchIfNeededAsync(this T obj) where T : ParseObject => obj.FetchIfNeededAsyncInternal(CancellationToken.None).OnSuccess(t => (T) t.Result); /// /// If this ParseObject has not been fetched (i.e. returns @@ -139,9 +108,6 @@ public static Task FetchIfNeededAsync(this T obj) where T : ParseObject /// The ParseObject to fetch. /// The cancellation token. public static Task FetchIfNeededAsync(this T obj, CancellationToken cancellationToken) - where T : ParseObject - { - return obj.FetchIfNeededAsyncInternal(cancellationToken).OnSuccess(t => (T) t.Result); - } + where T : ParseObject => obj.FetchIfNeededAsyncInternal(cancellationToken).OnSuccess(t => (T) t.Result); } } diff --git a/Parse/Public/ParseFieldNameAttribute.cs b/Parse/Utilities/ParseFieldNameAttribute.cs similarity index 82% rename from Parse/Public/ParseFieldNameAttribute.cs rename to Parse/Utilities/ParseFieldNameAttribute.cs index 7581ddb0..1a5e6eb3 100644 --- a/Parse/Public/ParseFieldNameAttribute.cs +++ b/Parse/Utilities/ParseFieldNameAttribute.cs @@ -1,10 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Parse { @@ -19,10 +15,7 @@ public sealed class ParseFieldNameAttribute : Attribute /// /// The name of the field on the ParseObject that the /// property represents. - public ParseFieldNameAttribute(string fieldName) - { - FieldName = fieldName; - } + public ParseFieldNameAttribute(string fieldName) => FieldName = fieldName; /// /// Gets the name of the field represented by this property. diff --git a/Parse/Internal/Utilities/ParseFileExtensions.cs b/Parse/Utilities/ParseFileExtensions.cs similarity index 88% rename from Parse/Internal/Utilities/ParseFileExtensions.cs rename to Parse/Utilities/ParseFileExtensions.cs index a5bbd236..31288c3d 100644 --- a/Parse/Internal/Utilities/ParseFileExtensions.cs +++ b/Parse/Utilities/ParseFileExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; -using System.Collections.Generic; namespace Parse.Core.Internal { @@ -17,9 +16,6 @@ namespace Parse.Core.Internal /// public static class ParseFileExtensions { - public static ParseFile Create(string name, Uri uri, string mimeType = null) - { - return new ParseFile(name, uri, mimeType); - } + public static ParseFile Create(string name, Uri uri, string mimeType = null) => new ParseFile(name, uri, mimeType); } } diff --git a/Parse/Internal/Utilities/ParseObjectExtensions.cs b/Parse/Utilities/ParseObjectExtensions.cs similarity index 63% rename from Parse/Internal/Utilities/ParseObjectExtensions.cs rename to Parse/Utilities/ParseObjectExtensions.cs index 9436a542..1803bd6d 100644 --- a/Parse/Internal/Utilities/ParseObjectExtensions.cs +++ b/Parse/Utilities/ParseObjectExtensions.cs @@ -1,6 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Collections.Generic; namespace Parse.Core.Internal @@ -17,44 +16,20 @@ namespace Parse.Core.Internal /// public static class ParseObjectExtensions { - public static T FromState(IObjectState state, string defaultClassName) where T : ParseObject - { - return ParseObject.FromState(state, defaultClassName); - } - - public static IObjectState GetState(this ParseObject obj) - { - return obj.State; - } - - public static void HandleFetchResult(this ParseObject obj, IObjectState serverState) - { - obj.HandleFetchResult(serverState); - } - - public static IDictionary GetCurrentOperations(this ParseObject obj) - { - return obj.CurrentOperations; - } - - public static IEnumerable DeepTraversal(object root, bool traverseParseObjects = false, bool yieldRoot = false) - { - return ParseObject.DeepTraversal(root, traverseParseObjects, yieldRoot); - } - - public static void SetIfDifferent(this ParseObject obj, string key, T value) - { - obj.SetIfDifferent(key, value); - } - - public static IDictionary ServerDataToJSONObjectForSerialization(this ParseObject obj) - { - return obj.ServerDataToJSONObjectForSerialization(); - } - - public static void Set(this ParseObject obj, string key, object value) - { - obj.Set(key, value); - } + public static T FromState(IObjectState state, string defaultClassName) where T : ParseObject => ParseObject.FromState(state, defaultClassName); + + public static IObjectState GetState(this ParseObject obj) => obj.State; + + public static void HandleFetchResult(this ParseObject obj, IObjectState serverState) => obj.HandleFetchResult(serverState); + + public static IDictionary GetCurrentOperations(this ParseObject obj) => obj.CurrentOperations; + + public static IEnumerable DeepTraversal(object root, bool traverseParseObjects = false, bool yieldRoot = false) => ParseObject.DeepTraversal(root, traverseParseObjects, yieldRoot); + + public static void SetIfDifferent(this ParseObject obj, string key, T value) => obj.SetIfDifferent(key, value); + + public static IDictionary ServerDataToJSONObjectForSerialization(this ParseObject obj) => obj.ServerDataToJSONObjectForSerialization(); + + public static void Set(this ParseObject obj, string key, object value) => obj.Set(key, value); } } diff --git a/Parse/Public/ParseQueryExtensions.cs b/Parse/Utilities/ParseQueryExtensions.cs similarity index 85% rename from Parse/Public/ParseQueryExtensions.cs rename to Parse/Utilities/ParseQueryExtensions.cs index 6b0d370b..d669ab29 100644 --- a/Parse/Public/ParseQueryExtensions.cs +++ b/Parse/Utilities/ParseQueryExtensions.cs @@ -1,18 +1,23 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Common.Internal; -using Parse.Core.Internal; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; +using Parse.Common.Internal; -namespace Parse +namespace Parse.Core.Internal { /// - /// Provides extension methods for to support - /// Linq-style queries. + /// So here's the deal. We have a lot of internal APIs for ParseObject, ParseUser, 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 ParseQueryExtensions { @@ -59,50 +64,35 @@ static ParseQueryExtensions() /// /// Gets a MethodInfo for a top-level method call. /// - private static MethodInfo GetMethod(Expression> expression) - { - return (expression.Body as MethodCallExpression).Method; - } + private static MethodInfo GetMethod(Expression> expression) => (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( + 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( + 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(ParseObject obj, string key) - { - throw new NotImplementedException( + private static bool ContainsKeyStub(ParseObject 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(ParseObject obj, string key) - { - throw new NotImplementedException( + private static bool NotContainsKeyStub(ParseObject 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 @@ -148,16 +138,16 @@ private class ObjectNormalizer : ExpressionVisitor { protected override Expression VisitIndex(IndexExpression node) { - var visitedObject = Visit(node.Object); - var indexer = visitedObject as MethodCallExpression; + Expression visitedObject = Visit(node.Object); + MethodCallExpression indexer = visitedObject as MethodCallExpression; if (IsParseObjectGet(indexer)) { - var indexValue = GetValue(node.Arguments[0]) as string; + string 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; + string newPath = GetValue(indexer.Arguments[0]) + "." + indexValue; return Expression.Call(indexer.Object, getMethod.MakeGenericMethod(node.Type), Expression.Constant(newPath, typeof(string))); @@ -171,11 +161,11 @@ protected override Expression VisitIndex(IndexExpression node) /// protected override Expression VisitMember(MemberExpression node) { - var fieldName = node.Member.GetCustomAttribute(); + ParseFieldNameAttribute fieldName = node.Member.GetCustomAttribute(); if (fieldName != null && typeof(ParseObject).GetTypeInfo().IsAssignableFrom(node.Expression.Type.GetTypeInfo())) { - var newPath = fieldName.FieldName; + string newPath = fieldName.FieldName; return Expression.Call(node.Expression, getMethod.MakeGenericMethod(node.Type), Expression.Constant(newPath, typeof(string))); @@ -188,7 +178,7 @@ protected override Expression VisitMember(MemberExpression node) /// protected override Expression VisitUnary(UnaryExpression node) { - var methodCall = Visit(node.Operand) as MethodCallExpression; + MethodCallExpression methodCall = Visit(node.Operand) as MethodCallExpression; if ((node.NodeType == ExpressionType.Convert || node.NodeType == ExpressionType.ConvertChecked) && IsParseObjectGet(methodCall)) @@ -205,7 +195,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) // Turn parseObject["foo"] into parseObject.Get("foo") if (node.Method.Name == "get_Item" && node.Object is ParameterExpression) { - var indexPath = GetValue(node.Arguments[0]) as string; + string indexPath = GetValue(node.Arguments[0]) as string; return Expression.Call(node.Object, getMethod.MakeGenericMethod(typeof(object)), Expression.Constant(indexPath, typeof(string))); @@ -214,16 +204,16 @@ protected override Expression VisitMethodCall(MethodCallExpression node) // Turn parseObject.Get("foo")["bar"] into parseObject.Get("foo.bar") if (node.Method.Name == "get_Item" || IsParseObjectGet(node)) { - var visitedObject = Visit(node.Object); - var indexer = visitedObject as MethodCallExpression; + Expression visitedObject = Visit(node.Object); + MethodCallExpression indexer = visitedObject as MethodCallExpression; if (IsParseObjectGet(indexer)) { - var indexValue = GetValue(node.Arguments[0]) as string; + string 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; + string newPath = GetValue(indexer.Arguments[0]) + "." + indexValue; return Expression.Call(indexer.Object, getMethod.MakeGenericMethod(node.Type), Expression.Constant(newPath, typeof(string))); @@ -246,8 +236,8 @@ private class WhereNormalizer : ExpressionVisitor /// 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 leftTransformed = new ObjectNormalizer().Visit(node.Left) as MethodCallExpression; + MethodCallExpression rightTransformed = new ObjectNormalizer().Visit(node.Right) as MethodCallExpression; MethodCallExpression objectExpression; Expression filterExpression; @@ -327,8 +317,8 @@ protected override Expression VisitUnary(UnaryExpression node) // Normalizes inversion if (node.NodeType == ExpressionType.Not) { - var visitedOperand = Visit(node.Operand); - var binaryOperand = visitedOperand as BinaryExpression; + Expression visitedOperand = Visit(node.Operand); + BinaryExpression binaryOperand = visitedOperand as BinaryExpression; if (binaryOperand != null) { switch (binaryOperand.NodeType) @@ -348,20 +338,20 @@ protected override Expression VisitUnary(UnaryExpression node) } } - var methodCallOperand = visitedOperand as MethodCallExpression; + MethodCallExpression methodCallOperand = visitedOperand as MethodCallExpression; if (methodCallOperand != null) { if (methodCallOperand.Method.IsGenericMethod) { if (methodCallOperand.Method.GetGenericMethodDefinition() == containsMethod) { - var genericNotContains = notContainsMethod.MakeGenericMethod( + MethodInfo genericNotContains = notContainsMethod.MakeGenericMethod( methodCallOperand.Method.GetGenericArguments()); return Expression.Call(genericNotContains, methodCallOperand.Arguments.ToArray()); } if (methodCallOperand.Method.GetGenericMethodDefinition() == notContainsMethod) { - var genericContains = containsMethod.MakeGenericMethod( + MethodInfo genericContains = containsMethod.MakeGenericMethod( methodCallOperand.Method.GetGenericArguments()); return Expression.Call(genericContains, methodCallOperand.Arguments.ToArray()); } @@ -389,8 +379,8 @@ protected override Expression VisitMethodCall(MethodCallExpression node) 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; + MethodCallExpression obj = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; + MethodCallExpression parameter = new ObjectNormalizer().Visit(node.Arguments[0]) as MethodCallExpression; if ((IsParseObjectGet(obj) && (obj.Object is ParameterExpression)) || (IsParseObjectGet(parameter) && (parameter.Object is ParameterExpression))) { @@ -404,22 +394,22 @@ protected override Expression VisitMethodCall(MethodCallExpression node) node.Method.ReturnType == typeof(bool) && node.Method.GetParameters().Length <= 2) { - var collection = node.Method.GetParameters().Length == 1 ? + Expression 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]) + int parameterIndex = node.Method.GetParameters().Length - 1; + MethodCallExpression parameter = new ObjectNormalizer().Visit(node.Arguments[parameterIndex]) as MethodCallExpression; if (IsParseObjectGet(parameter) && (parameter.Object is ParameterExpression)) { - var genericContains = containsMethod.MakeGenericMethod(parameter.Type); + MethodInfo genericContains = containsMethod.MakeGenericMethod(parameter.Type); return Expression.Call(genericContains, collection, parameter); } - var target = new ObjectNormalizer().Visit(collection) as MethodCallExpression; - var element = node.Arguments[parameterIndex]; + MethodCallExpression target = new ObjectNormalizer().Visit(collection) as MethodCallExpression; + Expression element = node.Arguments[parameterIndex]; if (IsParseObjectGet(target) && (target.Object is ParameterExpression)) { - var genericContains = containsMethod.MakeGenericMethod(element.Type); + MethodInfo genericContains = containsMethod.MakeGenericMethod(element.Type); return Expression.Call(genericContains, target, element); } } @@ -429,7 +419,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) node.Method.ReturnType == typeof(bool) && node.Method.GetParameters().Length == 1) { - var getter = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; + MethodCallExpression getter = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; Expression target = null; string path = null; if (IsParseObjectGet(getter) && getter.Object is ParameterExpression) @@ -465,19 +455,18 @@ private static ParseQuery WhereMethodCall( return source.WhereEqualTo(GetValue(node.Arguments[0]) as string, true); } - MethodInfo translatedMethod; - if (functionMappings.TryGetValue(node.Method, out translatedMethod)) + if (functionMappings.TryGetValue(node.Method, out MethodInfo translatedMethod)) { - var objTransformed = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; + MethodCallExpression objTransformed = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; if (!(IsParseObjectGet(objTransformed) && objTransformed.Object == expression.Parameters[0])) { throw new InvalidOperationException( "The left-hand side of a supported function call must be a ParseObject field access."); } - var fieldPath = GetValue(objTransformed.Arguments[0]); - var containedIn = GetValue(node.Arguments[0]); - var queryType = translatedMethod.DeclaringType.GetGenericTypeDefinition() + object fieldPath = GetValue(objTransformed.Arguments[0]); + object containedIn = GetValue(node.Arguments[0]); + Type queryType = translatedMethod.DeclaringType.GetGenericTypeDefinition() .MakeGenericType(typeof(T)); translatedMethod = ReflectionHelpers.GetMethod(queryType, translatedMethod.Name, @@ -513,7 +502,7 @@ private static ParseQuery WhereMethodCall( // someList.Contains(obj.Get("path")) if (IsParseObjectGet(node.Arguments[1] as MethodCallExpression)) { - var collection = GetValue(node.Arguments[0]) as System.Collections.IEnumerable; + System.Collections.IEnumerable collection = GetValue(node.Arguments[0]) as System.Collections.IEnumerable; return source.WhereContainedIn( GetValue(((MethodCallExpression) node.Arguments[1]).Arguments[0]) as string, collection.Cast()); @@ -532,7 +521,7 @@ private static ParseQuery WhereMethodCall( // !someList.Contains(obj.Get("path")) if (IsParseObjectGet(node.Arguments[1] as MethodCallExpression)) { - var collection = GetValue(node.Arguments[0]) as System.Collections.IEnumerable; + System.Collections.IEnumerable collection = GetValue(node.Arguments[0]) as System.Collections.IEnumerable; return source.WhereNotContainedIn( GetValue(((MethodCallExpression) node.Arguments[1]).Arguments[0]) as string, collection.Cast()); @@ -549,7 +538,7 @@ private static ParseQuery WhereBinaryExpression( this ParseQuery source, Expression> expression, BinaryExpression node) where T : ParseObject { - var leftTransformed = new ObjectNormalizer().Visit(node.Left) as MethodCallExpression; + MethodCallExpression leftTransformed = new ObjectNormalizer().Visit(node.Left) as MethodCallExpression; if (!(IsParseObjectGet(leftTransformed) && leftTransformed.Object == expression.Parameters[0])) @@ -558,8 +547,8 @@ private static ParseQuery WhereBinaryExpression( "Where expressions must have one side be a field operation on a ParseObject."); } - var fieldPath = GetValue(leftTransformed.Arguments[0]) as string; - var filterValue = GetValue(node.Right); + string fieldPath = GetValue(leftTransformed.Arguments[0]) as string; + object filterValue = GetValue(node.Right); if (filterValue != null && !ParseEncoder.IsValidType(filterValue)) { @@ -603,7 +592,7 @@ public static ParseQuery Where( where TSource : ParseObject { // Handle top-level logic operators && and || - var binaryExpression = predicate.Body as BinaryExpression; + BinaryExpression binaryExpression = predicate.Body as BinaryExpression; if (binaryExpression != null) { if (binaryExpression.NodeType == ExpressionType.AndAlso) @@ -616,32 +605,32 @@ public static ParseQuery Where( } if (binaryExpression.NodeType == ExpressionType.OrElse) { - var left = source.Where(Expression.Lambda>( + ParseQuery left = source.Where(Expression.Lambda>( binaryExpression.Left, predicate.Parameters)); - var right = source.Where(Expression.Lambda>( + ParseQuery right = source.Where(Expression.Lambda>( binaryExpression.Right, predicate.Parameters)); return left.Or(right); } } - var normalized = new WhereNormalizer().Visit(predicate.Body); + Expression normalized = new WhereNormalizer().Visit(predicate.Body); - var methodCallExpr = normalized as MethodCallExpression; + MethodCallExpression methodCallExpr = normalized as MethodCallExpression; if (methodCallExpr != null) { return source.WhereMethodCall(predicate, methodCallExpr); } - var binaryExpr = normalized as BinaryExpression; + BinaryExpression binaryExpr = normalized as BinaryExpression; if (binaryExpr != null) { return source.WhereBinaryExpression(predicate, binaryExpr); } - var unaryExpr = normalized as UnaryExpression; + UnaryExpression unaryExpr = normalized as UnaryExpression; if (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Not) { - var node = unaryExpr.Operand as MethodCallExpression; + MethodCallExpression node = unaryExpr.Operand as MethodCallExpression; if (IsParseObjectGet(node) && (node.Type == typeof(bool) || node.Type == typeof(bool?))) { // This is a raw boolean field access like 'where !obj.Get("foo")' @@ -661,8 +650,8 @@ private static string GetOrderByPath( Expression> keySelector) { string result = null; - var normalized = new ObjectNormalizer().Visit(keySelector.Body); - var callExpr = normalized as MethodCallExpression; + Expression normalized = new ObjectNormalizer().Visit(keySelector.Body); + MethodCallExpression callExpr = normalized as MethodCallExpression; if (IsParseObjectGet(callExpr) && callExpr.Object == keySelector.Parameters[0]) { // We're operating on the parameter @@ -687,10 +676,7 @@ private static string GetOrderByPath( /// the key specified in the keySelector. public static ParseQuery OrderBy( this ParseQuery source, Expression> keySelector) - where TSource : ParseObject - { - return source.OrderBy(GetOrderByPath(keySelector)); - } + where TSource : ParseObject => source.OrderBy(GetOrderByPath(keySelector)); /// /// Orders a query based upon the key selector provided. @@ -703,10 +689,7 @@ public static ParseQuery OrderBy( /// the key specified in the keySelector. public static ParseQuery OrderByDescending( this ParseQuery source, Expression> keySelector) - where TSource : ParseObject - { - return source.OrderByDescending(GetOrderByPath(keySelector)); - } + where TSource : ParseObject => source.OrderByDescending(GetOrderByPath(keySelector)); /// /// Performs a subsequent ordering of a query based upon the key selector provided. @@ -719,10 +702,7 @@ public static ParseQuery OrderByDescending( /// the key specified in the keySelector. public static ParseQuery ThenBy( this ParseQuery source, Expression> keySelector) - where TSource : ParseObject - { - return source.ThenBy(GetOrderByPath(keySelector)); - } + where TSource : ParseObject => source.ThenBy(GetOrderByPath(keySelector)); /// /// Performs a subsequent ordering of a query based upon the key selector provided. @@ -735,10 +715,7 @@ public static ParseQuery ThenBy( /// the key specified in the keySelector. public static ParseQuery ThenByDescending( this ParseQuery source, Expression> keySelector) - where TSource : ParseObject - { - return source.ThenByDescending(GetOrderByPath(keySelector)); - } + where TSource : ParseObject => source.ThenByDescending(GetOrderByPath(keySelector)); /// /// Correlates the elements of two queries based on matching keys. @@ -774,7 +751,7 @@ public static ParseQuery Join( if (resultSelector.Body == resultSelector.Parameters[1]) { // The inner object was selected. - return inner.Join( + return inner.Join( outer, innerKeySelector, outerKeySelector, @@ -792,12 +769,12 @@ public static ParseQuery Join( MethodCallExpression innerAsGet = innerNormalized as MethodCallExpression; if (IsParseObjectGet(outerAsGet) && outerAsGet.Object == outerKeySelector.Parameters[0]) { - var outerKey = GetValue(outerAsGet.Arguments[0]) as string; + string outerKey = GetValue(outerAsGet.Arguments[0]) as string; if (IsParseObjectGet(innerAsGet) && innerAsGet.Object == innerKeySelector.Parameters[0]) { // Both are key accesses, so treat this as a WhereMatchesKeyInQuery - var innerKey = GetValue(innerAsGet.Arguments[0]) as string; + string innerKey = GetValue(innerAsGet.Arguments[0]) as string; return outer.WhereMatchesKeyInQuery(outerKey, innerKey, inner) as ParseQuery; } @@ -819,5 +796,11 @@ public static ParseQuery Join( throw new InvalidOperationException( "The key for the selected object must be a field access on the ParseObject."); } + + public static string GetClassName(this ParseQuery query) where T : ParseObject => query.ClassName; + + public static IDictionary BuildParameters(this ParseQuery query) where T : ParseObject => query.BuildParameters(false); + + public static object GetConstraint(this ParseQuery query, string key) where T : ParseObject => query.GetConstraint(key); } } diff --git a/Parse/Internal/Utilities/ParseRelationExtensions.cs b/Parse/Utilities/ParseRelationExtensions.cs similarity index 75% rename from Parse/Internal/Utilities/ParseRelationExtensions.cs rename to Parse/Utilities/ParseRelationExtensions.cs index 7f80ffb9..ed2511bf 100644 --- a/Parse/Internal/Utilities/ParseRelationExtensions.cs +++ b/Parse/Utilities/ParseRelationExtensions.cs @@ -1,8 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; -using System.Collections.Generic; - namespace Parse.Core.Internal { /// @@ -17,19 +14,10 @@ namespace Parse.Core.Internal /// public static class ParseRelationExtensions { - public static ParseRelation Create(ParseObject parent, string childKey) where T : ParseObject - { - return new ParseRelation(parent, childKey); - } + public static ParseRelation Create(ParseObject parent, string childKey) where T : ParseObject => new ParseRelation(parent, childKey); - public static ParseRelation Create(ParseObject parent, string childKey, string targetClassName) where T : ParseObject - { - return new ParseRelation(parent, childKey, targetClassName); - } + public static ParseRelation Create(ParseObject parent, string childKey, string targetClassName) where T : ParseObject => new ParseRelation(parent, childKey, targetClassName); - public static string GetTargetClassName(this ParseRelation relation) where T : ParseObject - { - return relation.TargetClassName; - } + public static string GetTargetClassName(this ParseRelation relation) where T : ParseObject => relation.TargetClassName; } } diff --git a/Parse/Internal/Utilities/ParseSessionExtensions.cs b/Parse/Utilities/ParseSessionExtensions.cs similarity index 75% rename from Parse/Internal/Utilities/ParseSessionExtensions.cs rename to Parse/Utilities/ParseSessionExtensions.cs index 131cafca..32296ed6 100644 --- a/Parse/Internal/Utilities/ParseSessionExtensions.cs +++ b/Parse/Utilities/ParseSessionExtensions.cs @@ -1,7 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -19,14 +17,8 @@ namespace Parse.Core.Internal /// public static class ParseSessionExtensions { - public static Task UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) - { - return ParseSession.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken); - } + public static Task UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) => ParseSession.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken); - public static Task RevokeAsync(string sessionToken, CancellationToken cancellationToken) - { - return ParseSession.RevokeAsync(sessionToken, cancellationToken); - } + public static Task RevokeAsync(string sessionToken, CancellationToken cancellationToken) => ParseSession.RevokeAsync(sessionToken, cancellationToken); } } diff --git a/Parse/Internal/Utilities/ParseUserExtensions.cs b/Parse/Utilities/ParseUserExtensions.cs similarity index 64% rename from Parse/Internal/Utilities/ParseUserExtensions.cs rename to Parse/Utilities/ParseUserExtensions.cs index e8fc4ede..9d8e5d60 100644 --- a/Parse/Internal/Utilities/ParseUserExtensions.cs +++ b/Parse/Utilities/ParseUserExtensions.cs @@ -1,6 +1,5 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -19,39 +18,18 @@ namespace Parse.Core.Internal /// public static class ParseUserExtensions { - public static IDictionary> GetAuthData(this ParseUser user) - { - return user.AuthData; - } - - public static Task UnlinkFromAsync(this ParseUser user, string authType, CancellationToken cancellationToken) - { - return user.UnlinkFromAsync(authType, cancellationToken); - } - - public static Task LogInWithAsync(string authType, CancellationToken cancellationToken) - { - return ParseUser.LogInWithAsync(authType, cancellationToken); - } - - public static Task LogInWithAsync(string authType, IDictionary data, CancellationToken cancellationToken) - { - return ParseUser.LogInWithAsync(authType, data, cancellationToken); - } - - public static Task LinkWithAsync(this ParseUser user, string authType, CancellationToken cancellationToken) - { - return user.LinkWithAsync(authType, cancellationToken); - } - - public static Task LinkWithAsync(this ParseUser user, string authType, IDictionary data, CancellationToken cancellationToken) - { - return user.LinkWithAsync(authType, data, cancellationToken); - } - - public static Task UpgradeToRevocableSessionAsync(this ParseUser user, CancellationToken cancellationToken) - { - return user.UpgradeToRevocableSessionAsync(cancellationToken); - } + public static IDictionary> GetAuthData(this ParseUser user) => user.AuthData; + + public static Task UnlinkFromAsync(this ParseUser user, string authType, CancellationToken cancellationToken) => user.UnlinkFromAsync(authType, cancellationToken); + + public static Task LogInWithAsync(string authType, CancellationToken cancellationToken) => ParseUser.LogInWithAsync(authType, cancellationToken); + + public static Task LogInWithAsync(string authType, IDictionary data, CancellationToken cancellationToken) => ParseUser.LogInWithAsync(authType, data, cancellationToken); + + public static Task LinkWithAsync(this ParseUser user, string authType, CancellationToken cancellationToken) => user.LinkWithAsync(authType, cancellationToken); + + public static Task LinkWithAsync(this ParseUser user, string authType, IDictionary data, CancellationToken cancellationToken) => user.LinkWithAsync(authType, data, cancellationToken); + + public static Task UpgradeToRevocableSessionAsync(this ParseUser user, CancellationToken cancellationToken) => user.UpgradeToRevocableSessionAsync(cancellationToken); } } diff --git a/Parse/Internal/Utilities/ReflectionHelpers.cs b/Parse/Utilities/ReflectionHelpers.cs similarity index 74% rename from Parse/Internal/Utilities/ReflectionHelpers.cs rename to Parse/Utilities/ReflectionHelpers.cs index 6008ce25..09299db1 100644 --- a/Parse/Internal/Utilities/ReflectionHelpers.cs +++ b/Parse/Utilities/ReflectionHelpers.cs @@ -1,87 +1,79 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; -using System.Reflection; using System.Collections.Generic; using System.Linq; +using System.Reflection; namespace Parse.Common.Internal { public static class ReflectionHelpers { - public static IEnumerable GetProperties(Type type) - { + public static IEnumerable GetProperties(Type type) => #if MONO || UNITY return type.GetProperties(); #else - return type.GetRuntimeProperties(); + type.GetRuntimeProperties(); #endif - } - public static MethodInfo GetMethod(Type type, string name, Type[] parameters) - { + + public static MethodInfo GetMethod(Type type, string name, Type[] parameters) => #if MONO || UNITY return type.GetMethod(name, parameters); #else - return type.GetRuntimeMethod(name, parameters); + type.GetRuntimeMethod(name, parameters); #endif - } - public static bool IsPrimitive(Type type) - { + + public static bool IsPrimitive(Type type) => #if MONO || UNITY return type.IsPrimitive; #else - return type.GetTypeInfo().IsPrimitive; + type.GetTypeInfo().IsPrimitive; #endif - } - public static IEnumerable GetInterfaces(Type type) - { + + public static IEnumerable GetInterfaces(Type type) => #if MONO || UNITY return type.GetInterfaces(); #else - return type.GetTypeInfo().ImplementedInterfaces; + type.GetTypeInfo().ImplementedInterfaces; #endif - } - public static bool IsConstructedGenericType(Type type) - { + + public static bool IsConstructedGenericType(Type type) => #if UNITY return type.IsGenericType && !type.IsGenericTypeDefinition; #else - return type.IsConstructedGenericType; + type.IsConstructedGenericType; #endif - } - public static IEnumerable GetConstructors(Type type) - { + + 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 + type.GetTypeInfo().DeclaredConstructors .Where(c => (c.Attributes & MethodAttributes.Static) == 0); #endif - } - public static Type[] GetGenericTypeArguments(Type type) - { + + public static Type[] GetGenericTypeArguments(Type type) => #if UNITY return type.GetGenericArguments(); #else - return type.GenericTypeArguments; + type.GenericTypeArguments; #endif - } - public static PropertyInfo GetProperty(Type type, string name) - { + + public static PropertyInfo GetProperty(Type type, string name) => #if MONO || UNITY return type.GetProperty(name); #else - return type.GetRuntimeProperty(name); + type.GetRuntimeProperty(name); #endif - } + /// /// This method helps simplify the process of getting a constructor for a type. @@ -93,7 +85,7 @@ public static PropertyInfo GetProperty(Type type, string name) /// public static ConstructorInfo FindConstructor(this Type self, params Type[] parameterTypes) { - var constructors = + IEnumerable constructors = from constructor in GetConstructors(self) let parameters = constructor.GetParameters() let types = from p in parameters select p.ParameterType @@ -113,13 +105,12 @@ public static bool IsNullable(Type t) return isGeneric && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)); } - public static IEnumerable GetCustomAttributes(this Assembly assembly) where T : Attribute - { + 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); + CustomAttributeExtensions.GetCustomAttributes(assembly); #endif - } + } } diff --git a/Parse/Internal/Utilities/StorageManager.cs b/Parse/Utilities/StorageManager.cs similarity index 100% rename from Parse/Internal/Utilities/StorageManager.cs rename to Parse/Utilities/StorageManager.cs diff --git a/Parse/Internal/Utilities/SynchronizedEventHandler.cs b/Parse/Utilities/SynchronizedEventHandler.cs similarity index 88% rename from Parse/Internal/Utilities/SynchronizedEventHandler.cs rename to Parse/Utilities/SynchronizedEventHandler.cs index 30f8f081..c3da3e96 100644 --- a/Parse/Internal/Utilities/SynchronizedEventHandler.cs +++ b/Parse/Utilities/SynchronizedEventHandler.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -35,7 +34,7 @@ public void Add(Delegate del) { factory = Task.Factory; } - foreach (var d in del.GetInvocationList()) + foreach (Delegate d in del.GetInvocationList()) { delegates.AddLast(new Tuple(d, factory)); } @@ -50,9 +49,9 @@ public void Remove(Delegate del) { return; } - foreach (var d in del.GetInvocationList()) + foreach (Delegate d in del.GetInvocationList()) { - var node = delegates.First; + LinkedListNode> node = delegates.First; while (node != null) { if (node.Value.Item1 == d) @@ -69,12 +68,12 @@ public void Remove(Delegate del) public Task Invoke(object sender, T args) { IEnumerable> toInvoke; - var toContinue = new[] { Task.FromResult(0) }; + Task[] toContinue = new[] { Task.FromResult(0) }; lock (delegates) { toInvoke = delegates.ToList(); } - var invocations = toInvoke + List> invocations = toInvoke .Select(p => p.Item2.ContinueWhenAll(toContinue, _ => p.Item1.DynamicInvoke(sender, args))) .ToList(); diff --git a/Parse/Internal/Utilities/TaskQueue.cs b/Parse/Utilities/TaskQueue.cs similarity index 99% rename from Parse/Internal/Utilities/TaskQueue.cs rename to Parse/Utilities/TaskQueue.cs index b39daac8..a9e53f53 100644 --- a/Parse/Internal/Utilities/TaskQueue.cs +++ b/Parse/Utilities/TaskQueue.cs @@ -1,7 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; diff --git a/Parse/Internal/Utilities/ThreadingUtilities.cs b/Parse/Utilities/ThreadingUtilities.cs similarity index 90% rename from Parse/Internal/Utilities/ThreadingUtilities.cs rename to Parse/Utilities/ThreadingUtilities.cs index 5edca411..37b1e82e 100644 --- a/Parse/Internal/Utilities/ThreadingUtilities.cs +++ b/Parse/Utilities/ThreadingUtilities.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; namespace Parse.Internal.Utilities { diff --git a/Parse/Internal/Utilities/XamarinAttributes.cs b/Parse/Utilities/XamarinAttributes.cs similarity index 99% rename from Parse/Internal/Utilities/XamarinAttributes.cs rename to Parse/Utilities/XamarinAttributes.cs index d7930b06..eae589d1 100644 --- a/Parse/Internal/Utilities/XamarinAttributes.cs +++ b/Parse/Utilities/XamarinAttributes.cs @@ -31,9 +31,7 @@ internal class PreserveWrapperTypes /// /// This also applies to Unity. /// - private static List CreateWrapperTypes() - { - return new List { + private static List CreateWrapperTypes() => new List { typeof(FlexibleListWrapper), typeof(FlexibleListWrapper), typeof(FlexibleListWrapper), @@ -404,6 +402,5 @@ private static List CreateWrapperTypes() typeof(FlexibleDictionaryWrapper), typeof(FlexibleDictionaryWrapper), }; - } } } From bbc07abb4c770bd0248165321cfed4d44cbc0567 Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Mon, 23 Mar 2020 03:16:49 -0700 Subject: [PATCH 02/24] Organize file structure further, and extract certain configuration items into core dependency injection container. --- Parse.Test/ACLTests.cs | 4 +- Parse.Test/AnalyticsControllerTests.cs | 3 +- Parse.Test/AnalyticsTests.cs | 1 + Parse.Test/CloudControllerTests.cs | 3 +- Parse.Test/CommandTests.cs | 46 +-- Parse.Test/CurrentUserControllerTests.cs | 2 +- Parse.Test/FileControllerTests.cs | 3 +- Parse.Test/InstallationIdControllerTests.cs | 6 +- Parse.Test/InstallationTests.cs | 2 +- Parse.Test/ObjectControllerTests.cs | 3 +- Parse.Test/ObjectTests.cs | 6 +- Parse.Test/SessionControllerTests.cs | 3 +- Parse.Test/SessionTests.cs | 10 +- Parse.Test/UserControllerTests.cs | 3 +- Parse.Test/UserTests.cs | 68 ++-- .../Library/IHostApplicationVersioningData.cs | 40 +++ .../Library/IMetadataController.cs | 17 + .../IParseCorePlugins.cs | 23 +- .../Analytics/IParseAnalyticsPlugins.cs | 2 +- .../Installation/IInstallationIdController.cs | 2 +- .../Notifications/IParsePushPlugins.cs | 2 +- .../Storage/IStorageConfiguration.cs | 15 + .../Storage/IStorageController.cs | 33 +- Parse/Library/Configuration.cs | 39 +++ .../Library/HostApplicationVersioningData.cs | 56 +++ Parse/Library/MetadataController.cs | 15 + Parse/Library/ParseCorePlugins.cs | 75 +++++ .../ParseDownloadProgressEventArgs.cs | 0 .../{Framework => Library}/ParseException.cs | 0 .../ParsePushNotificationEventArgs.cs | 0 .../ParseUploadProgressEventArgs.cs | 0 Parse/Management/LightParseCorePlugins.cs | 51 +++ Parse/Management/ParseCommand.cs | 2 +- Parse/Management/ParseCommandRunner.cs | 120 +++---- Parse/Management/ParseCorePlugins.cs | 318 ------------------ Parse/Modules/ParseModuleController.cs | 3 +- Parse/Parse.csproj | 14 +- Parse/ParseClient.cs | 261 +++----------- Parse/ParseInstallation.cs | 243 ++++++------- Parse/ParseObject.cs | 2 +- .../Analytics/ParseAnalyticsPlugins.cs | 1 + .../{Controller => }/ParseConfigController.cs | 0 .../ParseCurrentConfigController.cs | 0 .../Installation/InstallationIdController.cs | 4 +- .../ParseCurrentInstallationController.cs | 4 +- .../Platform/Notifications/ParsePushModule.cs | 2 +- .../Notifications/ParsePushPlugins.cs | 3 +- Parse/Properties/Resources.Designer.cs | 72 ++++ Parse/Properties/Resources.resx | 123 +++++++ ...entifierBasedCacheLocationConfiguration.cs | 45 +++ ...MetadataBasedCacheLocationConfiguration.cs | 39 +++ Parse/Storage/StorageController.cs | 237 ++++++++----- Parse/Utilities/HttpClient.Portable.cs | 30 +- Parse/Utilities/IHttpClient.cs | 2 +- Parse/Utilities/StorageManager.cs | 73 +--- 55 files changed, 1156 insertions(+), 975 deletions(-) create mode 100644 Parse/Abstractions/Library/IHostApplicationVersioningData.cs create mode 100644 Parse/Abstractions/Library/IMetadataController.cs rename Parse/Abstractions/{Management => Library}/IParseCorePlugins.cs (57%) create mode 100644 Parse/Abstractions/Storage/IStorageConfiguration.cs create mode 100644 Parse/Library/Configuration.cs create mode 100644 Parse/Library/HostApplicationVersioningData.cs create mode 100644 Parse/Library/MetadataController.cs create mode 100644 Parse/Library/ParseCorePlugins.cs rename Parse/{Framework => Library}/ParseDownloadProgressEventArgs.cs (100%) rename Parse/{Framework => Library}/ParseException.cs (100%) rename Parse/{Framework => Library}/ParsePushNotificationEventArgs.cs (100%) rename Parse/{Framework => Library}/ParseUploadProgressEventArgs.cs (100%) create mode 100644 Parse/Management/LightParseCorePlugins.cs delete mode 100644 Parse/Management/ParseCorePlugins.cs rename Parse/Platform/Configuration/{Controller => }/ParseConfigController.cs (100%) rename Parse/Platform/Configuration/{Controller => }/ParseCurrentConfigController.cs (100%) create mode 100644 Parse/Properties/Resources.Designer.cs create mode 100644 Parse/Properties/Resources.resx create mode 100644 Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs create mode 100644 Parse/Storage/MetadataBasedCacheLocationConfiguration.cs diff --git a/Parse.Test/ACLTests.cs b/Parse.Test/ACLTests.cs index c35b2c9c..f5061613 100644 --- a/Parse.Test/ACLTests.cs +++ b/Parse.Test/ACLTests.cs @@ -10,8 +10,8 @@ public class ACLTests [TestInitialize] public void SetUp() { - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); } [TestCleanup] diff --git a/Parse.Test/AnalyticsControllerTests.cs b/Parse.Test/AnalyticsControllerTests.cs index 925693b3..8ab0a5a5 100644 --- a/Parse.Test/AnalyticsControllerTests.cs +++ b/Parse.Test/AnalyticsControllerTests.cs @@ -8,6 +8,7 @@ using Moq; using Parse.Analytics.Internal; using Parse.Core.Internal; +using Parse.Library; namespace Parse.Test { @@ -15,7 +16,7 @@ namespace Parse.Test public class AnalyticsControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new ParseClient.Configuration { ApplicationID = "", Key = "" }); + public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "" }); [TestMethod] [AsyncStateMachine(typeof(AnalyticsControllerTests))] diff --git a/Parse.Test/AnalyticsTests.cs b/Parse.Test/AnalyticsTests.cs index 75f2d708..7f1d6d23 100644 --- a/Parse.Test/AnalyticsTests.cs +++ b/Parse.Test/AnalyticsTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Parse.Abstractions.Library; using Parse.Analytics.Internal; using Parse.Core.Internal; diff --git a/Parse.Test/CloudControllerTests.cs b/Parse.Test/CloudControllerTests.cs index 5832b6c7..8c0d388b 100644 --- a/Parse.Test/CloudControllerTests.cs +++ b/Parse.Test/CloudControllerTests.cs @@ -7,6 +7,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Parse.Core.Internal; +using Parse.Library; namespace Parse.Test { @@ -14,7 +15,7 @@ namespace Parse.Test public class CloudControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new ParseClient.Configuration { ApplicationID = "", Key = "" }); + public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "" }); [TestMethod] [AsyncStateMachine(typeof(CloudControllerTests))] diff --git a/Parse.Test/CommandTests.cs b/Parse.Test/CommandTests.cs index b9f134b6..77d8c461 100644 --- a/Parse.Test/CommandTests.cs +++ b/Parse.Test/CommandTests.cs @@ -7,16 +7,24 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Parse.Abstractions.Library; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Library; namespace Parse.Test { [TestClass] public class CommandTests { + Mock MockMetadataController { get; } = new Mock { }; + [TestInitialize] - public void SetUp() => ParseClient.Initialize(new ParseClient.Configuration { ApplicationID = "", Key = "" }); + public void SetUp() + { + ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "" }); + MockMetadataController.Setup(metadata => metadata.HostVersioningData).Returns(new HostApplicationVersioningData { BuildVersion = "1", DisplayVersion = "1", HostOSVersion = "1" }); + } [TestCleanup] public void TearDown() => ParseCorePlugins.Instance.Reset(); @@ -35,14 +43,14 @@ public void TestMakeCommand() [AsyncStateMachine(typeof(CommandTests))] public Task TestRunCommand() { - Mock mockHttpClient = new Mock(); - Mock mockInstallationIdController = new Mock(); + Mock mockHttpClient = new Mock(); + Mock mockInstallationIdController = new Mock(); Task> fakeResponse = Task.FromResult(new Tuple(HttpStatusCode.OK, "{}")); mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(fakeResponse); mockInstallationIdController.Setup(i => i.GetAsync()).Returns(Task.FromResult(null)); - return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => + return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, MockMetadataController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); @@ -55,13 +63,13 @@ public Task TestRunCommand() [AsyncStateMachine(typeof(CommandTests))] public Task TestRunCommandWithArrayResult() { - Mock mockHttpClient = new Mock(); - Mock mockInstallationIdController = new Mock(); + Mock mockHttpClient = new Mock(); + Mock mockInstallationIdController = new Mock(); mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "[]"))); mockInstallationIdController.Setup(i => i.GetAsync()).Returns(Task.FromResult(null)); - return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => + return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, MockMetadataController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); @@ -76,13 +84,13 @@ public Task TestRunCommandWithArrayResult() [AsyncStateMachine(typeof(CommandTests))] public Task TestRunCommandWithInvalidString() { - Mock mockHttpClient = new Mock(); - Mock mockInstallationIdController = new Mock(); + Mock mockHttpClient = new Mock(); + Mock mockInstallationIdController = new Mock(); mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "invalid"))); mockInstallationIdController.Setup(i => i.GetAsync()).Returns(Task.FromResult(null)); - return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => + return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, MockMetadataController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => { Assert.IsTrue(t.IsFaulted); Assert.IsFalse(t.IsCanceled); @@ -95,13 +103,13 @@ public Task TestRunCommandWithInvalidString() [AsyncStateMachine(typeof(CommandTests))] public Task TestRunCommandWithErrorCode() { - Mock mockHttpClient = new Mock(); - Mock mockInstallationIdController = new Mock(); + Mock mockHttpClient = new Mock(); + Mock mockInstallationIdController = new Mock(); mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.NotFound, "{ \"code\": 101, \"error\": \"Object not found.\" }"))); mockInstallationIdController.Setup(i => i.GetAsync()).Returns(Task.FromResult(null)); - return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => + return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, MockMetadataController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => { Assert.IsTrue(t.IsFaulted); Assert.IsFalse(t.IsCanceled); @@ -116,13 +124,13 @@ public Task TestRunCommandWithErrorCode() [AsyncStateMachine(typeof(CommandTests))] public Task TestRunCommandWithInternalServerError() { - Mock mockHttpClient = new Mock(); - Mock mockInstallationIdController = new Mock(); - mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.InternalServerError, null))); - - mockInstallationIdController.Setup(i => i.GetAsync()).Returns(Task.FromResult(null)); + Mock mockHttpClient = new Mock(); + Mock mockInstallationIdController = new Mock(); + + mockHttpClient.Setup(client => client.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.InternalServerError, null))); + mockInstallationIdController.Setup(installationController => installationController.GetAsync()).Returns(Task.FromResult(null)); - return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => + return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, MockMetadataController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => { Assert.IsTrue(t.IsFaulted); Assert.IsFalse(t.IsCanceled); diff --git a/Parse.Test/CurrentUserControllerTests.cs b/Parse.Test/CurrentUserControllerTests.cs index 65059a12..e4eb24c8 100644 --- a/Parse.Test/CurrentUserControllerTests.cs +++ b/Parse.Test/CurrentUserControllerTests.cs @@ -14,7 +14,7 @@ namespace Parse.Test public class CurrentUserControllerTests { [TestInitialize] - public void SetUp() => ParseObject.RegisterSubclass(); + public void SetUp() => ParseObject.RegisterDerivative(); [TestCleanup] public void TearDown() => ParseCorePlugins.Instance.Reset(); diff --git a/Parse.Test/FileControllerTests.cs b/Parse.Test/FileControllerTests.cs index 88a51912..dc311539 100644 --- a/Parse.Test/FileControllerTests.cs +++ b/Parse.Test/FileControllerTests.cs @@ -8,6 +8,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Parse.Core.Internal; +using Parse.Library; namespace Parse.Test { @@ -15,7 +16,7 @@ namespace Parse.Test public class FileControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new ParseClient.Configuration { ApplicationID = "", Key = "" }); + public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "" }); [TestMethod] [AsyncStateMachine(typeof(FileControllerTests))] diff --git a/Parse.Test/InstallationIdControllerTests.cs b/Parse.Test/InstallationIdControllerTests.cs index b0d6ea83..8a37b4f2 100644 --- a/Parse.Test/InstallationIdControllerTests.cs +++ b/Parse.Test/InstallationIdControllerTests.cs @@ -18,7 +18,7 @@ public class InstallationIdControllerTests public void TestConstructor() { Mock storageMock = new Mock(MockBehavior.Strict); - InstallationIdController controller = new InstallationIdController(storageMock.Object); + ParseInstallationController controller = new ParseInstallationController(storageMock.Object); // Make sure it didn't touch storageMock. storageMock.Verify(); @@ -33,7 +33,7 @@ public Task TestGet() storageMock.Setup(s => s.LoadAsync()).Returns(Task.FromResult(storageDictionary.Object)); - InstallationIdController controller = new InstallationIdController(storageMock.Object); + ParseInstallationController controller = new ParseInstallationController(storageMock.Object); return controller.GetAsync().ContinueWith(installationIdTask => { Assert.IsFalse(installationIdTask.IsFaulted); @@ -80,7 +80,7 @@ public Task TestSet() storageMock.Setup(s => s.LoadAsync()).Returns(Task.FromResult(storageDictionary.Object)); - InstallationIdController controller = new InstallationIdController(storageMock.Object); + ParseInstallationController controller = new ParseInstallationController(storageMock.Object); return controller.GetAsync().ContinueWith(installationIdTask => { diff --git a/Parse.Test/InstallationTests.cs b/Parse.Test/InstallationTests.cs index 326b0a2a..ceae0285 100644 --- a/Parse.Test/InstallationTests.cs +++ b/Parse.Test/InstallationTests.cs @@ -13,7 +13,7 @@ namespace Parse.Test public class InstallationTests { [TestInitialize] - public void SetUp() => ParseObject.RegisterSubclass(); + public void SetUp() => ParseObject.RegisterDerivative(); [TestCleanup] public void TearDown() => ParseCorePlugins.Instance = null; diff --git a/Parse.Test/ObjectControllerTests.cs b/Parse.Test/ObjectControllerTests.cs index 8f5ed1f6..b414a43d 100644 --- a/Parse.Test/ObjectControllerTests.cs +++ b/Parse.Test/ObjectControllerTests.cs @@ -8,6 +8,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Parse.Core.Internal; +using Parse.Library; namespace Parse.Test { @@ -15,7 +16,7 @@ namespace Parse.Test public class ObjectControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new ParseClient.Configuration { ApplicationID = "", Key = "" }); + public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "" }); [TestMethod] [AsyncStateMachine(typeof(ObjectControllerTests))] diff --git a/Parse.Test/ObjectTests.cs b/Parse.Test/ObjectTests.cs index afbb054a..43c3440a 100644 --- a/Parse.Test/ObjectTests.cs +++ b/Parse.Test/ObjectTests.cs @@ -50,7 +50,7 @@ public void TestParseObjectCreate() [TestMethod] public void TestParseObjectCreateWithGeneric() { - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); ParseObject obj = ParseObject.Create(); Assert.AreEqual("SubClass", obj.ClassName); @@ -99,7 +99,7 @@ public void TestRegisterSubclass() try { - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); ParseObject.Create(); ParseCorePlugins.Instance.SubclassingController.UnregisterSubclass(typeof(UnregisteredSubClass)); @@ -430,7 +430,7 @@ public void TestEnumerator() [TestMethod] public void TestGetQuery() { - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); ParseQuery query = ParseObject.GetQuery("UnregisteredSubClass"); Assert.AreEqual("UnregisteredSubClass", query.GetClassName()); diff --git a/Parse.Test/SessionControllerTests.cs b/Parse.Test/SessionControllerTests.cs index be9d1d9e..ccffe91e 100644 --- a/Parse.Test/SessionControllerTests.cs +++ b/Parse.Test/SessionControllerTests.cs @@ -8,6 +8,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Parse.Core.Internal; +using Parse.Library; namespace Parse.Test { @@ -15,7 +16,7 @@ namespace Parse.Test public class SessionControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new ParseClient.Configuration { ApplicationID = "", Key = "" }); + public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "" }); [TestMethod] [AsyncStateMachine(typeof(SessionControllerTests))] diff --git a/Parse.Test/SessionTests.cs b/Parse.Test/SessionTests.cs index e83ccab4..a8ef61eb 100644 --- a/Parse.Test/SessionTests.cs +++ b/Parse.Test/SessionTests.cs @@ -14,8 +14,8 @@ public class SessionTests [TestInitialize] public void SetUp() { - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); } [TestCleanup] @@ -61,7 +61,7 @@ public Task TestGetCurrentSession() SessionController = mockController.Object, CurrentUserController = mockCurrentUserController.Object, }; - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); return ParseSession.GetCurrentSessionAsync().ContinueWith(t => { @@ -139,8 +139,8 @@ public Task TestUpgradeToRevocableSession() SessionController = mockController.Object, CurrentUserController = mockCurrentUserController.Object, }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); CancellationTokenSource source = new CancellationTokenSource(); return ParseSessionExtensions.UpgradeToRevocableSessionAsync("someSession", source.Token).ContinueWith(t => diff --git a/Parse.Test/UserControllerTests.cs b/Parse.Test/UserControllerTests.cs index 81ddb355..130235ee 100644 --- a/Parse.Test/UserControllerTests.cs +++ b/Parse.Test/UserControllerTests.cs @@ -7,6 +7,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Parse.Core.Internal; +using Parse.Library; namespace Parse.Test { @@ -14,7 +15,7 @@ namespace Parse.Test public class UserControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new ParseClient.Configuration { ApplicationID = "", Key = "" }); + public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "" }); [TestMethod] [AsyncStateMachine(typeof(UserControllerTests))] diff --git a/Parse.Test/UserTests.cs b/Parse.Test/UserTests.cs index 51c6722f..11b4cd1f 100644 --- a/Parse.Test/UserTests.cs +++ b/Parse.Test/UserTests.cs @@ -15,8 +15,8 @@ public class UserTests [TestInitialize] public void SetUp() { - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); } [TestCleanup] @@ -142,8 +142,8 @@ public void TestIsAuthenticated() { CurrentUserController = mockCurrentUserController.Object }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); Assert.IsTrue(user.IsAuthenticated); } @@ -174,8 +174,8 @@ public void TestIsAuthenticatedWithOtherParseUser() { CurrentUserController = mockCurrentUserController.Object }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); Assert.IsFalse(user2.IsAuthenticated); } @@ -224,8 +224,8 @@ public Task TestSignUp() { UserController = mockController.Object }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); return user.SignUpAsync().ContinueWith(t => { @@ -265,8 +265,8 @@ public Task TestLogIn() { UserController = mockController.Object }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); return ParseUser.LogInAsync("ihave", "adream").ContinueWith(t => { @@ -298,8 +298,8 @@ public Task TestBecome() { UserController = mockController.Object }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); return ParseUser.BecomeAsync("llaKcolnu").ContinueWith(t => { @@ -335,8 +335,8 @@ public Task TestLogOut() CurrentUserController = mockCurrentUserController.Object, SessionController = mockSessionController.Object }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); return ParseUser.LogOutAsync().ContinueWith(t => { @@ -364,8 +364,8 @@ public void TestCurrentUser() { CurrentUserController = mockCurrentUserController.Object, }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); Assert.AreEqual(user, ParseUser.CurrentUser); } @@ -378,8 +378,8 @@ public void TestCurrentUserWithEmptyResult() { CurrentUserController = mockCurrentUserController.Object }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); Assert.IsNull(ParseUser.CurrentUser); } @@ -408,8 +408,8 @@ public Task TestRevocableSession() { SessionController = mockSessionController.Object }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); return user.UpgradeToRevocableSessionAsync(CancellationToken.None).ContinueWith(t => { @@ -430,8 +430,8 @@ public Task TestRequestPasswordReset() { UserController = mockController.Object }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); return ParseUser.RequestPasswordResetAsync("gogo@parse.com").ContinueWith(t => { @@ -472,8 +472,8 @@ public Task TestUserSave() ObjectController = mockObjectController.Object, CurrentUserController = new Mock().Object }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); user["Alliance"] = "rekt"; return user.SaveAsync().ContinueWith(t => @@ -521,8 +521,8 @@ public Task TestUserFetch() ObjectController = mockObjectController.Object, CurrentUserController = new Mock().Object }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); user["Alliance"] = "rekt"; return user.FetchAsync().ContinueWith(t => @@ -568,8 +568,8 @@ public Task TestLink() ObjectController = mockObjectController.Object, CurrentUserController = new Mock().Object }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); return user.LinkWithAsync("parse", new Dictionary(), CancellationToken.None).ContinueWith(t => { @@ -620,8 +620,8 @@ public Task TestUnlink() ObjectController = mockObjectController.Object, CurrentUserController = mockCurrentUserController.Object, }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); return user.UnlinkFromAsync("parse", CancellationToken.None).ContinueWith(t => { @@ -672,8 +672,8 @@ public Task TestUnlinkNonCurrentUser() ObjectController = mockObjectController.Object, CurrentUserController = mockCurrentUserController.Object, }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); return user.UnlinkFromAsync("parse", CancellationToken.None).ContinueWith(t => { @@ -712,8 +712,8 @@ public Task TestLogInWith() { UserController = mockController.Object }; - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); return ParseUserExtensions.LogInWithAsync("parse", new Dictionary(), CancellationToken.None).ContinueWith(t => { diff --git a/Parse/Abstractions/Library/IHostApplicationVersioningData.cs b/Parse/Abstractions/Library/IHostApplicationVersioningData.cs new file mode 100644 index 00000000..3ab60cd6 --- /dev/null +++ b/Parse/Abstractions/Library/IHostApplicationVersioningData.cs @@ -0,0 +1,40 @@ +namespace Parse.Abstractions.Library +{ + /// + /// In the event that you would like to use the Parse SDK + /// from a completely portable project, with no platform-specific library required, + /// to get full access to all of our features available on Parse Dashboard + /// (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 interface IHostApplicationVersioningData + { + /// + /// The build number of your app. + /// + string BuildVersion { get; } + + /// + /// The human friendly version number of your app. + /// + string DisplayVersion { get; } + + /// + /// The operating system version of the platform the SDK is operating in.. + /// + string HostOSVersion { get; } + + /// + /// Gets a value for whether or not this instance of is populated with default values. + /// + bool IsDefault { get; } + + /// + /// Gets a value for whether or not this instance of can currently be used for the generation of . + /// + bool CanBeUsedForInference { get; } + } +} diff --git a/Parse/Abstractions/Library/IMetadataController.cs b/Parse/Abstractions/Library/IMetadataController.cs new file mode 100644 index 00000000..09cff231 --- /dev/null +++ b/Parse/Abstractions/Library/IMetadataController.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Text; + +namespace Parse.Abstractions.Library +{ + /// + /// A controller for metadata. This is provided in a dependency injection container because if a beta feature is activated for a client managing a specific aspect of application operation, then this might need to be reflected in the application versioning information as it is used to determine the data cache location. + /// + /// This container could have been implemented as a or , due to it's simplicity but, more information may be added in the future so it is kept general. + public interface IMetadataController + { + /// + /// The version information of your application environment. + /// + public IHostApplicationVersioningData HostVersioningData { get; } + } +} diff --git a/Parse/Abstractions/Management/IParseCorePlugins.cs b/Parse/Abstractions/Library/IParseCorePlugins.cs similarity index 57% rename from Parse/Abstractions/Management/IParseCorePlugins.cs rename to Parse/Abstractions/Library/IParseCorePlugins.cs index 76cc4bac..d64f3ff5 100644 --- a/Parse/Abstractions/Management/IParseCorePlugins.cs +++ b/Parse/Abstractions/Library/IParseCorePlugins.cs @@ -1,16 +1,23 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using Parse.Common.Internal; +using Parse.Core.Internal; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Library { + /// + /// The dependency injection container for the .NET Parse SDK. + /// public interface IParseCorePlugins { - void Reset(); + IMetadataController MetadataController { get; } - IHttpClient HttpClient { get; } - IParseCommandRunner CommandRunner { get; } + IWebClient WebClient { get; } IStorageController StorageController { get; } + IObjectSubclassingController SubclassingController { get; } + + IParseInstallationController InstallationController { get; } + IParseCommandRunner CommandRunner { get; } IParseCloudCodeController CloudCodeController { get; } IParseConfigController ConfigController { get; } @@ -19,8 +26,12 @@ public interface IParseCorePlugins IParseQueryController QueryController { get; } IParseSessionController SessionController { get; } IParseUserController UserController { get; } - IObjectSubclassingController SubclassingController { get; } IParseCurrentUserController CurrentUserController { get; } - IInstallationIdController InstallationIdController { get; } + IParseCurrentConfigController CurrentConfigController { get; } + + /// + /// Sets the default controller instances if not explicitly overridden. This method should effectively perform a -coalescing assign on all of the properties of the implementation instance. + /// + public void SetDefaults(); } } \ No newline at end of file diff --git a/Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs b/Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs index 709b8e7c..38bd1bcc 100644 --- a/Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs +++ b/Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs @@ -1,7 +1,7 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. +using Parse.Abstractions.Library; using Parse.Analytics.Internal; -using Parse.Core.Internal; namespace Parse.Analytics { diff --git a/Parse/Abstractions/Platform/Installation/IInstallationIdController.cs b/Parse/Abstractions/Platform/Installation/IInstallationIdController.cs index a95cfb1f..47b978b9 100644 --- a/Parse/Abstractions/Platform/Installation/IInstallationIdController.cs +++ b/Parse/Abstractions/Platform/Installation/IInstallationIdController.cs @@ -5,7 +5,7 @@ namespace Parse.Core.Internal { - public interface IInstallationIdController + public interface IParseInstallationController { /// /// Sets current installationId and saves it to local storage. diff --git a/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs b/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs index 695bc288..9146ecaa 100644 --- a/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs +++ b/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs @@ -1,4 +1,4 @@ -using Parse.Core.Internal; +using Parse.Abstractions.Library; namespace Parse.Push.Internal { diff --git a/Parse/Abstractions/Storage/IStorageConfiguration.cs b/Parse/Abstractions/Storage/IStorageConfiguration.cs new file mode 100644 index 00000000..e2fa4a31 --- /dev/null +++ b/Parse/Abstractions/Storage/IStorageConfiguration.cs @@ -0,0 +1,15 @@ +using Parse.Abstractions.Library; + +namespace Parse.Abstractions.Storage +{ + /// + /// A unit that can generate a relative path to a persistent storage file. + /// + public interface ICacheLocationConfiguration + { + /// + /// The corresponding relative path generated by this . + /// + string GetRelativeStorageFilePath(IParseCorePlugins plugins); + } +} diff --git a/Parse/Abstractions/Storage/IStorageController.cs b/Parse/Abstractions/Storage/IStorageController.cs index b0925a84..edd27a7c 100644 --- a/Parse/Abstractions/Storage/IStorageController.cs +++ b/Parse/Abstractions/Storage/IStorageController.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; namespace Parse.Common.Internal @@ -8,6 +10,26 @@ namespace Parse.Common.Internal /// public interface IStorageController { + /// + /// Cleans up any temporary files and/or directories created during SDK operation. + /// + public void Clean(); + + /// + /// Gets the file wrapper for the specified . + /// + /// The relative path to the target file + /// An instance of wrapping the the value + FileInfo GetWrapperForRelativePersistentStorageFilePath(string path); + + /// + /// Transfers a file from to . + /// + /// + /// + /// A task that completes once the file move operation form to completes. + Task TransferAsync(string originFilePath, string targetFilePath); + /// /// Load the contents of this storage controller asynchronously. /// @@ -27,17 +49,8 @@ public interface IStorageController /// /// They key type of the dictionary. /// The value type of the dictionary. - public interface IStorageDictionary : IEnumerable> + public interface IStorageDictionary : IDictionary { - 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. /// diff --git a/Parse/Library/Configuration.cs b/Parse/Library/Configuration.cs new file mode 100644 index 00000000..65531c0d --- /dev/null +++ b/Parse/Library/Configuration.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Parse.Library +{ + /// + /// Represents the configuration of the Parse SDK. + /// + public struct Configuration + { + internal bool Testing { get; set; } + + /// + /// The App ID of your app. + /// + public string ApplicationID { get; set; } + + /// + /// A URI pointing to the target Parse Server instance hosting the app targeted by . + /// + public string ServerURI { get; set; } + + /// + /// The .NET Key for the Parse app targeted by . + /// + public string Key { get; set; } + + /// + /// The Master Key for the Parse app targeted by . + /// + public string MasterKey { get; set; } + + /// + /// Additional HTTP headers to be sent with network requests from the SDK. + /// + public IDictionary AuxiliaryHeaders { get; set; } + } +} diff --git a/Parse/Library/HostApplicationVersioningData.cs b/Parse/Library/HostApplicationVersioningData.cs new file mode 100644 index 00000000..643dbf29 --- /dev/null +++ b/Parse/Library/HostApplicationVersioningData.cs @@ -0,0 +1,56 @@ +using System; +using System.Reflection; +using Parse.Abstractions.Library; +using Parse.Storage; + +namespace Parse.Library +{ + /// + /// In the event that you would like to use the Parse SDK + /// from a completely portable project, with no platform-specific library required, + /// to get full access to all of our features available on Parse Dashboard + /// (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 class HostApplicationVersioningData : IHostApplicationVersioningData + { + /// + /// An instance of with inferred values based on the entry assembly. + /// + /// Should not be used with Unity. + public static HostApplicationVersioningData Inferred { get; } = new HostApplicationVersioningData + { + BuildVersion = Assembly.GetEntryAssembly().GetName().Version.Build.ToString(), + DisplayVersion = Assembly.GetEntryAssembly().GetName().Version.ToString(), + HostOSVersion = Environment.OSVersion.ToString() + }; + + /// + /// The build number of your app. + /// + public string BuildVersion { get; set; } + + /// + /// The human friendly version number of your app. + /// + public string DisplayVersion { get; set; } + + /// + /// The host operating system version of the platform the host application is operating in. + /// + public string HostOSVersion { get; set; } + + /// + /// Gets a value for whether or not this instance of is populated with default values. + /// + public bool IsDefault => BuildVersion is null && DisplayVersion is null && HostOSVersion is null; + + /// + /// Gets a value for whether or not this instance of can currently be used for the generation of . + /// + public bool CanBeUsedForInference => !(IsDefault || String.IsNullOrWhiteSpace(DisplayVersion)); + } +} diff --git a/Parse/Library/MetadataController.cs b/Parse/Library/MetadataController.cs new file mode 100644 index 00000000..b90730c1 --- /dev/null +++ b/Parse/Library/MetadataController.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Parse.Abstractions.Library; + +namespace Parse.Library +{ + public class MetadataController : IMetadataController + { + /// + /// The version information of your application environment. + /// + public IHostApplicationVersioningData HostVersioningData { get; set; } + } +} diff --git a/Parse/Library/ParseCorePlugins.cs b/Parse/Library/ParseCorePlugins.cs new file mode 100644 index 00000000..c2863896 --- /dev/null +++ b/Parse/Library/ParseCorePlugins.cs @@ -0,0 +1,75 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using Parse.Abstractions.Library; +using Parse.Common.Internal; +using Parse.Library; + +#if DEBUG +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Parse.Test")] +#endif + +namespace Parse.Core.Internal +{ + public class ParseCorePlugins : IParseCorePlugins + { + public IMetadataController MetadataController { get; set; } + + public IWebClient WebClient { get; set; } + + public IStorageController StorageController { get; set; } + + public IObjectSubclassingController SubclassingController { get; set; } + + public IParseInstallationController InstallationController { get; set; } + + public IParseCommandRunner CommandRunner { get; set; } + + public IParseCloudCodeController CloudCodeController { get; set; } + + public IParseConfigController ConfigController { get; set; } + + public IParseFileController FileController { get; set; } + + public IParseObjectController ObjectController { get; set; } + + public IParseQueryController QueryController { get; set; } + + public IParseSessionController SessionController { get; set; } + + public IParseUserController UserController { get; set; } + + public IParseCurrentUserController CurrentUserController { get; set; } + + public IParseCurrentConfigController CurrentConfigController { get; set; } + + // ALTERNATE NAME: InitializeWithDefaults + + public static ParseCorePlugins Instance { get; set; } + + public void Activate() => Instance = this; + + public void Reset() => (MetadataController, WebClient, StorageController, SubclassingController, InstallationController, CommandRunner, CloudCodeController, ConfigController, FileController, ObjectController, QueryController, SessionController, UserController, CurrentUserController, CurrentConfigController) = (default, default, default, default, default, default, default, default, default, default, default, default, default, default, default); + + public void SetDefaults() + { + MetadataController ??= new MetadataController { }; + + WebClient ??= new UniversalWebClient { }; + StorageController ??= new StorageController { }; + SubclassingController ??= new ObjectSubclassingController { }; + + InstallationController ??= new ParseInstallationController(StorageController); + CommandRunner ??= new ParseCommandRunner(WebClient, InstallationController, MetadataController); + + CloudCodeController ??= new ParseCloudCodeController(CommandRunner); + ConfigController ??= new ParseConfigController(CommandRunner, StorageController); + FileController ??= new ParseFileController(CommandRunner); + ObjectController ??= new ParseObjectController(CommandRunner); + QueryController ??= new ParseQueryController(CommandRunner); + SessionController ??= new ParseSessionController(CommandRunner); + UserController ??= new ParseUserController(CommandRunner); + CurrentUserController ??= new ParseCurrentUserController(StorageController); + CurrentConfigController ??= new ParseCurrentConfigController(StorageController); + } + } +} diff --git a/Parse/Framework/ParseDownloadProgressEventArgs.cs b/Parse/Library/ParseDownloadProgressEventArgs.cs similarity index 100% rename from Parse/Framework/ParseDownloadProgressEventArgs.cs rename to Parse/Library/ParseDownloadProgressEventArgs.cs diff --git a/Parse/Framework/ParseException.cs b/Parse/Library/ParseException.cs similarity index 100% rename from Parse/Framework/ParseException.cs rename to Parse/Library/ParseException.cs diff --git a/Parse/Framework/ParsePushNotificationEventArgs.cs b/Parse/Library/ParsePushNotificationEventArgs.cs similarity index 100% rename from Parse/Framework/ParsePushNotificationEventArgs.cs rename to Parse/Library/ParsePushNotificationEventArgs.cs diff --git a/Parse/Framework/ParseUploadProgressEventArgs.cs b/Parse/Library/ParseUploadProgressEventArgs.cs similarity index 100% rename from Parse/Framework/ParseUploadProgressEventArgs.cs rename to Parse/Library/ParseUploadProgressEventArgs.cs diff --git a/Parse/Management/LightParseCorePlugins.cs b/Parse/Management/LightParseCorePlugins.cs new file mode 100644 index 00000000..9f91cf9f --- /dev/null +++ b/Parse/Management/LightParseCorePlugins.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Parse.Abstractions.Library; +using Parse.Common.Internal; +using Parse.Core.Internal; + +namespace Parse.Management +{ + /// + /// Parse dependency injection container implemented as a bare-minimum value type so instances cost less; this is to be used when a container is needed to pass only a few dependencies to a dependent entity, and the main container is unaccessible, but the indiviudal requirements are. + /// + /// This class has no implementation for . + public struct LightParseCorePlugins : IParseCorePlugins + { + public IMetadataController MetadataController { get; set; } + + public IWebClient WebClient { get; set; } + + public IStorageController StorageController { get; set; } + + public IObjectSubclassingController SubclassingController { get; set; } + + public IParseInstallationController InstallationController { get; set; } + + public IParseCommandRunner CommandRunner { get; set; } + + public IParseCloudCodeController CloudCodeController { get; set; } + + public IParseConfigController ConfigController { get; set; } + + public IParseFileController FileController { get; set; } + + public IParseObjectController ObjectController { get; set; } + + public IParseQueryController QueryController { get; set; } + + public IParseSessionController SessionController { get; set; } + + public IParseUserController UserController { get; set; } + + public IParseCurrentUserController CurrentUserController { get; set; } + + public IParseCurrentConfigController CurrentConfigController { get; set; } + + /// + /// Will a . + /// + public void SetDefaults() => throw new NotSupportedException { }; + } +} diff --git a/Parse/Management/ParseCommand.cs b/Parse/Management/ParseCommand.cs index 948fac70..42e75a6e 100644 --- a/Parse/Management/ParseCommand.cs +++ b/Parse/Management/ParseCommand.cs @@ -50,7 +50,7 @@ public ParseCommand(string relativeUri, Stream stream = null, string contentType = null) { - Uri = new Uri(new Uri(ParseClient.CurrentConfiguration.ServerURI), relativeUri); + Uri = new Uri(new Uri(ParseClient.Configuration.ServerURI), relativeUri); Method = method; Data = stream; Headers = new List>(headers ?? Enumerable.Empty>()); diff --git a/Parse/Management/ParseCommandRunner.cs b/Parse/Management/ParseCommandRunner.cs index 8925b0bc..34c40adc 100644 --- a/Parse/Management/ParseCommandRunner.cs +++ b/Parse/Management/ParseCommandRunner.cs @@ -5,7 +5,9 @@ using System.Net; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; +using Parse.Library; namespace Parse.Core.Internal { @@ -14,18 +16,20 @@ namespace Parse.Core.Internal /// public class ParseCommandRunner : IParseCommandRunner { - private readonly IHttpClient httpClient; - private readonly IInstallationIdController installationIdController; + IWebClient WebClient { get; } + IParseInstallationController InstallationController { get; } + IMetadataController MetadataController { get; } /// /// Creates a new Parse SDK command runner. /// - /// The implementation instance to use. - /// The implementation instance to use. - public ParseCommandRunner(IHttpClient httpClient, IInstallationIdController installationIdController) + /// The implementation instance to use. + /// The implementation instance to use. + public ParseCommandRunner(IWebClient webClient, IParseInstallationController installationIdController, IMetadataController metadataController) { - this.httpClient = httpClient; - this.installationIdController = installationIdController; + WebClient = webClient; + InstallationController = installationIdController; + MetadataController = metadataController; } /// @@ -37,62 +41,62 @@ public ParseCommandRunner(IHttpClient httpClient, IInstallationIdController inst /// An asynchronous operation cancellation token that dictates if and when the operation should be cancelled. /// public Task>> RunCommandAsync(ParseCommand command, IProgress uploadProgress = null, IProgress downloadProgress = null, CancellationToken cancellationToken = default) => PrepareCommand(command).ContinueWith(commandTask => - { - return httpClient.ExecuteAsync(commandTask.Result, uploadProgress, downloadProgress, cancellationToken).OnSuccess(t => - { - cancellationToken.ThrowIfCancellationRequested(); + { + return WebClient.ExecuteAsync(commandTask.Result, uploadProgress, downloadProgress, cancellationToken).OnSuccess(t => + { + cancellationToken.ThrowIfCancellationRequested(); - Tuple response = t.Result; - string contentString = response.Item2; - int responseCode = (int) response.Item1; - if (responseCode >= 500) - { - // Server error, return InternalServerError. - throw new ParseException(ParseException.ErrorCode.InternalServerError, response.Item2); - } - else if (contentString != null) - { - IDictionary contentJson = null; - try - { - // TODO: Newer versions of Parse Server send the failure results back as HTML. - contentJson = contentString.StartsWith("[") - ? new Dictionary { ["results"] = Json.Parse(contentString) } - : Json.Parse(contentString) as IDictionary; - } - catch (Exception e) - { - throw new ParseException(ParseException.ErrorCode.OtherCause, "Invalid or alternatively-formatted response recieved from server.", e); - } - if (responseCode < 200 || responseCode > 299) - { - int code = (int) (contentJson.ContainsKey("code") ? (long) contentJson["code"] : (int) ParseException.ErrorCode.OtherCause); - string error = contentJson.ContainsKey("error") ? - contentJson["error"] as string : - contentString; - throw new ParseException((ParseException.ErrorCode) code, error); - } - return new Tuple>(response.Item1, contentJson); - } - return new Tuple>(response.Item1, null); - }); - }).Unwrap(); + Tuple response = t.Result; + string contentString = response.Item2; + int responseCode = (int) response.Item1; + if (responseCode >= 500) + { + // Server error, return InternalServerError. + throw new ParseException(ParseException.ErrorCode.InternalServerError, response.Item2); + } + else if (contentString != null) + { + IDictionary contentJson = null; + try + { + // TODO: Newer versions of Parse Server send the failure results back as HTML. + contentJson = contentString.StartsWith("[") + ? new Dictionary { ["results"] = Json.Parse(contentString) } + : Json.Parse(contentString) as IDictionary; + } + catch (Exception e) + { + throw new ParseException(ParseException.ErrorCode.OtherCause, "Invalid or alternatively-formatted response recieved from server.", e); + } + if (responseCode < 200 || responseCode > 299) + { + int code = (int) (contentJson.ContainsKey("code") ? (long) contentJson["code"] : (int) ParseException.ErrorCode.OtherCause); + string error = contentJson.ContainsKey("error") ? + contentJson["error"] as string : + contentString; + throw new ParseException((ParseException.ErrorCode) code, error); + } + return new Tuple>(response.Item1, contentJson); + } + return new Tuple>(response.Item1, null); + }); + }).Unwrap(); private const string revocableSessionTokentrueValue = "1"; private Task PrepareCommand(ParseCommand command) { ParseCommand newCommand = new ParseCommand(command); - Task installationIdTask = installationIdController.GetAsync().ContinueWith(t => + Task installationIdTask = InstallationController.GetAsync().ContinueWith(t => { newCommand.Headers.Add(new KeyValuePair("X-Parse-Installation-Id", t.Result.ToString())); return newCommand; }); // TODO (richardross): Inject configuration instead of using shared static here. - ParseClient.Configuration configuration = ParseClient.CurrentConfiguration; + Configuration configuration = ParseClient.Configuration; newCommand.Headers.Add(new KeyValuePair("X-Parse-Application-Id", configuration.ApplicationID)); - newCommand.Headers.Add(new KeyValuePair("X-Parse-Client-Version", ParseClient.VersionString)); + newCommand.Headers.Add(new KeyValuePair("X-Parse-Client-Version", ParseClient.Version.ToString())); if (configuration.AuxiliaryHeaders != null) { @@ -102,24 +106,22 @@ private Task PrepareCommand(ParseCommand command) } } - if (!String.IsNullOrEmpty(configuration.VersionInfo.BuildVersion)) + if (!String.IsNullOrEmpty(MetadataController.HostVersioningData.BuildVersion)) { - newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Build-Version", configuration.VersionInfo.BuildVersion)); + newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Build-Version", MetadataController.HostVersioningData.BuildVersion)); } - if (!String.IsNullOrEmpty(configuration.VersionInfo.DisplayVersion)) + if (!String.IsNullOrEmpty(MetadataController.HostVersioningData.DisplayVersion)) { - newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Display-Version", configuration.VersionInfo.DisplayVersion)); + newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Display-Version", MetadataController.HostVersioningData.DisplayVersion)); } - if (!String.IsNullOrEmpty(configuration.VersionInfo.OSVersion)) + if (!String.IsNullOrEmpty(MetadataController.HostVersioningData.HostOSVersion)) { - newCommand.Headers.Add(new KeyValuePair("X-Parse-OS-Version", configuration.VersionInfo.OSVersion)); + newCommand.Headers.Add(new KeyValuePair("X-Parse-OS-Version", MetadataController.HostVersioningData.HostOSVersion)); } - // TODO (richardross): I hate the idea of having this super tightly coupled static variable in here. - // Lets eventually get rid of it. - if (!String.IsNullOrEmpty(ParseClient.MasterKey)) + if (!String.IsNullOrEmpty(configuration.MasterKey)) { - newCommand.Headers.Add(new KeyValuePair("X-Parse-Master-Key", ParseClient.MasterKey)); + newCommand.Headers.Add(new KeyValuePair("X-Parse-Master-Key", configuration.MasterKey)); } else { diff --git a/Parse/Management/ParseCorePlugins.cs b/Parse/Management/ParseCorePlugins.cs deleted file mode 100644 index 603d64e8..00000000 --- a/Parse/Management/ParseCorePlugins.cs +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using Parse.Common.Internal; - -#if DEBUG -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Parse.Test")] -#endif - -namespace Parse.Core.Internal -{ - public class ParseCorePlugins : IParseCorePlugins - { - private static readonly object instanceMutex = new object(); - private static IParseCorePlugins instance; - public static IParseCorePlugins Instance - { - get - { - lock (instanceMutex) - { - return instance = instance ?? new ParseCorePlugins(); - } - } - set - { - lock (instanceMutex) - { - instance = value; - } - } - } - - private readonly object mutex = new object(); - - #region Server Controllers - - private IHttpClient httpClient; - private IParseCommandRunner commandRunner; - private IStorageController storageController; - - private IParseCloudCodeController cloudCodeController; - private IParseConfigController configController; - private IParseFileController fileController; - private IParseObjectController objectController; - private IParseQueryController queryController; - private IParseSessionController sessionController; - private IParseUserController userController; - private IObjectSubclassingController subclassingController; - - #endregion - - #region Current Instance Controller - - private IParseCurrentUserController currentUserController; - private IInstallationIdController installationIdController; - - #endregion - - public void Reset() - { - lock (mutex) - { - HttpClient = 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) - { - return httpClient = httpClient ?? new HttpClient(); - } - } - set - { - lock (mutex) - { - httpClient = value; - } - } - } - - public IParseCommandRunner CommandRunner - { - get - { - lock (mutex) - { - return commandRunner = commandRunner ?? new ParseCommandRunner(HttpClient, InstallationIdController); - } - } - set - { - lock (mutex) - { - commandRunner = value; - } - } - } - - public IStorageController StorageController - { - get - { - lock (mutex) - { - return storageController = storageController ?? new StorageController(); - } - } - set - { - lock (mutex) - { - storageController = value; - } - } - } - - public IParseCloudCodeController CloudCodeController - { - get - { - lock (mutex) - { - return cloudCodeController = cloudCodeController ?? new ParseCloudCodeController(CommandRunner); - } - } - set - { - lock (mutex) - { - cloudCodeController = value; - } - } - } - - public IParseFileController FileController - { - get - { - lock (mutex) - { - return fileController = fileController ?? new ParseFileController(CommandRunner); - } - } - set - { - lock (mutex) - { - fileController = value; - } - } - } - - public IParseConfigController ConfigController - { - get - { - lock (mutex) - { - return configController ?? (configController = new ParseConfigController(CommandRunner, StorageController)); - } - } - set - { - lock (mutex) - { - configController = value; - } - } - } - - public IParseObjectController ObjectController - { - get - { - lock (mutex) - { - return objectController = objectController ?? new ParseObjectController(CommandRunner); - } - } - set - { - lock (mutex) - { - objectController = value; - } - } - } - - public IParseQueryController QueryController - { - get - { - lock (mutex) - { - return queryController ?? (queryController = new ParseQueryController(CommandRunner)); - } - } - set - { - lock (mutex) - { - queryController = value; - } - } - } - - public IParseSessionController SessionController - { - get - { - lock (mutex) - { - return sessionController = sessionController ?? new ParseSessionController(CommandRunner); - } - } - set - { - lock (mutex) - { - sessionController = value; - } - } - } - - public IParseUserController UserController - { - get - { - lock (mutex) - { - return (userController = userController ?? new ParseUserController(CommandRunner)); - } - } - set - { - lock (mutex) - { - userController = value; - } - } - } - - public IParseCurrentUserController CurrentUserController - { - get - { - lock (mutex) - { - return currentUserController = currentUserController ?? new ParseCurrentUserController(StorageController); - } - } - set - { - lock (mutex) - { - currentUserController = value; - } - } - } - - public IObjectSubclassingController SubclassingController - { - get - { - lock (mutex) - { - if (subclassingController == null) - { - subclassingController = new ObjectSubclassingController(); - subclassingController.AddRegisterHook(typeof(ParseUser), () => CurrentUserController.ClearFromMemory()); - } - return subclassingController; - } - } - set - { - lock (mutex) - { - subclassingController = value; - } - } - } - - public IInstallationIdController InstallationIdController - { - get - { - lock (mutex) - { - return installationIdController = installationIdController ?? new InstallationIdController(StorageController); - } - } - set - { - lock (mutex) - { - installationIdController = value; - } - } - } - } -} diff --git a/Parse/Modules/ParseModuleController.cs b/Parse/Modules/ParseModuleController.cs index 4412b9f5..7e75ac04 100644 --- a/Parse/Modules/ParseModuleController.cs +++ b/Parse/Modules/ParseModuleController.cs @@ -11,8 +11,7 @@ namespace Parse.Common.Internal /// public class ParseModuleController { - private static readonly ParseModuleController instance = new ParseModuleController(); - public static ParseModuleController Instance => instance; + public static ParseModuleController Instance { get; } = new ParseModuleController(); private readonly object mutex = new object(); private readonly List modules = new List(); diff --git a/Parse/Parse.csproj b/Parse/Parse.csproj index 489050c4..b70e01ad 100644 --- a/Parse/Parse.csproj +++ b/Parse/Parse.csproj @@ -4,7 +4,6 @@ netstandard2.0 bin\Release\netstandard2.0\Parse.xml 2.0.0-develop-0001 - 2.0.0 latest Parse @@ -25,7 +24,18 @@ - + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + diff --git a/Parse/ParseClient.cs b/Parse/ParseClient.cs index 56f4ddc1..1c6978b4 100644 --- a/Parse/ParseClient.cs +++ b/Parse/ParseClient.cs @@ -2,12 +2,13 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reflection; -using System.Threading.Tasks; +using Parse.Abstractions.Library; +using Parse.Abstractions.Storage; using Parse.Common.Internal; -using Parse.Internal.Utilities; +using Parse.Core.Internal; +using Parse.Library; namespace Parse { @@ -17,202 +18,25 @@ namespace Parse /// public static partial class ParseClient { - internal static readonly string[] DateFormatStrings = + /// + /// Contains, in order, the official ISO date and time format strings, and two modified versions that account for the possibility that the server-side string processing mechanism removed trailing zeroes. + /// + internal static string[] DateFormatStrings { get; } = { - // 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 Parse SDK. - /// - public struct Configuration - { - /// - /// A unit that can generate a relative path to a persistent storage file. - /// - public interface IStorageConfiguration - { - /// - /// The corresponding relative path generated by this . - /// - string RelativeStorageFilePath { get; } - } - - /// - /// A configuration of the Parse SDK persistent storage location based on product metadata such as company name and product name. - /// - public struct MetadataBasedStorageConfiguration : IStorageConfiguration - { - /// - /// An instance of with inferred values based on the entry assembly. Should be used with . - /// - /// Should not be used with Unity. - public static MetadataBasedStorageConfiguration NoCompanyInferred { get; } = new MetadataBasedStorageConfiguration { CompanyName = Assembly.GetEntryAssembly().GetName().Name, ProductName = String.Empty }; - - /// - /// The name of the company that owns the product specified by . - /// - public string CompanyName { get; set; } - - /// - /// The name of the product that is using the Parse .NET SDK. - /// - public string ProductName { get; set; } - - /// - /// The corresponding relative path generated by this . - /// - public string RelativeStorageFilePath => Path.Combine(CompanyName ?? "Parse", ProductName ?? "_global", $"{CurrentConfiguration.VersionInfo.DisplayVersion ?? "1.0.0.0"}.cachefile"); - } - - /// - /// A configuration of the Parse SDK persistent storage location based on an identifier. - /// - public struct IdentifierBasedStorageConfiguration : IStorageConfiguration - { - internal static IdentifierBasedStorageConfiguration Fallback { get; } = new IdentifierBasedStorageConfiguration { IsFallback = true }; - - /// - /// Dictates whether or not this instance should act as a fallback for when has not yet been initialized but the storage path is needed. - /// - internal bool IsFallback { get; set; } - - /// - /// The identifier that all Parse SDK cache files should be labelled with. - /// - public string Identifier { get; set; } - - /// - /// The corresponding relative path generated by this . - /// - /// This will cause a .cachefile file extension to be added to the cache file in order to prevent the creation of files with unwanted extensions due to the value of containing periods. - public string RelativeStorageFilePath - { - get - { - FileInfo file = default; - while ((file = StorageManager.GetWrapperForRelativePersistentStorageFilePath(GeneratePath())).Exists && IsFallback) - ; - - return file.FullName; - } - } - - /// - /// Generates a path for use in the getter. - /// - /// A potential path to the cachefile - string GeneratePath() => Path.Combine("Parse", IsFallback ? "_fallback" : "_global", $"{(IsFallback ? new Random { }.Next().ToString() : Identifier)}.cachefile"); - } - - /// - /// In the event that you would like to use the Parse SDK - /// from a completely portable project, with no platform-specific library required, - /// to get full access to all of our features available on Parse Dashboard - /// (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 - { - /// - /// An instance of with inferred values based on the entry assembly. - /// - /// Should not be used with Unity. - public static VersionInformation Inferred { get; } = new VersionInformation { BuildVersion = Assembly.GetEntryAssembly().GetName().Version.Build.ToString(), DisplayVersion = Assembly.GetEntryAssembly().GetName().Version.ToString(), OSVersion = Environment.OSVersion.ToString() }; - - /// - /// The build number of your app. - /// - public string BuildVersion { get; set; } - - /// - /// The human friendly version number of your app. - /// - public string DisplayVersion { get; set; } - - /// - /// The operating system version of the platform the SDK is operating in.. - /// - public string OSVersion { get; set; } - - /// - /// Gets a value for whether or not this instance of is populated with default values. - /// - internal bool IsDefault => BuildVersion is null && DisplayVersion is null && OSVersion is null; - - /// - /// Gets a value for whether or not this instance of can currently be used for the generation of . - /// - internal bool CanBeUsedForInference => !(IsDefault || String.IsNullOrWhiteSpace(DisplayVersion)); - } - - /// - /// The App ID of your app. - /// - public string ApplicationID { get; set; } - - /// - /// A URI pointing to the target Parse Server instance hosting the app targeted by . - /// - public string ServerURI { get; set; } - - /// - /// The .NET Key for the Parse app targeted by . - /// - public string Key { get; set; } - - /// - /// The Master Key for the Parse app targeted by . - /// - public string MasterKey - { - get => AuxiliaryHeaders?["X-Parse-Master-Key"]; - set => (AuxiliaryHeaders ?? (AuxiliaryHeaders = new Dictionary { }))["X-Parse-Master-Key"] = value; - } - - /// - /// Additional HTTP headers to be sent with network requests from the SDK. - /// - public IDictionary AuxiliaryHeaders { get; set; } - - /// - /// The version information of your application environment. - /// - public VersionInformation VersionInfo { get; set; } - - /// - /// The that Parse should use when generating cache files. - /// - public IStorageConfiguration StorageConfiguration { get; set; } - } - - private static readonly object mutex = new object(); - - - // TODO: Investigate if the version string header can be changed to simple "net-". - static ParseClient() => VersionString = "net-portable-" + Version; //ParseModuleController.Instance.ScanForModules(); + static object Mutex { get; } = new object { }; /// /// The current configuration that parse has been initialized with. /// - public static Configuration CurrentConfiguration { get; internal set; } - - internal static string MasterKey { get; set; } + public static Configuration Configuration { get; internal set; } internal static Version Version => new AssemblyName(typeof(ParseClient).GetTypeInfo().Assembly.FullName).Version; - internal static string VersionString { get; } - /// /// Authenticates this client as belonging to your application. This must be /// called before your application can use the Parse library. The recommended @@ -223,7 +47,7 @@ public string MasterKey /// /// The server URI provided in the Parse dashboard. /// - public static void Initialize(string identifier, string serverURI) => Initialize(new Configuration { ApplicationID = identifier, ServerURI = serverURI }); + public static void Initialize(string identifier, string serverURI, ICacheLocationConfiguration storageConfiguration = default, IHostApplicationVersioningData hostVersioning = default, IParseCorePlugins plugins = default) => Initialize(new Configuration { ApplicationID = identifier, ServerURI = serverURI }, storageConfiguration, hostVersioning, plugins); /// /// Authenticates this client as belonging to your application. This must be @@ -233,52 +57,51 @@ public string MasterKey /// /// The configuration to initialize Parse with. /// - public static void Initialize(Configuration configuration) + public static void Initialize(Configuration configuration, ICacheLocationConfiguration cacheConfiguration = default, IHostApplicationVersioningData hostVersioning = default, IParseCorePlugins plugins = default) { - lock (mutex) + lock (Mutex) { - configuration.ServerURI = configuration.ServerURI ?? "https://api.parse.com/1/"; - //if (configuration.Server == null || configuration.Server.Length < 11) throw new ArgumentNullException("Since the official parse server has shut down, you must specify the URI that points to another implementation."); + configuration.ServerURI ??= configuration.Testing ? "https://api.parse.com/1/" : throw new ArgumentException("Since the official parse server has shut down, you must specify a URI that points to a hosted instance."); + plugins ??= new ParseCorePlugins { }; + + bool keepRelativeStoragePath = plugins is { StorageController: { } }, keepVersion = plugins is { MetadataController: { } }; - switch (configuration.VersionInfo) + plugins.SetDefaults(); + + if (plugins is ParseCorePlugins corePlugins) { - case Configuration.VersionInformation info when info.CanBeUsedForInference: - break; - case Configuration.VersionInformation info when !info.IsDefault: - configuration.VersionInfo = new Configuration.VersionInformation { BuildVersion = info.BuildVersion, OSVersion = info.OSVersion, DisplayVersion = Configuration.VersionInformation.Inferred.DisplayVersion }; - break; - default: - configuration.VersionInfo = Configuration.VersionInformation.Inferred; - break; + corePlugins.Activate(); } - switch (configuration.StorageConfiguration) + if (hostVersioning is { } && !keepVersion && plugins.MetadataController is MetadataController { } metadataController) { - case null: - configuration.StorageConfiguration = Configuration.MetadataBasedStorageConfiguration.NoCompanyInferred; - break; - default: - break; + metadataController.HostVersioningData = hostVersioning switch + { + { CanBeUsedForInference: true } data => data, + { IsDefault: false } data => new HostApplicationVersioningData + { + BuildVersion = data.BuildVersion, + HostOSVersion = data.HostOSVersion, + DisplayVersion = HostApplicationVersioningData.Inferred.DisplayVersion + }, + _ => HostApplicationVersioningData.Inferred + }; } - CurrentConfiguration = configuration; + if (cacheConfiguration is { } && !keepRelativeStoragePath && plugins.StorageController is StorageController { } storageController) + { + storageController.RelativeStorageFilePath = cacheConfiguration.GetRelativeStorageFilePath(plugins); + } - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); - ParseObject.RegisterSubclass(); + Configuration = configuration; - ParseModuleController.Instance.ParseDidInitialize(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); + ParseObject.RegisterDerivative(); } } - /// - /// Reflects a change in the Parse SDK storage configuration by copying all of the cached data into the new location. - /// - /// - /// - public static async Task ReflectStorageChangeAsync(string originalRelativePath) => await StorageManager.TransferAsync(StorageManager.GetWrapperForRelativePersistentStorageFilePath(originalRelativePath).FullName, StorageManager.PersistentStorageFilePath); - internal static string BuildQueryString(IDictionary parameters) => String.Join("&", (from pair in parameters let valueString = pair.Value as string select $"{Uri.EscapeDataString(pair.Key)}={Uri.EscapeDataString(String.IsNullOrEmpty(valueString) ? Json.Encode(pair.Value) : valueString)}").ToArray()); internal static IDictionary DecodeQueryString(string queryString) diff --git a/Parse/ParseInstallation.cs b/Parse/ParseInstallation.cs index ab25682f..d5faa481 100644 --- a/Parse/ParseInstallation.cs +++ b/Parse/ParseInstallation.cs @@ -6,7 +6,10 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; +using Parse.Core.Internal; +using Parse.Library; using Parse.Push.Internal; namespace Parse @@ -26,6 +29,8 @@ public partial class ParseInstallation : ParseObject internal static IDeviceInfoController DeviceInfoController => ParsePushPlugins.Instance.DeviceInfoController; + internal static IMetadataController MetadataController => ParseCorePlugins.Instance.MetadataController; + /// /// Constructs a new ParseInstallation. Generally, you should not need to construct /// ParseInstallations yourself. Instead use . @@ -232,14 +237,14 @@ protected override Task SaveAsync(Task toAwait, CancellationToken cancellationTo Task platformHookTask = null; if (CurrentInstallationController.IsCurrent(this)) { - ParseClient.Configuration configuration = ParseClient.CurrentConfiguration; + Configuration configuration = ParseClient.Configuration; // 'this' is required in order for the extension method to be used. SetIfDifferent("deviceType", DeviceInfoController.DeviceType); SetIfDifferent("timeZone", DeviceInfoController.DeviceTimeZone); SetIfDifferent("localeIdentifier", GetLocaleIdentifier()); SetIfDifferent("parseVersion", GetParseVersion().ToString()); - SetIfDifferent("appVersion", configuration.VersionInfo.BuildVersion ?? DeviceInfoController.AppBuildVersion); + SetIfDifferent("appVersion", MetadataController.HostVersioningData.BuildVersion ?? DeviceInfoController.AppBuildVersion); SetIfDifferent("appIdentifier", DeviceInfoController.AppIdentifier); SetIfDifferent("appName", DeviceInfoController.AppName); @@ -269,127 +274,129 @@ protected override Task SaveAsync(Task toAwait, CancellationToken cancellationTo /// Microsoft does not give us more granular location information. /// Built from http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html /// - internal static readonly Dictionary TimeZoneNameMap = new Dictionary() { - {"Dateline Standard Time", "Etc/GMT+12"}, - {"UTC-11", "Etc/GMT+11"}, - {"Hawaiian Standard Time", "Pacific/Honolulu"}, - {"Alaskan Standard Time", "America/Anchorage"}, - {"Pacific Standard Time (Mexico)", "America/Santa_Isabel"}, - {"Pacific Standard Time", "America/Los_Angeles"}, - {"US Mountain Standard Time", "America/Phoenix"}, - {"Mountain Standard Time (Mexico)", "America/Chihuahua"}, - {"Mountain Standard Time", "America/Denver"}, - {"Central America Standard Time", "America/Guatemala"}, - {"Central Standard Time", "America/Chicago"}, - {"Central Standard Time (Mexico)", "America/Mexico_City"}, - {"Canada Central Standard Time", "America/Regina"}, - {"SA Pacific Standard Time", "America/Bogota"}, - {"Eastern Standard Time", "America/New_York"}, - {"US Eastern Standard Time", "America/Indianapolis"}, - {"Venezuela Standard Time", "America/Caracas"}, - {"Paraguay Standard Time", "America/Asuncion"}, - {"Atlantic Standard Time", "America/Halifax"}, - {"Central Brazilian Standard Time", "America/Cuiaba"}, - {"SA Western Standard Time", "America/La_Paz"}, - {"Pacific SA Standard Time", "America/Santiago"}, - {"Newfoundland Standard Time", "America/St_Johns"}, - {"E. South America Standard Time", "America/Sao_Paulo"}, - {"Argentina Standard Time", "America/Buenos_Aires"}, - {"SA Eastern Standard Time", "America/Cayenne"}, - {"Greenland Standard Time", "America/Godthab"}, - {"Montevideo Standard Time", "America/Montevideo"}, - {"Bahia Standard Time", "America/Bahia"}, - {"UTC-02", "Etc/GMT+2"}, - {"Azores Standard Time", "Atlantic/Azores"}, - {"Cape Verde Standard Time", "Atlantic/Cape_Verde"}, - {"Morocco Standard Time", "Africa/Casablanca"}, - {"UTC", "Etc/GMT"}, - {"GMT Standard Time", "Europe/London"}, - {"Greenwich Standard Time", "Atlantic/Reykjavik"}, - {"W. Europe Standard Time", "Europe/Berlin"}, - {"Central Europe Standard Time", "Europe/Budapest"}, - {"Romance Standard Time", "Europe/Paris"}, - {"Central European Standard Time", "Europe/Warsaw"}, - {"W. Central Africa Standard Time", "Africa/Lagos"}, - {"Namibia Standard Time", "Africa/Windhoek"}, - {"GTB Standard Time", "Europe/Bucharest"}, - {"Middle East Standard Time", "Asia/Beirut"}, - {"Egypt Standard Time", "Africa/Cairo"}, - {"Syria Standard Time", "Asia/Damascus"}, - {"E. Europe Standard Time", "Asia/Nicosia"}, - {"South Africa Standard Time", "Africa/Johannesburg"}, - {"FLE Standard Time", "Europe/Kiev"}, - {"Turkey Standard Time", "Europe/Istanbul"}, - {"Israel Standard Time", "Asia/Jerusalem"}, - {"Jordan Standard Time", "Asia/Amman"}, - {"Arabic Standard Time", "Asia/Baghdad"}, - {"Kaliningrad Standard Time", "Europe/Kaliningrad"}, - {"Arab Standard Time", "Asia/Riyadh"}, - {"E. Africa Standard Time", "Africa/Nairobi"}, - {"Iran Standard Time", "Asia/Tehran"}, - {"Arabian Standard Time", "Asia/Dubai"}, - {"Azerbaijan Standard Time", "Asia/Baku"}, - {"Russian Standard Time", "Europe/Moscow"}, - {"Mauritius Standard Time", "Indian/Mauritius"}, - {"Georgian Standard Time", "Asia/Tbilisi"}, - {"Caucasus Standard Time", "Asia/Yerevan"}, - {"Afghanistan Standard Time", "Asia/Kabul"}, - {"Pakistan Standard Time", "Asia/Karachi"}, - {"West Asia Standard Time", "Asia/Tashkent"}, - {"India Standard Time", "Asia/Calcutta"}, - {"Sri Lanka Standard Time", "Asia/Colombo"}, - {"Nepal Standard Time", "Asia/Katmandu"}, - {"Central Asia Standard Time", "Asia/Almaty"}, - {"Bangladesh Standard Time", "Asia/Dhaka"}, - {"Ekaterinburg Standard Time", "Asia/Yekaterinburg"}, - {"Myanmar Standard Time", "Asia/Rangoon"}, - {"SE Asia Standard Time", "Asia/Bangkok"}, - {"N. Central Asia Standard Time", "Asia/Novosibirsk"}, - {"China Standard Time", "Asia/Shanghai"}, - {"North Asia Standard Time", "Asia/Krasnoyarsk"}, - {"Singapore Standard Time", "Asia/Singapore"}, - {"W. Australia Standard Time", "Australia/Perth"}, - {"Taipei Standard Time", "Asia/Taipei"}, - {"Ulaanbaatar Standard Time", "Asia/Ulaanbaatar"}, - {"North Asia East Standard Time", "Asia/Irkutsk"}, - {"Tokyo Standard Time", "Asia/Tokyo"}, - {"Korea Standard Time", "Asia/Seoul"}, - {"Cen. Australia Standard Time", "Australia/Adelaide"}, - {"AUS Central Standard Time", "Australia/Darwin"}, - {"E. Australia Standard Time", "Australia/Brisbane"}, - {"AUS Eastern Standard Time", "Australia/Sydney"}, - {"West Pacific Standard Time", "Pacific/Port_Moresby"}, - {"Tasmania Standard Time", "Australia/Hobart"}, - {"Yakutsk Standard Time", "Asia/Yakutsk"}, - {"Central Pacific Standard Time", "Pacific/Guadalcanal"}, - {"Vladivostok Standard Time", "Asia/Vladivostok"}, - {"New Zealand Standard Time", "Pacific/Auckland"}, - {"UTC+12", "Etc/GMT-12"}, - {"Fiji Standard Time", "Pacific/Fiji"}, - {"Magadan Standard Time", "Asia/Magadan"}, - {"Tonga Standard Time", "Pacific/Tongatapu"}, - {"Samoa Standard Time", "Pacific/Apia"} - }; + internal static Dictionary TimeZoneNameMap { get; } = new Dictionary + { + ["Dateline Standard Time"] = "Etc/GMT+12", + ["UTC-11"] = "Etc/GMT+11", + ["Hawaiian Standard Time"] = "Pacific/Honolulu", + ["Alaskan Standard Time"] = "America/Anchorage", + ["Pacific Standard Time (Mexico)"] = "America/Santa_Isabel", + ["Pacific Standard Time"] = "America/Los_Angeles", + ["US Mountain Standard Time"] = "America/Phoenix", + ["Mountain Standard Time (Mexico)"] = "America/Chihuahua", + ["Mountain Standard Time"] = "America/Denver", + ["Central America Standard Time"] = "America/Guatemala", + ["Central Standard Time"] = "America/Chicago", + ["Central Standard Time (Mexico)"] = "America/Mexico_City", + ["Canada Central Standard Time"] = "America/Regina", + ["SA Pacific Standard Time"] = "America/Bogota", + ["Eastern Standard Time"] = "America/New_York", + ["US Eastern Standard Time"] = "America/Indianapolis", + ["Venezuela Standard Time"] = "America/Caracas", + ["Paraguay Standard Time"] = "America/Asuncion", + ["Atlantic Standard Time"] = "America/Halifax", + ["Central Brazilian Standard Time"] = "America/Cuiaba", + ["SA Western Standard Time"] = "America/La_Paz", + ["Pacific SA Standard Time"] = "America/Santiago", + ["Newfoundland Standard Time"] = "America/St_Johns", + ["E. South America Standard Time"] = "America/Sao_Paulo", + ["Argentina Standard Time"] = "America/Buenos_Aires", + ["SA Eastern Standard Time"] = "America/Cayenne", + ["Greenland Standard Time"] = "America/Godthab", + ["Montevideo Standard Time"] = "America/Montevideo", + ["Bahia Standard Time"] = "America/Bahia", + ["UTC-02"] = "Etc/GMT+2", + ["Azores Standard Time"] = "Atlantic/Azores", + ["Cape Verde Standard Time"] = "Atlantic/Cape_Verde", + ["Morocco Standard Time"] = "Africa/Casablanca", + ["UTC"] = "Etc/GMT", + ["GMT Standard Time"] = "Europe/London", + ["Greenwich Standard Time"] = "Atlantic/Reykjavik", + ["W. Europe Standard Time"] = "Europe/Berlin", + ["Central Europe Standard Time"] = "Europe/Budapest", + ["Romance Standard Time"] = "Europe/Paris", + ["Central European Standard Time"] = "Europe/Warsaw", + ["W. Central Africa Standard Time"] = "Africa/Lagos", + ["Namibia Standard Time"] = "Africa/Windhoek", + ["GTB Standard Time"] = "Europe/Bucharest", + ["Middle East Standard Time"] = "Asia/Beirut", + ["Egypt Standard Time"] = "Africa/Cairo", + ["Syria Standard Time"] = "Asia/Damascus", + ["E. Europe Standard Time"] = "Asia/Nicosia", + ["South Africa Standard Time"] = "Africa/Johannesburg", + ["FLE Standard Time"] = "Europe/Kiev", + ["Turkey Standard Time"] = "Europe/Istanbul", + ["Israel Standard Time"] = "Asia/Jerusalem", + ["Jordan Standard Time"] = "Asia/Amman", + ["Arabic Standard Time"] = "Asia/Baghdad", + ["Kaliningrad Standard Time"] = "Europe/Kaliningrad", + ["Arab Standard Time"] = "Asia/Riyadh", + ["E. Africa Standard Time"] = "Africa/Nairobi", + ["Iran Standard Time"] = "Asia/Tehran", + ["Arabian Standard Time"] = "Asia/Dubai", + ["Azerbaijan Standard Time"] = "Asia/Baku", + ["Russian Standard Time"] = "Europe/Moscow", + ["Mauritius Standard Time"] = "Indian/Mauritius", + ["Georgian Standard Time"] = "Asia/Tbilisi", + ["Caucasus Standard Time"] = "Asia/Yerevan", + ["Afghanistan Standard Time"] = "Asia/Kabul", + ["Pakistan Standard Time"] = "Asia/Karachi", + ["West Asia Standard Time"] = "Asia/Tashkent", + ["India Standard Time"] = "Asia/Calcutta", + ["Sri Lanka Standard Time"] = "Asia/Colombo", + ["Nepal Standard Time"] = "Asia/Katmandu", + ["Central Asia Standard Time"] = "Asia/Almaty", + ["Bangladesh Standard Time"] = "Asia/Dhaka", + ["Ekaterinburg Standard Time"] = "Asia/Yekaterinburg", + ["Myanmar Standard Time"] = "Asia/Rangoon", + ["SE Asia Standard Time"] = "Asia/Bangkok", + ["N. Central Asia Standard Time"] = "Asia/Novosibirsk", + ["China Standard Time"] = "Asia/Shanghai", + ["North Asia Standard Time"] = "Asia/Krasnoyarsk", + ["Singapore Standard Time"] = "Asia/Singapore", + ["W. Australia Standard Time"] = "Australia/Perth", + ["Taipei Standard Time"] = "Asia/Taipei", + ["Ulaanbaatar Standard Time"] = "Asia/Ulaanbaatar", + ["North Asia East Standard Time"] = "Asia/Irkutsk", + ["Tokyo Standard Time"] = "Asia/Tokyo", + ["Korea Standard Time"] = "Asia/Seoul", + ["Cen. Australia Standard Time"] = "Australia/Adelaide", + ["AUS Central Standard Time"] = "Australia/Darwin", + ["E. Australia Standard Time"] = "Australia/Brisbane", + ["AUS Eastern Standard Time"] = "Australia/Sydney", + ["West Pacific Standard Time"] = "Pacific/Port_Moresby", + ["Tasmania Standard Time"] = "Australia/Hobart", + ["Yakutsk Standard Time"] = "Asia/Yakutsk", + ["Central Pacific Standard Time"] = "Pacific/Guadalcanal", + ["Vladivostok Standard Time"] = "Asia/Vladivostok", + ["New Zealand Standard Time"] = "Pacific/Auckland", + ["UTC+12"] = "Etc/GMT-12", + ["Fiji Standard Time"] = "Pacific/Fiji", + ["Magadan Standard Time"] = "Asia/Magadan", + ["Tonga Standard Time"] = "Pacific/Tongatapu", + ["Samoa Standard Time"] = "Pacific/Apia" + }; /// /// This is a mapping of odd TimeZone offsets to their respective IANA codes across the world. /// This list was compiled from painstakingly pouring over the information available at /// https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. /// - internal static readonly Dictionary TimeZoneOffsetMap = new Dictionary() { - { new TimeSpan(12, 45, 0), "Pacific/Chatham" }, - { new TimeSpan(10, 30, 0), "Australia/Lord_Howe" }, - { new TimeSpan(9, 30, 0), "Australia/Adelaide" }, - { new TimeSpan(8, 45, 0), "Australia/Eucla" }, - { new TimeSpan(8, 30, 0), "Asia/Pyongyang" }, // Parse in North Korea confirmed. - { new TimeSpan(6, 30, 0), "Asia/Rangoon" }, - { new TimeSpan(5, 45, 0), "Asia/Kathmandu" }, - { new TimeSpan(5, 30, 0), "Asia/Colombo" }, - { new TimeSpan(4, 30, 0), "Asia/Kabul" }, - { new TimeSpan(3, 30, 0), "Asia/Tehran" }, - { new TimeSpan(-3, 30, 0), "America/St_Johns" }, - { new TimeSpan(-4, 30, 0), "America/Caracas" }, - { new TimeSpan(-9, 30, 0), "Pacific/Marquesas" }, - }; + internal static Dictionary TimeZoneOffsetMap { get; } = new Dictionary + { + [new TimeSpan(12, 45, 0)] = "Pacific/Chatham", + [new TimeSpan(10, 30, 0)] = "Australia/Lord_Howe", + [new TimeSpan(9, 30, 0)] = "Australia/Adelaide", + [new TimeSpan(8, 45, 0)] = "Australia/Eucla", + [new TimeSpan(8, 30, 0)] = "Asia/Pyongyang", // Parse in North Korea confirmed. + [new TimeSpan(6, 30, 0)] = "Asia/Rangoon", + [new TimeSpan(5, 45, 0)] = "Asia/Kathmandu", + [new TimeSpan(5, 30, 0)] = "Asia/Colombo", + [new TimeSpan(4, 30, 0)] = "Asia/Kabul", + [new TimeSpan(3, 30, 0)] = "Asia/Tehran", + [new TimeSpan(-3, 30, 0)] = "America/St_Johns", + [new TimeSpan(-4, 30, 0)] = "America/Caracas", + [new TimeSpan(-9, 30, 0)] = "Pacific/Marquesas", + }; } } diff --git a/Parse/ParseObject.cs b/Parse/ParseObject.cs index 870d81b4..2351c437 100644 --- a/Parse/ParseObject.cs +++ b/Parse/ParseObject.cs @@ -214,7 +214,7 @@ internal virtual void SetDefaultValues() { } /// backed by ParseObject fields should have ParseFieldName attributes supplied. /// /// The ParseObject subclass type to register. - public static void RegisterSubclass() where T : ParseObject, new() => SubclassingController.RegisterSubclass(typeof(T)); + public static void RegisterDerivative() where T : ParseObject, new() => SubclassingController.RegisterSubclass(typeof(T)); /// /// Registers a custom subclass type with the Parse SDK, enabling strong-typing of those ParseObjects whenever diff --git a/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs b/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs index 42f7b19f..d2d49957 100644 --- a/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs +++ b/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs @@ -1,5 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. +using Parse.Abstractions.Library; using Parse.Core.Internal; namespace Parse.Analytics.Internal diff --git a/Parse/Platform/Configuration/Controller/ParseConfigController.cs b/Parse/Platform/Configuration/ParseConfigController.cs similarity index 100% rename from Parse/Platform/Configuration/Controller/ParseConfigController.cs rename to Parse/Platform/Configuration/ParseConfigController.cs diff --git a/Parse/Platform/Configuration/Controller/ParseCurrentConfigController.cs b/Parse/Platform/Configuration/ParseCurrentConfigController.cs similarity index 100% rename from Parse/Platform/Configuration/Controller/ParseCurrentConfigController.cs rename to Parse/Platform/Configuration/ParseCurrentConfigController.cs diff --git a/Parse/Platform/Installation/InstallationIdController.cs b/Parse/Platform/Installation/InstallationIdController.cs index 56272b19..0beb1ad2 100644 --- a/Parse/Platform/Installation/InstallationIdController.cs +++ b/Parse/Platform/Installation/InstallationIdController.cs @@ -6,14 +6,14 @@ namespace Parse.Core.Internal { - public class InstallationIdController : IInstallationIdController + public class ParseInstallationController : IParseInstallationController { 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 ParseInstallationController(IStorageController storageController) => this.storageController = storageController; public Task SetAsync(Guid? installationId) { diff --git a/Parse/Platform/Installation/ParseCurrentInstallationController.cs b/Parse/Platform/Installation/ParseCurrentInstallationController.cs index 637f2c26..780b8cb4 100644 --- a/Parse/Platform/Installation/ParseCurrentInstallationController.cs +++ b/Parse/Platform/Installation/ParseCurrentInstallationController.cs @@ -14,11 +14,11 @@ internal class ParseCurrentInstallationController : IParseCurrentInstallationCon private readonly object mutex = new object(); private readonly TaskQueue taskQueue = new TaskQueue(); - private readonly IInstallationIdController installationIdController; + private readonly IParseInstallationController installationIdController; private readonly IStorageController storageController; private readonly IParseInstallationCoder installationCoder; - public ParseCurrentInstallationController(IInstallationIdController installationIdController, IStorageController storageController, IParseInstallationCoder installationCoder) + public ParseCurrentInstallationController(IParseInstallationController installationIdController, IStorageController storageController, IParseInstallationCoder installationCoder) { this.installationIdController = installationIdController; this.storageController = storageController; diff --git a/Parse/Platform/Notifications/ParsePushModule.cs b/Parse/Platform/Notifications/ParsePushModule.cs index 5d77a7a1..45d12a51 100644 --- a/Parse/Platform/Notifications/ParsePushModule.cs +++ b/Parse/Platform/Notifications/ParsePushModule.cs @@ -11,7 +11,7 @@ public void OnModuleRegistered() public void OnParseInitialized() { - ParseObject.RegisterSubclass(); + ParseObject.RegisterDerivative(); ParseCorePlugins.Instance.SubclassingController.AddRegisterHook(typeof(ParseInstallation), () => { diff --git a/Parse/Platform/Notifications/ParsePushPlugins.cs b/Parse/Platform/Notifications/ParsePushPlugins.cs index aab2f0cb..86108968 100644 --- a/Parse/Platform/Notifications/ParsePushPlugins.cs +++ b/Parse/Platform/Notifications/ParsePushPlugins.cs @@ -1,3 +1,4 @@ +using Parse.Abstractions.Library; using Parse.Core.Internal; namespace Parse.Push.Internal @@ -106,7 +107,7 @@ public IParseCurrentInstallationController CurrentInstallationController lock (mutex) { currentInstallationController = currentInstallationController ?? new ParseCurrentInstallationController( - CorePlugins.InstallationIdController, CorePlugins.StorageController, ParseInstallationCoder.Instance + CorePlugins.InstallationController, CorePlugins.StorageController, ParseInstallationCoder.Instance ); return currentInstallationController; } diff --git a/Parse/Properties/Resources.Designer.cs b/Parse/Properties/Resources.Designer.cs new file mode 100644 index 00000000..bba82286 --- /dev/null +++ b/Parse/Properties/Resources.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Parse.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Parse.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Mutating a storage dictionary is an asynchronous operation as the storing file needs to be modified.. + /// + internal static string StorageDictionarySynchronousMutationNotSupportedMessage { + get { + return ResourceManager.GetString("StorageDictionarySynchronousMutationNotSupportedMessage", resourceCulture); + } + } + } +} diff --git a/Parse/Properties/Resources.resx b/Parse/Properties/Resources.resx new file mode 100644 index 00000000..452cdfd1 --- /dev/null +++ b/Parse/Properties/Resources.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Mutating a storage dictionary is an asynchronous operation as the storing file needs to be modified. + + \ No newline at end of file diff --git a/Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs b/Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs new file mode 100644 index 00000000..dc28f3a5 --- /dev/null +++ b/Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; +using Parse.Abstractions.Library; +using Parse.Abstractions.Storage; + +namespace Parse.Storage +{ + /// + /// A configuration of the Parse SDK persistent storage location based on an identifier. + /// + public struct IdentifierBasedCacheLocationConfiguration : ICacheLocationConfiguration + { + internal static IdentifierBasedCacheLocationConfiguration Fallback { get; } = new IdentifierBasedCacheLocationConfiguration { IsFallback = true }; + + /// + /// Dictates whether or not this instance should act as a fallback for when has not yet been initialized but the storage path is needed. + /// + internal bool IsFallback { get; set; } + + /// + /// The identifier that all Parse SDK cache files should be labelled with. + /// + public string Identifier { get; set; } + + /// + /// The corresponding relative path generated by this . + /// + /// This will cause a .cachefile file extension to be added to the cache file in order to prevent the creation of files with unwanted extensions due to the value of containing periods. + public string GetRelativeStorageFilePath(IParseCorePlugins plugins) + { + FileInfo file; + + while ((file = plugins.StorageController.GetWrapperForRelativePersistentStorageFilePath(GeneratePath())).Exists && IsFallback) + ; + + return file.FullName; + } + + /// + /// Generates a path for use in the method. + /// + /// A potential path to the cachefile + string GeneratePath() => Path.Combine("Parse", IsFallback ? "_fallback" : "_global", $"{(IsFallback ? new Random { }.Next().ToString() : Identifier)}.cachefile"); + } +} diff --git a/Parse/Storage/MetadataBasedCacheLocationConfiguration.cs b/Parse/Storage/MetadataBasedCacheLocationConfiguration.cs new file mode 100644 index 00000000..652686d5 --- /dev/null +++ b/Parse/Storage/MetadataBasedCacheLocationConfiguration.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; +using System.Reflection; +using Parse.Abstractions.Library; +using Parse.Abstractions.Storage; + +namespace Parse.Storage +{ + /// + /// A configuration of the Parse SDK persistent storage location based on product metadata such as company name and product name. + /// + public struct MetadataBasedCacheLocationConfiguration : ICacheLocationConfiguration + { + /// + /// An instance of with inferred values based on the entry assembly. Should be used with . + /// + /// Should not be used with Unity. + public static MetadataBasedCacheLocationConfiguration NoCompanyInferred { get; } = new MetadataBasedCacheLocationConfiguration + { + CompanyName = Assembly.GetEntryAssembly().GetName().Name, + ProductName = String.Empty + }; + + /// + /// The name of the company that owns the product specified by . + /// + public string CompanyName { get; set; } + + /// + /// The name of the product that is using the Parse .NET SDK. + /// + public string ProductName { get; set; } + + /// + /// The corresponding relative path generated by this . + /// + public string GetRelativeStorageFilePath(IParseCorePlugins plugins) => Path.Combine(CompanyName ?? "Parse", ProductName ?? "_global", $"{plugins.MetadataController.HostVersioningData.DisplayVersion ?? "1.0.0.0"}.cachefile"); + } +} diff --git a/Parse/Storage/StorageController.cs b/Parse/Storage/StorageController.cs index ab826931..e6eaaff3 100644 --- a/Parse/Storage/StorageController.cs +++ b/Parse/Storage/StorageController.cs @@ -1,10 +1,15 @@ using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; +using Parse.Core.Internal; using Parse.Internal.Utilities; +using Parse.Management; +using Parse.Storage; namespace Parse.Common.Internal { @@ -15,123 +20,112 @@ public class StorageController : IStorageController { private class StorageDictionary : IStorageDictionary { - private object mutex; - private Dictionary dictionary; - private FileInfo file; + public StorageDictionary(FileInfo file) => File = file; - public StorageDictionary(FileInfo file) - { - this.file = file; - - mutex = new object(); - dictionary = new Dictionary(); - } - - internal Task SaveAsync() - { - string json; - lock (mutex) - json = Json.Encode(dictionary); + internal Task SaveAsync() => Lock(() => File.WriteContentAsync(Json.Encode(Storage))); - return file.WriteToAsync(json); - } - - internal Task LoadAsync() => 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) + internal Task LoadAsync() => File.ReadAllTextAsync().ContinueWith(task => { - lock (mutex) + lock (Mutex) { - dictionary = contents.ToDictionary(p => p.Key, p => p.Value); + try + { + Storage = Json.Parse(task.Result) as Dictionary; + } + catch + { + Storage = new Dictionary { }; + } } - } + }); + + internal void Update(IDictionary contents) => Lock(() => Storage = contents.ToDictionary(element => element.Key, element => element.Value)); public Task AddAsync(string key, object value) { - lock (mutex) + lock (Mutex) { - dictionary[key] = value; + Storage[key] = value; + return SaveAsync(); } - return SaveAsync(); } public Task RemoveAsync(string key) { - lock (mutex) + lock (Mutex) { - dictionary.Remove(key); + Storage.Remove(key); + return SaveAsync(); } - return SaveAsync(); } - public bool ContainsKey(string key) - { - lock (mutex) - { - return dictionary.ContainsKey(key); - } - } + public void Add(string key, object value) => throw new NotSupportedException(Properties.Resources.StorageDictionarySynchronousMutationNotSupportedMessage); - public IEnumerable Keys - { - get { lock (mutex) { return dictionary.Keys; } } - } + public bool Remove(string key) => throw new NotSupportedException(Properties.Resources.StorageDictionarySynchronousMutationNotSupportedMessage); + + public void Add(KeyValuePair item) => throw new NotSupportedException(Properties.Resources.StorageDictionarySynchronousMutationNotSupportedMessage); + + public bool Remove(KeyValuePair item) => throw new NotSupportedException(Properties.Resources.StorageDictionarySynchronousMutationNotSupportedMessage); + + public bool ContainsKey(string key) => Lock(() => Storage.ContainsKey(key)); public bool TryGetValue(string key, out object value) { - lock (mutex) + lock (Mutex) { - return dictionary.TryGetValue(key, out value); + return (Result: Storage.TryGetValue(key, out object found), value = found).Result; } } - public IEnumerable Values - { - get { lock (mutex) { return dictionary.Values; } } - } + public void Clear() => Lock(() => Storage.Clear()); - public object this[string key] - { - get { lock (mutex) { return dictionary[key]; } } - } + public bool Contains(KeyValuePair item) => Lock(() => Elements.Contains(item)); - public int Count - { - get { lock (mutex) { return dictionary.Count; } } - } + public void CopyTo(KeyValuePair[] array, int arrayIndex) => Lock(() => Elements.CopyTo(array, arrayIndex)); + + public IEnumerator> GetEnumerator() => Storage.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => Storage.GetEnumerator(); + + public FileInfo File { get; set; } + + public object Mutex { get; set; } = new object { }; + + // ALTNAME: Operate - public IEnumerator> GetEnumerator() + TResult Lock(Func operation) { - lock (mutex) + lock (Mutex) { - return dictionary.GetEnumerator(); + return operation.Invoke(); } } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + void Lock(Action operation) { - lock (mutex) + lock (Mutex) { - return dictionary.GetEnumerator(); + operation.Invoke(); } } + + ICollection> Elements => Storage as ICollection>; + + Dictionary Storage { get; set; } = new Dictionary { }; + + public ICollection Keys => Storage.Keys; + + public ICollection Values => Storage.Values; + + public int Count => Storage.Count; + + public bool IsReadOnly => Elements.IsReadOnly; + + public object this[string key] + { + get => Storage[key]; + set => throw new NotSupportedException(Properties.Resources.StorageDictionarySynchronousMutationNotSupportedMessage); + } } FileInfo File { get; } @@ -141,7 +135,7 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() /// /// Creates a Parse storage controller and attempts to extract a previously created settings storage file from the persistent storage location. /// - public StorageController() => Storage = new StorageDictionary(File = StorageManager.PersistentStorageFileWrapper); + public StorageController() => Storage = new StorageDictionary(File = PersistentStorageFileWrapper); /// /// Creates a Parse storage controller with the provided wrapper. @@ -155,10 +149,10 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() /// A storage dictionary containing the deserialized content of the storage file targeted by the instance public Task> LoadAsync() { - // check if storage dictionary is already created from the controllers file (create if not) - if (Storage == null) - Storage = new StorageDictionary(File); - // load storage dictionary content async and return the resulting dictionary type + // Check if storage dictionary is already created from the controllers file (create if not) + Storage ??= new StorageDictionary(File); + + // Load storage dictionary content async and return the resulting dictionary type return Queue.Enqueue(toAwait => toAwait.ContinueWith(_ => Storage.LoadAsync().OnSuccess(__ => Storage as IStorageDictionary)).Unwrap(), CancellationToken.None); } @@ -169,8 +163,81 @@ public Task> LoadAsync() /// public Task> SaveAsync(IDictionary contents) => Queue.Enqueue(toAwait => toAwait.ContinueWith(_ => { - (Storage ?? (Storage = new StorageDictionary(File))).Update(contents); + (Storage ??= new StorageDictionary(File)).Update(contents); return Storage.SaveAsync().OnSuccess(__ => Storage as IStorageDictionary); }).Unwrap()); + + // TODO: Attach the following method to AppDomain.CurrentDomain.ProcessExit. + + public void Clean() + { + if (new FileInfo(FallbackPersistentStorageFilePath) is { Exists: true } file) + { + file.Delete(); + } + } + + /// + /// The relative path from the on the device an to application-specific persistent storage folder. + /// + public string RelativeStorageFilePath { get; set; } + + /// + /// The path to a persistent user-specific storage location specific to the final client assembly of the Parse library. + /// + public string PersistentStorageFilePath => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), RelativeStorageFilePath ?? FallbackPersistentStorageFilePath)); + + /// + /// Gets the calculated persistent storage file fallback path for this app execution. + /// + public string FallbackPersistentStorageFilePath => StoredFallbackPersistentStorageFilePath ??= IdentifierBasedCacheLocationConfiguration.Fallback.GetRelativeStorageFilePath(new LightParseCorePlugins { StorageController = this }); + + string StoredFallbackPersistentStorageFilePath { get; set; } + + /// + /// Gets or creates the file pointed to by and returns it's wrapper as a instance. + /// + public FileInfo PersistentStorageFileWrapper + { + get + { + Directory.CreateDirectory(PersistentStorageFilePath.Substring(0, PersistentStorageFilePath.LastIndexOf(Path.DirectorySeparatorChar))); + + FileInfo file = new FileInfo(PersistentStorageFilePath); + if (!file.Exists) + using (file.Create()) + ; // Hopefully the JIT doesn't no-op this. The behaviour of the "using" clause should dictate how the stream is closed, to make sure it happens properly. + + return file; + } + } + + /// + /// Gets the file wrapper for the specified . + /// + /// The relative path to the target file + /// An instance of wrapping the the value + public FileInfo GetWrapperForRelativePersistentStorageFilePath(string path) + { + Directory.CreateDirectory((path = Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), path))).Substring(0, path.LastIndexOf(Path.DirectorySeparatorChar))); + return new FileInfo(path); + } + + /// + /// Transfers a file from to . + /// + /// + /// + /// A task that completes once the file move operation form to completes. + public async Task TransferAsync(string originFilePath, string targetFilePath) + { + if (!String.IsNullOrWhiteSpace(originFilePath) && !String.IsNullOrWhiteSpace(targetFilePath) && new FileInfo(originFilePath) is { Exists: true } originFile && new FileInfo(targetFilePath) is { } targetFile) + { + using StreamWriter writer = new StreamWriter(targetFile.OpenWrite(), Encoding.Unicode); + using StreamReader reader = new StreamReader(originFile.OpenRead(), Encoding.Unicode); + + await writer.WriteAsync(await reader.ReadToEndAsync()); + } + } } } diff --git a/Parse/Utilities/HttpClient.Portable.cs b/Parse/Utilities/HttpClient.Portable.cs index 85834c49..71417d16 100644 --- a/Parse/Utilities/HttpClient.Portable.cs +++ b/Parse/Utilities/HttpClient.Portable.cs @@ -8,19 +8,35 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using NetHttpClient = System.Net.Http.HttpClient; +using BuiltInClient = System.Net.Http.HttpClient; namespace Parse.Common.Internal { - public class HttpClient : IHttpClient + /// + /// + /// + public class UniversalWebClient : IWebClient { - 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" } }; + static HashSet ContentHeaders = new HashSet + { + { "Allow" }, + { "Content-Disposition" }, + { "Content-Encoding" }, + { "Content-Language" }, + { "Content-Length" }, + { "Content-Location" }, + { "Content-MD5" }, + { "Content-Range" }, + { "Content-Type" }, + { "Expires" }, + { "Last-Modified" } + }; - public HttpClient() : this(new NetHttpClient { }) { } + public UniversalWebClient() : this(new BuiltInClient { }) { } - public HttpClient(NetHttpClient client) => this.client = client; + public UniversalWebClient(BuiltInClient client) => this.client = client; - private NetHttpClient client; + private BuiltInClient client; public Task> ExecuteAsync(HttpRequest httpRequest, IProgress uploadProgress, IProgress downloadProgress, CancellationToken cancellationToken) { @@ -38,7 +54,7 @@ public Task> ExecuteAsync(HttpRequest httpRequest, { foreach (KeyValuePair header in httpRequest.Headers) { - if (HttpContentHeaders.Contains(header.Key)) + if (ContentHeaders.Contains(header.Key)) message.Content.Headers.Add(header.Key, header.Value); else message.Headers.Add(header.Key, header.Value); diff --git a/Parse/Utilities/IHttpClient.cs b/Parse/Utilities/IHttpClient.cs index 5d9a5cbd..4c10303e 100644 --- a/Parse/Utilities/IHttpClient.cs +++ b/Parse/Utilities/IHttpClient.cs @@ -7,7 +7,7 @@ namespace Parse.Common.Internal { - public interface IHttpClient + public interface IWebClient { /// /// Executes HTTP request to a with HTTP verb diff --git a/Parse/Utilities/StorageManager.cs b/Parse/Utilities/StorageManager.cs index a921c4fd..d99f217c 100644 --- a/Parse/Utilities/StorageManager.cs +++ b/Parse/Utilities/StorageManager.cs @@ -10,33 +10,6 @@ namespace Parse.Internal.Utilities /// internal static class StorageManager { - static StorageManager() => AppDomain.CurrentDomain.ProcessExit += (_, __) => { if (new FileInfo(FallbackPersistentStorageFilePath) is FileInfo file && file.Exists) file.Delete(); }; - - /// - /// The path to a persistent user-specific storage location specific to the final client assembly of the Parse library. - /// - public static string PersistentStorageFilePath => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ParseClient.CurrentConfiguration.StorageConfiguration?.RelativeStorageFilePath ?? FallbackPersistentStorageFilePath)); - - /// - /// Gets the calculated persistent storage file fallback path for this app execution. - /// - public static string FallbackPersistentStorageFilePath { get; } = ParseClient.Configuration.IdentifierBasedStorageConfiguration.Fallback.RelativeStorageFilePath; - - /// - /// Asynchronously writes the provided little-endian 16-bit character string to the file wrapped by the provided instance. - /// - /// The instance wrapping the target file that is to be written to - /// The little-endian 16-bit Unicode character string (UTF-16) that is to be written to the - /// A task that completes once the write operation to the completes - public static async Task WriteToAsync(this FileInfo file, string content) - { - using (FileStream stream = new FileStream(Path.GetFullPath(file.FullName), FileMode.Create, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan | FileOptions.Asynchronous)) - { - byte[] data = Encoding.Unicode.GetBytes(content); - await stream.WriteAsync(data, 0, data.Length); - } - } - /// /// Asynchronously read all of the little-endian 16-bit character units (UTF-16) contained within the file wrapped by the provided instance. /// @@ -44,47 +17,21 @@ public static async Task WriteToAsync(this FileInfo file, string content) /// A task that should contain the little-endian 16-bit character string (UTF-16) extracted from the if the read completes successfully public static async Task ReadAllTextAsync(this FileInfo file) { - using (StreamReader reader = new StreamReader(file.OpenRead(), Encoding.Unicode)) - return await reader.ReadToEndAsync(); + using StreamReader reader = new StreamReader(file.OpenRead(), Encoding.Unicode); + return await reader.ReadToEndAsync(); } /// - /// Gets or creates the file pointed to by and returns it's wrapper as a instance. - /// - public static FileInfo PersistentStorageFileWrapper - { - get - { - Directory.CreateDirectory(PersistentStorageFilePath.Substring(0, PersistentStorageFilePath.LastIndexOf(Path.DirectorySeparatorChar))); - - FileInfo file = new FileInfo(PersistentStorageFilePath); - if (!file.Exists) - using (file.Create()) - ; // Hopefully the JIT doesn't no-op this. The behaviour of the "using" clause should dictate how the stream is closed, to make sure it happens properly. - - return file; - } - } - - /// - /// Gets the file wrapper for the specified . + /// Asynchronously writes the provided little-endian 16-bit character string to the file wrapped by the provided instance. /// - /// The relative path to the target file - /// An instance of wrapping the the value - public static FileInfo GetWrapperForRelativePersistentStorageFilePath(string path) - { - path = Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), path)); - - Directory.CreateDirectory(path.Substring(0, path.LastIndexOf(Path.DirectorySeparatorChar))); - return new FileInfo(path); - } - - public static async Task TransferAsync(string originFilePath, string targetFilePath) + /// The instance wrapping the target file that is to be written to + /// The little-endian 16-bit Unicode character string (UTF-16) that is to be written to the + /// A task that completes once the write operation to the completes + public static async Task WriteContentAsync(this FileInfo file, string content) { - if (!String.IsNullOrWhiteSpace(originFilePath) && !String.IsNullOrWhiteSpace(targetFilePath) && new FileInfo(originFilePath) is FileInfo originFile && originFile.Exists && new FileInfo(targetFilePath) is FileInfo targetFile) - using (StreamWriter writer = new StreamWriter(targetFile.OpenWrite(), Encoding.Unicode)) - using (StreamReader reader = new StreamReader(originFile.OpenRead(), Encoding.Unicode)) - await writer.WriteAsync(await reader.ReadToEndAsync()); + using FileStream stream = new FileStream(Path.GetFullPath(file.FullName), FileMode.Create, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan | FileOptions.Asynchronous); + byte[] data = Encoding.Unicode.GetBytes(content); + await stream.WriteAsync(data, 0, data.Length); } } } From 6b1e1a88f6dcc4446ed37327175d93db4659d5a1 Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Mon, 23 Mar 2020 19:43:22 -0700 Subject: [PATCH 03/24] Make sure that temporary cache is cleared when the host application is closed. --- Parse/ParseClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Parse/ParseClient.cs b/Parse/ParseClient.cs index 1c6978b4..88e3b6bc 100644 --- a/Parse/ParseClient.cs +++ b/Parse/ParseClient.cs @@ -99,6 +99,8 @@ public static void Initialize(Configuration configuration, ICacheLocationConfigu ParseObject.RegisterDerivative(); ParseObject.RegisterDerivative(); ParseObject.RegisterDerivative(); + + AppDomain.CurrentDomain.ProcessExit += (_, __) => plugins.StorageController.Clean(); } } From fc1d958d520e2c3cf9d7d06a4ed0e613ae74bb4c Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Tue, 24 Mar 2020 00:51:03 -0700 Subject: [PATCH 04/24] Undo simplification of IParseCorePlugins and implementation in order to allow tests to pass, and apply minor refactorings. --- Parse.Test/AnalyticsControllerTests.cs | 2 +- Parse.Test/CloudControllerTests.cs | 2 +- Parse.Test/CommandTests.cs | 2 +- Parse.Test/FileControllerTests.cs | 2 +- Parse.Test/ObjectControllerTests.cs | 17 +- Parse.Test/SessionControllerTests.cs | 29 +- Parse.Test/UserControllerTests.cs | 111 +++--- .../Abstractions/Library/IParseCorePlugins.cs | 6 +- ...ler.cs => IParseInstallationController.cs} | 0 Parse/Library/Configuration.cs | 2 +- Parse/Library/ParseCorePlugins.cs | 337 ++++++++++++++++-- Parse/Management/LightParseCorePlugins.cs | 2 + .../Management/ParseCurrentUserController.cs | 4 +- .../Tracking/ParseRelationOperation.cs | 4 +- Parse/ParseClient.cs | 12 +- Parse/ParseObject.cs | 8 +- Parse/ParseRelation.cs | 6 +- Parse/ParseSession.cs | 10 +- Parse/Platform/Analytics/ParseAnalytics.cs | 2 +- .../Analytics/ParseAnalyticsPlugins.cs | 6 +- Parse/Platform/Files/ParseFileController.cs | 2 +- .../ParseCurrentInstallationController.cs | 4 +- .../Notifications/MutablePushState.cs | 10 +- .../Notifications/ParsePushPlugins.cs | 12 +- Parse/Utilities/AssemblyLister.cs | 2 +- Parse/Utilities/Encoding/ParseDecoder.cs | 2 +- Parse/Utilities/HttpClient.Portable.cs | 4 +- Parse/Utilities/InternalExtensions.cs | 2 +- Parse/Utilities/ParseQueryExtensions.cs | 8 +- 29 files changed, 433 insertions(+), 177 deletions(-) rename Parse/Abstractions/Platform/Installation/{IInstallationIdController.cs => IParseInstallationController.cs} (100%) diff --git a/Parse.Test/AnalyticsControllerTests.cs b/Parse.Test/AnalyticsControllerTests.cs index 8ab0a5a5..eb29033f 100644 --- a/Parse.Test/AnalyticsControllerTests.cs +++ b/Parse.Test/AnalyticsControllerTests.cs @@ -16,7 +16,7 @@ namespace Parse.Test public class AnalyticsControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "" }); + public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(AnalyticsControllerTests))] diff --git a/Parse.Test/CloudControllerTests.cs b/Parse.Test/CloudControllerTests.cs index 8c0d388b..1bc4d730 100644 --- a/Parse.Test/CloudControllerTests.cs +++ b/Parse.Test/CloudControllerTests.cs @@ -15,7 +15,7 @@ namespace Parse.Test public class CloudControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "" }); + public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(CloudControllerTests))] diff --git a/Parse.Test/CommandTests.cs b/Parse.Test/CommandTests.cs index 77d8c461..2a79ec36 100644 --- a/Parse.Test/CommandTests.cs +++ b/Parse.Test/CommandTests.cs @@ -22,7 +22,7 @@ public class CommandTests [TestInitialize] public void SetUp() { - ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "" }); + ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "", Test = true }); MockMetadataController.Setup(metadata => metadata.HostVersioningData).Returns(new HostApplicationVersioningData { BuildVersion = "1", DisplayVersion = "1", HostOSVersion = "1" }); } diff --git a/Parse.Test/FileControllerTests.cs b/Parse.Test/FileControllerTests.cs index dc311539..d47f9e81 100644 --- a/Parse.Test/FileControllerTests.cs +++ b/Parse.Test/FileControllerTests.cs @@ -16,7 +16,7 @@ namespace Parse.Test public class FileControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "" }); + public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(FileControllerTests))] diff --git a/Parse.Test/ObjectControllerTests.cs b/Parse.Test/ObjectControllerTests.cs index b414a43d..2afc5a5c 100644 --- a/Parse.Test/ObjectControllerTests.cs +++ b/Parse.Test/ObjectControllerTests.cs @@ -16,7 +16,7 @@ namespace Parse.Test public class ObjectControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "" }); + public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(ObjectControllerTests))] @@ -89,10 +89,7 @@ public Task TestSaveNewObject() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/classes/Corgi"), - It.IsAny>(), - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/classes/Corgi"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = t.Result; Assert.AreEqual("isShibaInu", newState["doge"]); @@ -161,10 +158,7 @@ public Task TestSaveAll() Assert.IsNotNull(serverState.UpdatedAt); } - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/batch"), - It.IsAny>(), - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -179,7 +173,10 @@ public Task TestSaveAllManyObjects() { ClassName = "Corgi", ObjectId = "st4nl3yW" + i, - ServerData = new Dictionary { ["corgi"] = "isNotDoge" } + ServerData = new Dictionary + { + ["corgi"] = "isNotDoge" + } }); } List> operationsList = new List>(); diff --git a/Parse.Test/SessionControllerTests.cs b/Parse.Test/SessionControllerTests.cs index ccffe91e..0281a111 100644 --- a/Parse.Test/SessionControllerTests.cs +++ b/Parse.Test/SessionControllerTests.cs @@ -16,37 +16,34 @@ namespace Parse.Test public class SessionControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "" }); + public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(SessionControllerTests))] public Task TestGetSessionWithEmptyResult() => new ParseSessionController(CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, null)).Object).GetSessionAsync("S0m3Se551on", CancellationToken.None).ContinueWith(t => - { - Assert.IsTrue(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - }); + { + Assert.IsTrue(t.IsFaulted); + Assert.IsFalse(t.IsCanceled); + }); [TestMethod] [AsyncStateMachine(typeof(SessionControllerTests))] public Task TestGetSession() { - Tuple> response = new Tuple>(HttpStatusCode.Accepted, - new Dictionary() { - { "__type", "Object" }, - { "className", "Session" }, - { "sessionToken", "S0m3Se551on" }, - { "restricted", true } - }); + Tuple> response = new Tuple>(HttpStatusCode.Accepted, new Dictionary + { + ["__type"] = "Object", + ["className"] = "Session", + ["sessionToken"] = "S0m3Se551on", + ["restricted"] = true + }); Mock mockRunner = CreateMockRunner(response); return new ParseSessionController(mockRunner.Object).GetSessionAsync("S0m3Se551on", CancellationToken.None).ContinueWith(t => { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/sessions/me"), - It.IsAny>(), - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/sessions/me"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState session = t.Result; Assert.AreEqual(2, session.Count()); diff --git a/Parse.Test/UserControllerTests.cs b/Parse.Test/UserControllerTests.cs index 130235ee..538c9ecc 100644 --- a/Parse.Test/UserControllerTests.cs +++ b/Parse.Test/UserControllerTests.cs @@ -15,7 +15,7 @@ namespace Parse.Test public class UserControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "" }); + public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(UserControllerTests))] @@ -24,22 +24,27 @@ public Task TestSignUp() MutableObjectState state = new MutableObjectState { ClassName = "_User", - ServerData = new Dictionary() { - { "username", "hallucinogen" }, - { "password", "secret" } - } + ServerData = new Dictionary + { + ["username"] = "hallucinogen", + ["password"] = "secret" + } + }; + + Dictionary operations = new Dictionary + { + ["gogo"] = new Mock().Object + }; + + Dictionary responseDict = new Dictionary + { + ["__type"] = "Object", + ["className"] = "_User", + ["objectId"] = "d3ImSh3ki", + ["sessionToken"] = "s3ss10nt0k3n", + ["createdAt"] = "2015-09-18T18:11:28.943Z" }; - Dictionary operations = new Dictionary() { - { "gogo", new Mock().Object } - }; - Dictionary responseDict = new Dictionary() { - { "__type", "Object" }, - { "className", "_User" }, - { "objectId", "d3ImSh3ki" }, - { "sessionToken", "s3ss10nt0k3n" }, - { "createdAt", "2015-09-18T18:11:28.943Z" } - }; Tuple> response = new Tuple>(HttpStatusCode.Accepted, responseDict); Mock mockRunner = CreateMockRunner(response); @@ -49,10 +54,7 @@ public Task TestSignUp() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/classes/_User"), - It.IsAny>(), - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/classes/_User"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = t.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); @@ -66,13 +68,15 @@ public Task TestSignUp() [AsyncStateMachine(typeof(UserControllerTests))] public Task TestLogInWithUsernamePassword() { - Dictionary responseDict = new Dictionary() { - { "__type", "Object" }, - { "className", "_User" }, - { "objectId", "d3ImSh3ki" }, - { "sessionToken", "s3ss10nt0k3n" }, - { "createdAt", "2015-09-18T18:11:28.943Z" } - }; + Dictionary responseDict = new Dictionary + { + ["__type"] = "Object", + ["className"] = "_User", + ["objectId"] = "d3ImSh3ki", + ["sessionToken"] = "s3ss10nt0k3n", + ["createdAt"] = "2015-09-18T18:11:28.943Z" + }; + Tuple> response = new Tuple>(HttpStatusCode.Accepted, responseDict); Mock mockRunner = CreateMockRunner(response); @@ -82,10 +86,7 @@ public Task TestLogInWithUsernamePassword() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/login"), - It.IsAny>(), - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/login"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = t.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); @@ -99,13 +100,15 @@ public Task TestLogInWithUsernamePassword() [AsyncStateMachine(typeof(UserControllerTests))] public Task TestLogInWithAuthData() { - Dictionary responseDict = new Dictionary() { - { "__type", "Object" }, - { "className", "_User" }, - { "objectId", "d3ImSh3ki" }, - { "sessionToken", "s3ss10nt0k3n" }, - { "createdAt", "2015-09-18T18:11:28.943Z" } - }; + Dictionary responseDict = new Dictionary + { + ["__type"] = "Object" , + ["className"] = "_User" , + ["objectId"] = "d3ImSh3ki" , + ["sessionToken"] = "s3ss10nt0k3n" , + ["createdAt"] = "2015-09-18T18:11:28.943Z" + }; + Tuple> response = new Tuple>(HttpStatusCode.Accepted, responseDict); Mock mockRunner = CreateMockRunner(response); @@ -115,10 +118,7 @@ public Task TestLogInWithAuthData() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/users"), - It.IsAny>(), - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/users"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = t.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); @@ -133,12 +133,13 @@ public Task TestLogInWithAuthData() public Task TestGetUserFromSessionToken() { Dictionary responseDict = new Dictionary() { - { "__type", "Object" }, - { "className", "_User" }, - { "objectId", "d3ImSh3ki" }, - { "sessionToken", "s3ss10nt0k3n" }, - { "createdAt", "2015-09-18T18:11:28.943Z" } - }; + ["__type"] = "Object", + ["className"] = "_User", + ["objectId"] = "d3ImSh3ki", + ["sessionToken"] = "s3ss10nt0k3n", + ["createdAt"] = "2015-09-18T18:11:28.943Z" + }; + Tuple> response = new Tuple>(HttpStatusCode.Accepted, responseDict); Mock mockRunner = CreateMockRunner(response); @@ -148,10 +149,7 @@ public Task TestGetUserFromSessionToken() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/users/me"), - It.IsAny>(), - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/users/me"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = t.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); @@ -175,21 +173,14 @@ public Task TestRequestPasswordReset() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/requestPasswordReset"), - It.IsAny>(), - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/requestPasswordReset"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } private Mock CreateMockRunner(Tuple> response) { Mock mockRunner = new Mock(); - mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), - It.IsAny>(), - It.IsAny>(), - It.IsAny())) - .Returns(Task>>.FromResult(response)); + mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); return mockRunner; } diff --git a/Parse/Abstractions/Library/IParseCorePlugins.cs b/Parse/Abstractions/Library/IParseCorePlugins.cs index d64f3ff5..45c25674 100644 --- a/Parse/Abstractions/Library/IParseCorePlugins.cs +++ b/Parse/Abstractions/Library/IParseCorePlugins.cs @@ -27,11 +27,13 @@ public interface IParseCorePlugins IParseSessionController SessionController { get; } IParseUserController UserController { get; } IParseCurrentUserController CurrentUserController { get; } - IParseCurrentConfigController CurrentConfigController { get; } + //IParseCurrentConfigController CurrentConfigController { get; } + + public void Reset(); /// /// Sets the default controller instances if not explicitly overridden. This method should effectively perform a -coalescing assign on all of the properties of the implementation instance. /// - public void SetDefaults(); + //public void SetDefaults(); } } \ No newline at end of file diff --git a/Parse/Abstractions/Platform/Installation/IInstallationIdController.cs b/Parse/Abstractions/Platform/Installation/IParseInstallationController.cs similarity index 100% rename from Parse/Abstractions/Platform/Installation/IInstallationIdController.cs rename to Parse/Abstractions/Platform/Installation/IParseInstallationController.cs diff --git a/Parse/Library/Configuration.cs b/Parse/Library/Configuration.cs index 65531c0d..819fd9fd 100644 --- a/Parse/Library/Configuration.cs +++ b/Parse/Library/Configuration.cs @@ -9,7 +9,7 @@ namespace Parse.Library /// public struct Configuration { - internal bool Testing { get; set; } + internal bool Test { get; set; } /// /// The App ID of your app. diff --git a/Parse/Library/ParseCorePlugins.cs b/Parse/Library/ParseCorePlugins.cs index c2863896..527e1d9d 100644 --- a/Parse/Library/ParseCorePlugins.cs +++ b/Parse/Library/ParseCorePlugins.cs @@ -12,64 +12,331 @@ namespace Parse.Core.Internal { public class ParseCorePlugins : IParseCorePlugins { - public IMetadataController MetadataController { get; set; } + static object Mutex { get; } = new object(); + static IParseCorePlugins instance; - public IWebClient WebClient { get; set; } + public static IParseCorePlugins Instance + { + get + { + lock (Mutex) + { + return instance ??= new ParseCorePlugins(); + } + } + set + { + lock (Mutex) + { + instance = value; + } + } + } - public IStorageController StorageController { get; set; } + private readonly object mutex = new object(); - public IObjectSubclassingController SubclassingController { get; set; } + #region Server Controllers - public IParseInstallationController InstallationController { get; set; } + IMetadataController metadataController; - public IParseCommandRunner CommandRunner { get; set; } + IWebClient httpClient; + IParseCommandRunner commandRunner; + IStorageController storageController; - public IParseCloudCodeController CloudCodeController { get; set; } + IParseCloudCodeController cloudCodeController; + IParseConfigController configController; + IParseFileController fileController; + IParseObjectController objectController; + IParseQueryController queryController; + IParseSessionController sessionController; + IParseUserController userController; + IObjectSubclassingController subclassingController; - public IParseConfigController ConfigController { get; set; } + #endregion - public IParseFileController FileController { get; set; } + #region Current Instance Controller - public IParseObjectController ObjectController { get; set; } + IParseCurrentUserController currentUserController; + IParseInstallationController installationController; - public IParseQueryController QueryController { get; set; } + #endregion - public IParseSessionController SessionController { get; set; } + public void Reset() + { + lock (mutex) + { + MetadataController = null; + WebClient = null; + CommandRunner = null; + StorageController = null; - public IParseUserController UserController { get; set; } + CloudCodeController = null; + FileController = null; + ObjectController = null; + SessionController = null; + UserController = null; + SubclassingController = null; - public IParseCurrentUserController CurrentUserController { get; set; } + CurrentUserController = null; + InstallationController = null; + } + } - public IParseCurrentConfigController CurrentConfigController { get; set; } + public IMetadataController MetadataController + { + get + { + lock (mutex) + { + return metadataController ??= new MetadataController { }; + } + } + set + { + lock (mutex) + { + metadataController = value; + } + } + } - // ALTERNATE NAME: InitializeWithDefaults + public IWebClient WebClient + { + get + { + lock (mutex) + { + return httpClient ??= new UniversalWebClient { }; + } + } + set + { + lock (mutex) + { + httpClient = value; + } + } + } - public static ParseCorePlugins Instance { get; set; } + public IParseCommandRunner CommandRunner + { + get + { + lock (mutex) + { + return commandRunner ??= new ParseCommandRunner(WebClient, InstallationController, MetadataController); + } + } + set + { + lock (mutex) + { + commandRunner = value; + } + } + } - public void Activate() => Instance = this; + public IStorageController StorageController + { + get + { + lock (mutex) + { + return storageController ??= new StorageController(); + } + } + set + { + lock (mutex) + { + storageController = value; + } + } + } - public void Reset() => (MetadataController, WebClient, StorageController, SubclassingController, InstallationController, CommandRunner, CloudCodeController, ConfigController, FileController, ObjectController, QueryController, SessionController, UserController, CurrentUserController, CurrentConfigController) = (default, default, default, default, default, default, default, default, default, default, default, default, default, default, default); + public IParseCloudCodeController CloudCodeController + { + get + { + lock (mutex) + { + return cloudCodeController ??= new ParseCloudCodeController(CommandRunner); + } + } + set + { + lock (mutex) + { + cloudCodeController = value; + } + } + } - public void SetDefaults() + public IParseFileController FileController { - MetadataController ??= new MetadataController { }; + get + { + lock (mutex) + { + return fileController ??= new ParseFileController(CommandRunner); + } + } + set + { + lock (mutex) + { + fileController = value; + } + } + } - WebClient ??= new UniversalWebClient { }; - StorageController ??= new StorageController { }; - SubclassingController ??= new ObjectSubclassingController { }; + public IParseConfigController ConfigController + { + get + { + lock (mutex) + { + return configController ?? (configController = new ParseConfigController(CommandRunner, StorageController)); + } + } + set + { + lock (mutex) + { + configController = value; + } + } + } - InstallationController ??= new ParseInstallationController(StorageController); - CommandRunner ??= new ParseCommandRunner(WebClient, InstallationController, MetadataController); + public IParseObjectController ObjectController + { + get + { + lock (mutex) + { + return objectController ??= new ParseObjectController(CommandRunner); + } + } + set + { + lock (mutex) + { + objectController = value; + } + } + } - CloudCodeController ??= new ParseCloudCodeController(CommandRunner); - ConfigController ??= new ParseConfigController(CommandRunner, StorageController); - FileController ??= new ParseFileController(CommandRunner); - ObjectController ??= new ParseObjectController(CommandRunner); - QueryController ??= new ParseQueryController(CommandRunner); - SessionController ??= new ParseSessionController(CommandRunner); - UserController ??= new ParseUserController(CommandRunner); - CurrentUserController ??= new ParseCurrentUserController(StorageController); - CurrentConfigController ??= new ParseCurrentConfigController(StorageController); + public IParseQueryController QueryController + { + get + { + lock (mutex) + { + return queryController ?? (queryController = new ParseQueryController(CommandRunner)); + } + } + set + { + lock (mutex) + { + queryController = value; + } + } + } + + public IParseSessionController SessionController + { + get + { + lock (mutex) + { + return sessionController ??= new ParseSessionController(CommandRunner); + } + } + set + { + lock (mutex) + { + sessionController = value; + } + } + } + + public IParseUserController UserController + { + get + { + lock (mutex) + { + return (userController ??= new ParseUserController(CommandRunner)); + } + } + set + { + lock (mutex) + { + userController = value; + } + } + } + + public IParseCurrentUserController CurrentUserController + { + get + { + lock (mutex) + { + return currentUserController ??= new ParseCurrentUserController(StorageController); + } + } + set + { + lock (mutex) + { + currentUserController = value; + } + } + } + + public IObjectSubclassingController SubclassingController + { + get + { + lock (mutex) + { + if (subclassingController == null) + { + subclassingController = new ObjectSubclassingController(); + subclassingController.AddRegisterHook(typeof(ParseUser), () => CurrentUserController.ClearFromMemory()); + } + return subclassingController; + } + } + set + { + lock (mutex) + { + subclassingController = value; + } + } + } + + public IParseInstallationController InstallationController + { + get + { + lock (mutex) + { + return installationController ??= new ParseInstallationController(StorageController); + } + } + set + { + lock (mutex) + { + installationController = value; + } + } } } } diff --git a/Parse/Management/LightParseCorePlugins.cs b/Parse/Management/LightParseCorePlugins.cs index 9f91cf9f..2b30b730 100644 --- a/Parse/Management/LightParseCorePlugins.cs +++ b/Parse/Management/LightParseCorePlugins.cs @@ -43,6 +43,8 @@ public struct LightParseCorePlugins : IParseCorePlugins public IParseCurrentConfigController CurrentConfigController { get; set; } + public void Reset() => throw new NotImplementedException { }; + /// /// Will a . /// diff --git a/Parse/Management/ParseCurrentUserController.cs b/Parse/Management/ParseCurrentUserController.cs index e1c83160..5b8ddbee 100644 --- a/Parse/Management/ParseCurrentUserController.cs +++ b/Parse/Management/ParseCurrentUserController.cs @@ -87,7 +87,7 @@ public Task GetAsync(CancellationToken cancellationToken) if (cachedCurrent != null) { - return Task.FromResult(cachedCurrent); + return Task.FromResult(cachedCurrent); } return taskQueue.Enqueue(toAwait => @@ -117,7 +117,7 @@ public Task ExistsAsync(CancellationToken cancellationToken) { if (CurrentUser != null) { - return Task.FromResult(true); + return Task.FromResult(true); } return taskQueue.Enqueue(toAwait => diff --git a/Parse/Management/Tracking/ParseRelationOperation.cs b/Parse/Management/Tracking/ParseRelationOperation.cs index ce0eae2f..81a27890 100644 --- a/Parse/Management/Tracking/ParseRelationOperation.cs +++ b/Parse/Management/Tracking/ParseRelationOperation.cs @@ -25,8 +25,8 @@ private ParseRelationOperation(IEnumerable adds, public ParseRelationOperation(IEnumerable adds, IEnumerable removes) { - adds = adds ?? new ParseObject[0]; - removes = removes ?? new ParseObject[0]; + adds ??= new ParseObject[0]; + removes ??= new ParseObject[0]; targetClassName = adds.Concat(removes).Select(o => o.ClassName).FirstOrDefault(); this.adds = new ReadOnlyCollection(IdsFromObjects(adds).ToList()); this.removes = new ReadOnlyCollection(IdsFromObjects(removes).ToList()); diff --git a/Parse/ParseClient.cs b/Parse/ParseClient.cs index 88e3b6bc..2678c119 100644 --- a/Parse/ParseClient.cs +++ b/Parse/ParseClient.cs @@ -61,17 +61,17 @@ public static void Initialize(Configuration configuration, ICacheLocationConfigu { lock (Mutex) { - configuration.ServerURI ??= configuration.Testing ? "https://api.parse.com/1/" : throw new ArgumentException("Since the official parse server has shut down, you must specify a URI that points to a hosted instance."); + configuration.ServerURI ??= configuration.Test ? "https://api.parse.com/1/" : throw new ArgumentException("Since the official parse server has shut down, you must specify a URI that points to a hosted instance."); plugins ??= new ParseCorePlugins { }; bool keepRelativeStoragePath = plugins is { StorageController: { } }, keepVersion = plugins is { MetadataController: { } }; - plugins.SetDefaults(); + //plugins.SetDefaults(); - if (plugins is ParseCorePlugins corePlugins) - { - corePlugins.Activate(); - } + //if (plugins is ParseCorePlugins corePlugins) + //{ + // corePlugins.Activate(); + //} if (hostVersioning is { } && !keepVersion && plugins.MetadataController is MetadataController { } metadataController) { diff --git a/Parse/ParseObject.cs b/Parse/ParseObject.cs index 2351c437..eefac211 100644 --- a/Parse/ParseObject.cs +++ b/Parse/ParseObject.cs @@ -530,7 +530,7 @@ private static Task DeepSaveAsync(object obj, string sessionToken, CancellationT } // Save all of the objects in current. - return ParseObject.EnqueueForAll(current, toAwait => + return EnqueueForAll(current, toAwait => { return toAwait.OnSuccess(__ => { @@ -627,7 +627,7 @@ public static Task> FetchAllIfNeededAsync( /// The cancellation token. /// The list passed in for convenience. public static Task> FetchAllIfNeededAsync( - IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject => ParseObject.EnqueueForAll(objects.Cast(), (Task toAwait) => + IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject => EnqueueForAll(objects.Cast(), (Task toAwait) => { return FetchAllInternalAsync(objects, false, toAwait, cancellationToken); }, cancellationToken); @@ -647,7 +647,7 @@ public static Task> FetchAllAsync( /// The cancellation token. /// The list passed in for convenience. public static Task> FetchAllAsync( - IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject => ParseObject.EnqueueForAll(objects.Cast(), (Task toAwait) => + IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject => EnqueueForAll(objects.Cast(), (Task toAwait) => { return FetchAllInternalAsync(objects, true, toAwait, cancellationToken); }, cancellationToken); @@ -761,7 +761,7 @@ public static Task DeleteAllAsync( HashSet uniqueObjects = new HashSet(objects.OfType().ToList(), new IdentityEqualityComparer()); - return ParseObject.EnqueueForAll(uniqueObjects, toAwait => + return EnqueueForAll(uniqueObjects, toAwait => { List states = uniqueObjects.Select(t => t.state).ToList(); return toAwait.OnSuccess(_ => diff --git a/Parse/ParseRelation.cs b/Parse/ParseRelation.cs index b4516b83..1f4aca02 100644 --- a/Parse/ParseRelation.cs +++ b/Parse/ParseRelation.cs @@ -30,8 +30,8 @@ internal ParseRelationBase(ParseObject parent, string key, string targetClassNam internal void EnsureParentAndKey(ParseObject parent, string key) { - this.parent = this.parent ?? parent; - this.key = this.key ?? key; + this.parent ??= parent; + 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"); } @@ -125,6 +125,6 @@ internal ParseRelation(ParseObject parent, string key, string targetClassName) /// /// Gets a query that can be used to query the objects in this relation. /// - public ParseQuery Query => base.GetQuery(); + public ParseQuery Query => GetQuery(); } } diff --git a/Parse/ParseSession.cs b/Parse/ParseSession.cs index fd01a509..78f0ec2e 100644 --- a/Parse/ParseSession.cs +++ b/Parse/ParseSession.cs @@ -47,18 +47,18 @@ public static Task GetCurrentSessionAsync(CancellationToken cancel ParseUser user = t1.Result; if (user == null) { - return Task.FromResult((ParseSession) null); + return Task.FromResult((ParseSession) null); } string sessionToken = user.SessionToken; if (sessionToken == null) { - return Task.FromResult((ParseSession) null); + return Task.FromResult((ParseSession) null); } return SessionController.GetSessionAsync(sessionToken, cancellationToken).OnSuccess(t => { - ParseSession session = ParseObject.FromState(t.Result, "_Session"); + ParseSession session = FromState(t.Result, "_Session"); return session; }); }).Unwrap(); @@ -76,12 +76,12 @@ internal static Task UpgradeToRevocableSessionAsync(string sessionToken, { if (sessionToken == null || SessionController.IsRevocableSessionToken(sessionToken)) { - return Task.FromResult(sessionToken); + return Task.FromResult(sessionToken); } return SessionController.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken).OnSuccess(t => { - ParseSession session = ParseObject.FromState(t.Result, "_Session"); + ParseSession session = FromState(t.Result, "_Session"); return session.SessionToken; }); } diff --git a/Parse/Platform/Analytics/ParseAnalytics.cs b/Parse/Platform/Analytics/ParseAnalytics.cs index f1218890..7b89392a 100644 --- a/Parse/Platform/Analytics/ParseAnalytics.cs +++ b/Parse/Platform/Analytics/ParseAnalytics.cs @@ -26,7 +26,7 @@ public partial class ParseAnalytics /// Tracks this application being launched. /// /// An Async Task that can be waited on or ignored. - public static Task TrackAppOpenedAsync() => ParseAnalytics.TrackAppOpenedWithPushHashAsync(); + public static Task TrackAppOpenedAsync() => TrackAppOpenedWithPushHashAsync(); /// /// Tracks the occurrence of a custom event with additional dimensions. diff --git a/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs b/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs index d2d49957..06276fa4 100644 --- a/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs +++ b/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs @@ -15,7 +15,7 @@ public static IParseAnalyticsPlugins Instance { lock (instanceMutex) { - instance = instance ?? new ParseAnalyticsPlugins(); + instance ??= new ParseAnalyticsPlugins(); return instance; } } @@ -48,7 +48,7 @@ public IParseCorePlugins CorePlugins { lock (mutex) { - corePlugins = corePlugins ?? ParseCorePlugins.Instance; + corePlugins ??= ParseCorePlugins.Instance; return corePlugins; } } @@ -67,7 +67,7 @@ public IParseAnalyticsController AnalyticsController { lock (mutex) { - analyticsController = analyticsController ?? new ParseAnalyticsController(CorePlugins.CommandRunner); + analyticsController ??= new ParseAnalyticsController(CorePlugins.CommandRunner); return analyticsController; } } diff --git a/Parse/Platform/Files/ParseFileController.cs b/Parse/Platform/Files/ParseFileController.cs index 3f18d1cf..ce7c72a3 100644 --- a/Parse/Platform/Files/ParseFileController.cs +++ b/Parse/Platform/Files/ParseFileController.cs @@ -23,7 +23,7 @@ public Task SaveAsync(FileState state, if (state.Url != null) { // !isDirty - return Task.FromResult(state); + return Task.FromResult(state); } if (cancellationToken.IsCancellationRequested) diff --git a/Parse/Platform/Installation/ParseCurrentInstallationController.cs b/Parse/Platform/Installation/ParseCurrentInstallationController.cs index 780b8cb4..5791c492 100644 --- a/Parse/Platform/Installation/ParseCurrentInstallationController.cs +++ b/Parse/Platform/Installation/ParseCurrentInstallationController.cs @@ -73,7 +73,7 @@ public Task GetAsync(CancellationToken cancellationToken) if (cachedCurrent != null) { - return Task.FromResult(cachedCurrent); + return Task.FromResult(cachedCurrent); } return taskQueue.Enqueue(toAwait => @@ -113,7 +113,7 @@ public Task ExistsAsync(CancellationToken cancellationToken) { if (CurrentInstallation != null) { - return Task.FromResult(true); + return Task.FromResult(true); } return taskQueue.Enqueue(toAwait => diff --git a/Parse/Platform/Notifications/MutablePushState.cs b/Parse/Platform/Notifications/MutablePushState.cs index aa2a3abd..e5ec7b57 100644 --- a/Parse/Platform/Notifications/MutablePushState.cs +++ b/Parse/Platform/Notifications/MutablePushState.cs @@ -42,13 +42,13 @@ public override bool Equals(object obj) } MutablePushState other = obj as MutablePushState; - return Object.Equals(Query, other.Query) && + return Equals(Query, other.Query) && Channels.CollectionsEqual(other.Channels) && - Object.Equals(Expiration, other.Expiration) && - Object.Equals(ExpirationInterval, other.ExpirationInterval) && - Object.Equals(PushTime, other.PushTime) && + Equals(Expiration, other.Expiration) && + Equals(ExpirationInterval, other.ExpirationInterval) && + Equals(PushTime, other.PushTime) && Data.CollectionsEqual(other.Data) && - Object.Equals(Alert, other.Alert); + Equals(Alert, other.Alert); } public override int GetHashCode() => diff --git a/Parse/Platform/Notifications/ParsePushPlugins.cs b/Parse/Platform/Notifications/ParsePushPlugins.cs index 86108968..6f612e39 100644 --- a/Parse/Platform/Notifications/ParsePushPlugins.cs +++ b/Parse/Platform/Notifications/ParsePushPlugins.cs @@ -11,7 +11,7 @@ public static IParsePushPlugins Instance { get { - instance = instance ?? new ParsePushPlugins(); + instance ??= new ParsePushPlugins(); return instance; } set @@ -49,7 +49,7 @@ public IParseCorePlugins CorePlugins { lock (mutex) { - corePlugins = corePlugins ?? ParseCorePlugins.Instance; + corePlugins ??= ParseCorePlugins.Instance; return corePlugins; } } @@ -68,7 +68,7 @@ public IParsePushChannelsController PushChannelsController { lock (mutex) { - pushChannelsController = pushChannelsController ?? new ParsePushChannelsController(); + pushChannelsController ??= new ParsePushChannelsController(); return pushChannelsController; } } @@ -87,7 +87,7 @@ public IParsePushController PushController { lock (mutex) { - pushController = pushController ?? new ParsePushController(CorePlugins.CommandRunner, CorePlugins.CurrentUserController); + pushController ??= new ParsePushController(CorePlugins.CommandRunner, CorePlugins.CurrentUserController); return pushController; } } @@ -106,7 +106,7 @@ public IParseCurrentInstallationController CurrentInstallationController { lock (mutex) { - currentInstallationController = currentInstallationController ?? new ParseCurrentInstallationController( + currentInstallationController ??= new ParseCurrentInstallationController( CorePlugins.InstallationController, CorePlugins.StorageController, ParseInstallationCoder.Instance ); return currentInstallationController; @@ -127,7 +127,7 @@ public IDeviceInfoController DeviceInfoController { lock (mutex) { - deviceInfoController = deviceInfoController ?? new DeviceInfoController(); + deviceInfoController ??= new DeviceInfoController(); return deviceInfoController; } } diff --git a/Parse/Utilities/AssemblyLister.cs b/Parse/Utilities/AssemblyLister.cs index 12344c8a..49be336d 100644 --- a/Parse/Utilities/AssemblyLister.cs +++ b/Parse/Utilities/AssemblyLister.cs @@ -25,7 +25,7 @@ public static IEnumerable AllAssemblies private static IEnumerable DeepWalkReferences(this Assembly assembly, HashSet seen = null) { - seen = seen ?? new HashSet(); + seen ??= new HashSet(); if (!seen.Add(assembly.FullName)) { diff --git a/Parse/Utilities/Encoding/ParseDecoder.cs b/Parse/Utilities/Encoding/ParseDecoder.cs index 81238d99..12c69b53 100644 --- a/Parse/Utilities/Encoding/ParseDecoder.cs +++ b/Parse/Utilities/Encoding/ParseDecoder.cs @@ -104,6 +104,6 @@ public object Decode(object data) public static DateTime ParseDate(string input) => // TODO(hallucinogen): Figure out if we should be more flexible with the date formats // we accept. - System.DateTime.ParseExact(input, ParseClient.DateFormatStrings, CultureInfo.InvariantCulture, DateTimeStyles.None); + DateTime.ParseExact(input, ParseClient.DateFormatStrings, CultureInfo.InvariantCulture, DateTimeStyles.None); } } diff --git a/Parse/Utilities/HttpClient.Portable.cs b/Parse/Utilities/HttpClient.Portable.cs index 71417d16..3d8afeac 100644 --- a/Parse/Utilities/HttpClient.Portable.cs +++ b/Parse/Utilities/HttpClient.Portable.cs @@ -40,8 +40,8 @@ public UniversalWebClient() : this(new BuiltInClient { }) { } public Task> ExecuteAsync(HttpRequest httpRequest, IProgress uploadProgress, IProgress downloadProgress, CancellationToken cancellationToken) { - uploadProgress = uploadProgress ?? new Progress { }; - downloadProgress = downloadProgress ?? new Progress { }; + uploadProgress ??= new Progress { }; + downloadProgress ??= new Progress { }; HttpRequestMessage message = new HttpRequestMessage(new HttpMethod(httpRequest.Method), httpRequest.Uri); diff --git a/Parse/Utilities/InternalExtensions.cs b/Parse/Utilities/InternalExtensions.cs index a37dfc75..86c404ee 100644 --- a/Parse/Utilities/InternalExtensions.cs +++ b/Parse/Utilities/InternalExtensions.cs @@ -41,7 +41,7 @@ public static TValue GetOrDefault(this IDictionary s return defaultValue; } - public static bool CollectionsEqual(this IEnumerable a, IEnumerable b) => Object.Equals(a, b) || + public static bool CollectionsEqual(this IEnumerable a, IEnumerable b) => Equals(a, b) || (a != null && b != null && a.SequenceEqual(b)); diff --git a/Parse/Utilities/ParseQueryExtensions.cs b/Parse/Utilities/ParseQueryExtensions.cs index d669ab29..5ec38e6c 100644 --- a/Parse/Utilities/ParseQueryExtensions.cs +++ b/Parse/Utilities/ParseQueryExtensions.cs @@ -51,14 +51,14 @@ static ParseQueryExtensions() }, }; containsMethod = GetMethod( - o => ParseQueryExtensions.ContainsStub(null, null)).GetGenericMethodDefinition(); + o => ContainsStub(null, null)).GetGenericMethodDefinition(); notContainsMethod = GetMethod( - o => ParseQueryExtensions.NotContainsStub(null, null)) + o => NotContainsStub(null, null)) .GetGenericMethodDefinition(); - containsKeyMethod = GetMethod(o => ParseQueryExtensions.ContainsKeyStub(null, null)); + containsKeyMethod = GetMethod(o => ContainsKeyStub(null, null)); notContainsKeyMethod = GetMethod( - o => ParseQueryExtensions.NotContainsKeyStub(null, null)); + o => NotContainsKeyStub(null, null)); } /// From fa91f738efdf7acd296f909b0f91fe8ccfc36bfb Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Tue, 24 Mar 2020 00:59:01 -0700 Subject: [PATCH 05/24] Move IParseCorePlugins and implementation to management-related namespaces until it is converted into a generalized DI container or one is made. --- Parse.Test/ACLTests.cs | 1 + Parse.Test/AnalyticsTests.cs | 2 +- Parse.Test/CloudTests.cs | 1 + Parse.Test/CommandTests.cs | 1 + Parse.Test/ConfigTests.cs | 1 + Parse.Test/CurrentUserControllerTests.cs | 1 + Parse.Test/FileTests.cs | 1 + Parse.Test/InstallationIdControllerTests.cs | 1 + Parse.Test/InstallationTests.cs | 1 + Parse.Test/ObjectTests.cs | 1 + Parse.Test/PushTests.cs | 2 +- Parse.Test/SessionTests.cs | 1 + Parse.Test/UserTests.cs | 1 + .../IParseCorePlugins.cs | 3 +- .../Analytics/IParseAnalyticsPlugins.cs | 2 +- .../Notifications/IParsePushPlugins.cs | 2 +- .../Storage/IStorageConfiguration.cs | 2 +- Parse/Management/LightParseCorePlugins.cs | 1 + .../ParseCorePlugins.cs | 64 ++----------------- Parse/ParseClient.cs | 13 ++-- Parse/ParseFile.cs | 1 + Parse/ParseInstallation.cs | 2 +- Parse/ParseObject.cs | 1 + Parse/ParseQuery.cs | 1 + Parse/ParseRelation.cs | 1 + Parse/ParseSession.cs | 1 + Parse/ParseUser.cs | 1 + .../Analytics/ParseAnalyticsPlugins.cs | 4 +- Parse/Platform/Code/ParseCloud.cs | 1 + Parse/Platform/Configuration/ParseConfig.cs | 1 + .../Platform/Notifications/ParsePushModule.cs | 2 +- .../Notifications/ParsePushPlugins.cs | 4 +- ...entifierBasedCacheLocationConfiguration.cs | 2 +- ...MetadataBasedCacheLocationConfiguration.cs | 2 +- 34 files changed, 45 insertions(+), 81 deletions(-) rename Parse/Abstractions/{Library => Management}/IParseCorePlugins.cs (95%) rename Parse/{Library => Management}/ParseCorePlugins.cs (86%) diff --git a/Parse.Test/ACLTests.cs b/Parse.Test/ACLTests.cs index f5061613..255d3f3e 100644 --- a/Parse.Test/ACLTests.cs +++ b/Parse.Test/ACLTests.cs @@ -1,6 +1,7 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Parse.Core.Internal; +using Parse.Management; namespace Parse.Test { diff --git a/Parse.Test/AnalyticsTests.cs b/Parse.Test/AnalyticsTests.cs index 7f1d6d23..fb817f13 100644 --- a/Parse.Test/AnalyticsTests.cs +++ b/Parse.Test/AnalyticsTests.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Abstractions.Library; +using Parse.Abstractions.Management; using Parse.Analytics.Internal; using Parse.Core.Internal; diff --git a/Parse.Test/CloudTests.cs b/Parse.Test/CloudTests.cs index eb043a30..48024793 100644 --- a/Parse.Test/CloudTests.cs +++ b/Parse.Test/CloudTests.cs @@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Parse.Core.Internal; +using Parse.Management; namespace Parse.Test { diff --git a/Parse.Test/CommandTests.cs b/Parse.Test/CommandTests.cs index 2a79ec36..9d29d843 100644 --- a/Parse.Test/CommandTests.cs +++ b/Parse.Test/CommandTests.cs @@ -11,6 +11,7 @@ using Parse.Common.Internal; using Parse.Core.Internal; using Parse.Library; +using Parse.Management; namespace Parse.Test { diff --git a/Parse.Test/ConfigTests.cs b/Parse.Test/ConfigTests.cs index 85fec09c..4aec9d17 100644 --- a/Parse.Test/ConfigTests.cs +++ b/Parse.Test/ConfigTests.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Management; namespace Parse.Test { diff --git a/Parse.Test/CurrentUserControllerTests.cs b/Parse.Test/CurrentUserControllerTests.cs index e4eb24c8..9230c28c 100644 --- a/Parse.Test/CurrentUserControllerTests.cs +++ b/Parse.Test/CurrentUserControllerTests.cs @@ -7,6 +7,7 @@ using Moq; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Management; namespace Parse.Test { diff --git a/Parse.Test/FileTests.cs b/Parse.Test/FileTests.cs index 79a6ffbf..f8a2f407 100644 --- a/Parse.Test/FileTests.cs +++ b/Parse.Test/FileTests.cs @@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Parse.Core.Internal; +using Parse.Management; namespace Parse.Test { diff --git a/Parse.Test/InstallationIdControllerTests.cs b/Parse.Test/InstallationIdControllerTests.cs index 8a37b4f2..4b09a77c 100644 --- a/Parse.Test/InstallationIdControllerTests.cs +++ b/Parse.Test/InstallationIdControllerTests.cs @@ -5,6 +5,7 @@ using Moq; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Management; namespace Parse.Test { diff --git a/Parse.Test/InstallationTests.cs b/Parse.Test/InstallationTests.cs index ceae0285..1b878f2f 100644 --- a/Parse.Test/InstallationTests.cs +++ b/Parse.Test/InstallationTests.cs @@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Parse.Core.Internal; +using Parse.Management; using Parse.Push.Internal; namespace Parse.Test diff --git a/Parse.Test/ObjectTests.cs b/Parse.Test/ObjectTests.cs index 43c3440a..3608201d 100644 --- a/Parse.Test/ObjectTests.cs +++ b/Parse.Test/ObjectTests.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Parse.Core.Internal; +using Parse.Management; namespace Parse.Test { diff --git a/Parse.Test/PushTests.cs b/Parse.Test/PushTests.cs index 45c55b22..666bcfc4 100644 --- a/Parse.Test/PushTests.cs +++ b/Parse.Test/PushTests.cs @@ -5,7 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Parse.Common.Internal; -using Parse.Core.Internal; +using Parse.Management; using Parse.Push.Internal; namespace Parse.Test diff --git a/Parse.Test/SessionTests.cs b/Parse.Test/SessionTests.cs index a8ef61eb..b9f6d590 100644 --- a/Parse.Test/SessionTests.cs +++ b/Parse.Test/SessionTests.cs @@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Parse.Core.Internal; +using Parse.Management; namespace Parse.Test { diff --git a/Parse.Test/UserTests.cs b/Parse.Test/UserTests.cs index 11b4cd1f..f7c940d6 100644 --- a/Parse.Test/UserTests.cs +++ b/Parse.Test/UserTests.cs @@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Parse.Core.Internal; +using Parse.Management; namespace Parse.Test { diff --git a/Parse/Abstractions/Library/IParseCorePlugins.cs b/Parse/Abstractions/Management/IParseCorePlugins.cs similarity index 95% rename from Parse/Abstractions/Library/IParseCorePlugins.cs rename to Parse/Abstractions/Management/IParseCorePlugins.cs index 45c25674..2ef1aaab 100644 --- a/Parse/Abstractions/Library/IParseCorePlugins.cs +++ b/Parse/Abstractions/Management/IParseCorePlugins.cs @@ -1,9 +1,10 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. +using Parse.Abstractions.Library; using Parse.Common.Internal; using Parse.Core.Internal; -namespace Parse.Abstractions.Library +namespace Parse.Abstractions.Management { /// /// The dependency injection container for the .NET Parse SDK. diff --git a/Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs b/Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs index 38bd1bcc..5b07eafa 100644 --- a/Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs +++ b/Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs @@ -1,6 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Abstractions.Library; +using Parse.Abstractions.Management; using Parse.Analytics.Internal; namespace Parse.Analytics diff --git a/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs b/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs index 9146ecaa..af2f1005 100644 --- a/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs +++ b/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs @@ -1,4 +1,4 @@ -using Parse.Abstractions.Library; +using Parse.Abstractions.Management; namespace Parse.Push.Internal { diff --git a/Parse/Abstractions/Storage/IStorageConfiguration.cs b/Parse/Abstractions/Storage/IStorageConfiguration.cs index e2fa4a31..0aa22a91 100644 --- a/Parse/Abstractions/Storage/IStorageConfiguration.cs +++ b/Parse/Abstractions/Storage/IStorageConfiguration.cs @@ -1,4 +1,4 @@ -using Parse.Abstractions.Library; +using Parse.Abstractions.Management; namespace Parse.Abstractions.Storage { diff --git a/Parse/Management/LightParseCorePlugins.cs b/Parse/Management/LightParseCorePlugins.cs index 2b30b730..412232d3 100644 --- a/Parse/Management/LightParseCorePlugins.cs +++ b/Parse/Management/LightParseCorePlugins.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using Parse.Abstractions.Library; +using Parse.Abstractions.Management; using Parse.Common.Internal; using Parse.Core.Internal; diff --git a/Parse/Library/ParseCorePlugins.cs b/Parse/Management/ParseCorePlugins.cs similarity index 86% rename from Parse/Library/ParseCorePlugins.cs rename to Parse/Management/ParseCorePlugins.cs index 527e1d9d..ad40e7d1 100644 --- a/Parse/Library/ParseCorePlugins.cs +++ b/Parse/Management/ParseCorePlugins.cs @@ -1,14 +1,16 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using Parse.Abstractions.Library; +using Parse.Abstractions.Management; using Parse.Common.Internal; +using Parse.Core.Internal; using Parse.Library; #if DEBUG [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Parse.Test")] #endif -namespace Parse.Core.Internal +namespace Parse.Management { public class ParseCorePlugins : IParseCorePlugins { @@ -20,16 +22,12 @@ public static IParseCorePlugins Instance get { lock (Mutex) - { return instance ??= new ParseCorePlugins(); - } } set { lock (Mutex) - { instance = value; - } } } @@ -87,16 +85,12 @@ public IMetadataController MetadataController get { lock (mutex) - { return metadataController ??= new MetadataController { }; - } } set { lock (mutex) - { metadataController = value; - } } } @@ -105,16 +99,12 @@ public IWebClient WebClient get { lock (mutex) - { return httpClient ??= new UniversalWebClient { }; - } } set { lock (mutex) - { httpClient = value; - } } } @@ -123,16 +113,12 @@ public IParseCommandRunner CommandRunner get { lock (mutex) - { return commandRunner ??= new ParseCommandRunner(WebClient, InstallationController, MetadataController); - } } set { lock (mutex) - { commandRunner = value; - } } } @@ -141,16 +127,12 @@ public IStorageController StorageController get { lock (mutex) - { return storageController ??= new StorageController(); - } } set { lock (mutex) - { storageController = value; - } } } @@ -159,16 +141,12 @@ public IParseCloudCodeController CloudCodeController get { lock (mutex) - { return cloudCodeController ??= new ParseCloudCodeController(CommandRunner); - } } set { lock (mutex) - { cloudCodeController = value; - } } } @@ -177,16 +155,12 @@ public IParseFileController FileController get { lock (mutex) - { return fileController ??= new ParseFileController(CommandRunner); - } } set { lock (mutex) - { fileController = value; - } } } @@ -195,16 +169,12 @@ public IParseConfigController ConfigController get { lock (mutex) - { return configController ?? (configController = new ParseConfigController(CommandRunner, StorageController)); - } } set { lock (mutex) - { configController = value; - } } } @@ -213,16 +183,12 @@ public IParseObjectController ObjectController get { lock (mutex) - { return objectController ??= new ParseObjectController(CommandRunner); - } } set { lock (mutex) - { objectController = value; - } } } @@ -231,16 +197,12 @@ public IParseQueryController QueryController get { lock (mutex) - { return queryController ?? (queryController = new ParseQueryController(CommandRunner)); - } } set { lock (mutex) - { queryController = value; - } } } @@ -249,16 +211,12 @@ public IParseSessionController SessionController get { lock (mutex) - { return sessionController ??= new ParseSessionController(CommandRunner); - } } set { lock (mutex) - { sessionController = value; - } } } @@ -267,16 +225,12 @@ public IParseUserController UserController get { lock (mutex) - { - return (userController ??= new ParseUserController(CommandRunner)); - } + return userController ??= new ParseUserController(CommandRunner); } set { lock (mutex) - { userController = value; - } } } @@ -285,16 +239,12 @@ public IParseCurrentUserController CurrentUserController get { lock (mutex) - { return currentUserController ??= new ParseCurrentUserController(StorageController); - } } set { lock (mutex) - { currentUserController = value; - } } } @@ -315,9 +265,7 @@ public IObjectSubclassingController SubclassingController set { lock (mutex) - { subclassingController = value; - } } } @@ -326,16 +274,12 @@ public IParseInstallationController InstallationController get { lock (mutex) - { return installationController ??= new ParseInstallationController(StorageController); - } } set { lock (mutex) - { installationController = value; - } } } } diff --git a/Parse/ParseClient.cs b/Parse/ParseClient.cs index 2678c119..7406e3b3 100644 --- a/Parse/ParseClient.cs +++ b/Parse/ParseClient.cs @@ -5,10 +5,11 @@ using System.Linq; using System.Reflection; using Parse.Abstractions.Library; +using Parse.Abstractions.Management; using Parse.Abstractions.Storage; using Parse.Common.Internal; -using Parse.Core.Internal; using Parse.Library; +using Parse.Management; namespace Parse { @@ -66,12 +67,10 @@ public static void Initialize(Configuration configuration, ICacheLocationConfigu bool keepRelativeStoragePath = plugins is { StorageController: { } }, keepVersion = plugins is { MetadataController: { } }; - //plugins.SetDefaults(); - - //if (plugins is ParseCorePlugins corePlugins) - //{ - // corePlugins.Activate(); - //} + if (plugins is ParseCorePlugins) + { + ParseCorePlugins.Instance = plugins; + } if (hostVersioning is { } && !keepVersion && plugins.MetadataController is MetadataController { } metadataController) { diff --git a/Parse/ParseFile.cs b/Parse/ParseFile.cs index 1eb66cdf..aca34a81 100644 --- a/Parse/ParseFile.cs +++ b/Parse/ParseFile.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Management; namespace Parse { diff --git a/Parse/ParseInstallation.cs b/Parse/ParseInstallation.cs index d5faa481..89860001 100644 --- a/Parse/ParseInstallation.cs +++ b/Parse/ParseInstallation.cs @@ -8,8 +8,8 @@ using System.Threading.Tasks; using Parse.Abstractions.Library; using Parse.Common.Internal; -using Parse.Core.Internal; using Parse.Library; +using Parse.Management; using Parse.Push.Internal; namespace Parse diff --git a/Parse/ParseObject.cs b/Parse/ParseObject.cs index eefac211..5579560c 100644 --- a/Parse/ParseObject.cs +++ b/Parse/ParseObject.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Management; using Parse.Utilities; namespace Parse diff --git a/Parse/ParseQuery.cs b/Parse/ParseQuery.cs index 760d595e..e714dd32 100644 --- a/Parse/ParseQuery.cs +++ b/Parse/ParseQuery.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Management; namespace Parse { diff --git a/Parse/ParseRelation.cs b/Parse/ParseRelation.cs index 1f4aca02..5b7eb720 100644 --- a/Parse/ParseRelation.cs +++ b/Parse/ParseRelation.cs @@ -8,6 +8,7 @@ using System.Reflection; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Management; namespace Parse { diff --git a/Parse/ParseSession.cs b/Parse/ParseSession.cs index 78f0ec2e..37c038bf 100644 --- a/Parse/ParseSession.cs +++ b/Parse/ParseSession.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Management; namespace Parse { diff --git a/Parse/ParseUser.cs b/Parse/ParseUser.cs index 157550f6..5eff7e56 100644 --- a/Parse/ParseUser.cs +++ b/Parse/ParseUser.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Management; namespace Parse { diff --git a/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs b/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs index 06276fa4..ea3fd790 100644 --- a/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs +++ b/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs @@ -1,7 +1,7 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Abstractions.Library; -using Parse.Core.Internal; +using Parse.Abstractions.Management; +using Parse.Management; namespace Parse.Analytics.Internal { diff --git a/Parse/Platform/Code/ParseCloud.cs b/Parse/Platform/Code/ParseCloud.cs index 45b334e9..524c24de 100644 --- a/Parse/Platform/Code/ParseCloud.cs +++ b/Parse/Platform/Code/ParseCloud.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Parse.Core.Internal; +using Parse.Management; namespace Parse { diff --git a/Parse/Platform/Configuration/ParseConfig.cs b/Parse/Platform/Configuration/ParseConfig.cs index e461f1e3..f2decc8e 100644 --- a/Parse/Platform/Configuration/ParseConfig.cs +++ b/Parse/Platform/Configuration/ParseConfig.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Management; using Parse.Utilities; namespace Parse diff --git a/Parse/Platform/Notifications/ParsePushModule.cs b/Parse/Platform/Notifications/ParsePushModule.cs index 45d12a51..b29980f0 100644 --- a/Parse/Platform/Notifications/ParsePushModule.cs +++ b/Parse/Platform/Notifications/ParsePushModule.cs @@ -1,5 +1,5 @@ using Parse.Common.Internal; -using Parse.Core.Internal; +using Parse.Management; namespace Parse.Push.Internal { diff --git a/Parse/Platform/Notifications/ParsePushPlugins.cs b/Parse/Platform/Notifications/ParsePushPlugins.cs index 6f612e39..efa248fc 100644 --- a/Parse/Platform/Notifications/ParsePushPlugins.cs +++ b/Parse/Platform/Notifications/ParsePushPlugins.cs @@ -1,5 +1,5 @@ -using Parse.Abstractions.Library; -using Parse.Core.Internal; +using Parse.Abstractions.Management; +using Parse.Management; namespace Parse.Push.Internal { diff --git a/Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs b/Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs index dc28f3a5..4e3b25e4 100644 --- a/Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs +++ b/Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs @@ -1,6 +1,6 @@ using System; using System.IO; -using Parse.Abstractions.Library; +using Parse.Abstractions.Management; using Parse.Abstractions.Storage; namespace Parse.Storage diff --git a/Parse/Storage/MetadataBasedCacheLocationConfiguration.cs b/Parse/Storage/MetadataBasedCacheLocationConfiguration.cs index 652686d5..06e7a68e 100644 --- a/Parse/Storage/MetadataBasedCacheLocationConfiguration.cs +++ b/Parse/Storage/MetadataBasedCacheLocationConfiguration.cs @@ -1,7 +1,7 @@ using System; using System.IO; using System.Reflection; -using Parse.Abstractions.Library; +using Parse.Abstractions.Management; using Parse.Abstractions.Storage; namespace Parse.Storage From 69be94d6e526e9198dfb60ecd24f08f2b0b93c94 Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Mon, 6 Apr 2020 16:58:01 -0700 Subject: [PATCH 06/24] Introduce new ServiceHub-based infrastructure elements and use them instead of the old plugins system, then make the main library compile. Also, restructure metadata injection, and inject revocable session token information as well as all server connection data, as opposed to having any of it statically defined or stored in the ParseClient class. --- .editorconfig | 14 +- Parse.Test/AnalyticsControllerTests.cs | 12 +- Parse.Test/AnalyticsTests.cs | 6 +- Parse.Test/CloudControllerTests.cs | 4 +- Parse.Test/CloudTests.cs | 2 +- Parse.Test/CommandTests.cs | 30 +- Parse.Test/ConfigTests.cs | 20 +- Parse.Test/DecoderTests.cs | 40 +- Parse.Test/EncoderTests.cs | 51 +- Parse.Test/FileControllerTests.cs | 16 +- Parse.Test/FileStateTests.cs | 18 +- Parse.Test/FileTests.cs | 2 +- Parse.Test/GeoPointTests.cs | 6 +- Parse.Test/ObjectControllerTests.cs | 72 +- Parse.Test/ObjectStateTests.cs | 10 +- Parse.Test/ObjectTests.cs | 28 +- Parse.Test/ProgressTests.cs | 48 +- Parse.Test/PushStateTests.cs | 5 +- Parse.Test/SessionControllerTests.cs | 20 +- Parse.Test/UserControllerTests.cs | 14 +- .../Abstractions/Library/CustomServiceHub.cs | 58 + .../Abstractions/Library/ICustomServiceHub.cs | 11 + .../Library/IDataTransferLevel.cs} | 13 +- .../Abstractions/Library/IEnvironmentData.cs | 24 + .../Library/IHostApplicationVersioningData.cs | 40 - .../Abstractions/Library/IHostManifestData.cs | 28 + .../Library/IMetadataController.cs | 9 +- .../Library/IMutableServiceHub.cs | 47 + .../Library/IServerConnectionData.cs | 32 + Parse/Abstractions/Library/IServiceHub.cs | 52 + .../Abstractions/Library/IServiceHubCloner.cs | 10 + .../Library/IServiceHubComposer.cs | 9 + .../Library/IServiceHubMutator.cs | 18 + .../Management/IParseCommandRunner.cs | 5 +- .../Management/IParseCorePlugins.cs | 16 +- ...ller.cs => IParseObjectClassController.cs} | 13 +- .../IParseObjectCurrentController.cs | 6 +- .../Management/IParseUserController.cs | 16 +- ...er.cs => IParseConfigurationController.cs} | 10 +- ...> IParseCurrentConfigurationController.cs} | 6 +- .../Platform/Files/IParseFileController.cs | 6 +- .../IParseInstallationDataFinalizer.cs} | 14 +- .../Notifications/IParsePushPlugins.cs | 2 +- .../Storage/IStorageConfiguration.cs | 3 +- ...ytics.cs => AnalyticsServiceExtensions.cs} | 32 +- ...Cloud.cs => CloudCodeServiceExtensions.cs} | 13 +- Parse/ConfigurationServiceExtensions.cs | 42 + Parse/Device/DeviceInfoController.cs | 43 - Parse/InstallationServiceExtensions.cs | 44 + Parse/Library/CacheLocationMutator.cs | 26 + .../Library/ConcurrentUserServiceHubCloner.cs | 15 + Parse/Library/EnvironmentData.cs | 39 + .../Library/HostApplicationVersioningData.cs | 56 - Parse/Library/HostManifestData.cs | 63 + Parse/Library/MetadataController.cs | 9 +- Parse/Library/MetadataMutator.cs | 26 + Parse/Library/MutableServiceHub.cs | 87 + Parse/Library/OrchestrationServiceHub.cs | 64 + .../Library/ParseDownloadProgressEventArgs.cs | 19 - ...eException.cs => ParseFailureException.cs} | 5 +- ...tArgs.cs => ParsePushNotificationEvent.cs} | 25 +- ...nfiguration.cs => ServerConnectionData.cs} | 10 +- Parse/Library/ServiceHub.cs | 53 + Parse/Library/Utilities/LateInitializer.cs | 31 + Parse/Management/LightParseCorePlugins.cs | 6 +- Parse/Management/ObjectSubclassInfo.cs | 22 +- .../Management/ObjectSubclassingController.cs | 111 +- Parse/Management/ParseCommand.cs | 41 +- Parse/Management/ParseCommandRunner.cs | 124 +- Parse/Management/ParseCorePlugins.cs | 509 +++-- .../Management/ParseCurrentUserController.cs | 168 +- Parse/Management/ParseObjectController.cs | 177 +- Parse/Management/ParseUserController.cs | 93 +- .../Management/Tracking/MutableObjectState.cs | 2 +- .../Management/Tracking/ParseAddOperation.cs | 2 +- .../Tracking/ParseAddUniqueOperation.cs | 2 +- .../Tracking/ParseDeleteOperation.cs | 13 +- .../Tracking/ParseIncrementOperation.cs | 2 +- .../Tracking/ParseRelationOperation.cs | 154 +- .../Tracking/ParseRemoveOperation.cs | 2 +- Parse/Modules/IParseModule.cs | 4 +- Parse/Modules/ParseModuleController.cs | 49 +- Parse/ObjectServiceExtensions.cs | 510 +++++ Parse/Parse.csproj | 5 + Parse/ParseClient.cs | 123 +- .../ParseConfig.cs => ParseConfiguration.cs} | 70 +- Parse/ParseFile.cs | 117 +- Parse/ParseGeoPoint.cs | 6 +- Parse/ParseInstallation.cs | 118 +- Parse/ParseObject.cs | 1833 ++++++----------- Parse/ParsePush.cs | 466 +---- Parse/ParseQuery.cs | 247 +-- Parse/ParseRelation.cs | 90 +- Parse/ParseRole.cs | 18 +- Parse/ParseSession.cs | 71 +- Parse/ParseUser.cs | 356 +--- .../Analytics/ParseAnalyticsController.cs | 4 +- .../Analytics/ParseAnalyticsPlugins.cs | 152 +- .../Platform/Code/ParseCloudCodeController.cs | 26 +- .../Configuration/ParseConfigController.cs | 47 - .../ParseConfigurationController.cs | 41 + .../ParseCurrentConfigController.cs | 80 - .../ParseCurrentConfigurationController.cs | 55 + Parse/Platform/Files/FileState.cs | 36 +- Parse/Platform/Files/ParseFileController.cs | 64 +- .../Installation/InstallationIdController.cs | 77 - .../ParseCurrentInstallationController.cs | 132 +- .../Installation/ParseInstallationCoder.cs | 30 +- .../ParseInstallationController.cs | 66 + .../ParseInstallationDataFinalizer.cs | 15 + .../ParsePushChannelsController.cs | 23 +- .../Notifications/ParsePushEncoder.cs | 31 +- .../Platform/Notifications/ParsePushModule.cs | 29 +- .../Notifications/ParsePushPlugins.cs | 268 +-- Parse/Platform/Security/ParseACL.cs | 2 +- .../Session/ParseSessionController.cs | 50 +- Parse/PushServiceExtensions.cs | 201 ++ Parse/Query/ParseQueryController.cs | 14 +- Parse/QueryServiceExtensions.cs | 67 + Parse/RoleServiceExtensions.cs | 14 + Parse/SessionsServiceExtensions.cs | 38 + ...entifierBasedCacheLocationConfiguration.cs | 9 +- ...MetadataBasedCacheLocationConfiguration.cs | 18 +- Parse/Storage/StorageController.cs | 3 +- Parse/UserServiceExtensions.cs | 232 +++ Parse/Utilities/Conversion.cs | 53 +- Parse/Utilities/Encoding/IParseDataDecoder.cs | 9 + Parse/Utilities/Encoding/NoObjectsEncoder.cs | 13 +- Parse/Utilities/Encoding/ParseDataDecoder.cs | 47 + Parse/Utilities/Encoding/ParseDataEncoder.cs | 72 + Parse/Utilities/Encoding/ParseDecoder.cs | 109 - Parse/Utilities/Encoding/ParseEncoder.cs | 120 -- Parse/Utilities/Encoding/ParseObjectCoder.cs | 28 +- .../Encoding/PointerOrLocalIdEncoder.cs | 28 +- Parse/Utilities/IJsonConvertible.cs | 2 +- .../{IHttpClient.cs => IWebClient.cs} | 9 +- Parse/Utilities/InternalExtensions.cs | 9 +- Parse/Utilities/Json.cs | 6 +- Parse/Utilities/ParseConfigExtensions.cs | 21 - Parse/Utilities/ParseExtensions.cs | 77 +- Parse/Utilities/ParseObjectExtensions.cs | 35 - Parse/Utilities/ParseQueryExtensions.cs | 532 ++--- Parse/Utilities/ParseSessionExtensions.cs | 24 - Parse/Utilities/ParseUserExtensions.cs | 7 +- Parse/Utilities/ReflectionHelpers.cs | 111 +- ...ient.Portable.cs => UniversalWebClient.cs} | 61 +- .../{HttpRequest.cs => WebRequest.cs} | 9 +- 147 files changed, 5008 insertions(+), 5277 deletions(-) create mode 100644 Parse/Abstractions/Library/CustomServiceHub.cs create mode 100644 Parse/Abstractions/Library/ICustomServiceHub.cs rename Parse/{Library/ParseUploadProgressEventArgs.cs => Abstractions/Library/IDataTransferLevel.cs} (70%) create mode 100644 Parse/Abstractions/Library/IEnvironmentData.cs delete mode 100644 Parse/Abstractions/Library/IHostApplicationVersioningData.cs create mode 100644 Parse/Abstractions/Library/IHostManifestData.cs create mode 100644 Parse/Abstractions/Library/IMutableServiceHub.cs create mode 100644 Parse/Abstractions/Library/IServerConnectionData.cs create mode 100644 Parse/Abstractions/Library/IServiceHub.cs create mode 100644 Parse/Abstractions/Library/IServiceHubCloner.cs create mode 100644 Parse/Abstractions/Library/IServiceHubComposer.cs create mode 100644 Parse/Abstractions/Library/IServiceHubMutator.cs rename Parse/Abstractions/Management/{IObjectSubclassingController.cs => IParseObjectClassController.cs} (63%) rename Parse/Abstractions/Platform/Configuration/{IParseConfigController.cs => IParseConfigurationController.cs} (64%) rename Parse/Abstractions/Platform/Configuration/{IParseCurrentConfigController.cs => IParseCurrentConfigurationController.cs} (86%) rename Parse/Abstractions/{Device/IDeviceInfoController.cs => Platform/Installation/IParseInstallationDataFinalizer.cs} (54%) rename Parse/{Platform/Analytics/ParseAnalytics.cs => AnalyticsServiceExtensions.cs} (73%) rename Parse/{Platform/Code/ParseCloud.cs => CloudCodeServiceExtensions.cs} (78%) create mode 100644 Parse/ConfigurationServiceExtensions.cs delete mode 100644 Parse/Device/DeviceInfoController.cs create mode 100644 Parse/InstallationServiceExtensions.cs create mode 100644 Parse/Library/CacheLocationMutator.cs create mode 100644 Parse/Library/ConcurrentUserServiceHubCloner.cs create mode 100644 Parse/Library/EnvironmentData.cs delete mode 100644 Parse/Library/HostApplicationVersioningData.cs create mode 100644 Parse/Library/HostManifestData.cs create mode 100644 Parse/Library/MetadataMutator.cs create mode 100644 Parse/Library/MutableServiceHub.cs create mode 100644 Parse/Library/OrchestrationServiceHub.cs delete mode 100644 Parse/Library/ParseDownloadProgressEventArgs.cs rename Parse/Library/{ParseException.cs => ParseFailureException.cs} (98%) rename Parse/Library/{ParsePushNotificationEventArgs.cs => ParsePushNotificationEvent.cs} (64%) rename Parse/Library/{Configuration.cs => ServerConnectionData.cs} (72%) create mode 100644 Parse/Library/ServiceHub.cs create mode 100644 Parse/Library/Utilities/LateInitializer.cs create mode 100644 Parse/ObjectServiceExtensions.cs rename Parse/{Platform/Configuration/ParseConfig.cs => ParseConfiguration.cs} (51%) delete mode 100644 Parse/Platform/Configuration/ParseConfigController.cs create mode 100644 Parse/Platform/Configuration/ParseConfigurationController.cs delete mode 100644 Parse/Platform/Configuration/ParseCurrentConfigController.cs create mode 100644 Parse/Platform/Configuration/ParseCurrentConfigurationController.cs delete mode 100644 Parse/Platform/Installation/InstallationIdController.cs create mode 100644 Parse/Platform/Installation/ParseInstallationController.cs create mode 100644 Parse/Platform/Installation/ParseInstallationDataFinalizer.cs create mode 100644 Parse/PushServiceExtensions.cs create mode 100644 Parse/QueryServiceExtensions.cs create mode 100644 Parse/RoleServiceExtensions.cs create mode 100644 Parse/SessionsServiceExtensions.cs create mode 100644 Parse/UserServiceExtensions.cs create mode 100644 Parse/Utilities/Encoding/IParseDataDecoder.cs create mode 100644 Parse/Utilities/Encoding/ParseDataDecoder.cs create mode 100644 Parse/Utilities/Encoding/ParseDataEncoder.cs delete mode 100644 Parse/Utilities/Encoding/ParseDecoder.cs delete mode 100644 Parse/Utilities/Encoding/ParseEncoder.cs rename Parse/Utilities/{IHttpClient.cs => IWebClient.cs} (66%) delete mode 100644 Parse/Utilities/ParseConfigExtensions.cs delete mode 100644 Parse/Utilities/ParseObjectExtensions.cs delete mode 100644 Parse/Utilities/ParseSessionExtensions.cs rename Parse/Utilities/{HttpClient.Portable.cs => UniversalWebClient.cs} (68%) rename Parse/Utilities/{HttpRequest.cs => WebRequest.cs} (83%) diff --git a/.editorconfig b/.editorconfig index ef749478..330d330b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -81,4 +81,16 @@ csharp_space_between_method_call_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_preserve_single_line_statements = false -csharp_preserve_single_line_blocks = true \ No newline at end of file +csharp_preserve_single_line_blocks = true + +# CC0005: Empty Object Initializer +dotnet_diagnostic.CC0005.severity = none + +# CC0105: You should use 'var' whenever possible. +dotnet_diagnostic.CC0105.severity = none + +# CC0001: You should use 'var' whenever possible. +dotnet_diagnostic.CC0001.severity = none + +# CC0057: Unused parameters +dotnet_diagnostic.CC0057.severity = none \ No newline at end of file diff --git a/Parse.Test/AnalyticsControllerTests.cs b/Parse.Test/AnalyticsControllerTests.cs index eb29033f..852ce1cb 100644 --- a/Parse.Test/AnalyticsControllerTests.cs +++ b/Parse.Test/AnalyticsControllerTests.cs @@ -16,7 +16,7 @@ namespace Parse.Test public class AnalyticsControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "", Test = true }); + public void SetUp() => ParseClient.Initialize(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(AnalyticsControllerTests))] @@ -28,7 +28,7 @@ public Task TestTrackEventWithEmptyDimensions() { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/events/SomeEvent"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/events/SomeEvent"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -44,7 +44,7 @@ public Task TestTrackEventWithNonEmptyDimensions() { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath.Contains("/1/events/SomeEvent")), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath.Contains("/1/events/SomeEvent")), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -58,7 +58,7 @@ public Task TestTrackAppOpenedWithEmptyPushHash() { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/events/AppOpened"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/events/AppOpened"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -74,14 +74,14 @@ public Task TestTrackAppOpenedWithNonEmptyPushHash() { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } Mock CreateMockRunner(Tuple> response) { Mock mockRunner = new Mock(); - mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); + mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); return mockRunner; } diff --git a/Parse.Test/AnalyticsTests.cs b/Parse.Test/AnalyticsTests.cs index fb817f13..515ecb14 100644 --- a/Parse.Test/AnalyticsTests.cs +++ b/Parse.Test/AnalyticsTests.cs @@ -34,7 +34,7 @@ public Task TestTrackEvent() CorePlugins = mockCorePlugins.Object }; - return ParseAnalytics.TrackEventAsync("SomeEvent").ContinueWith(t => + return AnalyticsServiceExtensions.TrackEventAsync("SomeEvent").ContinueWith(t => { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); @@ -60,7 +60,7 @@ public Task TestTrackEventWithDimension() CorePlugins = mockCorePlugins.Object }; - return ParseAnalytics.TrackEventAsync("SomeEvent", new Dictionary { ["facebook"] = "hq" }).ContinueWith(t => + return AnalyticsServiceExtensions.TrackEventAsync("SomeEvent", new Dictionary { ["facebook"] = "hq" }).ContinueWith(t => { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); @@ -86,7 +86,7 @@ public Task TestTrackAppOpened() CorePlugins = mockCorePlugins.Object }; - return ParseAnalytics.TrackAppOpenedAsync().ContinueWith(t => + return AnalyticsServiceExtensions.TrackAppOpenedAsync().ContinueWith(t => { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); diff --git a/Parse.Test/CloudControllerTests.cs b/Parse.Test/CloudControllerTests.cs index 1bc4d730..1156b9a3 100644 --- a/Parse.Test/CloudControllerTests.cs +++ b/Parse.Test/CloudControllerTests.cs @@ -15,7 +15,7 @@ namespace Parse.Test public class CloudControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "", Test = true }); + public void SetUp() => ParseClient.Initialize(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(CloudControllerTests))] @@ -64,7 +64,7 @@ public Task TestCallFunction() private Mock CreateMockRunner(Tuple> response) { Mock mockRunner = new Mock { }; - mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); + mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); return mockRunner; } diff --git a/Parse.Test/CloudTests.cs b/Parse.Test/CloudTests.cs index 48024793..fe87768b 100644 --- a/Parse.Test/CloudTests.cs +++ b/Parse.Test/CloudTests.cs @@ -28,7 +28,7 @@ public Task TestCloudFunctions() CurrentUserController = new Mock().Object }; - return ParseCloud.CallFunctionAsync>("someFunction", null, CancellationToken.None).ContinueWith(t => + return CloudCodeServiceExtensions.CallFunctionAsync>("someFunction", null, CancellationToken.None).ContinueWith(t => { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); diff --git a/Parse.Test/CommandTests.cs b/Parse.Test/CommandTests.cs index 9d29d843..a9ccdd2e 100644 --- a/Parse.Test/CommandTests.cs +++ b/Parse.Test/CommandTests.cs @@ -23,8 +23,8 @@ public class CommandTests [TestInitialize] public void SetUp() { - ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "", Test = true }); - MockMetadataController.Setup(metadata => metadata.HostVersioningData).Returns(new HostApplicationVersioningData { BuildVersion = "1", DisplayVersion = "1", HostOSVersion = "1" }); + ParseClient.Initialize(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); + MockMetadataController.Setup(metadata => metadata.HostManifestData).Returns(new HostManifestData { Version = "1", ShortVersion = "1", HostOSVersion = "1" }); } [TestCleanup] @@ -35,7 +35,7 @@ public void TestMakeCommand() { ParseCommand command = new ParseCommand("endpoint", method: "GET", sessionToken: "abcd", headers: null, data: null); - Assert.AreEqual("/1/endpoint", command.Uri.AbsolutePath); + Assert.AreEqual("/1/endpoint", command.Target.AbsolutePath); Assert.AreEqual("GET", command.Method); Assert.IsTrue(command.Headers.Any(pair => pair.Key == "X-Parse-Session-Token" && pair.Value == "abcd")); } @@ -47,7 +47,7 @@ public Task TestRunCommand() Mock mockHttpClient = new Mock(); Mock mockInstallationIdController = new Mock(); Task> fakeResponse = Task.FromResult(new Tuple(HttpStatusCode.OK, "{}")); - mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(fakeResponse); + mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(fakeResponse); mockInstallationIdController.Setup(i => i.GetAsync()).Returns(Task.FromResult(null)); @@ -66,7 +66,7 @@ public Task TestRunCommandWithArrayResult() { Mock mockHttpClient = new Mock(); Mock mockInstallationIdController = new Mock(); - mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "[]"))); + mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "[]"))); mockInstallationIdController.Setup(i => i.GetAsync()).Returns(Task.FromResult(null)); @@ -87,7 +87,7 @@ public Task TestRunCommandWithInvalidString() { Mock mockHttpClient = new Mock(); Mock mockInstallationIdController = new Mock(); - mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "invalid"))); + mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "invalid"))); mockInstallationIdController.Setup(i => i.GetAsync()).Returns(Task.FromResult(null)); @@ -95,8 +95,8 @@ public Task TestRunCommandWithInvalidString() { Assert.IsTrue(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - Assert.IsInstanceOfType(t.Exception.InnerException, typeof(ParseException)); - Assert.AreEqual(ParseException.ErrorCode.OtherCause, (t.Exception.InnerException as ParseException).Code); + Assert.IsInstanceOfType(t.Exception.InnerException, typeof(ParseFailureException)); + Assert.AreEqual(ParseFailureException.ErrorCode.OtherCause, (t.Exception.InnerException as ParseFailureException).Code); }); } @@ -106,7 +106,7 @@ public Task TestRunCommandWithErrorCode() { Mock mockHttpClient = new Mock(); Mock mockInstallationIdController = new Mock(); - mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.NotFound, "{ \"code\": 101, \"error\": \"Object not found.\" }"))); + mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.NotFound, "{ \"code\": 101, \"error\": \"Object not found.\" }"))); mockInstallationIdController.Setup(i => i.GetAsync()).Returns(Task.FromResult(null)); @@ -114,9 +114,9 @@ public Task TestRunCommandWithErrorCode() { Assert.IsTrue(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - Assert.IsInstanceOfType(t.Exception.InnerException, typeof(ParseException)); - ParseException parseException = t.Exception.InnerException as ParseException; - Assert.AreEqual(ParseException.ErrorCode.ObjectNotFound, parseException.Code); + Assert.IsInstanceOfType(t.Exception.InnerException, typeof(ParseFailureException)); + ParseFailureException parseException = t.Exception.InnerException as ParseFailureException; + Assert.AreEqual(ParseFailureException.ErrorCode.ObjectNotFound, parseException.Code); Assert.AreEqual("Object not found.", parseException.Message); }); } @@ -128,15 +128,15 @@ public Task TestRunCommandWithInternalServerError() Mock mockHttpClient = new Mock(); Mock mockInstallationIdController = new Mock(); - mockHttpClient.Setup(client => client.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.InternalServerError, null))); + mockHttpClient.Setup(client => client.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.InternalServerError, null))); mockInstallationIdController.Setup(installationController => installationController.GetAsync()).Returns(Task.FromResult(null)); return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, MockMetadataController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => { Assert.IsTrue(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - Assert.IsInstanceOfType(t.Exception.InnerException, typeof(ParseException)); - Assert.AreEqual(ParseException.ErrorCode.InternalServerError, (t.Exception.InnerException as ParseException).Code); + Assert.IsInstanceOfType(t.Exception.InnerException, typeof(ParseFailureException)); + Assert.AreEqual(ParseFailureException.ErrorCode.InternalServerError, (t.Exception.InnerException as ParseFailureException).Code); }); } } diff --git a/Parse.Test/ConfigTests.cs b/Parse.Test/ConfigTests.cs index 4aec9d17..bfacff0d 100644 --- a/Parse.Test/ConfigTests.cs +++ b/Parse.Test/ConfigTests.cs @@ -14,20 +14,20 @@ namespace Parse.Test [TestClass] public class ConfigTests { - private IParseConfigController MockedConfigController + private IParseConfigurationController MockedConfigController { get { - Mock mockedConfigController = new Mock(); - Mock mockedCurrentConfigController = new Mock(); + Mock mockedConfigController = new Mock(); + Mock mockedCurrentConfigController = new Mock(); - ParseConfig theConfig = ParseConfigExtensions.Create(new Dictionary { ["params"] = new Dictionary { ["testKey"] = "testValue" } }); + ParseConfiguration theConfig = ParseConfigExtensions.Create(new Dictionary { ["params"] = new Dictionary { ["testKey"] = "testValue" } }); mockedCurrentConfigController.Setup(obj => obj.GetCurrentConfigAsync()).Returns(Task.FromResult(theConfig)); - mockedConfigController.Setup(obj => obj.CurrentConfigController).Returns(mockedCurrentConfigController.Object); + mockedConfigController.Setup(obj => obj.CurrentConfigurationController).Returns(mockedCurrentConfigController.Object); - TaskCompletionSource tcs = new TaskCompletionSource(); + TaskCompletionSource tcs = new TaskCompletionSource(); tcs.TrySetCanceled(); mockedConfigController.Setup(obj => obj.FetchConfigAsync(It.IsAny(), It.Is(ct => ct.IsCancellationRequested))).Returns(tcs.Task); @@ -47,7 +47,7 @@ private IParseConfigController MockedConfigController [TestMethod] public void TestCurrentConfig() { - ParseConfig config = ParseConfig.CurrentConfig; + ParseConfiguration config = ParseConfiguration.CurrentConfig; Assert.AreEqual("testValue", config["testKey"]); Assert.AreEqual("testValue", config.Get("testKey")); @@ -57,12 +57,12 @@ public void TestCurrentConfig() public void TestToJSON() { IDictionary expectedJson = new Dictionary { { "params", new Dictionary { { "testKey", "testValue" } } } }; - Assert.AreEqual(JsonConvert.SerializeObject((ParseConfig.CurrentConfig as IJsonConvertible).ToJSON()), JsonConvert.SerializeObject(expectedJson)); + Assert.AreEqual(JsonConvert.SerializeObject((ParseConfiguration.CurrentConfig as IJsonConvertible).ConvertToJSON()), JsonConvert.SerializeObject(expectedJson)); } [TestMethod] [AsyncStateMachine(typeof(ConfigTests))] - public Task TestGetConfig() => ParseConfig.GetAsync().ContinueWith(t => + public Task TestGetConfig() => ParseConfiguration.GetAsync().ContinueWith(t => { Assert.AreEqual("testValue", t.Result["testKey"]); Assert.AreEqual("testValue", t.Result.Get("testKey")); @@ -74,7 +74,7 @@ public Task TestGetConfigCancel() { CancellationTokenSource tokenSource = new CancellationTokenSource(); tokenSource.Cancel(); - return ParseConfig.GetAsync(tokenSource.Token).ContinueWith(t => Assert.IsTrue(t.IsCanceled)); + return ParseConfiguration.GetAsync(tokenSource.Token).ContinueWith(t => Assert.IsTrue(t.IsCanceled)); } } } diff --git a/Parse.Test/DecoderTests.cs b/Parse.Test/DecoderTests.cs index 1233d7e1..8993cb72 100644 --- a/Parse.Test/DecoderTests.cs +++ b/Parse.Test/DecoderTests.cs @@ -11,7 +11,7 @@ public class DecoderTests [TestMethod] public void TestParseDate() { - DateTime dateTime = (DateTime) ParseDecoder.Instance.Decode(ParseDecoder.ParseDate("1990-08-30T12:03:59.000Z")); + DateTime dateTime = (DateTime) ParseDataDecoder.Instance.Decode(ParseDataDecoder.ParseDate("1990-08-30T12:03:59.000Z")); Assert.AreEqual(1990, dateTime.Year); Assert.AreEqual(8, dateTime.Month); Assert.AreEqual(30, dateTime.Day); @@ -24,21 +24,21 @@ public void TestParseDate() [TestMethod] public void TestDecodePrimitives() { - Assert.AreEqual(1, ParseDecoder.Instance.Decode(1)); - Assert.AreEqual(0.3, ParseDecoder.Instance.Decode(0.3)); - Assert.AreEqual("halyosy", ParseDecoder.Instance.Decode("halyosy")); + Assert.AreEqual(1, ParseDataDecoder.Instance.Decode(1)); + Assert.AreEqual(0.3, ParseDataDecoder.Instance.Decode(0.3)); + Assert.AreEqual("halyosy", ParseDataDecoder.Instance.Decode("halyosy")); - Assert.IsNull(ParseDecoder.Instance.Decode(null)); + Assert.IsNull(ParseDataDecoder.Instance.Decode(null)); } [TestMethod] // Decoding ParseFieldOperation is not supported on .NET now. We only need this for LDS. - public void TestDecodeFieldOperation() => Assert.ThrowsException(() => ParseDecoder.Instance.Decode(new Dictionary() { { "__op", "Increment" }, { "amount", "322" } })); + public void TestDecodeFieldOperation() => Assert.ThrowsException(() => ParseDataDecoder.Instance.Decode(new Dictionary() { { "__op", "Increment" }, { "amount", "322" } })); [TestMethod] public void TestDecodeDate() { - DateTime dateTime = (DateTime) ParseDecoder.Instance.Decode(new Dictionary() { { "__type", "Date" }, { "iso", "1990-08-30T12:03:59.000Z" } }); + DateTime dateTime = (DateTime) ParseDataDecoder.Instance.Decode(new Dictionary() { { "__type", "Date" }, { "iso", "1990-08-30T12:03:59.000Z" } }); Assert.AreEqual(1990, dateTime.Year); Assert.AreEqual(8, dateTime.Month); Assert.AreEqual(30, dateTime.Day); @@ -55,7 +55,7 @@ public void TestDecodeImproperDate() for (int i = 0; i < 2; i++, value["iso"] = (value["iso"] as string).Substring(0, (value["iso"] as string).Length - 1) + "0Z") { - DateTime dateTime = (DateTime) ParseDecoder.Instance.Decode(value); + DateTime dateTime = (DateTime) ParseDataDecoder.Instance.Decode(value); Assert.AreEqual(1990, dateTime.Year); Assert.AreEqual(8, dateTime.Month); Assert.AreEqual(30, dateTime.Day); @@ -67,12 +67,12 @@ public void TestDecodeImproperDate() } [TestMethod] - public void TestDecodeBytes() => Assert.AreEqual("This is an encoded string", System.Text.Encoding.UTF8.GetString(ParseDecoder.Instance.Decode(new Dictionary() { { "__type", "Bytes" }, { "base64", "VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw==" } }) as byte[])); + public void TestDecodeBytes() => Assert.AreEqual("This is an encoded string", System.Text.Encoding.UTF8.GetString(ParseDataDecoder.Instance.Decode(new Dictionary() { { "__type", "Bytes" }, { "base64", "VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw==" } }) as byte[])); [TestMethod] public void TestDecodePointer() { - ParseObject obj = ParseDecoder.Instance.Decode(new Dictionary { ["__type"] = "Pointer", ["className"] = "Corgi", ["objectId"] = "lLaKcolnu" }) as ParseObject; + ParseObject obj = ParseDataDecoder.Instance.Decode(new Dictionary { ["__type"] = "Pointer", ["className"] = "Corgi", ["objectId"] = "lLaKcolnu" }) as ParseObject; Assert.IsFalse(obj.IsDataAvailable); Assert.AreEqual("Corgi", obj.ClassName); Assert.AreEqual("lLaKcolnu", obj.ObjectId); @@ -82,23 +82,23 @@ public void TestDecodePointer() public void TestDecodeFile() { - ParseFile file1 = ParseDecoder.Instance.Decode(new Dictionary { ["__type"] = "File", ["name"] = "Corgi.png", ["url"] = "http://corgi.xyz/gogo.png" }) as ParseFile; + ParseFile file1 = ParseDataDecoder.Instance.Decode(new Dictionary { ["__type"] = "File", ["name"] = "Corgi.png", ["url"] = "http://corgi.xyz/gogo.png" }) as ParseFile; Assert.AreEqual("Corgi.png", file1.Name); Assert.AreEqual("http://corgi.xyz/gogo.png", file1.Url.AbsoluteUri); Assert.IsFalse(file1.IsDirty); - Assert.ThrowsException(() => ParseDecoder.Instance.Decode(new Dictionary { ["__type"] = "File", ["name"] = "Corgi.png" })); + Assert.ThrowsException(() => ParseDataDecoder.Instance.Decode(new Dictionary { ["__type"] = "File", ["name"] = "Corgi.png" })); } [TestMethod] public void TestDecodeGeoPoint() { - ParseGeoPoint point1 = (ParseGeoPoint) ParseDecoder.Instance.Decode(new Dictionary { ["__type"] = "GeoPoint", ["latitude"] = 0.9, ["longitude"] = 0.3 }); + ParseGeoPoint point1 = (ParseGeoPoint) ParseDataDecoder.Instance.Decode(new Dictionary { ["__type"] = "GeoPoint", ["latitude"] = 0.9, ["longitude"] = 0.3 }); Assert.IsNotNull(point1); Assert.AreEqual(0.9, point1.Latitude); Assert.AreEqual(0.3, point1.Longitude); - Assert.ThrowsException(() => ParseDecoder.Instance.Decode(new Dictionary { ["__type"] = "GeoPoint", ["latitude"] = 0.9 })); + Assert.ThrowsException(() => ParseDataDecoder.Instance.Decode(new Dictionary { ["__type"] = "GeoPoint", ["latitude"] = 0.9 })); } [TestMethod] @@ -113,7 +113,7 @@ public void TestDecodeObject() ["updatedAt"] = "2015-06-22T22:06:41.733Z" }; - ParseObject obj = ParseDecoder.Instance.Decode(value) as ParseObject; + ParseObject obj = ParseDataDecoder.Instance.Decode(value) as ParseObject; Assert.IsTrue(obj.IsDataAvailable); Assert.AreEqual("Corgi", obj.ClassName); Assert.AreEqual("lLaKcolnu", obj.ObjectId); @@ -131,7 +131,7 @@ public void TestDecodeRelation() ["objectId"] = "lLaKcolnu" }; - ParseRelation relation = ParseDecoder.Instance.Decode(value) as ParseRelation; + ParseRelation relation = ParseDataDecoder.Instance.Decode(value) as ParseRelation; Assert.IsNotNull(relation); Assert.AreEqual("Corgi", relation.GetTargetClassName()); } @@ -160,7 +160,7 @@ public void TestDecodeDictionary() } }; - IDictionary dict = ParseDecoder.Instance.Decode(value) as IDictionary; + IDictionary dict = ParseDataDecoder.Instance.Decode(value) as IDictionary; Assert.AreEqual("luka", dict["megurine"]); Assert.IsTrue(dict["hatsune"] is ParseObject); Assert.IsTrue(dict["decodedGeoPoint"] is ParseGeoPoint); @@ -174,7 +174,7 @@ public void TestDecodeDictionary() [new ParseACL()] = "lLaKcolnu" }; - IDictionary randomDict = ParseDecoder.Instance.Decode(randomValue) as IDictionary; + IDictionary randomDict = ParseDataDecoder.Instance.Decode(randomValue) as IDictionary; Assert.AreEqual("elements", randomDict["ultimate"]); Assert.AreEqual(2, randomDict.Keys.Count); } @@ -202,7 +202,7 @@ public void TestDecodeList() } }; - IList list = ParseDecoder.Instance.Decode(value) as IList; + IList list = ParseDataDecoder.Instance.Decode(value) as IList; Assert.AreEqual(1, list[0]); Assert.IsTrue(list[1] is ParseACL); Assert.AreEqual("wiz", list[2]); @@ -217,7 +217,7 @@ public void TestDecodeArray() { int[] value = new int[] { 1, 2, 3, 4 }; - int[] array = ParseDecoder.Instance.Decode(value) as int[]; + int[] array = ParseDataDecoder.Instance.Decode(value) as int[]; Assert.AreEqual(4, array.Length); Assert.AreEqual(1, array[0]); Assert.AreEqual(2, array[1]); diff --git a/Parse.Test/EncoderTests.cs b/Parse.Test/EncoderTests.cs index 1b021da2..b21f1726 100644 --- a/Parse.Test/EncoderTests.cs +++ b/Parse.Test/EncoderTests.cs @@ -12,41 +12,40 @@ namespace Parse.Test public class EncoderTests { /// - /// A that's used only for testing. This class is used to test - /// 's base methods. + /// A that's used only for testing. This class is used to test + /// 's base methods. /// - private class ParseEncoderTestClass : ParseEncoder + private class ParseEncoderTestClass : ParseDataEncoder { - private static readonly ParseEncoderTestClass instance = new ParseEncoderTestClass(); - public static ParseEncoderTestClass Instance => instance; + public static ParseEncoderTestClass Instance { get; } = new ParseEncoderTestClass(); - protected override IDictionary EncodeParseObject(ParseObject value) => null; + protected override IDictionary EncodeObject(ParseObject value) => null; } [TestMethod] public void TestIsValidType() { ParseObject corgi = new ParseObject("Corgi"); - ParseRelation corgiRelation = corgi.GetRelation("corgi"); - - Assert.IsTrue(ParseEncoder.IsValidType(322)); - Assert.IsTrue(ParseEncoder.IsValidType(0.3f)); - Assert.IsTrue(ParseEncoder.IsValidType(new byte[] { 1, 2, 3, 4 })); - Assert.IsTrue(ParseEncoder.IsValidType("corgi")); - Assert.IsTrue(ParseEncoder.IsValidType(corgi)); - Assert.IsTrue(ParseEncoder.IsValidType(new ParseACL())); - Assert.IsTrue(ParseEncoder.IsValidType(new ParseFile("Corgi", new byte[0]))); - Assert.IsTrue(ParseEncoder.IsValidType(new ParseGeoPoint(1, 2))); - Assert.IsTrue(ParseEncoder.IsValidType(corgiRelation)); - Assert.IsTrue(ParseEncoder.IsValidType(new DateTime())); - Assert.IsTrue(ParseEncoder.IsValidType(new List())); - Assert.IsTrue(ParseEncoder.IsValidType(new Dictionary())); - Assert.IsTrue(ParseEncoder.IsValidType(new Dictionary())); - - Assert.IsFalse(ParseEncoder.IsValidType(new ParseAddOperation(new List()))); - Assert.IsFalse(ParseEncoder.IsValidType(Task.FromResult(new ParseObject("Corgi")))); - Assert.ThrowsException(() => ParseEncoder.IsValidType(new Dictionary())); - Assert.ThrowsException(() => ParseEncoder.IsValidType(new Dictionary())); + ParseRelation corgiRelation = corgi.GetRelation(nameof(corgi)); + + Assert.IsTrue(ParseDataEncoder.Validate(322)); + Assert.IsTrue(ParseDataEncoder.Validate(0.3f)); + Assert.IsTrue(ParseDataEncoder.Validate(new byte[] { 1, 2, 3, 4 })); + Assert.IsTrue(ParseDataEncoder.Validate(nameof(corgi))); + Assert.IsTrue(ParseDataEncoder.Validate(corgi)); + Assert.IsTrue(ParseDataEncoder.Validate(new ParseACL())); + Assert.IsTrue(ParseDataEncoder.Validate(new ParseFile("Corgi", new byte[0]))); + Assert.IsTrue(ParseDataEncoder.Validate(new ParseGeoPoint(1, 2))); + Assert.IsTrue(ParseDataEncoder.Validate(corgiRelation)); + Assert.IsTrue(ParseDataEncoder.Validate(new DateTime())); + Assert.IsTrue(ParseDataEncoder.Validate(new List())); + Assert.IsTrue(ParseDataEncoder.Validate(new Dictionary())); + Assert.IsTrue(ParseDataEncoder.Validate(new Dictionary())); + + Assert.IsFalse(ParseDataEncoder.Validate(new ParseAddOperation(new List()))); + Assert.IsFalse(ParseDataEncoder.Validate(Task.FromResult(new ParseObject("Corgi")))); + Assert.ThrowsException(() => ParseDataEncoder.Validate(new Dictionary())); + Assert.ThrowsException(() => ParseDataEncoder.Validate(new Dictionary())); } [TestMethod] diff --git a/Parse.Test/FileControllerTests.cs b/Parse.Test/FileControllerTests.cs index d47f9e81..9fdc3642 100644 --- a/Parse.Test/FileControllerTests.cs +++ b/Parse.Test/FileControllerTests.cs @@ -16,7 +16,7 @@ namespace Parse.Test public class FileControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "", Test = true }); + public void SetUp() => ParseClient.Initialize(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(FileControllerTests))] @@ -27,7 +27,7 @@ public Task TestFileControllerSaveWithInvalidResult() FileState state = new FileState { Name = "bekti.png", - MimeType = "image/png" + MediaType = "image/png" }; ParseFileController controller = new ParseFileController(mockRunner.Object); @@ -43,7 +43,7 @@ public Task TestFileControllerSaveWithEmptyResult() FileState state = new FileState { Name = "bekti.png", - MimeType = "image/png" + MediaType = "image/png" }; ParseFileController controller = new ParseFileController(mockRunner.Object); @@ -59,7 +59,7 @@ public Task TestFileControllerSaveWithIncompleteResult() FileState state = new FileState { Name = "bekti.png", - MimeType = "image/png" + MediaType = "image/png" }; ParseFileController controller = new ParseFileController(mockRunner.Object); @@ -73,7 +73,7 @@ public Task TestFileControllerSave() FileState state = new FileState { Name = "bekti.png", - MimeType = "image/png" + MediaType = "image/png" }; return new ParseFileController(CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary { ["name"] = "newBekti.png", ["url"] = "https://www.parse.com/newBekti.png" })).Object).SaveAsync(state, dataStream: new MemoryStream(), sessionToken: null, progress: null).ContinueWith(t => @@ -81,16 +81,16 @@ public Task TestFileControllerSave() Assert.IsFalse(t.IsFaulted); FileState newState = t.Result; - Assert.AreEqual(state.MimeType, newState.MimeType); + Assert.AreEqual(state.MediaType, newState.MediaType); Assert.AreEqual("newBekti.png", newState.Name); - Assert.AreEqual("https://www.parse.com/newBekti.png", newState.Url.AbsoluteUri); + Assert.AreEqual("https://www.parse.com/newBekti.png", newState.Location.AbsoluteUri); }); } private Mock CreateMockRunner(Tuple> response) { Mock mockRunner = new Mock(); - mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); + mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); return mockRunner; } diff --git a/Parse.Test/FileStateTests.cs b/Parse.Test/FileStateTests.cs index 2ba4d991..c5596e6c 100644 --- a/Parse.Test/FileStateTests.cs +++ b/Parse.Test/FileStateTests.cs @@ -17,25 +17,25 @@ public void TestSecureUrl() FileState state = new FileState { Name = "A", - Url = unsecureUri, - MimeType = null + Location = unsecureUri, + MediaType = null }; - Assert.AreEqual(unsecureUri, state.Url); - Assert.AreEqual(secureUri, state.SecureUrl); + Assert.AreEqual(unsecureUri, state.Location); + Assert.AreEqual(secureUri, state.SecureLocation); // Make sure the proper port was given back. - Assert.AreEqual(443, state.SecureUrl.Port); + Assert.AreEqual(443, state.SecureLocation.Port); state = new FileState { Name = "B", - Url = randomUri, - MimeType = null + Location = randomUri, + MediaType = null }; - Assert.AreEqual(randomUri, state.Url); - Assert.AreEqual(randomUri, state.Url); + Assert.AreEqual(randomUri, state.Location); + Assert.AreEqual(randomUri, state.Location); } } } diff --git a/Parse.Test/FileTests.cs b/Parse.Test/FileTests.cs index f8a2f407..503ae84b 100644 --- a/Parse.Test/FileTests.cs +++ b/Parse.Test/FileTests.cs @@ -21,7 +21,7 @@ public class FileTests public Task TestFileSave() { Mock mockController = new Mock(); - mockController.Setup(obj => obj.SaveAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new FileState { Name = "newBekti.png", Url = new Uri("https://www.parse.com/newBekti.png"), MimeType = "image/png" })); + mockController.Setup(obj => obj.SaveAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new FileState { Name = "newBekti.png", Location = new Uri("https://www.parse.com/newBekti.png"), MediaType = "image/png" })); Mock mockCurrentUserController = new Mock(); ParseCorePlugins.Instance = new ParseCorePlugins { FileController = mockController.Object, CurrentUserController = mockCurrentUserController.Object }; diff --git a/Parse.Test/GeoPointTests.cs b/Parse.Test/GeoPointTests.cs index 4d99d60a..fc3684ab 100644 --- a/Parse.Test/GeoPointTests.cs +++ b/Parse.Test/GeoPointTests.cs @@ -19,9 +19,9 @@ public void TestGeoPointCultureInvariantParsing() { Thread.CurrentThread.CurrentCulture = c; ParseGeoPoint point = new ParseGeoPoint(1.234, 1.234); - string serialized = Json.Encode(new Dictionary { { "point", NoObjectsEncoder.Instance.Encode(point) } }); - IDictionary deserialized = ParseDecoder.Instance.Decode(Json.Parse(serialized)) as IDictionary; - ParseGeoPoint pointAgain = (ParseGeoPoint) deserialized["point"]; + string serialized = Json.Encode(new Dictionary { { nameof(point), NoObjectsEncoder.Instance.Encode(point) } }); + IDictionary deserialized = ParseDataDecoder.Instance.Decode(Json.Parse(serialized)) as IDictionary; + ParseGeoPoint pointAgain = (ParseGeoPoint) deserialized[nameof(point)]; Assert.AreEqual(1.234, pointAgain.Latitude); Assert.AreEqual(1.234, pointAgain.Longitude); } diff --git a/Parse.Test/ObjectControllerTests.cs b/Parse.Test/ObjectControllerTests.cs index 2afc5a5c..e12a7de0 100644 --- a/Parse.Test/ObjectControllerTests.cs +++ b/Parse.Test/ObjectControllerTests.cs @@ -16,7 +16,7 @@ namespace Parse.Test public class ObjectControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "", Test = true }); + public void SetUp() => ParseClient.Initialize(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(ObjectControllerTests))] @@ -29,7 +29,7 @@ public Task TestFetch() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = t.Result; Assert.AreEqual("isShibaInu", newState["doge"]); @@ -50,7 +50,7 @@ public Task TestSave() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = t.Result; Assert.AreEqual("isShibaInu", newState["doge"]); @@ -89,7 +89,7 @@ public Task TestSaveNewObject() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/classes/Corgi"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = t.Result; Assert.AreEqual("isShibaInu", newState["doge"]); @@ -112,7 +112,7 @@ public Task TestSaveAll() states.Add(new MutableObjectState { ClassName = "Corgi", - ObjectId = ((i % 2 == 0) ? null : "st4nl3yW" + i), + ObjectId = (i % 2 == 0) ? null : "st4nl3yW" + i, ServerData = new Dictionary { ["corgi"] = "isNotDoge" } }); } @@ -135,7 +135,7 @@ public Task TestSaveAll() } }); } - Dictionary responseDict = new Dictionary { ["results"] = results }; + Dictionary responseDict = new Dictionary { [nameof(results)] = results }; Tuple> response = new Tuple>(HttpStatusCode.OK, responseDict); Mock mockRunner = CreateMockRunner(response); @@ -158,7 +158,7 @@ public Task TestSaveAll() Assert.IsNotNull(serverState.UpdatedAt); } - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -200,7 +200,7 @@ public Task TestSaveAllManyObjects() } }); } - Dictionary responseDict = new Dictionary { ["results"] = results }; + Dictionary responseDict = new Dictionary { [nameof(results)] = results }; Tuple> response = new Tuple>(HttpStatusCode.OK, responseDict); List> results2 = new List>(); @@ -218,11 +218,11 @@ public Task TestSaveAllManyObjects() } }); } - Dictionary responseDict2 = new Dictionary { ["results"] = results2 }; + Dictionary responseDict2 = new Dictionary { [nameof(results)] = results2 }; Tuple> response2 = new Tuple>(HttpStatusCode.OK, responseDict2); Mock mockRunner = new Mock { }; - mockRunner.SetupSequence(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)).Returns(Task.FromResult(response)).Returns(Task.FromResult(response2)); + mockRunner.SetupSequence(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)).Returns(Task.FromResult(response)).Returns(Task.FromResult(response2)); ParseObjectController controller = new ParseObjectController(mockRunner.Object); IList> tasks = controller.SaveAllAsync(states, operationsList, null, CancellationToken.None); @@ -234,7 +234,7 @@ public Task TestSaveAllManyObjects() for (int i = 0; i < 102; ++i) { IObjectState serverState = tasks[i].Result; - Assert.AreEqual("st4nl3yW" + (i % 50), serverState.ObjectId); + Assert.AreEqual("st4nl3yW" + i % 50, serverState.ObjectId); Assert.IsFalse(serverState.ContainsKey("gogo")); Assert.IsFalse(serverState.ContainsKey("corgi")); Assert.AreEqual("isShibaInu", serverState["doge"]); @@ -242,7 +242,7 @@ public Task TestSaveAllManyObjects() Assert.IsNotNull(serverState.UpdatedAt); } - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(3)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(3)); }); } @@ -266,9 +266,9 @@ public Task TestDelete() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), - It.IsAny>(), - It.IsAny>(), + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), + It.IsAny>(), + It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -293,7 +293,7 @@ public Task TestDeleteAll() for (int i = 0; i < 30; ++i) results.Add(new Dictionary { ["success"] = null }); - Dictionary responseDict = new Dictionary { ["results"] = results }; + Dictionary responseDict = new Dictionary { [nameof(results)] = results }; Tuple> response = new Tuple>(HttpStatusCode.OK, responseDict); Mock mockRunner = CreateMockRunner(response); @@ -305,7 +305,7 @@ public Task TestDeleteAll() { Assert.IsTrue(tasks.All(task => task.IsCompleted && !task.IsCanceled && !task.IsFaulted)); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -330,7 +330,7 @@ public Task TestDeleteAllManyObjects() for (int i = 0; i < 50; ++i) results.Add(new Dictionary { ["success"] = null }); - Dictionary responseDict = new Dictionary { ["results"] = results }; + Dictionary responseDict = new Dictionary { [nameof(results)] = results }; Tuple> response = new Tuple>(HttpStatusCode.OK, responseDict); List> results2 = new List>(); @@ -338,11 +338,11 @@ public Task TestDeleteAllManyObjects() for (int i = 0; i < 2; ++i) results2.Add(new Dictionary { ["success"] = null }); - Dictionary responseDict2 = new Dictionary { ["results"] = results2 }; + Dictionary responseDict2 = new Dictionary { [nameof(results)] = results2 }; Tuple> response2 = new Tuple>(HttpStatusCode.OK, responseDict2); Mock mockRunner = new Mock(); - mockRunner.SetupSequence(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)).Returns(Task.FromResult(response)).Returns(Task.FromResult(response2)); + mockRunner.SetupSequence(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)).Returns(Task.FromResult(response)).Returns(Task.FromResult(response2)); ParseObjectController controller = new ParseObjectController(mockRunner.Object); IList tasks = controller.DeleteAllAsync(states, null, CancellationToken.None); @@ -351,7 +351,7 @@ public Task TestDeleteAllManyObjects() { Assert.IsTrue(tasks.All(task => task.IsCompleted && !task.IsCanceled && !task.IsFaulted)); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(3)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(3)); }); } @@ -365,7 +365,7 @@ public Task TestDeleteAllFailSome() states.Add(new MutableObjectState { ClassName = "Corgi", - ObjectId = ((i % 2 == 0) ? null : "st4nl3yW" + i), + ObjectId = (i % 2 == 0) ? null : "st4nl3yW" + i, ServerData = new Dictionary { ["corgi"] = "isNotDoge" } }); } @@ -380,7 +380,7 @@ public Task TestDeleteAllFailSome() { ["error"] = new Dictionary { - ["code"] = (long) ParseException.ErrorCode.ObjectNotFound, + ["code"] = (long) ParseFailureException.ErrorCode.ObjectNotFound, ["error"] = "Object not found." } }); @@ -389,7 +389,7 @@ public Task TestDeleteAllFailSome() results.Add(new Dictionary { ["success"] = null }); } - Dictionary responseDict = new Dictionary { ["results"] = results }; + Dictionary responseDict = new Dictionary { [nameof(results)] = results }; Tuple> response = new Tuple>(HttpStatusCode.OK, responseDict); Mock mockRunner = CreateMockRunner(response); @@ -404,9 +404,9 @@ public Task TestDeleteAllFailSome() if (i % 2 == 0) { Assert.IsTrue(tasks[i].IsFaulted); - Assert.IsInstanceOfType(tasks[i].Exception.InnerException, typeof(ParseException)); - ParseException exception = tasks[i].Exception.InnerException as ParseException; - Assert.AreEqual(ParseException.ErrorCode.ObjectNotFound, exception.Code); + Assert.IsInstanceOfType(tasks[i].Exception.InnerException, typeof(ParseFailureException)); + ParseFailureException exception = tasks[i].Exception.InnerException as ParseFailureException; + Assert.AreEqual(ParseFailureException.ErrorCode.ObjectNotFound, exception.Code); } else { @@ -415,9 +415,9 @@ public Task TestDeleteAllFailSome() } } - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/batch"), - It.IsAny>(), - It.IsAny>(), + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), + It.IsAny>(), + It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -447,7 +447,7 @@ public Task TestDeleteAllInconsistent() }); } Dictionary responseDict = new Dictionary() { - { "results", results } + { nameof(results), results } }; Tuple> response = new Tuple>(HttpStatusCode.OK, responseDict); @@ -461,9 +461,9 @@ public Task TestDeleteAllInconsistent() Assert.IsTrue(tasks.All(task => task.IsFaulted)); Assert.IsInstanceOfType(tasks[0].Exception.InnerException, typeof(InvalidOperationException)); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/batch"), - It.IsAny>(), - It.IsAny>(), + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), + It.IsAny>(), + It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -472,8 +472,8 @@ private Mock CreateMockRunner(Tuple mockRunner = new Mock(); mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), - It.IsAny>(), - It.IsAny>(), + It.IsAny>(), + It.IsAny>(), It.IsAny())) .Returns(Task.FromResult(response)); diff --git a/Parse.Test/ObjectStateTests.cs b/Parse.Test/ObjectStateTests.cs index 60942b32..053560d7 100644 --- a/Parse.Test/ObjectStateTests.cs +++ b/Parse.Test/ObjectStateTests.cs @@ -91,10 +91,7 @@ public void TestApplyOperation() Assert.AreEqual(2, state["exist"]); Assert.AreEqual("teletubies", state["change"]); - state = state.MutatedClone(mutableClone => - { - mutableClone.Apply(operations); - }); + state = state.MutatedClone(mutableClone => mutableClone.Apply(operations)); Assert.AreEqual(3, state.Count()); Assert.AreEqual(9, state["exist"]); @@ -127,10 +124,7 @@ public void TestApplyState() } }; - state = state.MutatedClone(mutableClone => - { - mutableClone.Apply(appliedState); - }); + state = state.MutatedClone(mutableClone => mutableClone.Apply(appliedState)); Assert.AreEqual("Corgi", state.ClassName); Assert.AreEqual("1234", state.ObjectId); diff --git a/Parse.Test/ObjectTests.cs b/Parse.Test/ObjectTests.cs index 3608201d..3c890792 100644 --- a/Parse.Test/ObjectTests.cs +++ b/Parse.Test/ObjectTests.cs @@ -12,10 +12,10 @@ namespace Parse.Test [TestClass] public class ObjectTests { - [ParseClassName("SubClass")] + [ParseClassName(nameof(SubClass))] private class SubClass : ParseObject { } - [ParseClassName("UnregisteredSubClass")] + [ParseClassName(nameof(UnregisteredSubClass))] private class UnregisteredSubClass : ParseObject { } [TestCleanup] @@ -54,13 +54,13 @@ public void TestParseObjectCreateWithGeneric() ParseObject.RegisterDerivative(); ParseObject obj = ParseObject.Create(); - Assert.AreEqual("SubClass", obj.ClassName); + Assert.AreEqual(nameof(SubClass), obj.ClassName); Assert.IsNull(obj.CreatedAt); Assert.IsTrue(obj.IsDataAvailable); Assert.IsTrue(obj.IsDirty); ParseObject obj2 = ParseObject.CreateWithoutData("waGiManPutr4Pet1r"); - Assert.AreEqual("SubClass", obj2.ClassName); + Assert.AreEqual(nameof(SubClass), obj2.ClassName); Assert.AreEqual("waGiManPutr4Pet1r", obj2.ObjectId); Assert.IsNull(obj2.CreatedAt); Assert.IsFalse(obj2.IsDataAvailable); @@ -103,12 +103,12 @@ public void TestRegisterSubclass() ParseObject.RegisterDerivative(); ParseObject.Create(); - ParseCorePlugins.Instance.SubclassingController.UnregisterSubclass(typeof(UnregisteredSubClass)); + ParseCorePlugins.Instance.SubclassingController.RemoveClass(typeof(UnregisteredSubClass)); ParseObject.Create(); } catch { Assert.Fail(); } - ParseCorePlugins.Instance.SubclassingController.UnregisterSubclass(typeof(SubClass)); + ParseCorePlugins.Instance.SubclassingController.RemoveClass(typeof(SubClass)); Assert.ThrowsException(() => ParseObject.Create()); } @@ -136,7 +136,7 @@ public void TestDeepTraversal() IDictionary someDict = new Dictionary() { { "someList", new List() } }; - obj["obj"] = ParseObject.Create("Pug"); + obj[nameof(obj)] = ParseObject.Create("Pug"); obj["obj2"] = ParseObject.Create("Pug"); obj["list"] = new List(); obj["dict"] = someDict; @@ -197,7 +197,7 @@ public void TestIndexGetterSetter() obj["list"] = new List(); obj["dict"] = new Dictionary(); obj["fakeACL"] = new ParseACL(); - obj["obj"] = new ParseObject("Corgi"); + obj[nameof(obj)] = new ParseObject("Corgi"); Assert.IsTrue(obj.ContainsKey("gogo")); Assert.IsInstanceOfType(obj["gogo"], typeof(bool)); @@ -211,8 +211,8 @@ public void TestIndexGetterSetter() Assert.IsTrue(obj.ContainsKey("fakeACL")); Assert.IsInstanceOfType(obj["fakeACL"], typeof(ParseACL)); - Assert.IsTrue(obj.ContainsKey("obj")); - Assert.IsInstanceOfType(obj["obj"], typeof(ParseObject)); + Assert.IsTrue(obj.ContainsKey(nameof(obj))); + Assert.IsInstanceOfType(obj[nameof(obj)], typeof(ParseObject)); Assert.ThrowsException(() => { object gogo = obj["missingItem"]; }); } @@ -433,12 +433,12 @@ public void TestGetQuery() { ParseObject.RegisterDerivative(); - ParseQuery query = ParseObject.GetQuery("UnregisteredSubClass"); - Assert.AreEqual("UnregisteredSubClass", query.GetClassName()); + ParseQuery query = ParseObject.GetQuery(nameof(UnregisteredSubClass)); + Assert.AreEqual(nameof(UnregisteredSubClass), query.GetClassName()); - Assert.ThrowsException(() => ParseObject.GetQuery("SubClass")); + Assert.ThrowsException(() => ParseObject.GetQuery(nameof(SubClass))); - ParseCorePlugins.Instance.SubclassingController.UnregisterSubclass(typeof(SubClass)); + ParseCorePlugins.Instance.SubclassingController.RemoveClass(typeof(SubClass)); } [TestMethod] diff --git a/Parse.Test/ProgressTests.cs b/Parse.Test/ProgressTests.cs index e12ddf43..cbaf8cc2 100644 --- a/Parse.Test/ProgressTests.cs +++ b/Parse.Test/ProgressTests.cs @@ -10,36 +10,36 @@ public class ProgressTests [TestMethod] public void TestDownloadProgressEventGetterSetter() { - ParseDownloadProgressEventArgs downloadProgressEvent = new ParseDownloadProgressEventArgs { Progress = 0.5f }; - Assert.AreEqual(0.5f, downloadProgressEvent.Progress); + DataRecievalPresenter downloadProgressEvent = new DataRecievalPresenter { Amount = 0.5f }; + Assert.AreEqual(0.5f, downloadProgressEvent.Amount); - downloadProgressEvent.Progress = 1.0f; - Assert.AreEqual(1.0f, downloadProgressEvent.Progress); + downloadProgressEvent.Amount = 1.0f; + Assert.AreEqual(1.0f, downloadProgressEvent.Amount); } [TestMethod] public void TestUploadProgressEventGetterSetter() { - ParseDownloadProgressEventArgs uploadProgressEvent = new ParseDownloadProgressEventArgs { Progress = 0.5f }; - Assert.AreEqual(0.5f, uploadProgressEvent.Progress); + DataRecievalPresenter uploadProgressEvent = new DataRecievalPresenter { Amount = 0.5f }; + Assert.AreEqual(0.5f, uploadProgressEvent.Amount); - uploadProgressEvent.Progress = 1.0f; - Assert.AreEqual(1.0f, uploadProgressEvent.Progress); + uploadProgressEvent.Amount = 1.0f; + Assert.AreEqual(1.0f, uploadProgressEvent.Amount); } [TestMethod] public void TestObservingDownloadProgress() { int called = 0; - Mock> mockProgress = new Mock>(); - mockProgress.Setup(obj => obj.Report(It.IsAny())).Callback(() => called++); - IProgress progress = mockProgress.Object; + Mock> mockProgress = new Mock>(); + mockProgress.Setup(obj => obj.Report(It.IsAny())).Callback(() => called++); + IProgress progress = mockProgress.Object; - progress.Report(new ParseDownloadProgressEventArgs { Progress = 0.2f }); - progress.Report(new ParseDownloadProgressEventArgs { Progress = 0.42f }); - progress.Report(new ParseDownloadProgressEventArgs { Progress = 0.53f }); - progress.Report(new ParseDownloadProgressEventArgs { Progress = 0.68f }); - progress.Report(new ParseDownloadProgressEventArgs { Progress = 0.88f }); + progress.Report(new DataRecievalPresenter { Amount = 0.2f }); + progress.Report(new DataRecievalPresenter { Amount = 0.42f }); + progress.Report(new DataRecievalPresenter { Amount = 0.53f }); + progress.Report(new DataRecievalPresenter { Amount = 0.68f }); + progress.Report(new DataRecievalPresenter { Amount = 0.88f }); Assert.AreEqual(5, called); } @@ -48,15 +48,15 @@ public void TestObservingDownloadProgress() public void TestObservingUploadProgress() { int called = 0; - Mock> mockProgress = new Mock>(); - mockProgress.Setup(obj => obj.Report(It.IsAny())).Callback(() => called++); - IProgress progress = mockProgress.Object; + Mock> mockProgress = new Mock>(); + mockProgress.Setup(obj => obj.Report(It.IsAny())).Callback(() => called++); + IProgress progress = mockProgress.Object; - progress.Report(new ParseUploadProgressEventArgs { Progress = 0.2f }); - progress.Report(new ParseUploadProgressEventArgs { Progress = 0.42f }); - progress.Report(new ParseUploadProgressEventArgs { Progress = 0.53f }); - progress.Report(new ParseUploadProgressEventArgs { Progress = 0.68f }); - progress.Report(new ParseUploadProgressEventArgs { Progress = 0.88f }); + progress.Report(new DataTransmissionAdvancementLevel { Amount = 0.2f }); + progress.Report(new DataTransmissionAdvancementLevel { Amount = 0.42f }); + progress.Report(new DataTransmissionAdvancementLevel { Amount = 0.53f }); + progress.Report(new DataTransmissionAdvancementLevel { Amount = 0.68f }); + progress.Report(new DataTransmissionAdvancementLevel { Amount = 0.88f }); Assert.AreEqual(5, called); } diff --git a/Parse.Test/PushStateTests.cs b/Parse.Test/PushStateTests.cs index e980e84c..0c99d471 100644 --- a/Parse.Test/PushStateTests.cs +++ b/Parse.Test/PushStateTests.cs @@ -11,10 +11,7 @@ public void TestMutatedClone() { MutablePushState state = new MutablePushState(); - IPushState mutated = state.MutatedClone(s => - { - s.Alert = "test"; - }); + IPushState mutated = state.MutatedClone(s => s.Alert = "test"); Assert.AreEqual(null, state.Alert); Assert.AreEqual("test", mutated.Alert); diff --git a/Parse.Test/SessionControllerTests.cs b/Parse.Test/SessionControllerTests.cs index 0281a111..00256cfa 100644 --- a/Parse.Test/SessionControllerTests.cs +++ b/Parse.Test/SessionControllerTests.cs @@ -16,7 +16,7 @@ namespace Parse.Test public class SessionControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "", Test = true }); + public void SetUp() => ParseClient.Initialize(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(SessionControllerTests))] @@ -43,7 +43,7 @@ public Task TestGetSession() { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/sessions/me"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/sessions/me"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState session = t.Result; Assert.AreEqual(2, session.Count()); @@ -64,9 +64,9 @@ public Task TestRevoke() { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/logout"), - It.IsAny>(), - It.IsAny>(), + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/logout"), + It.IsAny>(), + It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -89,9 +89,9 @@ public Task TestUpgradeToRevocableSession() { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/upgradeToRevocableSession"), - It.IsAny>(), - It.IsAny>(), + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/upgradeToRevocableSession"), + It.IsAny>(), + It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState session = t.Result; @@ -118,8 +118,8 @@ private Mock CreateMockRunner(Tuple mockRunner = new Mock(); mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), - It.IsAny>(), - It.IsAny>(), + It.IsAny>(), + It.IsAny>(), It.IsAny())) .Returns(Task.FromResult(response)); diff --git a/Parse.Test/UserControllerTests.cs b/Parse.Test/UserControllerTests.cs index 538c9ecc..860c61eb 100644 --- a/Parse.Test/UserControllerTests.cs +++ b/Parse.Test/UserControllerTests.cs @@ -15,7 +15,7 @@ namespace Parse.Test public class UserControllerTests { [TestInitialize] - public void SetUp() => ParseClient.Initialize(new Configuration { ApplicationID = "", Key = "", Test = true }); + public void SetUp() => ParseClient.Initialize(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(UserControllerTests))] @@ -54,7 +54,7 @@ public Task TestSignUp() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/classes/_User"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/_User"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = t.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); @@ -86,7 +86,7 @@ public Task TestLogInWithUsernamePassword() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/login"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/login"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = t.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); @@ -118,7 +118,7 @@ public Task TestLogInWithAuthData() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/users"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/users"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = t.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); @@ -149,7 +149,7 @@ public Task TestGetUserFromSessionToken() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/users/me"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/users/me"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = t.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); @@ -173,14 +173,14 @@ public Task TestRequestPasswordReset() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Uri.AbsolutePath == "/1/requestPasswordReset"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/requestPasswordReset"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } private Mock CreateMockRunner(Tuple> response) { Mock mockRunner = new Mock(); - mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); + mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); return mockRunner; } diff --git a/Parse/Abstractions/Library/CustomServiceHub.cs b/Parse/Abstractions/Library/CustomServiceHub.cs new file mode 100644 index 00000000..cea2a8d2 --- /dev/null +++ b/Parse/Abstractions/Library/CustomServiceHub.cs @@ -0,0 +1,58 @@ +using Parse.Analytics.Internal; +using Parse.Common.Internal; +using Parse.Core.Internal; +using Parse.Push.Internal; + +namespace Parse.Abstractions.Library +{ + public abstract class CustomServiceHub : ICustomServiceHub + { + public virtual IServiceHub Services { get; protected set; } + + public virtual IServiceHubCloner Cloner => Services.Cloner; + + public virtual IMetadataController MetadataController => Services.MetadataController; + + public virtual IWebClient WebClient => Services.WebClient; + + public virtual IStorageController StorageController => Services.StorageController; + + public virtual IParseObjectClassController ClassController => Services.ClassController; + + public virtual IParseInstallationController InstallationController => Services.InstallationController; + + public virtual IParseCommandRunner CommandRunner => Services.CommandRunner; + + public virtual IParseCloudCodeController CloudCodeController => Services.CloudCodeController; + + public virtual IParseConfigurationController ConfigurationController => Services.ConfigurationController; + + public virtual IParseFileController FileController => Services.FileController; + + public virtual IParseObjectController ObjectController => Services.ObjectController; + + public virtual IParseQueryController QueryController => Services.QueryController; + + public virtual IParseSessionController SessionController => Services.SessionController; + + public virtual IParseUserController UserController => Services.UserController; + + public virtual IParseCurrentUserController CurrentUserController => Services.CurrentUserController; + + public virtual IParseAnalyticsController AnalyticsController => Services.AnalyticsController; + + public virtual IParseInstallationCoder InstallationCoder => Services.InstallationCoder; + + public virtual IParsePushChannelsController PushChannelsController => Services.PushChannelsController; + + public virtual IParsePushController PushController => Services.PushController; + + public virtual IParseCurrentInstallationController CurrentInstallationController => Services.CurrentInstallationController; + + public virtual IServerConnectionData ServerConnectionData { set; get; } + + public virtual IParseDataDecoder Decoder { set; get; } + + public virtual IParseInstallationDataFinalizer InstallationDataFinalizer { set; get; } + } +} diff --git a/Parse/Abstractions/Library/ICustomServiceHub.cs b/Parse/Abstractions/Library/ICustomServiceHub.cs new file mode 100644 index 00000000..fdee9ac2 --- /dev/null +++ b/Parse/Abstractions/Library/ICustomServiceHub.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Parse.Abstractions.Library +{ + public interface ICustomServiceHub : IServiceHub + { + IServiceHub Services { get; } + } +} diff --git a/Parse/Library/ParseUploadProgressEventArgs.cs b/Parse/Abstractions/Library/IDataTransferLevel.cs similarity index 70% rename from Parse/Library/ParseUploadProgressEventArgs.cs rename to Parse/Abstractions/Library/IDataTransferLevel.cs index 35df390c..bb652e16 100644 --- a/Parse/Library/ParseUploadProgressEventArgs.cs +++ b/Parse/Abstractions/Library/IDataTransferLevel.cs @@ -4,16 +4,19 @@ namespace Parse { + public interface IDataTransferLevel + { + double Amount { get; set; } + } + /// /// Represents upload progress. /// - public class ParseUploadProgressEventArgs : EventArgs + public class DataTransferLevel : EventArgs, IDataTransferLevel { - public ParseUploadProgressEventArgs() { } - /// - /// Gets the progress (a number between 0.0 and 1.0) of an upload. + /// Gets the progress (a number between 0.0 and 1.0) of an upload or download. /// - public double Progress { get; set; } + public double Amount { get; set; } } } diff --git a/Parse/Abstractions/Library/IEnvironmentData.cs b/Parse/Abstractions/Library/IEnvironmentData.cs new file mode 100644 index 00000000..1bb3a965 --- /dev/null +++ b/Parse/Abstractions/Library/IEnvironmentData.cs @@ -0,0 +1,24 @@ +namespace Parse.Abstractions.Library +{ + /// + /// Information about the environment in which the library will be operating. + /// + public interface IEnvironmentData + { + /// + /// The currently active time zone when the library will be used. + /// + string TimeZone { get; } + + /// + /// The operating system version of the platform the SDK is operating in. + /// + string OSVersion { get; } + + /// + /// An identifier of the platform. + /// + /// Expected to be one of ios, android, winrt, winphone, or dotnet. + public string Platform { get; set; } + } +} diff --git a/Parse/Abstractions/Library/IHostApplicationVersioningData.cs b/Parse/Abstractions/Library/IHostApplicationVersioningData.cs deleted file mode 100644 index 3ab60cd6..00000000 --- a/Parse/Abstractions/Library/IHostApplicationVersioningData.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Parse.Abstractions.Library -{ - /// - /// In the event that you would like to use the Parse SDK - /// from a completely portable project, with no platform-specific library required, - /// to get full access to all of our features available on Parse Dashboard - /// (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 interface IHostApplicationVersioningData - { - /// - /// The build number of your app. - /// - string BuildVersion { get; } - - /// - /// The human friendly version number of your app. - /// - string DisplayVersion { get; } - - /// - /// The operating system version of the platform the SDK is operating in.. - /// - string HostOSVersion { get; } - - /// - /// Gets a value for whether or not this instance of is populated with default values. - /// - bool IsDefault { get; } - - /// - /// Gets a value for whether or not this instance of can currently be used for the generation of . - /// - bool CanBeUsedForInference { get; } - } -} diff --git a/Parse/Abstractions/Library/IHostManifestData.cs b/Parse/Abstractions/Library/IHostManifestData.cs new file mode 100644 index 00000000..88b01cc6 --- /dev/null +++ b/Parse/Abstractions/Library/IHostManifestData.cs @@ -0,0 +1,28 @@ +namespace Parse.Abstractions.Library +{ + /// + /// Information about the application using the Parse SDK. + /// + public interface IHostManifestData + { + /// + /// The build number of your app. + /// + string Version { get; } + + /// + /// The human friendly version number of your app. + /// + string ShortVersion { get; } + + /// + /// A unique string representing your app. + /// + string Identifier { get; } + + /// + /// The name of your app. + /// + string Name { get; } + } +} diff --git a/Parse/Abstractions/Library/IMetadataController.cs b/Parse/Abstractions/Library/IMetadataController.cs index 09cff231..a7239284 100644 --- a/Parse/Abstractions/Library/IMetadataController.cs +++ b/Parse/Abstractions/Library/IMetadataController.cs @@ -10,8 +10,13 @@ namespace Parse.Abstractions.Library public interface IMetadataController { /// - /// The version information of your application environment. + /// Information about the application using the Parse SDK. /// - public IHostApplicationVersioningData HostVersioningData { get; } + public IHostManifestData HostManifestData { get; } + + /// + /// Environment data specific to the application hosting the Parse SDK. + /// + public IEnvironmentData EnvironmentData { get; } } } diff --git a/Parse/Abstractions/Library/IMutableServiceHub.cs b/Parse/Abstractions/Library/IMutableServiceHub.cs new file mode 100644 index 00000000..0afb8ac4 --- /dev/null +++ b/Parse/Abstractions/Library/IMutableServiceHub.cs @@ -0,0 +1,47 @@ +#pragma warning disable CS0108 // Member hides inherited member; missing new keyword + +using System; +using System.Collections.Generic; +using System.Text; +using Parse.Analytics.Internal; +using Parse.Common.Internal; +using Parse.Core.Internal; +using Parse.Push.Internal; + +namespace Parse.Abstractions.Library +{ + public interface IMutableServiceHub : IServiceHub + { + IServerConnectionData ServerConnectionData { set; } + IMetadataController MetadataController { set; } + + IServiceHubCloner Cloner { set; } + + IWebClient WebClient { set; } + IStorageController StorageController { set; } + IParseObjectClassController ClassController { set; } + + IParseDataDecoder Decoder { set; } + + IParseInstallationController InstallationController { set; } + IParseCommandRunner CommandRunner { set; } + + IParseCloudCodeController CloudCodeController { set; } + IParseConfigurationController ConfigurationController { set; } + IParseFileController FileController { set; } + IParseObjectController ObjectController { set; } + IParseQueryController QueryController { set; } + IParseSessionController SessionController { set; } + IParseUserController UserController { set; } + IParseCurrentUserController CurrentUserController { set; } + + IParseAnalyticsController AnalyticsController { set; } + + IParseInstallationCoder InstallationCoder { set; } + + IParsePushChannelsController PushChannelsController { set; } + IParsePushController PushController { set; } + IParseCurrentInstallationController CurrentInstallationController { set; } + IParseInstallationDataFinalizer InstallationDataFinalizer { set; } + } +} diff --git a/Parse/Abstractions/Library/IServerConnectionData.cs b/Parse/Abstractions/Library/IServerConnectionData.cs new file mode 100644 index 00000000..5ef8933f --- /dev/null +++ b/Parse/Abstractions/Library/IServerConnectionData.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace Parse.Abstractions.Library +{ + public interface IServerConnectionData + { + /// + /// The App ID of your app. + /// + string ApplicationID { get; set; } + + /// + /// A URI pointing to the target Parse Server instance hosting the app targeted by . + /// + string ServerURI { get; set; } + + /// + /// The .NET Key for the Parse app targeted by . + /// + string Key { get; set; } + + /// + /// The Master Key for the Parse app targeted by . + /// + string MasterKey { get; set; } + + /// + /// Additional HTTP headers to be sent with network requests from the SDK. + /// + IDictionary Headers { get; set; } + } +} diff --git a/Parse/Abstractions/Library/IServiceHub.cs b/Parse/Abstractions/Library/IServiceHub.cs new file mode 100644 index 00000000..d4544ad3 --- /dev/null +++ b/Parse/Abstractions/Library/IServiceHub.cs @@ -0,0 +1,52 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +using System; +using System.Collections.Generic; +using System.Text; +using Parse.Analytics.Internal; +using Parse.Common.Internal; +using Parse.Core.Internal; +using Parse.Push.Internal; + +namespace Parse.Abstractions.Library +{ + // TODO: Consider splitting up IServiceHub into IResourceHub and IServiceHub, where the former would provide the current functionality of IServiceHub and the latter would be a public-facing sub-section containing formerly-static memebers from classes such as ParseObject which require the use of some broader resource. + + /// + /// The dependency injection container for all internal .NET Parse SDK services. + /// + public interface IServiceHub + { + IServerConnectionData ServerConnectionData { get; } + IMetadataController MetadataController { get; } + + IServiceHubCloner Cloner { get; } + + IWebClient WebClient { get; } + IStorageController StorageController { get; } + IParseObjectClassController ClassController { get; } + + IParseDataDecoder Decoder { get; } + + IParseInstallationController InstallationController { get; } + IParseCommandRunner CommandRunner { get; } + + IParseCloudCodeController CloudCodeController { get; } + IParseConfigurationController ConfigurationController { get; } + IParseFileController FileController { get; } + IParseObjectController ObjectController { get; } + IParseQueryController QueryController { get; } + IParseSessionController SessionController { get; } + IParseUserController UserController { get; } + IParseCurrentUserController CurrentUserController { get; } + + IParseAnalyticsController AnalyticsController { get; } + + IParseInstallationCoder InstallationCoder { get; } + + IParsePushChannelsController PushChannelsController { get; } + IParsePushController PushController { get; } + IParseCurrentInstallationController CurrentInstallationController { get; } + IParseInstallationDataFinalizer InstallationDataFinalizer { get; } + } +} diff --git a/Parse/Abstractions/Library/IServiceHubCloner.cs b/Parse/Abstractions/Library/IServiceHubCloner.cs new file mode 100644 index 00000000..35316d40 --- /dev/null +++ b/Parse/Abstractions/Library/IServiceHubCloner.cs @@ -0,0 +1,10 @@ +using System; +using System.Text; + +namespace Parse.Abstractions.Library +{ + public interface IServiceHubCloner + { + public IServiceHub BuildHub(in IServiceHub reference, IServiceHubComposer composer); + } +} diff --git a/Parse/Abstractions/Library/IServiceHubComposer.cs b/Parse/Abstractions/Library/IServiceHubComposer.cs new file mode 100644 index 00000000..7f515d58 --- /dev/null +++ b/Parse/Abstractions/Library/IServiceHubComposer.cs @@ -0,0 +1,9 @@ +namespace Parse.Abstractions.Library +{ + // ALTERNATE NAME: IClient, IDataContainmentHub, IResourceContainmentHub, IDataContainer, IServiceHubComposer + + public interface IServiceHubComposer + { + public IServiceHub BuildHub(IMutableServiceHub serviceHub = default, IServiceHub extension = default, params IServiceHubMutator[] configurators); + } +} diff --git a/Parse/Abstractions/Library/IServiceHubMutator.cs b/Parse/Abstractions/Library/IServiceHubMutator.cs new file mode 100644 index 00000000..d5eebedc --- /dev/null +++ b/Parse/Abstractions/Library/IServiceHubMutator.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Parse.Abstractions.Library +{ + // IServiceHubComposer, IServiceHubMutator, IServiceHubConfigurator, IClientConfigurator, IServiceConfigurationLayer + + /// + /// A class which makes a deliberate mutation to a service. + /// + public interface IServiceHubMutator + { + bool Valid { get; } + + void Mutate(ref IMutableServiceHub target, in IServiceHub composedHub); + } +} diff --git a/Parse/Abstractions/Management/IParseCommandRunner.cs b/Parse/Abstractions/Management/IParseCommandRunner.cs index 0739a45d..d4d7cb3b 100644 --- a/Parse/Abstractions/Management/IParseCommandRunner.cs +++ b/Parse/Abstractions/Management/IParseCommandRunner.cs @@ -18,9 +18,6 @@ public interface IParseCommandRunner /// Download progress callback. /// The cancellation token for the request. /// - Task>> RunCommandAsync(ParseCommand command, - IProgress uploadProgress = null, - IProgress downloadProgress = null, - CancellationToken cancellationToken = default(CancellationToken)); + Task>> RunCommandAsync(ParseCommand command, IProgress uploadProgress = null, IProgress downloadProgress = null, CancellationToken cancellationToken = default); } } diff --git a/Parse/Abstractions/Management/IParseCorePlugins.cs b/Parse/Abstractions/Management/IParseCorePlugins.cs index 2ef1aaab..7829d299 100644 --- a/Parse/Abstractions/Management/IParseCorePlugins.cs +++ b/Parse/Abstractions/Management/IParseCorePlugins.cs @@ -15,26 +15,28 @@ public interface IParseCorePlugins IWebClient WebClient { get; } IStorageController StorageController { get; } - IObjectSubclassingController SubclassingController { get; } + IParseObjectClassController SubclassingController { get; } IParseInstallationController InstallationController { get; } IParseCommandRunner CommandRunner { get; } IParseCloudCodeController CloudCodeController { get; } - IParseConfigController ConfigController { get; } + IParseConfigurationController ConfigController { get; } IParseFileController FileController { get; } IParseObjectController ObjectController { get; } IParseQueryController QueryController { get; } IParseSessionController SessionController { get; } IParseUserController UserController { get; } IParseCurrentUserController CurrentUserController { get; } - //IParseCurrentConfigController CurrentConfigController { get; } + + // IParseCurrentConfigController CurrentConfigController { get; } public void Reset(); - /// - /// Sets the default controller instances if not explicitly overridden. This method should effectively perform a -coalescing assign on all of the properties of the implementation instance. - /// - //public void SetDefaults(); + // /// + // /// Sets the default controller instances if not explicitly overridden. This method should effectively perform a -coalescing assign on all of the properties of the implementation instance. + // /// + + // public void SetDefaults(); } } \ No newline at end of file diff --git a/Parse/Abstractions/Management/IObjectSubclassingController.cs b/Parse/Abstractions/Management/IParseObjectClassController.cs similarity index 63% rename from Parse/Abstractions/Management/IObjectSubclassingController.cs rename to Parse/Abstractions/Management/IParseObjectClassController.cs index d899f917..b63933ef 100644 --- a/Parse/Abstractions/Management/IObjectSubclassingController.cs +++ b/Parse/Abstractions/Management/IParseObjectClassController.cs @@ -3,19 +3,24 @@ namespace Parse.Core.Internal { - public interface IObjectSubclassingController + public interface IParseObjectClassController { string GetClassName(Type type); + Type GetType(string className); - bool IsTypeValid(string className, Type type); + bool GetClassMatch(string className, Type type); + + void AddValid(Type t); - void RegisterSubclass(Type t); - void UnregisterSubclass(Type t); + void RemoveClass(Type t); void AddRegisterHook(Type t, Action action); ParseObject Instantiate(string className); + IDictionary GetPropertyMappings(string className); + + void AddIntrinsic(); } } diff --git a/Parse/Abstractions/Management/IParseObjectCurrentController.cs b/Parse/Abstractions/Management/IParseObjectCurrentController.cs index 016e54ba..c1f0d608 100644 --- a/Parse/Abstractions/Management/IParseObjectCurrentController.cs +++ b/Parse/Abstractions/Management/IParseObjectCurrentController.cs @@ -18,20 +18,20 @@ public interface IParseObjectCurrentController where T : ParseObject /// /// to be persisted. /// The cancellation token. - Task SetAsync(T obj, CancellationToken cancellationToken); + Task SetAsync(T obj, CancellationToken cancellationToken = default); /// /// Gets the persisted current . /// /// The cancellation token. - Task GetAsync(CancellationToken cancellationToken); + Task GetAsync(CancellationToken cancellationToken = default); /// /// Returns a that resolves to true if current /// exists. /// /// The cancellation token. - Task ExistsAsync(CancellationToken cancellationToken); + Task ExistsAsync(CancellationToken cancellationToken = default); /// /// Returns true if the given is the persisted current diff --git a/Parse/Abstractions/Management/IParseUserController.cs b/Parse/Abstractions/Management/IParseUserController.cs index c3ccaff9..7a3b2411 100644 --- a/Parse/Abstractions/Management/IParseUserController.cs +++ b/Parse/Abstractions/Management/IParseUserController.cs @@ -8,20 +8,18 @@ namespace Parse.Core.Internal { public interface IParseUserController { - Task SignUpAsync(IObjectState state, - IDictionary operations, - CancellationToken cancellationToken); + Task SignUpAsync(IObjectState state, IDictionary operations, CancellationToken cancellationToken); - Task LogInAsync(string username, - string password, - CancellationToken cancellationToken); + Task LogInAsync(string username, string password, CancellationToken cancellationToken); - Task LogInAsync(string authType, - IDictionary data, - CancellationToken cancellationToken); + Task LogInAsync(string authType, IDictionary data, CancellationToken cancellationToken); Task GetUserAsync(string sessionToken, CancellationToken cancellationToken); Task RequestPasswordResetAsync(string email, CancellationToken cancellationToken); + + bool RevocableSessionEnabled { get; set; } + + object RevocableSessionEnabledMutex { get; } } } diff --git a/Parse/Abstractions/Platform/Configuration/IParseConfigController.cs b/Parse/Abstractions/Platform/Configuration/IParseConfigurationController.cs similarity index 64% rename from Parse/Abstractions/Platform/Configuration/IParseConfigController.cs rename to Parse/Abstractions/Platform/Configuration/IParseConfigurationController.cs index f3b8c07f..b5967390 100644 --- a/Parse/Abstractions/Platform/Configuration/IParseConfigController.cs +++ b/Parse/Abstractions/Platform/Configuration/IParseConfigurationController.cs @@ -5,13 +5,9 @@ namespace Parse.Core.Internal { - public interface IParseConfigController + public interface IParseConfigurationController { - /// - /// Gets the current config controller. - /// - /// The current config controller. - IParseCurrentConfigController CurrentConfigController { get; } + public IParseCurrentConfigurationController CurrentConfigurationController { get; } /// /// Fetches the config from the server asynchronously. @@ -19,6 +15,6 @@ public interface IParseConfigController /// The config async. /// Session token. /// Cancellation token. - Task FetchConfigAsync(string sessionToken, CancellationToken cancellationToken); + Task FetchConfigAsync(string sessionToken, CancellationToken cancellationToken); } } diff --git a/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigController.cs b/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigurationController.cs similarity index 86% rename from Parse/Abstractions/Platform/Configuration/IParseCurrentConfigController.cs rename to Parse/Abstractions/Platform/Configuration/IParseCurrentConfigurationController.cs index 35571e52..4ef36361 100644 --- a/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigController.cs +++ b/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigurationController.cs @@ -4,20 +4,20 @@ namespace Parse.Core.Internal { - public interface IParseCurrentConfigController + public interface IParseCurrentConfigurationController { /// /// Gets the current config async. /// /// The current config async. - Task GetCurrentConfigAsync(); + Task GetCurrentConfigAsync(); /// /// Sets the current config async. /// /// The current config async. /// Config. - Task SetCurrentConfigAsync(ParseConfig config); + Task SetCurrentConfigAsync(ParseConfiguration config); /// /// Clears the current config async. diff --git a/Parse/Abstractions/Platform/Files/IParseFileController.cs b/Parse/Abstractions/Platform/Files/IParseFileController.cs index f2e1e6a2..90d2bf94 100644 --- a/Parse/Abstractions/Platform/Files/IParseFileController.cs +++ b/Parse/Abstractions/Platform/Files/IParseFileController.cs @@ -9,10 +9,6 @@ namespace Parse.Core.Internal { public interface IParseFileController { - Task SaveAsync(FileState state, - Stream dataStream, - string sessionToken, - IProgress progress, - CancellationToken cancellationToken); + Task SaveAsync(FileState state, Stream dataStream, string sessionToken, IProgress progress, CancellationToken cancellationToken); } } diff --git a/Parse/Abstractions/Device/IDeviceInfoController.cs b/Parse/Abstractions/Platform/Installation/IParseInstallationDataFinalizer.cs similarity index 54% rename from Parse/Abstractions/Device/IDeviceInfoController.cs rename to Parse/Abstractions/Platform/Installation/IParseInstallationDataFinalizer.cs index 9d4d36ca..7ded545b 100644 --- a/Parse/Abstractions/Device/IDeviceInfoController.cs +++ b/Parse/Abstractions/Platform/Installation/IParseInstallationDataFinalizer.cs @@ -2,23 +2,19 @@ namespace Parse.Push.Internal { - public interface IDeviceInfoController + public interface IParseInstallationDataFinalizer { - string DeviceType { get; } - string DeviceTimeZone { get; } - string AppBuildVersion { get; } - string AppIdentifier { get; } - string AppName { get; } - - /// /// Executes platform specific hook that mutate the installation based on /// the device platforms. /// /// Installation to be mutated. /// - Task ExecuteParseInstallationSaveHookAsync(ParseInstallation installation); + Task FinalizeAsync(ParseInstallation installation); + /// + /// Allows an implementation to get static information that needs to be used in . + /// void Initialize(); } } diff --git a/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs b/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs index af2f1005..9d782a0e 100644 --- a/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs +++ b/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs @@ -10,6 +10,6 @@ public interface IParsePushPlugins IParsePushChannelsController PushChannelsController { get; } IParsePushController PushController { get; } IParseCurrentInstallationController CurrentInstallationController { get; } - IDeviceInfoController DeviceInfoController { get; } + IParseInstallationDataFinalizer DeviceInfoController { get; } } } \ No newline at end of file diff --git a/Parse/Abstractions/Storage/IStorageConfiguration.cs b/Parse/Abstractions/Storage/IStorageConfiguration.cs index 0aa22a91..af721dd3 100644 --- a/Parse/Abstractions/Storage/IStorageConfiguration.cs +++ b/Parse/Abstractions/Storage/IStorageConfiguration.cs @@ -1,3 +1,4 @@ +using Parse.Abstractions.Library; using Parse.Abstractions.Management; namespace Parse.Abstractions.Storage @@ -10,6 +11,6 @@ public interface ICacheLocationConfiguration /// /// The corresponding relative path generated by this . /// - string GetRelativeStorageFilePath(IParseCorePlugins plugins); + string GetRelativeStorageFilePath(IServiceHub plugins); } } diff --git a/Parse/Platform/Analytics/ParseAnalytics.cs b/Parse/AnalyticsServiceExtensions.cs similarity index 73% rename from Parse/Platform/Analytics/ParseAnalytics.cs rename to Parse/AnalyticsServiceExtensions.cs index 7b89392a..72c02640 100644 --- a/Parse/Platform/Analytics/ParseAnalytics.cs +++ b/Parse/AnalyticsServiceExtensions.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Analytics.Internal; using Parse.Common.Internal; using Parse.Core.Internal; @@ -16,17 +17,13 @@ namespace Parse /// Methods will return immediately and cache requests (along with timestamps) /// to be handled in the background. /// - public partial class ParseAnalytics + public static class AnalyticsServiceExtensions { - internal static IParseAnalyticsController AnalyticsController => ParseAnalyticsPlugins.Instance.AnalyticsController; - - internal static IParseCurrentUserController CurrentUserController => ParseAnalyticsPlugins.Instance.CorePlugins.CurrentUserController; - /// /// Tracks this application being launched. /// /// An Async Task that can be waited on or ignored. - public static Task TrackAppOpenedAsync() => TrackAppOpenedWithPushHashAsync(); + public static Task TrackLaunchAsync(this IServiceHub serviceHub) => TrackLaunchWithPushHashAsync(serviceHub); /// /// Tracks the occurrence of a custom event with additional dimensions. @@ -52,7 +49,7 @@ public partial class ParseAnalytics /// The name of the custom event to report to ParseClient /// as having happened. /// An Async Task that can be waited on or ignored. - public static Task TrackEventAsync(string name) => TrackEventAsync(name, null); + public static Task TrackAnalyticsEventAsync(this IServiceHub serviceHub, string name) => TrackAnalyticsEventAsync(serviceHub, name, default); /// /// Tracks the occurrence of a custom event with additional dimensions. @@ -80,21 +77,14 @@ public partial class ParseAnalytics /// The dictionary of information by which to /// segment this event. /// An Async Task that can be waited on or ignored. - public static Task TrackEventAsync(string name, IDictionary dimensions) + public static Task TrackAnalyticsEventAsync(this IServiceHub serviceHub, string name, IDictionary dimensions) { - if (name == null || name.Trim().Length == 0) + if (name is null || name.Trim().Length == 0) { throw new ArgumentException("A name for the custom event must be provided."); } - return CurrentUserController.GetCurrentSessionTokenAsync(CancellationToken.None) - .OnSuccess(t => - { - return AnalyticsController.TrackEventAsync(name, - dimensions, - t.Result, - CancellationToken.None); - }).Unwrap(); + return serviceHub.CurrentUserController.GetCurrentSessionTokenAsync(CancellationToken.None).OnSuccess(task => serviceHub.AnalyticsController.TrackEventAsync(name, dimensions, task.Result, CancellationToken.None)).Unwrap(); } /// @@ -104,12 +94,6 @@ public static Task TrackEventAsync(string name, IDictionary dime /// An identifying hash for a given push notification, /// passed down from the server. /// An Async Task that can be waited on or ignored. - private static Task TrackAppOpenedWithPushHashAsync(string pushHash = null) => CurrentUserController.GetCurrentSessionTokenAsync(CancellationToken.None) - .OnSuccess(t => - { - return AnalyticsController.TrackAppOpenedAsync(pushHash, - t.Result, - CancellationToken.None); - }).Unwrap(); + static Task TrackLaunchWithPushHashAsync(this IServiceHub serviceHub, string pushHash = null) => serviceHub.CurrentUserController.GetCurrentSessionTokenAsync(CancellationToken.None).OnSuccess(task => serviceHub.AnalyticsController.TrackAppOpenedAsync(pushHash, task.Result, CancellationToken.None)).Unwrap(); } } diff --git a/Parse/Platform/Code/ParseCloud.cs b/Parse/CloudCodeServiceExtensions.cs similarity index 78% rename from Parse/Platform/Code/ParseCloud.cs rename to Parse/CloudCodeServiceExtensions.cs index 524c24de..dd14bf18 100644 --- a/Parse/Platform/Code/ParseCloud.cs +++ b/Parse/CloudCodeServiceExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Core.Internal; using Parse.Management; @@ -21,10 +22,8 @@ namespace Parse /// await ParseCloud.CallFunctionAsync<IDictionary<string, object>>("validateGame", parameters); /// /// - public static class ParseCloud + public static class CloudCodeServiceExtensions { - internal static IParseCloudCodeController CloudCodeController => ParseCorePlugins.Instance.CloudCodeController; - /// /// Calls a cloud function. /// @@ -36,7 +35,7 @@ public static class ParseCloud /// dictionary can contain anything that could be passed into a ParseObject except for /// ParseObjects themselves. /// The result of the cloud call. - public static Task CallFunctionAsync(string name, IDictionary parameters) => CallFunctionAsync(name, parameters, CancellationToken.None); + public static Task CallCloudCodeFunctionAsync(this IServiceHub serviceHub, string name, IDictionary parameters) => CallCloudCodeFunctionAsync(serviceHub, name, parameters, CancellationToken.None); /// /// Calls a cloud function. @@ -50,10 +49,6 @@ public static class ParseCloud /// ParseObjects themselves. /// The cancellation token. /// The result of the cloud call. - public static Task CallFunctionAsync(string name, - IDictionary parameters, CancellationToken cancellationToken) => CloudCodeController.CallFunctionAsync(name, - parameters, - ParseUser.CurrentSessionToken, - cancellationToken); + public static Task CallCloudCodeFunctionAsync(this IServiceHub serviceHub, string name, IDictionary parameters, CancellationToken cancellationToken) => serviceHub.CloudCodeController.CallFunctionAsync(name, parameters, serviceHub.GetCurrentSessionToken(), cancellationToken); } } diff --git a/Parse/ConfigurationServiceExtensions.cs b/Parse/ConfigurationServiceExtensions.cs new file mode 100644 index 00000000..20df985a --- /dev/null +++ b/Parse/ConfigurationServiceExtensions.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Parse.Abstractions.Library; +using Parse.Core.Internal; + +namespace Parse +{ + public static class ConfigurationServiceExtensions + { + public static ParseConfiguration BuildConfiguration(this IServiceHub serviceHub, IDictionary configurationData) => ParseConfiguration.Create(configurationData, serviceHub.Decoder); + + public static ParseConfiguration BuildConfiguration(this IParseDataDecoder dataDecoder, IDictionary configurationData) => ParseConfiguration.Create(configurationData, dataDecoder); + +#warning Investigate if these methods which simply block a thread waiting for an asynchronous process to complete should be eliminated. + + /// + /// Gets the latest fetched ParseConfig. + /// + /// ParseConfig object + public static ParseConfiguration GetCurrentConfig(this IServiceHub serviceHub) + { + Task task = serviceHub.ConfigurationController.CurrentConfigurationController.GetCurrentConfigAsync(); + + task.Wait(); + return task.Result; + } + + internal static void ClearCurrentConfig(this IServiceHub serviceHub) => serviceHub.ConfigurationController.CurrentConfigurationController.ClearCurrentConfigAsync().Wait(); + + internal static void ClearCurrentConfigInMemory(this IServiceHub serviceHub) => serviceHub.ConfigurationController.CurrentConfigurationController.ClearCurrentConfigInMemoryAsync().Wait(); + + /// + /// Retrieves the ParseConfig asynchronously from the server. + /// + /// The cancellation token. + /// ParseConfig object that was fetched + public static Task GetAsync(this IServiceHub serviceHub, CancellationToken cancellationToken = default) => serviceHub.ConfigurationController.FetchConfigAsync(serviceHub.GetCurrentSessionToken(), cancellationToken); + } +} diff --git a/Parse/Device/DeviceInfoController.cs b/Parse/Device/DeviceInfoController.cs deleted file mode 100644 index d402f41c..00000000 --- a/Parse/Device/DeviceInfoController.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Parse.Push.Internal -{ - - /// - /// Controls the device information. - /// - public class DeviceInfoController : IDeviceInfoController - { - /// - /// The device platform that the app is currently running on. - /// - public string DeviceType { get; } = Environment.OSVersion.ToString(); - - /// - /// The active time zone on the device that the app is currently running on. - /// - public string DeviceTimeZone => TimeZoneInfo.Local.StandardName; - - /// - /// The version number of the application. - /// - public string AppBuildVersion { get; } = System.Reflection.Assembly.GetEntryAssembly().GetName().Version.Build.ToString(); - - // TODO: Verify if this means Parse appId or just a unique identifier. - - /// - /// The identifier of the application - /// - public string AppIdentifier => AppDomain.CurrentDomain.FriendlyName; - - /// - /// The name of the current application. - /// - public string AppName { get; } = System.Reflection.Assembly.GetEntryAssembly().GetName().Name; - - public Task ExecuteParseInstallationSaveHookAsync(ParseInstallation installation) => Task.FromResult(null); - - public void Initialize() { } - } -} \ No newline at end of file diff --git a/Parse/InstallationServiceExtensions.cs b/Parse/InstallationServiceExtensions.cs new file mode 100644 index 00000000..73b766bc --- /dev/null +++ b/Parse/InstallationServiceExtensions.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Threading; +using System.Threading.Tasks; +using Parse.Abstractions.Library; + +namespace Parse +{ + public static class InstallationServiceExtensions + { + /// + /// Constructs a for ParseInstallations. + /// + /// + /// Only the following types of queries are allowed for installations: + /// + /// + /// query.GetAsync(objectId) + /// query.WhereEqualTo(key, value) + /// query.WhereMatchesKeyInQuery<TOther>(key, keyInQuery, otherQuery) + /// + /// + /// You can add additional query conditions, but one of the above must appear as a top-level AND + /// clause in the query. + /// + public static ParseQuery GetInstallationQuery(this IServiceHub serviceHub) => new ParseQuery(serviceHub); + +#warning Consider making the following method asynchronous. + + /// + /// Gets the ParseInstallation representing this app on this device. + /// + public static ParseInstallation GetCurrentInstallation(this IServiceHub serviceHub) + { + Task task = serviceHub.CurrentInstallationController.GetAsync(CancellationToken.None); + + // TODO (hallucinogen): this will absolutely break on Unity, but how should we resolve this? + task.Wait(); + return task.Result; + } + + internal static void ClearInMemoryInstallation(this IServiceHub serviceHub) => serviceHub.CurrentInstallationController.ClearFromMemory(); + } +} diff --git a/Parse/Library/CacheLocationMutator.cs b/Parse/Library/CacheLocationMutator.cs new file mode 100644 index 00000000..8422ef8f --- /dev/null +++ b/Parse/Library/CacheLocationMutator.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Parse.Abstractions.Library; +using Parse.Abstractions.Storage; +using Parse.Common.Internal; + +namespace Parse.Library +{ + /// + /// An for the relative storagecache location. + /// + public class CacheLocationMutator : IServiceHubMutator + { + ICacheLocationConfiguration CacheLocationConfiguration { get; set; } + + public bool Valid => CacheLocationConfiguration is { }; + + public void Mutate(ref IMutableServiceHub target, in IServiceHub referenceHub) => target.StorageController = (target as IServiceHub).StorageController switch + { + null => new StorageController { RelativeStorageFilePath = CacheLocationConfiguration.GetRelativeStorageFilePath(referenceHub) }, + StorageController { } controller => (Controller: controller, controller.RelativeStorageFilePath = CacheLocationConfiguration.GetRelativeStorageFilePath(referenceHub)).Controller, + { } controller => controller + }; + } +} diff --git a/Parse/Library/ConcurrentUserServiceHubCloner.cs b/Parse/Library/ConcurrentUserServiceHubCloner.cs new file mode 100644 index 00000000..b1bd729d --- /dev/null +++ b/Parse/Library/ConcurrentUserServiceHubCloner.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Parse.Abstractions.Library; + +namespace Parse.Library +{ + public class ConcurrentUserServiceHubCloner : IServiceHubCloner + { + public IServiceHub BuildHub(in IServiceHub reference, IServiceHubComposer composer) + { + throw new NotImplementedException { }; + } + } +} diff --git a/Parse/Library/EnvironmentData.cs b/Parse/Library/EnvironmentData.cs new file mode 100644 index 00000000..19dcbc64 --- /dev/null +++ b/Parse/Library/EnvironmentData.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using Parse.Abstractions.Library; + +namespace Parse.Library +{ + /// + /// Inferred data about the environment in which parse is operating. + /// + public class EnvironmentData : IEnvironmentData + { + /// + /// A instance that the Parse SDK will attempt to generate from environment metadata it should be able to access. + /// + public static EnvironmentData Inferred => new EnvironmentData + { + TimeZone = TimeZoneInfo.Local.StandardName, + OSVersion = RuntimeInformation.OSDescription ?? Environment.OSVersion.ToString(), + Platform = RuntimeInformation.FrameworkDescription ?? ".NET" + }; + + /// + /// The active time zone for the app and/or system. + /// + public string TimeZone { get; set; } + + /// + /// The host operating system version of the platform the host application is operating in. + /// + public string OSVersion { get; set; } + + /// + /// The target platform the app is running on. Defaults to .NET. + /// + public string Platform { get; set; } + } +} diff --git a/Parse/Library/HostApplicationVersioningData.cs b/Parse/Library/HostApplicationVersioningData.cs deleted file mode 100644 index 643dbf29..00000000 --- a/Parse/Library/HostApplicationVersioningData.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Reflection; -using Parse.Abstractions.Library; -using Parse.Storage; - -namespace Parse.Library -{ - /// - /// In the event that you would like to use the Parse SDK - /// from a completely portable project, with no platform-specific library required, - /// to get full access to all of our features available on Parse Dashboard - /// (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 class HostApplicationVersioningData : IHostApplicationVersioningData - { - /// - /// An instance of with inferred values based on the entry assembly. - /// - /// Should not be used with Unity. - public static HostApplicationVersioningData Inferred { get; } = new HostApplicationVersioningData - { - BuildVersion = Assembly.GetEntryAssembly().GetName().Version.Build.ToString(), - DisplayVersion = Assembly.GetEntryAssembly().GetName().Version.ToString(), - HostOSVersion = Environment.OSVersion.ToString() - }; - - /// - /// The build number of your app. - /// - public string BuildVersion { get; set; } - - /// - /// The human friendly version number of your app. - /// - public string DisplayVersion { get; set; } - - /// - /// The host operating system version of the platform the host application is operating in. - /// - public string HostOSVersion { get; set; } - - /// - /// Gets a value for whether or not this instance of is populated with default values. - /// - public bool IsDefault => BuildVersion is null && DisplayVersion is null && HostOSVersion is null; - - /// - /// Gets a value for whether or not this instance of can currently be used for the generation of . - /// - public bool CanBeUsedForInference => !(IsDefault || String.IsNullOrWhiteSpace(DisplayVersion)); - } -} diff --git a/Parse/Library/HostManifestData.cs b/Parse/Library/HostManifestData.cs new file mode 100644 index 00000000..ce43fda8 --- /dev/null +++ b/Parse/Library/HostManifestData.cs @@ -0,0 +1,63 @@ +using System; +using System.Reflection; +using Parse.Abstractions.Library; +using Parse.Storage; + +namespace Parse.Library +{ + /// + /// In the event that you would like to use the Parse SDK + /// from a completely portable project, with no platform-specific library required, + /// to get full access to all of our features available on Parse Dashboard + /// (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 class HostManifestData : IHostManifestData + { + /// + /// An instance of with inferred values based on the entry assembly. + /// + /// Should not be used with Unity. + public static HostManifestData Inferred => new HostManifestData + { + Version = Assembly.GetEntryAssembly().GetCustomAttribute().InformationalVersion, + Name = Assembly.GetEntryAssembly().GetCustomAttribute()?.Title ?? Assembly.GetEntryAssembly().GetCustomAttribute()?.Product ?? Assembly.GetEntryAssembly().GetName().Name, + ShortVersion = Assembly.GetEntryAssembly().GetName().Version.ToString(), + // TODO: For Xamarin, use manifest parsing, and for Unity, use some kind of package identifier API. + Identifier = AppDomain.CurrentDomain.FriendlyName + }; + + /// + /// The build version of your app. + /// + public string Version { get; set; } + + /// + /// The human-friendly display version number of your app. + /// + public string ShortVersion { get; set; } + + /// + /// The identifier of the application + /// + public string Identifier { get; set; } + + /// + /// The friendly name of your app. + /// + public string Name { get; set; } + + /// + /// Gets a value for whether or not this instance of is populated with default values. + /// + public bool IsDefault => Version is null && ShortVersion is null && Identifier is null && Name is null; + + /// + /// Gets a value for whether or not this instance of can currently be used for the generation of . + /// + public bool CanBeUsedForInference => !(IsDefault || String.IsNullOrWhiteSpace(ShortVersion)); + } +} diff --git a/Parse/Library/MetadataController.cs b/Parse/Library/MetadataController.cs index b90730c1..a5fc1a42 100644 --- a/Parse/Library/MetadataController.cs +++ b/Parse/Library/MetadataController.cs @@ -8,8 +8,13 @@ namespace Parse.Library public class MetadataController : IMetadataController { /// - /// The version information of your application environment. + /// Information about your app. /// - public IHostApplicationVersioningData HostVersioningData { get; set; } + public IHostManifestData HostManifestData { get; set; } + + /// + /// Information about the environment the library is operating in. + /// + public IEnvironmentData EnvironmentData { get; set; } } } diff --git a/Parse/Library/MetadataMutator.cs b/Parse/Library/MetadataMutator.cs new file mode 100644 index 00000000..6b7adb6a --- /dev/null +++ b/Parse/Library/MetadataMutator.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Parse.Abstractions.Library; +using Parse.Abstractions.Storage; + +namespace Parse.Library +{ + /// + /// An for setting metadata information manually. + /// + public class MetadataMutator : MetadataController, IServiceHubMutator + { + /// + /// A value representing whether or not all of the required metadata information has been provided. + /// + public bool Valid => this is { EnvironmentData: { OSVersion: { }, Platform: { }, TimeZone: { } }, HostManifestData: { Identifier: { }, Name: { }, ShortVersion: { }, Version: { } } }; + + /// + /// Sets the to the instance. + /// + /// The to compose the information onto. + /// Thhe to use if a default service instance is required. + public void Mutate(ref IMutableServiceHub target, in IServiceHub referenceHub) => target.MetadataController = this; + } +} diff --git a/Parse/Library/MutableServiceHub.cs b/Parse/Library/MutableServiceHub.cs new file mode 100644 index 00000000..f8431cc9 --- /dev/null +++ b/Parse/Library/MutableServiceHub.cs @@ -0,0 +1,87 @@ +using System; +using Parse.Abstractions.Library; +using Parse.Analytics.Internal; +using Parse.Common.Internal; +using Parse.Core.Internal; +using Parse.Push.Internal; + +namespace Parse.Library +{ + /// + /// A service hub that is mutable. + /// + /// This class is not thread safe; the mutability is allowed for the purposes of overriding values before it is used, as opposed to modifying it while it is in use. + public class MutableServiceHub : IMutableServiceHub + { + public IServerConnectionData ServerConnectionData { get; set; } + public IMetadataController MetadataController { get; set; } + + public IServiceHubCloner Cloner { get; set; } + + public IWebClient WebClient { get; set; } + public IStorageController StorageController { get; set; } + public IParseObjectClassController ClassController { get; set; } + + public IParseDataDecoder Decoder { get; set; } + + public IParseInstallationController InstallationController { get; set; } + public IParseCommandRunner CommandRunner { get; set; } + + public IParseCloudCodeController CloudCodeController { get; set; } + public IParseConfigurationController ConfigurationController { get; set; } + public IParseFileController FileController { get; set; } + public IParseObjectController ObjectController { get; set; } + public IParseQueryController QueryController { get; set; } + public IParseSessionController SessionController { get; set; } + public IParseUserController UserController { get; set; } + public IParseCurrentUserController CurrentUserController { get; set; } + + public IParseAnalyticsController AnalyticsController { get; set; } + + public IParseInstallationCoder InstallationCoder { get; set; } + + public IParsePushChannelsController PushChannelsController { get; set; } + public IParsePushController PushController { get; set; } + public IParseCurrentInstallationController CurrentInstallationController { get; set; } + public IParseInstallationDataFinalizer InstallationDataFinalizer { get; set; } + + public void SetDefaults(IServerConnectionData connectionData) + { + ServerConnectionData ??= connectionData; + MetadataController ??= new MetadataController + { + EnvironmentData = EnvironmentData.Inferred, + HostManifestData = HostManifestData.Inferred + }; + + Cloner ??= new ConcurrentUserServiceHubCloner { }; + + WebClient ??= new UniversalWebClient { }; + StorageController ??= new StorageController { }; + ClassController ??= new ObjectSubclassingController { }; + + Decoder ??= new ParseDataDecoder(ClassController); + + InstallationController ??= new ParseInstallationController(StorageController); + CommandRunner ??= new ParseCommandRunner(WebClient, InstallationController, MetadataController, ServerConnectionData, new Lazy(() => UserController)); + + CloudCodeController ??= new ParseCloudCodeController(CommandRunner, Decoder); + ConfigurationController ??= new ParseConfigurationController(CommandRunner, StorageController, Decoder); + FileController ??= new ParseFileController(CommandRunner); + ObjectController ??= new ParseObjectController(CommandRunner, Decoder); + QueryController ??= new ParseQueryController(CommandRunner, Decoder); + SessionController ??= new ParseSessionController(CommandRunner, Decoder); + UserController ??= new ParseUserController(CommandRunner, Decoder); + CurrentUserController ??= new ParseCurrentUserController(StorageController, ClassController, Decoder); + + AnalyticsController ??= new ParseAnalyticsController(CommandRunner); + + InstallationCoder ??= new ParseInstallationCoder(Decoder, ClassController); + + PushController ??= new ParsePushController(CommandRunner, CurrentUserController); + CurrentInstallationController ??= new ParseCurrentInstallationController(InstallationController, StorageController, InstallationCoder, ClassController); + PushChannelsController ??= new ParsePushChannelsController(CurrentInstallationController); + InstallationDataFinalizer ??= new ParseInstallationDataFinalizer { }; + } + } +} diff --git a/Parse/Library/OrchestrationServiceHub.cs b/Parse/Library/OrchestrationServiceHub.cs new file mode 100644 index 00000000..51224d3f --- /dev/null +++ b/Parse/Library/OrchestrationServiceHub.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Parse.Abstractions.Library; +using Parse.Analytics.Internal; +using Parse.Common.Internal; +using Parse.Core.Internal; +using Parse.Push.Internal; + +namespace Parse.Library +{ + public class OrchestrationServiceHub : IServiceHub + { + public IServiceHub Default { get; set; } + + public IServiceHub Custom { get; set; } + + public IServiceHubCloner Cloner => Custom.Cloner ?? Default.Cloner; + + public IMetadataController MetadataController => Custom.MetadataController ?? Default.MetadataController; + + public IWebClient WebClient => Custom.WebClient ?? Default.WebClient; + + public IStorageController StorageController => Custom.StorageController ?? Default.StorageController; + + public IParseObjectClassController ClassController => Custom.ClassController ?? Default.ClassController; + + public IParseInstallationController InstallationController => Custom.InstallationController ?? Default.InstallationController; + + public IParseCommandRunner CommandRunner => Custom.CommandRunner ?? Default.CommandRunner; + + public IParseCloudCodeController CloudCodeController => Custom.CloudCodeController ?? Default.CloudCodeController; + + public IParseConfigurationController ConfigurationController => Custom.ConfigurationController ?? Default.ConfigurationController; + + public IParseFileController FileController => Custom.FileController ?? Default.FileController; + + public IParseObjectController ObjectController => Custom.ObjectController ?? Default.ObjectController; + + public IParseQueryController QueryController => Custom.QueryController ?? Default.QueryController; + + public IParseSessionController SessionController => Custom.SessionController ?? Default.SessionController; + + public IParseUserController UserController => Custom.UserController ?? Default.UserController; + + public IParseCurrentUserController CurrentUserController => Custom.CurrentUserController ?? Default.CurrentUserController; + + public IParseAnalyticsController AnalyticsController => Custom.AnalyticsController ?? Default.AnalyticsController; + + public IParseInstallationCoder InstallationCoder => Custom.InstallationCoder ?? Default.InstallationCoder; + + public IParsePushChannelsController PushChannelsController => Custom.PushChannelsController ?? Default.PushChannelsController; + + public IParsePushController PushController => Custom.PushController ?? Default.PushController; + + public IParseCurrentInstallationController CurrentInstallationController => Custom.CurrentInstallationController ?? Default.CurrentInstallationController; + + public IServerConnectionData ServerConnectionData => Custom.ServerConnectionData ?? Default.ServerConnectionData; + + public IParseDataDecoder Decoder => Custom.Decoder ?? Default.Decoder; + + public IParseInstallationDataFinalizer InstallationDataFinalizer => Custom.InstallationDataFinalizer ?? Default.InstallationDataFinalizer; + } +} diff --git a/Parse/Library/ParseDownloadProgressEventArgs.cs b/Parse/Library/ParseDownloadProgressEventArgs.cs deleted file mode 100644 index b9da12ec..00000000 --- a/Parse/Library/ParseDownloadProgressEventArgs.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; - -namespace Parse -{ - /// - /// Represents download progress. - /// - public class ParseDownloadProgressEventArgs : EventArgs - { - public ParseDownloadProgressEventArgs() { } - - /// - /// Gets the progress (a number between 0.0 and 1.0) of a download. - /// - public double Progress { get; set; } - } -} diff --git a/Parse/Library/ParseException.cs b/Parse/Library/ParseFailureException.cs similarity index 98% rename from Parse/Library/ParseException.cs rename to Parse/Library/ParseFailureException.cs index 355e2643..32e3d1ba 100644 --- a/Parse/Library/ParseException.cs +++ b/Parse/Library/ParseFailureException.cs @@ -7,7 +7,7 @@ namespace Parse /// /// Exceptions that may occur when sending requests to Parse. /// - public class ParseException : Exception + public class ParseFailureException : Exception { /// /// Error codes that may be delivered in response to requests to Parse. @@ -259,8 +259,7 @@ public enum ErrorCode UnsupportedService = 252 } - internal ParseException(ErrorCode code, string message, Exception cause = null) - : base(message, cause) => Code = code; + internal ParseFailureException(ErrorCode code, string message, Exception cause = null) : base(message, cause) => Code = code; /// /// The Parse error code associated with the exception. diff --git a/Parse/Library/ParsePushNotificationEventArgs.cs b/Parse/Library/ParsePushNotificationEvent.cs similarity index 64% rename from Parse/Library/ParsePushNotificationEventArgs.cs rename to Parse/Library/ParsePushNotificationEvent.cs index a13f16d7..bf4ae316 100644 --- a/Parse/Library/ParsePushNotificationEventArgs.cs +++ b/Parse/Library/ParsePushNotificationEvent.cs @@ -9,37 +9,32 @@ namespace Parse /// /// A wrapper around Parse push notification payload. /// - public class ParsePushNotificationEventArgs : EventArgs + public class ParsePushNotificationEvent : EventArgs { - internal ParsePushNotificationEventArgs(IDictionary payload) + internal ParsePushNotificationEvent(IDictionary content) { - Payload = payload; - -#if !IOS - StringPayload = Json.Encode(payload); -#endif + Content = content; + TextContent = Json.Encode(content); } // TODO: (richardross) investigate this. // Obj-C type -> .NET type is impossible to do flawlessly (especially // on NSNumber). We can't transform NSDictionary into string because of this reason. -#if !IOS - internal ParsePushNotificationEventArgs(string stringPayload) - { - StringPayload = stringPayload; - Payload = Json.Parse(stringPayload) as IDictionary; + internal ParsePushNotificationEvent(string stringPayload) + { + TextContent = stringPayload; + Content = Json.Parse(stringPayload) as IDictionary; } -#endif /// /// The payload of the push notification as IDictionary. /// - public IDictionary Payload { get; internal set; } + public IDictionary Content { get; internal set; } /// /// The payload of the push notification as string. /// - public string StringPayload { get; internal set; } + public string TextContent { get; internal set; } } } diff --git a/Parse/Library/Configuration.cs b/Parse/Library/ServerConnectionData.cs similarity index 72% rename from Parse/Library/Configuration.cs rename to Parse/Library/ServerConnectionData.cs index 819fd9fd..cdcd49cb 100644 --- a/Parse/Library/Configuration.cs +++ b/Parse/Library/ServerConnectionData.cs @@ -1,14 +1,18 @@ using System; using System.Collections.Generic; using System.Text; +using Parse.Abstractions.Library; namespace Parse.Library { /// /// Represents the configuration of the Parse SDK. /// - public struct Configuration + public struct ServerConnectionData : IServerConnectionData { + // TODO: Consider simplification of names: ApplicationID => Application | Target, ServerURI => Server, MasterKey => Master. + // TODO: Move Test property elsewhere. + internal bool Test { get; set; } /// @@ -31,9 +35,11 @@ public struct Configuration /// public string MasterKey { get; set; } + // ALTERNATE NAME: AuxiliaryHeaders, AdditionalHeaders + /// /// Additional HTTP headers to be sent with network requests from the SDK. /// - public IDictionary AuxiliaryHeaders { get; set; } + public IDictionary Headers { get; set; } } } diff --git a/Parse/Library/ServiceHub.cs b/Parse/Library/ServiceHub.cs new file mode 100644 index 00000000..b8c14711 --- /dev/null +++ b/Parse/Library/ServiceHub.cs @@ -0,0 +1,53 @@ +using System; +using System.Linq; +using System.Text; +using Parse.Abstractions.Library; +using Parse.Analytics.Internal; +using Parse.Common.Internal; +using Parse.Core.Internal; +using Parse.Library.Utilities; +using Parse.Push.Internal; + +namespace Parse.Library +{ + + /// + /// A service hub that uses late initialization to efficiently provide controllers and other dependencies to internal Parse SDK systems. + /// + public class ServiceHub : IServiceHub + { + LateInitializer LateInitializer { get; } = new LateInitializer { }; + + public IServerConnectionData ServerConnectionData { get; set; } + public IMetadataController MetadataController => LateInitializer.GetValue(() => new MetadataController { HostManifestData = HostManifestData.Inferred, EnvironmentData = EnvironmentData.Inferred }); + + public IServiceHubCloner Cloner => LateInitializer.GetValue(() => new { } as object as IServiceHubCloner); + + public IWebClient WebClient => LateInitializer.GetValue(() => new UniversalWebClient { }); + public IStorageController StorageController => LateInitializer.GetValue(() => new StorageController { }); + public IParseObjectClassController ClassController => LateInitializer.GetValue(() => new ObjectSubclassingController { }); + + public IParseDataDecoder Decoder => LateInitializer.GetValue(() => new ParseDataDecoder(ClassController)); + + public IParseInstallationController InstallationController => LateInitializer.GetValue(() => new ParseInstallationController(StorageController)); + public IParseCommandRunner CommandRunner => LateInitializer.GetValue(() => new ParseCommandRunner(WebClient, InstallationController, MetadataController, ServerConnectionData, new Lazy(() => UserController))); + + public IParseCloudCodeController CloudCodeController => LateInitializer.GetValue(() => new ParseCloudCodeController(CommandRunner, Decoder)); + public IParseConfigurationController ConfigurationController => LateInitializer.GetValue(() => new ParseConfigurationController(CommandRunner, StorageController, Decoder)); + public IParseFileController FileController => LateInitializer.GetValue(() => new ParseFileController(CommandRunner)); + public IParseObjectController ObjectController => LateInitializer.GetValue(() => new ParseObjectController(CommandRunner, Decoder)); + public IParseQueryController QueryController => LateInitializer.GetValue(() => new ParseQueryController(CommandRunner, Decoder)); + public IParseSessionController SessionController => LateInitializer.GetValue(() => new ParseSessionController(CommandRunner, Decoder)); + public IParseUserController UserController => LateInitializer.GetValue(() => new ParseUserController(CommandRunner, Decoder)); + public IParseCurrentUserController CurrentUserController => LateInitializer.GetValue(() => new ParseCurrentUserController(StorageController, ClassController, Decoder)); + + public IParseAnalyticsController AnalyticsController => LateInitializer.GetValue(() => new ParseAnalyticsController(CommandRunner)); + + public IParseInstallationCoder InstallationCoder => LateInitializer.GetValue(() => new ParseInstallationCoder(Decoder, ClassController)); + + public IParsePushChannelsController PushChannelsController => LateInitializer.GetValue(() => new ParsePushChannelsController(CurrentInstallationController)); + public IParsePushController PushController => LateInitializer.GetValue(() => new ParsePushController(CommandRunner, CurrentUserController)); + public IParseCurrentInstallationController CurrentInstallationController => LateInitializer.GetValue(() => new ParseCurrentInstallationController(InstallationController, StorageController, InstallationCoder, ClassController)); + public IParseInstallationDataFinalizer InstallationDataFinalizer => LateInitializer.GetValue(() => new ParseInstallationDataFinalizer { }); + } +} diff --git a/Parse/Library/Utilities/LateInitializer.cs b/Parse/Library/Utilities/LateInitializer.cs new file mode 100644 index 00000000..5bc0655f --- /dev/null +++ b/Parse/Library/Utilities/LateInitializer.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +namespace Parse.Library.Utilities +{ + /// + /// A wrapper over a dictionary from value generator to value. Uses the fact that lambda expressions in a specific location are cached, so the cost of instantiating a generator delegate is only incurred once at the call site of and subsequent calls look up the result of the first generation from the dictionary based on the hash of the generator delegate. This is effectively a lazy initialization mechanism that allows the member type to remain unchanged. + /// + public class LateInitializer + { + Lazy, object>> Storage { get; } = new Lazy, object>> { }; + + public TData GetValue(Func generator) + { + lock (generator) + { + if (Storage.Value.TryGetValue(generator as Func, out object data)) + { + return (TData) data; + } + else + { + TData result = generator.Invoke(); + + Storage.Value.Add(generator as Func, result); + return result; + } + } + } + } +} diff --git a/Parse/Management/LightParseCorePlugins.cs b/Parse/Management/LightParseCorePlugins.cs index 412232d3..34f36a07 100644 --- a/Parse/Management/LightParseCorePlugins.cs +++ b/Parse/Management/LightParseCorePlugins.cs @@ -20,7 +20,7 @@ public struct LightParseCorePlugins : IParseCorePlugins public IStorageController StorageController { get; set; } - public IObjectSubclassingController SubclassingController { get; set; } + public IParseObjectClassController SubclassingController { get; set; } public IParseInstallationController InstallationController { get; set; } @@ -28,7 +28,7 @@ public struct LightParseCorePlugins : IParseCorePlugins public IParseCloudCodeController CloudCodeController { get; set; } - public IParseConfigController ConfigController { get; set; } + public IParseConfigurationController ConfigController { get; set; } public IParseFileController FileController { get; set; } @@ -42,7 +42,7 @@ public struct LightParseCorePlugins : IParseCorePlugins public IParseCurrentUserController CurrentUserController { get; set; } - public IParseCurrentConfigController CurrentConfigController { get; set; } + public IParseCurrentConfigurationController CurrentConfigController { get; set; } public void Reset() => throw new NotImplementedException { }; diff --git a/Parse/Management/ObjectSubclassInfo.cs b/Parse/Management/ObjectSubclassInfo.cs index d1f1c819..815df622 100644 --- a/Parse/Management/ObjectSubclassInfo.cs +++ b/Parse/Management/ObjectSubclassInfo.cs @@ -8,21 +8,17 @@ namespace Parse.Core.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 ObjectSubclassInfo(Type type, ConstructorInfo constructor) => (TypeInfo, DeclaredName, Constructor, PropertyMappings) = (type.GetTypeInfo(), TypeInfo.GetParseClassName(), Constructor = constructor, PropertyMappings = type.GetProperties().Select(property => (Property: property, FieldNameAttribute: property.GetCustomAttribute(true))).Where(set => set.FieldNameAttribute is { }).ToDictionary(set => set.Property.Name, set => set.FieldNameAttribute.FieldName)); - public TypeInfo TypeInfo { get; private set; } - public string ClassName { get; private set; } - public IDictionary PropertyMappings { get; private set; } - private ConstructorInfo Constructor { get; set; } + public TypeInfo TypeInfo { get; } - public ParseObject Instantiate() => (ParseObject) Constructor.Invoke(null); + public string DeclaredName { get; } + + public IDictionary PropertyMappings { get; } + + public ParseObject Instantiate() => Constructor.Invoke(default) as ParseObject; + + ConstructorInfo Constructor { get; } - internal static string GetClassName(TypeInfo type) => type.GetCustomAttribute()?.ClassName; } } diff --git a/Parse/Management/ObjectSubclassingController.cs b/Parse/Management/ObjectSubclassingController.cs index 0f325412..fef91707 100644 --- a/Parse/Management/ObjectSubclassingController.cs +++ b/Parse/Management/ObjectSubclassingController.cs @@ -6,50 +6,43 @@ namespace Parse.Core.Internal { - internal class ObjectSubclassingController : IObjectSubclassingController + internal class ObjectSubclassingController : IParseObjectClassController { - // Class names starting with _ are documented to be reserved. Use this one - // here to allow us to 'inherit' certain properties. - private static readonly string parseObjectClassName = "_ParseObject"; + // Class names starting with _ are documented to be reserved. Use this one here to allow us to "inherit" certain properties. + static string ReservedParseObjectClassName { get; } = "_ParseObject"; - private readonly ReaderWriterLockSlim mutex; - private readonly IDictionary registeredSubclasses; - private Dictionary registerActions; + ReaderWriterLockSlim Mutex { get; } = new ReaderWriterLockSlim { }; - public ObjectSubclassingController() - { - mutex = new ReaderWriterLockSlim(); - registeredSubclasses = new Dictionary(); - registerActions = new Dictionary(); + IDictionary RegisteredSubclasses { get; } = new Dictionary { }; - // Register the ParseObject subclass, so we get access to the ACL, - // objectId, and other ParseFieldName properties. - RegisterSubclass(typeof(ParseObject)); - } + Dictionary RegisterActions { get; set; } = new Dictionary { }; + + public ObjectSubclassingController() => AddValid(typeof(ParseObject)); - public string GetClassName(Type type) => type == typeof(ParseObject) ? parseObjectClassName : ObjectSubclassInfo.GetClassName(type.GetTypeInfo()); + public string GetClassName(Type type) => type == typeof(ParseObject) ? ReservedParseObjectClassName : type.GetParseClassName(); public Type GetType(string className) { - mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo info); - mutex.ExitReadLock(); + Mutex.EnterReadLock(); + RegisteredSubclasses.TryGetValue(className, out ObjectSubclassInfo info); + Mutex.ExitReadLock(); return info?.TypeInfo.AsType(); } - public bool IsTypeValid(string className, Type type) + public bool GetClassMatch(string className, Type type) { - mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo subclassInfo); - mutex.ExitReadLock(); + Mutex.EnterReadLock(); + RegisteredSubclasses.TryGetValue(className, out ObjectSubclassInfo subclassInfo); + Mutex.ExitReadLock(); return subclassInfo == null ? type == typeof(ParseObject) : subclassInfo.TypeInfo == type.GetTypeInfo(); } - public void RegisterSubclass(Type type) + public void AddValid(Type type) { TypeInfo typeInfo = type.GetTypeInfo(); + if (!typeof(ParseObject).GetTypeInfo().IsAssignableFrom(typeInfo)) { throw new ArgumentException("Cannot register a type that is not a subclass of ParseObject"); @@ -62,9 +55,10 @@ public void RegisterSubclass(Type type) // 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)) + Mutex.EnterWriteLock(); + + if (RegisteredSubclasses.TryGetValue(className, out ObjectSubclassInfo previousInfo)) { if (typeInfo.IsAssignableFrom(previousInfo.TypeInfo)) { @@ -79,73 +73,76 @@ public void RegisterSubclass(Type type) } else { - throw new ArgumentException( - "Tried to register both " + previousInfo.TypeInfo.FullName + " and " + typeInfo.FullName + - " as the ParseObject subclass of " + className + ". Cannot determine the right class " + - "to use because neither inherits from the other." - ); + throw new ArgumentException($"Tried to register both {previousInfo.TypeInfo.FullName} and {typeInfo.FullName} as the ParseObject 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); + RegisteredSubclasses[className] = new ObjectSubclassInfo(type, constructor); } finally { - mutex.ExitWriteLock(); + Mutex.ExitWriteLock(); } - - mutex.EnterReadLock(); - registerActions.TryGetValue(className, out Action toPerform); - mutex.ExitReadLock(); + Mutex.EnterReadLock(); + RegisterActions.TryGetValue(className, out Action toPerform); + Mutex.ExitReadLock(); toPerform?.Invoke(); } - public void UnregisterSubclass(Type type) + public void RemoveClass(Type type) { - mutex.EnterWriteLock(); - registeredSubclasses.Remove(GetClassName(type)); - mutex.ExitWriteLock(); + Mutex.EnterWriteLock(); + RegisteredSubclasses.Remove(GetClassName(type)); + Mutex.ExitWriteLock(); } public void AddRegisterHook(Type t, Action action) { - mutex.EnterWriteLock(); - registerActions.Add(GetClassName(t), action); - mutex.ExitWriteLock(); + Mutex.EnterWriteLock(); + RegisterActions.Add(GetClassName(t), action); + Mutex.ExitWriteLock(); } public ParseObject Instantiate(string className) { + Mutex.EnterReadLock(); + RegisteredSubclasses.TryGetValue(className, out ObjectSubclassInfo info); + Mutex.ExitReadLock(); - mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo info); - mutex.ExitReadLock(); - - return info != null - ? info.Instantiate() - : new ParseObject(className); + return info is { } ? info.Instantiate() : new ParseObject(className); } public IDictionary GetPropertyMappings(string className) { - mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo info); - if (info == null) + Mutex.EnterReadLock(); + RegisteredSubclasses.TryGetValue(className, out ObjectSubclassInfo info); + + if (info is null) { - registeredSubclasses.TryGetValue(parseObjectClassName, out info); + RegisteredSubclasses.TryGetValue(ReservedParseObjectClassName, out info); } - mutex.ExitReadLock(); + Mutex.ExitReadLock(); return info.PropertyMappings; } + // ALTERNATE NAME: AddObject, AddType, AcknowledgeType, CatalogType + + public void AddIntrinsic() + { + AddValid(typeof(ParseUser)); + AddValid(typeof(ParseRole)); + AddValid(typeof(ParseSession)); + AddValid(typeof(ParseInstallation)); + } } } diff --git a/Parse/Management/ParseCommand.cs b/Parse/Management/ParseCommand.cs index 42e75a6e..0ab81e84 100644 --- a/Parse/Management/ParseCommand.cs +++ b/Parse/Management/ParseCommand.cs @@ -10,47 +10,24 @@ namespace Parse.Core.Internal { /// - /// ParseCommand is an with pre-populated + /// ParseCommand is an with pre-populated /// headers. /// - public class ParseCommand : HttpRequest + public class ParseCommand : WebRequest { 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); - } + get => base.Data ??= DataObject is { } ? new MemoryStream(Encoding.UTF8.GetBytes(Json.Encode(DataObject))) : default; set => base.Data = value; } - public ParseCommand(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 ParseCommand(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 ParseCommand(string relativeUri, - string method, - string sessionToken = null, - IList> headers = null, - Stream stream = null, - string contentType = null) + public ParseCommand(string relativeUri, string method, string sessionToken = null, IList> headers = null, Stream stream = null, string contentType = null) { - Uri = new Uri(new Uri(ParseClient.Configuration.ServerURI), relativeUri); + Path = relativeUri; Method = method; Data = stream; Headers = new List>(headers ?? Enumerable.Empty>()); @@ -59,6 +36,7 @@ public ParseCommand(string relativeUri, { Headers.Add(new KeyValuePair("X-Parse-Session-Token", sessionToken)); } + if (!String.IsNullOrEmpty(contentType)) { Headers.Add(new KeyValuePair("Content-Type", contentType)); @@ -67,7 +45,8 @@ public ParseCommand(string relativeUri, public ParseCommand(ParseCommand other) { - Uri = other.Uri; + Resource = other.Resource; + Path = other.Path; Method = other.Method; DataObject = other.DataObject; Headers = new List>(other.Headers); diff --git a/Parse/Management/ParseCommandRunner.cs b/Parse/Management/ParseCommandRunner.cs index 34c40adc..f241ff8a 100644 --- a/Parse/Management/ParseCommandRunner.cs +++ b/Parse/Management/ParseCommandRunner.cs @@ -1,6 +1,7 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; +using System.Linq; using System.Collections.Generic; using System.Net; using System.Threading; @@ -19,17 +20,21 @@ public class ParseCommandRunner : IParseCommandRunner IWebClient WebClient { get; } IParseInstallationController InstallationController { get; } IMetadataController MetadataController { get; } + IServerConnectionData ServerConnectionData { get; } + Lazy UserController { get; } /// /// Creates a new Parse SDK command runner. /// /// The implementation instance to use. - /// The implementation instance to use. - public ParseCommandRunner(IWebClient webClient, IParseInstallationController installationIdController, IMetadataController metadataController) + /// The implementation instance to use. + public ParseCommandRunner(IWebClient webClient, IParseInstallationController installationController, IMetadataController metadataController, IServerConnectionData serverConnectionData, Lazy userController) { WebClient = webClient; - InstallationController = installationIdController; + InstallationController = installationController; MetadataController = metadataController; + ServerConnectionData = serverConnectionData; + UserController = userController; } /// @@ -40,101 +45,100 @@ public ParseCommandRunner(IWebClient webClient, IParseInstallationController ins /// An instance to push download progress data to. /// An asynchronous operation cancellation token that dictates if and when the operation should be cancelled. /// - public Task>> RunCommandAsync(ParseCommand command, IProgress uploadProgress = null, IProgress downloadProgress = null, CancellationToken cancellationToken = default) => PrepareCommand(command).ContinueWith(commandTask => + public Task>> RunCommandAsync(ParseCommand command, IProgress uploadProgress = null, IProgress downloadProgress = null, CancellationToken cancellationToken = default) => PrepareCommand(command).ContinueWith(commandTask => WebClient.ExecuteAsync(commandTask.Result, uploadProgress, downloadProgress, cancellationToken).OnSuccess(task => { - return WebClient.ExecuteAsync(commandTask.Result, uploadProgress, downloadProgress, cancellationToken).OnSuccess(t => + cancellationToken.ThrowIfCancellationRequested(); + + Tuple response = task.Result; + string content = response.Item2; + int responseCode = (int) response.Item1; + + if (responseCode >= 500) + { + // Server error, return InternalServerError. + + throw new ParseFailureException(ParseFailureException.ErrorCode.InternalServerError, response.Item2); + } + else if (content is { }) { - cancellationToken.ThrowIfCancellationRequested(); + IDictionary contentJson = default; - Tuple response = t.Result; - string contentString = response.Item2; - int responseCode = (int) response.Item1; - if (responseCode >= 500) + try { - // Server error, return InternalServerError. - throw new ParseException(ParseException.ErrorCode.InternalServerError, response.Item2); + // TODO: Newer versions of Parse Server send the failure results back as HTML. + + contentJson = content.StartsWith("[") ? new Dictionary { ["results"] = Json.Parse(content) } : Json.Parse(content) as IDictionary; } - else if (contentString != null) + catch (Exception e) { - IDictionary contentJson = null; - try - { - // TODO: Newer versions of Parse Server send the failure results back as HTML. - contentJson = contentString.StartsWith("[") - ? new Dictionary { ["results"] = Json.Parse(contentString) } - : Json.Parse(contentString) as IDictionary; - } - catch (Exception e) - { - throw new ParseException(ParseException.ErrorCode.OtherCause, "Invalid or alternatively-formatted response recieved from server.", e); - } - if (responseCode < 200 || responseCode > 299) - { - int code = (int) (contentJson.ContainsKey("code") ? (long) contentJson["code"] : (int) ParseException.ErrorCode.OtherCause); - string error = contentJson.ContainsKey("error") ? - contentJson["error"] as string : - contentString; - throw new ParseException((ParseException.ErrorCode) code, error); - } - return new Tuple>(response.Item1, contentJson); + throw new ParseFailureException(ParseFailureException.ErrorCode.OtherCause, "Invalid or alternatively-formatted response recieved from server.", e); } - return new Tuple>(response.Item1, null); - }); - }).Unwrap(); - private const string revocableSessionTokentrueValue = "1"; - private Task PrepareCommand(ParseCommand command) + if (responseCode < 200 || responseCode > 299) + { + throw new ParseFailureException(contentJson.ContainsKey("code") ? (ParseFailureException.ErrorCode) contentJson["code"] : ParseFailureException.ErrorCode.OtherCause, contentJson.ContainsKey("error") ? contentJson["error"] as string : content); + } + + return new Tuple>(response.Item1, contentJson); + } + return new Tuple>(response.Item1, null); + })).Unwrap(); + + Task PrepareCommand(ParseCommand command) { - ParseCommand newCommand = new ParseCommand(command); + ParseCommand newCommand = new ParseCommand(command) + { + Resource = ServerConnectionData.ServerURI + }; - Task installationIdTask = InstallationController.GetAsync().ContinueWith(t => + Task installationIdFetchTask = InstallationController.GetAsync().ContinueWith(task => { - newCommand.Headers.Add(new KeyValuePair("X-Parse-Installation-Id", t.Result.ToString())); + newCommand.Headers.Add(new KeyValuePair("X-Parse-Installation-Id", task.Result.ToString())); + return newCommand; }); - // TODO (richardross): Inject configuration instead of using shared static here. - Configuration configuration = ParseClient.Configuration; - newCommand.Headers.Add(new KeyValuePair("X-Parse-Application-Id", configuration.ApplicationID)); + newCommand.Headers.Add(new KeyValuePair("X-Parse-Application-Id", ServerConnectionData.ApplicationID)); newCommand.Headers.Add(new KeyValuePair("X-Parse-Client-Version", ParseClient.Version.ToString())); - if (configuration.AuxiliaryHeaders != null) + if (ServerConnectionData.Headers != null) { - foreach (KeyValuePair header in configuration.AuxiliaryHeaders) + foreach (KeyValuePair header in ServerConnectionData.Headers) { newCommand.Headers.Add(header); } } - if (!String.IsNullOrEmpty(MetadataController.HostVersioningData.BuildVersion)) + if (!String.IsNullOrEmpty(MetadataController.HostManifestData.Version)) { - newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Build-Version", MetadataController.HostVersioningData.BuildVersion)); + newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Build-Version", MetadataController.HostManifestData.Version)); } - if (!String.IsNullOrEmpty(MetadataController.HostVersioningData.DisplayVersion)) + + if (!String.IsNullOrEmpty(MetadataController.HostManifestData.ShortVersion)) { - newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Display-Version", MetadataController.HostVersioningData.DisplayVersion)); + newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Display-Version", MetadataController.HostManifestData.ShortVersion)); } - if (!String.IsNullOrEmpty(MetadataController.HostVersioningData.HostOSVersion)) + + if (!String.IsNullOrEmpty(MetadataController.EnvironmentData.OSVersion)) { - newCommand.Headers.Add(new KeyValuePair("X-Parse-OS-Version", MetadataController.HostVersioningData.HostOSVersion)); + newCommand.Headers.Add(new KeyValuePair("X-Parse-OS-Version", MetadataController.EnvironmentData.OSVersion)); } - if (!String.IsNullOrEmpty(configuration.MasterKey)) + if (!String.IsNullOrEmpty(ServerConnectionData.MasterKey)) { - newCommand.Headers.Add(new KeyValuePair("X-Parse-Master-Key", configuration.MasterKey)); + newCommand.Headers.Add(new KeyValuePair("X-Parse-Master-Key", ServerConnectionData.MasterKey)); } else { - newCommand.Headers.Add(new KeyValuePair("X-Parse-Windows-Key", configuration.Key)); + newCommand.Headers.Add(new KeyValuePair("X-Parse-Windows-Key", ServerConnectionData.Key)); } - // TODO (richardross): Inject this instead of using static here. - if (ParseUser.IsRevocableSessionEnabled) + if (UserController.Value.RevocableSessionEnabled) { - newCommand.Headers.Add(new KeyValuePair("X-Parse-Revocable-Session", revocableSessionTokentrueValue)); + newCommand.Headers.Add(new KeyValuePair("X-Parse-Revocable-Session", "1")); } - return installationIdTask; + return installationIdFetchTask; } } } diff --git a/Parse/Management/ParseCorePlugins.cs b/Parse/Management/ParseCorePlugins.cs index ad40e7d1..8f65f230 100644 --- a/Parse/Management/ParseCorePlugins.cs +++ b/Parse/Management/ParseCorePlugins.cs @@ -1,286 +1,283 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. +//// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Abstractions.Library; -using Parse.Abstractions.Management; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Library; +//using Parse.Abstractions.Library; +//using Parse.Abstractions.Management; +//using Parse.Common.Internal; +//using Parse.Core.Internal; +//using Parse.Library; -#if DEBUG -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Parse.Test")] -#endif +//namespace Parse.Management +//{ +// public class ParseCorePlugins : IParseCorePlugins +// { +// //static object Mutex { get; } = new object { }; +// //static IParseCorePlugins instance; -namespace Parse.Management -{ - public class ParseCorePlugins : IParseCorePlugins - { - static object Mutex { get; } = new object(); - static IParseCorePlugins instance; +// //public static IParseCorePlugins Instance +// //{ +// // get +// // { +// // lock (Mutex) +// // return instance ??= new ParseCorePlugins { }; +// // } +// // set +// // { +// // lock (Mutex) +// // instance = value; +// // } +// //} - public static IParseCorePlugins Instance - { - get - { - lock (Mutex) - return instance ??= new ParseCorePlugins(); - } - set - { - lock (Mutex) - instance = value; - } - } +// object InstanceMutex { get; } = new object { }; - private readonly object mutex = new object(); +// #region Server Controllers - #region Server Controllers +// IMetadataController metadataController; - IMetadataController metadataController; +// IWebClient webClient; +// IParseCommandRunner commandRunner; +// IStorageController storageController; - IWebClient httpClient; - IParseCommandRunner commandRunner; - IStorageController storageController; +// IParseCloudCodeController cloudCodeController; +// IParseConfigController configController; +// IParseFileController fileController; +// IParseObjectController objectController; +// IParseQueryController queryController; +// IParseSessionController sessionController; +// IParseUserController userController; +// IObjectSubclassingController subclassingController; - IParseCloudCodeController cloudCodeController; - IParseConfigController configController; - IParseFileController fileController; - IParseObjectController objectController; - IParseQueryController queryController; - IParseSessionController sessionController; - IParseUserController userController; - IObjectSubclassingController subclassingController; +// #endregion - #endregion +// #region Current Instance Controller - #region Current Instance Controller +// IParseCurrentUserController currentUserController; +// IParseInstallationController installationController; - IParseCurrentUserController currentUserController; - IParseInstallationController installationController; +// #endregion - #endregion +// public void Reset() +// { +// lock (InstanceMutex) +// { +// MetadataController = null; - public void Reset() - { - lock (mutex) - { - MetadataController = null; - WebClient = null; - CommandRunner = null; - StorageController = null; +// WebClient = null; +// CommandRunner = null; +// StorageController = null; - CloudCodeController = null; - FileController = null; - ObjectController = null; - SessionController = null; - UserController = null; - SubclassingController = null; +// CloudCodeController = null; +// FileController = null; +// ObjectController = null; +// SessionController = null; +// UserController = null; +// SubclassingController = null; - CurrentUserController = null; - InstallationController = null; - } - } +// CurrentUserController = null; +// InstallationController = null; +// } +// } - public IMetadataController MetadataController - { - get - { - lock (mutex) - return metadataController ??= new MetadataController { }; - } - set - { - lock (mutex) - metadataController = value; - } - } +// public IMetadataController MetadataController +// { +// get +// { +// lock (InstanceMutex) +// return metadataController ??= new MetadataController { }; +// } +// set +// { +// lock (InstanceMutex) +// metadataController = value; +// } +// } - public IWebClient WebClient - { - get - { - lock (mutex) - return httpClient ??= new UniversalWebClient { }; - } - set - { - lock (mutex) - httpClient = value; - } - } +// public IWebClient WebClient +// { +// get +// { +// lock (InstanceMutex) +// return webClient ??= new UniversalWebClient { }; +// } +// set +// { +// lock (InstanceMutex) +// webClient = value; +// } +// } - public IParseCommandRunner CommandRunner - { - get - { - lock (mutex) - return commandRunner ??= new ParseCommandRunner(WebClient, InstallationController, MetadataController); - } - set - { - lock (mutex) - commandRunner = value; - } - } +// public IParseCommandRunner CommandRunner +// { +// get +// { +// lock (InstanceMutex) +// return commandRunner ??= new ParseCommandRunner(WebClient, InstallationController, MetadataController); +// } +// set +// { +// lock (InstanceMutex) +// commandRunner = value; +// } +// } - public IStorageController StorageController - { - get - { - lock (mutex) - return storageController ??= new StorageController(); - } - set - { - lock (mutex) - storageController = value; - } - } +// public IStorageController StorageController +// { +// get +// { +// lock (InstanceMutex) +// return storageController ??= new StorageController(); +// } +// set +// { +// lock (InstanceMutex) +// storageController = value; +// } +// } - public IParseCloudCodeController CloudCodeController - { - get - { - lock (mutex) - return cloudCodeController ??= new ParseCloudCodeController(CommandRunner); - } - set - { - lock (mutex) - cloudCodeController = value; - } - } +// public IParseCloudCodeController CloudCodeController +// { +// get +// { +// lock (InstanceMutex) +// return cloudCodeController ??= new ParseCloudCodeController(CommandRunner); +// } +// set +// { +// lock (InstanceMutex) +// cloudCodeController = value; +// } +// } - public IParseFileController FileController - { - get - { - lock (mutex) - return fileController ??= new ParseFileController(CommandRunner); - } - set - { - lock (mutex) - fileController = value; - } - } +// public IParseFileController FileController +// { +// get +// { +// lock (InstanceMutex) +// return fileController ??= new ParseFileController(CommandRunner); +// } +// set +// { +// lock (InstanceMutex) +// fileController = value; +// } +// } - public IParseConfigController ConfigController - { - get - { - lock (mutex) - return configController ?? (configController = new ParseConfigController(CommandRunner, StorageController)); - } - set - { - lock (mutex) - configController = value; - } - } +// public IParseConfigController ConfigController +// { +// get +// { +// lock (InstanceMutex) +// return configController ??= new ParseConfigController(CommandRunner, StorageController); +// } +// set +// { +// lock (InstanceMutex) +// configController = value; +// } +// } - public IParseObjectController ObjectController - { - get - { - lock (mutex) - return objectController ??= new ParseObjectController(CommandRunner); - } - set - { - lock (mutex) - objectController = value; - } - } +// public IParseObjectController ObjectController +// { +// get +// { +// lock (InstanceMutex) +// return objectController ??= new ParseObjectController(CommandRunner); +// } +// set +// { +// lock (InstanceMutex) +// objectController = value; +// } +// } - public IParseQueryController QueryController - { - get - { - lock (mutex) - return queryController ?? (queryController = new ParseQueryController(CommandRunner)); - } - set - { - lock (mutex) - queryController = value; - } - } +// public IParseQueryController QueryController +// { +// get +// { +// lock (InstanceMutex) +// return queryController ??= new ParseQueryController(CommandRunner); +// } +// set +// { +// lock (InstanceMutex) +// queryController = value; +// } +// } - public IParseSessionController SessionController - { - get - { - lock (mutex) - return sessionController ??= new ParseSessionController(CommandRunner); - } - set - { - lock (mutex) - sessionController = value; - } - } +// public IParseSessionController SessionController +// { +// get +// { +// lock (InstanceMutex) +// return sessionController ??= new ParseSessionController(CommandRunner); +// } +// set +// { +// lock (InstanceMutex) +// sessionController = value; +// } +// } - public IParseUserController UserController - { - get - { - lock (mutex) - return userController ??= new ParseUserController(CommandRunner); - } - set - { - lock (mutex) - userController = value; - } - } +// public IParseUserController UserController +// { +// get +// { +// lock (InstanceMutex) +// return userController ??= new ParseUserController(CommandRunner); +// } +// set +// { +// lock (InstanceMutex) +// userController = value; +// } +// } - public IParseCurrentUserController CurrentUserController - { - get - { - lock (mutex) - return currentUserController ??= new ParseCurrentUserController(StorageController); - } - set - { - lock (mutex) - currentUserController = value; - } - } +// public IParseCurrentUserController CurrentUserController +// { +// get +// { +// lock (InstanceMutex) +// return currentUserController ??= new ParseCurrentUserController(StorageController); +// } +// set +// { +// lock (InstanceMutex) +// currentUserController = value; +// } +// } - public IObjectSubclassingController SubclassingController - { - get - { - lock (mutex) - { - if (subclassingController == null) - { - subclassingController = new ObjectSubclassingController(); - subclassingController.AddRegisterHook(typeof(ParseUser), () => CurrentUserController.ClearFromMemory()); - } - return subclassingController; - } - } - set - { - lock (mutex) - subclassingController = value; - } - } +// public IObjectSubclassingController SubclassingController +// { +// get +// { +// lock (InstanceMutex) +// { +// if (subclassingController == null) +// { +// subclassingController = new ObjectSubclassingController(); +// subclassingController.AddRegisterHook(typeof(ParseUser), () => CurrentUserController.ClearFromMemory()); +// } +// return subclassingController; +// } +// } +// set +// { +// lock (InstanceMutex) +// subclassingController = value; +// } +// } - public IParseInstallationController InstallationController - { - get - { - lock (mutex) - return installationController ??= new ParseInstallationController(StorageController); - } - set - { - lock (mutex) - installationController = value; - } - } - } -} +// public IParseInstallationController InstallationController +// { +// get +// { +// lock (InstanceMutex) +// return installationController ??= new ParseInstallationController(StorageController); +// } +// set +// { +// lock (InstanceMutex) +// installationController = value; +// } +// } +// } +//} diff --git a/Parse/Management/ParseCurrentUserController.cs b/Parse/Management/ParseCurrentUserController.cs index 5b8ddbee..1b6ba7e7 100644 --- a/Parse/Management/ParseCurrentUserController.cs +++ b/Parse/Management/ParseCurrentUserController.cs @@ -9,163 +9,119 @@ namespace Parse.Core.Internal { +#warning This class needs to be rewritten (PCuUsC). + public class ParseCurrentUserController : IParseCurrentUserController { - private readonly object mutex = new object(); - private readonly TaskQueue taskQueue = new TaskQueue(); + object Mutex { get; } = new object { }; + + TaskQueue TaskQueue { get; } = new TaskQueue { }; + + IStorageController StorageController { get; } + + IParseObjectClassController ClassController { get; } - private IStorageController storageController; + IParseDataDecoder Decoder { get; } - public ParseCurrentUserController(IStorageController storageController) => this.storageController = storageController; + public ParseCurrentUserController(IStorageController storageController, IParseObjectClassController classController, IParseDataDecoder decoder) => (StorageController, ClassController, Decoder) = (storageController, classController, decoder); - private ParseUser currentUser; + ParseUser currentUser; public ParseUser CurrentUser { get { - lock (mutex) + lock (Mutex) { return currentUser; } } set { - lock (mutex) + lock (Mutex) { currentUser = value; } } } - public Task SetAsync(ParseUser user, CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - Task saveTask = null; - if (user == null) - { - saveTask = storageController - .LoadAsync() - .OnSuccess(t => t.Result.RemoveAsync("CurrentUser")) - .Unwrap(); - } - else - { - // TODO (hallucinogen): we need to use ParseCurrentCoder instead of this janky encoding - IDictionary data = user.ServerDataToJSONObjectForSerialization(); - data["objectId"] = user.ObjectId; - if (user.CreatedAt != null) - { - data["createdAt"] = user.CreatedAt.Value.ToString(ParseClient.DateFormatStrings.First(), - CultureInfo.InvariantCulture); - } - if (user.UpdatedAt != null) - { - data["updatedAt"] = user.UpdatedAt.Value.ToString(ParseClient.DateFormatStrings.First(), - CultureInfo.InvariantCulture); - } - - saveTask = storageController - .LoadAsync() - .OnSuccess(t => t.Result.AddAsync("CurrentUser", Json.Encode(data))) - .Unwrap(); - } - CurrentUser = user; - - return saveTask; - }).Unwrap(); - }, cancellationToken); - - public Task GetAsync(CancellationToken cancellationToken) + public Task SetAsync(ParseUser user, CancellationToken cancellationToken) => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => { - ParseUser cachedCurrent; + Task saveTask = default; - lock (mutex) + if (user is null) { - cachedCurrent = CurrentUser; + saveTask = StorageController.LoadAsync().OnSuccess(task => task.Result.RemoveAsync(nameof(CurrentUser))).Unwrap(); } - - if (cachedCurrent != null) + else { - return Task.FromResult(cachedCurrent); - } + // TODO (hallucinogen): we need to use ParseCurrentCoder instead of this janky encoding - return taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => + IDictionary data = user.ServerDataToJSONObjectForSerialization(); + data["objectId"] = user.ObjectId; + + if (user.CreatedAt != null) { - return storageController.LoadAsync().OnSuccess(t => - { - t.Result.TryGetValue("CurrentUser", out object temp); - string userDataString = temp as string; - ParseUser user = null; - if (userDataString != null) - { - IDictionary userData = Json.Parse(userDataString) as IDictionary; - IObjectState state = ParseObjectCoder.Instance.Decode(userData, ParseDecoder.Instance); - user = ParseObject.FromState(state, "_User"); - } - - CurrentUser = user; - return user; - }); - }).Unwrap(); - }, cancellationToken); - } + data["createdAt"] = user.CreatedAt.Value.ToString(ParseClient.DateFormatStrings.First(), CultureInfo.InvariantCulture); + } + if (user.UpdatedAt != null) + { + data["updatedAt"] = user.UpdatedAt.Value.ToString(ParseClient.DateFormatStrings.First(), CultureInfo.InvariantCulture); + } + + saveTask = StorageController.LoadAsync().OnSuccess(task => task.Result.AddAsync(nameof(CurrentUser), Json.Encode(data))).Unwrap(); + } + + CurrentUser = user; + return saveTask; + }).Unwrap(), cancellationToken); - public Task ExistsAsync(CancellationToken cancellationToken) + public Task GetAsync(CancellationToken cancellationToken) { - if (CurrentUser != null) + ParseUser cachedCurrent; + + lock (Mutex) { - return Task.FromResult(true); + cachedCurrent = CurrentUser; } - return taskQueue.Enqueue(toAwait => + return cachedCurrent is { } ? Task.FromResult(cachedCurrent) : TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => StorageController.LoadAsync().OnSuccess(task => { - return toAwait.ContinueWith(_ => - storageController.LoadAsync().OnSuccess(t => t.Result.ContainsKey("CurrentUser")) - ).Unwrap(); - }, cancellationToken); + task.Result.TryGetValue(nameof(CurrentUser), out object data); + ParseUser user = default; + + if (data is string { } serialization) + { + user = ClassController.GenerateObjectFromState(ParseObjectCoder.Instance.Decode(Json.Parse(serialization) as IDictionary, Decoder), "_User"); + } + + return CurrentUser = user; + })).Unwrap(), cancellationToken); } + public Task ExistsAsync(CancellationToken cancellationToken) => CurrentUser is { } ? Task.FromResult(true) : TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => StorageController.LoadAsync().OnSuccess(t => t.Result.ContainsKey(nameof(CurrentUser)))).Unwrap(), cancellationToken); + public bool IsCurrent(ParseUser user) { - lock (mutex) + lock (Mutex) { return CurrentUser == user; } } - public void ClearFromMemory() => CurrentUser = null; + public void ClearFromMemory() => CurrentUser = default; public void ClearFromDisk() { - lock (mutex) + lock (Mutex) { ClearFromMemory(); - taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - return storageController.LoadAsync().OnSuccess(t => t.Result.RemoveAsync("CurrentUser")); - }).Unwrap().Unwrap(); - }, CancellationToken.None); + TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => StorageController.LoadAsync().OnSuccess(t => t.Result.RemoveAsync(nameof(CurrentUser)))).Unwrap().Unwrap(), CancellationToken.None); } } - public Task GetCurrentSessionTokenAsync(CancellationToken cancellationToken) => GetAsync(cancellationToken).OnSuccess(t => - { - ParseUser user = t.Result; - return user == null ? null : user.SessionToken; - }); - - public Task LogOutAsync(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => GetAsync(cancellationToken)).Unwrap().OnSuccess(t => - { - ClearFromDisk(); - }); - }, cancellationToken); + public Task GetCurrentSessionTokenAsync(CancellationToken cancellationToken) => GetAsync(cancellationToken).OnSuccess(task => task.Result?.SessionToken); + + public Task LogOutAsync(CancellationToken cancellationToken) => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => GetAsync(cancellationToken)).Unwrap().OnSuccess(t => ClearFromDisk()), cancellationToken); } } diff --git a/Parse/Management/ParseObjectController.cs b/Parse/Management/ParseObjectController.cs index f083a86f..0017df36 100644 --- a/Parse/Management/ParseObjectController.cs +++ b/Parse/Management/ParseObjectController.cs @@ -12,212 +12,149 @@ namespace Parse.Core.Internal { public class ParseObjectController : IParseObjectController { - private readonly IParseCommandRunner commandRunner; + IParseCommandRunner CommandRunner { get; } - public ParseObjectController(IParseCommandRunner commandRunner) => this.commandRunner = commandRunner; + IParseDataDecoder Decoder { get; } - public Task FetchAsync(IObjectState state, - string sessionToken, - CancellationToken cancellationToken) + public ParseObjectController(IParseCommandRunner commandRunner, IParseDataDecoder decoder) => (CommandRunner, Decoder) = (commandRunner, decoder); + + public Task FetchAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken) { - ParseCommand command = new ParseCommand(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 ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); - }); + ParseCommand command = new ParseCommand($"classes/{Uri.EscapeDataString(state.ClassName)}/{Uri.EscapeDataString(state.ObjectId)}", method: "GET", sessionToken: sessionToken, data: default); + return CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder)); } - public Task SaveAsync(IObjectState state, - IDictionary operations, - string sessionToken, - CancellationToken cancellationToken) + public Task SaveAsync(IObjectState state, IDictionary operations, string sessionToken, CancellationToken cancellationToken) { - IDictionary objectJSON = ParseObject.ToJSONObjectForSaving(operations); - - ParseCommand command = new ParseCommand((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 => - { - IObjectState serverState = ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); - serverState = serverState.MutatedClone(mutableClone => - { - mutableClone.IsNew = t.Result.Item1 == System.Net.HttpStatusCode.Created; - }); - return serverState; - }); + ParseCommand command = new ParseCommand(state.ObjectId == null ? $"classes/{Uri.EscapeDataString(state.ClassName)}" : $"classes/{Uri.EscapeDataString(state.ClassName)}/{state.ObjectId}", method: state.ObjectId == null ? "POST" : "PUT", sessionToken: sessionToken, data: operations.GenerateJSONObjectForSaving()); + return CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder).MutatedClone(mutableClone => mutableClone.IsNew = task.Result.Item1 == System.Net.HttpStatusCode.Created)); } - public IList> SaveAllAsync(IList states, - IList> operationsList, - string sessionToken, - CancellationToken cancellationToken) + public IList> SaveAllAsync(IList states, IList> operationsList, string sessionToken, CancellationToken cancellationToken) { - - List requests = states - .Zip(operationsList, (item, ops) => new ParseCommand( - 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: ParseObject.ToJSONObjectForSaving(ops))) - .ToList(); + List requests = states.Zip(operationsList, (item, ops) => new ParseCommand(item.ObjectId is null ? $"classes/{Uri.EscapeDataString(item.ClassName)}" : $"classes/{Uri.EscapeDataString(item.ClassName)}/{Uri.EscapeDataString(item.ObjectId)}", method: item.ObjectId == null ? "POST" : "PUT", data: ops.GenerateJSONObjectForSaving())).ToList(); IList>> batchTasks = ExecuteBatchRequests(requests, sessionToken, cancellationToken); - List> stateTasks = new List>(); + List> stateTasks = new List> { }; + foreach (Task> task in batchTasks) { - stateTasks.Add(task.OnSuccess(t => - { - return ParseObjectCoder.Instance.Decode(t.Result, ParseDecoder.Instance); - })); + stateTasks.Add(task.OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result, Decoder))); } return stateTasks; } - public Task DeleteAsync(IObjectState state, - string sessionToken, - CancellationToken cancellationToken) - { - ParseCommand command = new ParseCommand(String.Format("classes/{0}/{1}", - state.ClassName, state.ObjectId), - method: "DELETE", - sessionToken: sessionToken, - data: null); + public Task DeleteAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand($"classes/{state.ClassName}/{state.ObjectId}", method: "DELETE", sessionToken: sessionToken, data: null), cancellationToken: cancellationToken); - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken); - } + public IList DeleteAllAsync(IList states, string sessionToken, CancellationToken cancellationToken) => ExecuteBatchRequests(states.Where(item => item.ObjectId != null).Select(item => new ParseCommand(String.Format("classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), Uri.EscapeDataString(item.ObjectId)), method: "DELETE", data: null)).ToList(), sessionToken, cancellationToken).Cast().ToList(); - public IList DeleteAllAsync(IList states, - string sessionToken, - CancellationToken cancellationToken) - { - List requests = states - .Where(item => item.ObjectId != null) - .Select(item => new ParseCommand( - 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(); - } + int MaximumBatchSize { get; } = 50; // 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) + + internal IList>> ExecuteBatchRequests(IList requests, string sessionToken, CancellationToken cancellationToken) { List>> tasks = new List>>(); int batchSize = requests.Count; IEnumerable remaining = requests; + while (batchSize > MaximumBatchSize) { List process = remaining.Take(MaximumBatchSize).ToList(); - remaining = remaining.Skip(MaximumBatchSize); + remaining = remaining.Skip(MaximumBatchSize); tasks.AddRange(ExecuteBatchRequest(process, sessionToken, cancellationToken)); - batchSize = remaining.Count(); } - tasks.AddRange(ExecuteBatchRequest(remaining.ToList(), sessionToken, cancellationToken)); + tasks.AddRange(ExecuteBatchRequest(remaining.ToList(), sessionToken, cancellationToken)); return tasks; } - private IList>> ExecuteBatchRequest(IList requests, - string sessionToken, - CancellationToken cancellationToken) + IList>> ExecuteBatchRequest(IList requests, string sessionToken, CancellationToken cancellationToken) { - List>> tasks = new List>>(); int batchSize = requests.Count; - List>> tcss = new List>>(); + + List>> tasks = new List>> { }; + List>> completionSources = new List>> { }; + for (int i = 0; i < batchSize; ++i) { TaskCompletionSource> tcs = new TaskCompletionSource>(); - tcss.Add(tcs); + + completionSources.Add(tcs); tasks.Add(tcs.Task); } - List encodedRequests = requests.Select(r => + List encodedRequests = requests.Select(request => { - Dictionary results = new Dictionary { - { "method", r.Method }, - { "path", r.Uri.AbsolutePath }, - }; + Dictionary results = new Dictionary + { + ["method"] = request.Method, + ["path"] = request.Target.AbsolutePath, + }; - if (r.DataObject != null) + if (request.DataObject != null) { - results["body"] = r.DataObject; + results["body"] = request.DataObject; } + return results; }).Cast().ToList(); - ParseCommand command = new ParseCommand("batch", - method: "POST", - sessionToken: sessionToken, - data: new Dictionary { { "requests", encodedRequests } }); - commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(t => + ParseCommand command = new ParseCommand("batch", method: "POST", sessionToken: sessionToken, data: new Dictionary { [nameof(requests)] = encodedRequests }); + + CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(task => { - if (t.IsFaulted || t.IsCanceled) + if (task.IsFaulted || task.IsCanceled) { - foreach (TaskCompletionSource> tcs in tcss) + foreach (TaskCompletionSource> tcs in completionSources) { - if (t.IsFaulted) + if (task.IsFaulted) { - tcs.TrySetException(t.Exception); + tcs.TrySetException(task.Exception); } - else if (t.IsCanceled) + else if (task.IsCanceled) { tcs.TrySetCanceled(); } } + return; } - IList resultsArray = Conversion.As>(t.Result.Item2["results"]); + IList resultsArray = Conversion.As>(task.Result.Item2["results"]); int resultLength = resultsArray.Count; + if (resultLength != batchSize) { - foreach (TaskCompletionSource> tcs in tcss) + foreach (TaskCompletionSource> completionSource in completionSources) { - tcs.TrySetException(new InvalidOperationException( - "Batch command result count expected: " + batchSize + " but was: " + resultLength + ".")); + completionSource.TrySetException(new InvalidOperationException($"Batch command result count expected: {batchSize} but was: {resultLength}.")); } + return; } for (int i = 0; i < batchSize; ++i) { Dictionary result = resultsArray[i] as Dictionary; - TaskCompletionSource> tcs = tcss[i]; + TaskCompletionSource> target = completionSources[i]; if (result.ContainsKey("success")) { - tcs.TrySetResult(result["success"] as IDictionary); + target.TrySetResult(result["success"] as IDictionary); } else if (result.ContainsKey("error")) { IDictionary error = result["error"] as IDictionary; - long errorCode = (long) error["code"]; - tcs.TrySetException(new ParseException((ParseException.ErrorCode) errorCode, error["error"] as string)); + target.TrySetException(new ParseFailureException((ParseFailureException.ErrorCode) (long) error["code"], error[nameof(error)] as string)); } else { - tcs.TrySetException(new InvalidOperationException( - "Invalid batch command response.")); + target.TrySetException(new InvalidOperationException("Invalid batch command response.")); } } }); diff --git a/Parse/Management/ParseUserController.cs b/Parse/Management/ParseUserController.cs index 7b3c5c4d..8d2b6d9d 100644 --- a/Parse/Management/ParseUserController.cs +++ b/Parse/Management/ParseUserController.cs @@ -10,103 +10,32 @@ namespace Parse.Core.Internal { public class ParseUserController : IParseUserController { - private readonly IParseCommandRunner commandRunner; + IParseCommandRunner CommandRunner { get; } - public ParseUserController(IParseCommandRunner commandRunner) => this.commandRunner = commandRunner; + IParseDataDecoder Decoder { get; } - public Task SignUpAsync(IObjectState state, - IDictionary operations, - CancellationToken cancellationToken) - { - IDictionary objectJSON = ParseObject.ToJSONObjectForSaving(operations); - - ParseCommand command = new ParseCommand("classes/_User", - method: "POST", - data: objectJSON); + public bool RevocableSessionEnabled { get; set; } - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - IObjectState serverState = ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); - serverState = serverState.MutatedClone(mutableClone => - { - mutableClone.IsNew = true; - }); - return serverState; - }); - } + public object RevocableSessionEnabledMutex { get; } = new object { }; - public Task LogInAsync(string username, - string password, - CancellationToken cancellationToken) - { - Dictionary data = new Dictionary{ - {"username", username}, - {"password", password} - }; + public ParseUserController(IParseCommandRunner commandRunner, IParseDataDecoder decoder) => (CommandRunner, Decoder) = (commandRunner, decoder); - ParseCommand command = new ParseCommand(String.Format("login?{0}", ParseClient.BuildQueryString(data)), - method: "GET", - data: null); + public Task SignUpAsync(IObjectState state, IDictionary operations, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand("classes/_User", method: "POST", data: operations.GenerateJSONObjectForSaving()), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder).MutatedClone(mutableClone => mutableClone.IsNew = true)); - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - IObjectState serverState = ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); - serverState = serverState.MutatedClone(mutableClone => - { - mutableClone.IsNew = t.Result.Item1 == System.Net.HttpStatusCode.Created; - }); - return serverState; - }); - } + public Task LogInAsync(string username, string password, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand($"login?{ParseClient.BuildQueryString(new Dictionary { [nameof(username)] = username, [nameof(password)] = password })}", method: "GET", data: null), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder).MutatedClone(mutableClone => mutableClone.IsNew = task.Result.Item1 == System.Net.HttpStatusCode.Created)); - public Task LogInAsync(string authType, - IDictionary data, - CancellationToken cancellationToken) + public Task LogInAsync(string authType, IDictionary data, CancellationToken cancellationToken) { Dictionary authData = new Dictionary { [authType] = data }; - ParseCommand command = new ParseCommand("users", - method: "POST", - data: new Dictionary { - {"authData", authData} - }); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - IObjectState serverState = ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); - serverState = serverState.MutatedClone(mutableClone => - { - mutableClone.IsNew = t.Result.Item1 == System.Net.HttpStatusCode.Created; - }); - return serverState; - }); - } - - public Task GetUserAsync(string sessionToken, CancellationToken cancellationToken) - { - ParseCommand command = new ParseCommand("users/me", - method: "GET", - sessionToken: sessionToken, - data: null); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - return ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); - }); + return CommandRunner.RunCommandAsync(new ParseCommand("users", method: "POST", data: new Dictionary { [nameof(authData)] = authData }), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder).MutatedClone(mutableClone => mutableClone.IsNew = task.Result.Item1 == System.Net.HttpStatusCode.Created)); } - public Task RequestPasswordResetAsync(string email, CancellationToken cancellationToken) - { - ParseCommand command = new ParseCommand("requestPasswordReset", - method: "POST", - data: new Dictionary { - {"email", email} - }); + public Task GetUserAsync(string sessionToken, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand("users/me", method: "GET", sessionToken: sessionToken, data: default), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder)); - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken); - } + public Task RequestPasswordResetAsync(string email, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand("requestPasswordReset", method: "POST", data: new Dictionary { [nameof(email)] = email }), cancellationToken: cancellationToken); } } diff --git a/Parse/Management/Tracking/MutableObjectState.cs b/Parse/Management/Tracking/MutableObjectState.cs index 16623628..26498bf7 100644 --- a/Parse/Management/Tracking/MutableObjectState.cs +++ b/Parse/Management/Tracking/MutableObjectState.cs @@ -34,7 +34,7 @@ public void Apply(IDictionary operationSet) { ServerData.TryGetValue(pair.Key, out object oldValue); object newValue = pair.Value.Apply(oldValue, pair.Key); - if (newValue != ParseDeleteOperation.DeleteToken) + if (newValue != ParseDeleteOperation.Token) { ServerData[pair.Key] = newValue; } diff --git a/Parse/Management/Tracking/ParseAddOperation.cs b/Parse/Management/Tracking/ParseAddOperation.cs index e6f2d39f..c31defbd 100644 --- a/Parse/Management/Tracking/ParseAddOperation.cs +++ b/Parse/Management/Tracking/ParseAddOperation.cs @@ -15,7 +15,7 @@ public class ParseAddOperation : IParseFieldOperation public object Encode() => new Dictionary { {"__op", "Add"}, - {"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)} + {nameof(objects), PointerOrLocalIdEncoder.Instance.Encode(objects)} }; public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) diff --git a/Parse/Management/Tracking/ParseAddUniqueOperation.cs b/Parse/Management/Tracking/ParseAddUniqueOperation.cs index eb1c58a9..108caba4 100644 --- a/Parse/Management/Tracking/ParseAddUniqueOperation.cs +++ b/Parse/Management/Tracking/ParseAddUniqueOperation.cs @@ -15,7 +15,7 @@ public class ParseAddUniqueOperation : IParseFieldOperation public object Encode() => new Dictionary { {"__op", "AddUnique"}, - {"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)} + {nameof(objects), PointerOrLocalIdEncoder.Instance.Encode(objects)} }; public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) diff --git a/Parse/Management/Tracking/ParseDeleteOperation.cs b/Parse/Management/Tracking/ParseDeleteOperation.cs index 4bc4cc3d..cbf14fb0 100644 --- a/Parse/Management/Tracking/ParseDeleteOperation.cs +++ b/Parse/Management/Tracking/ParseDeleteOperation.cs @@ -9,17 +9,16 @@ namespace Parse.Core.Internal /// public class ParseDeleteOperation : IParseFieldOperation { - internal static readonly object DeleteToken = new object(); - private static ParseDeleteOperation _Instance = new ParseDeleteOperation(); - public static ParseDeleteOperation Instance => _Instance; + internal static object Token { get; } = new object { }; + + public static ParseDeleteOperation Instance { get; } = new ParseDeleteOperation { }; private ParseDeleteOperation() { } - public object Encode() => new Dictionary { - {"__op", "Delete"} - }; + + public object Encode() => new Dictionary { ["__op"] = "Delete" }; public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) => this; - public object Apply(object oldValue, string key) => DeleteToken; + public object Apply(object oldValue, string key) => Token; } } diff --git a/Parse/Management/Tracking/ParseIncrementOperation.cs b/Parse/Management/Tracking/ParseIncrementOperation.cs index 72af8e3f..aa5fc650 100644 --- a/Parse/Management/Tracking/ParseIncrementOperation.cs +++ b/Parse/Management/Tracking/ParseIncrementOperation.cs @@ -97,7 +97,7 @@ static ParseIncrementOperation() public object Encode() => new Dictionary { {"__op", "Increment"}, - {"amount", amount} + {nameof(amount), amount} }; private static object Add(object obj1, object obj2) diff --git a/Parse/Management/Tracking/ParseRelationOperation.cs b/Parse/Management/Tracking/ParseRelationOperation.cs index 81a27890..b2fe09a4 100644 --- a/Parse/Management/Tracking/ParseRelationOperation.cs +++ b/Parse/Management/Tracking/ParseRelationOperation.cs @@ -9,130 +9,94 @@ namespace Parse.Core.Internal { public class ParseRelationOperation : IParseFieldOperation { - private readonly IList adds; - private readonly IList removes; - private readonly string targetClassName; + IList Additions { get; } - private ParseRelationOperation(IEnumerable adds, - IEnumerable removes, - string targetClassName) + IList Removals { get; } + + IParseObjectClassController ClassController { get; } + + ParseRelationOperation(IParseObjectClassController classController) => ClassController = classController; + + ParseRelationOperation(IParseObjectClassController classController, IEnumerable adds, IEnumerable removes, string targetClassName) : this(classController) { - this.targetClassName = targetClassName; - this.adds = new ReadOnlyCollection(adds.ToList()); - this.removes = new ReadOnlyCollection(removes.ToList()); + TargetClassName = targetClassName; + Additions = new ReadOnlyCollection(adds.ToList()); + Removals = new ReadOnlyCollection(removes.ToList()); } - public ParseRelationOperation(IEnumerable adds, - IEnumerable removes) + public ParseRelationOperation(IParseObjectClassController classController, IEnumerable adds, IEnumerable removes) : this(classController) { adds ??= new ParseObject[0]; removes ??= new ParseObject[0]; - targetClassName = adds.Concat(removes).Select(o => o.ClassName).FirstOrDefault(); - this.adds = new ReadOnlyCollection(IdsFromObjects(adds).ToList()); - this.removes = new ReadOnlyCollection(IdsFromObjects(removes).ToList()); + + TargetClassName = adds.Concat(removes).Select(entity => entity.ClassName).FirstOrDefault(); + Additions = new ReadOnlyCollection(GetIdsFromObjects(adds).ToList()); + Removals = new ReadOnlyCollection(GetIdsFromObjects(removes).ToList()); } public object Encode() { - List adds = this.adds - .Select(id => PointerOrLocalIdEncoder.Instance.Encode( - ParseObject.CreateWithoutData(targetClassName, id))) - .ToList(); - List removes = this.removes - .Select(id => PointerOrLocalIdEncoder.Instance.Encode( - ParseObject.CreateWithoutData(targetClassName, id))) - .ToList(); - Dictionary addDict = adds.Count == 0 ? null : new Dictionary { - {"__op", "AddRelation"}, - {"objects", adds} - }; - Dictionary 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; - } + List additions = Additions.Select(id => PointerOrLocalIdEncoder.Instance.Encode(ClassController.CreateObjectWithoutData(TargetClassName, id))).ToList(), removals = Removals.Select(id => PointerOrLocalIdEncoder.Instance.Encode(ClassController.CreateObjectWithoutData(TargetClassName, id))).ToList(); - public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) - { - if (previous == null) + Dictionary addition = additions.Count == 0 ? default : new Dictionary { - return this; - } - if (previous is ParseDeleteOperation) + ["__op"] = "AddRelation", + ["objects"] = additions + }; + + Dictionary removal = removals.Count == 0 ? default : new Dictionary { - throw new InvalidOperationException("You can't modify a relation after deleting it."); - } - ParseRelationOperation other = previous as ParseRelationOperation; - if (other != null) + ["__op"] = "RemoveRelation", + ["objects"] = removals + }; + + if (addition is { } && removal is { }) { - if (other.TargetClassName != TargetClassName) + return new Dictionary { - throw new InvalidOperationException( - String.Format("Related object must be of class {0}, but {1} was passed in.", - other.TargetClassName, - TargetClassName)); - } - List newAdd = adds.Union(other.adds.Except(removes)).ToList(); - List newRemove = removes.Union(other.removes.Except(adds)).ToList(); - return new ParseRelationOperation(newAdd, newRemove, TargetClassName); + ["__op"] = "Batch", + ["ops"] = new[] { addition, removal } + }; } - throw new InvalidOperationException("Operation is invalid after previous operation."); + return addition ?? removal; } - public object Apply(object oldValue, string key) + public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) => previous switch { - if (adds.Count == 0 && removes.Count == 0) - { - return null; - } - if (oldValue == null) - { - return ParseRelationBase.CreateRelation(null, key, targetClassName); - } - if (oldValue is ParseRelationBase) - { - ParseRelationBase oldRelation = (ParseRelationBase) oldValue; - string 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."); - } + null => this, + ParseDeleteOperation { } => throw new InvalidOperationException("You can't modify a relation after deleting it."), + ParseRelationOperation { } other when other.TargetClassName != TargetClassName => throw new InvalidOperationException($"Related object must be of class {other.TargetClassName}, but {TargetClassName} was passed in."), + ParseRelationOperation { ClassController: var classController } other => new ParseRelationOperation(classController, Additions.Union(other.Additions.Except(Removals)).ToList(), Removals.Union(other.Removals.Except(Additions)).ToList(), TargetClassName), + _ => throw new InvalidOperationException("Operation is invalid after previous operation.") + }; - public string TargetClassName => targetClassName; + public object Apply(object oldValue, string key) => oldValue switch + { + _ when Additions.Count == 0 && Removals.Count == 0 => default, + null => ParseRelationBase.CreateRelation(null, key, TargetClassName), + ParseRelationBase { TargetClassName: { } oldClassname } when oldClassname != TargetClassName => throw new InvalidOperationException($"Related object must be a {oldClassname}, but a {TargetClassName} was passed in."), + ParseRelationBase { } oldRelation => (Relation: oldRelation, oldRelation.TargetClassName = TargetClassName).Relation, + _ => throw new InvalidOperationException("Operation is invalid after previous operation.") + }; - private IEnumerable IdsFromObjects(IEnumerable objects) + public string TargetClassName { get; } + + IEnumerable GetIdsFromObjects(IEnumerable objects) { - foreach (ParseObject obj in objects) + foreach (ParseObject entity in objects) { - if (obj.ObjectId == null) + if (entity.ObjectId is null) { - throw new ArgumentException( - "You can't add an unsaved ParseObject to a relation."); + throw new ArgumentException("You can't add an unsaved ParseObject to a relation."); } - if (obj.ClassName != targetClassName) + + if (entity.ClassName != TargetClassName) { - throw new ArgumentException(String.Format( - "Tried to create a ParseRelation with 2 different types: {0} and {1}", - targetClassName, - obj.ClassName)); + throw new ArgumentException($"Tried to create a ParseRelation with 2 different types: {TargetClassName} and {entity.ClassName}"); } } - return objects.Select(o => o.ObjectId).Distinct(); + + return objects.Select(entity => entity.ObjectId).Distinct(); } } } diff --git a/Parse/Management/Tracking/ParseRemoveOperation.cs b/Parse/Management/Tracking/ParseRemoveOperation.cs index bdb4b583..a1d54e9e 100644 --- a/Parse/Management/Tracking/ParseRemoveOperation.cs +++ b/Parse/Management/Tracking/ParseRemoveOperation.cs @@ -16,7 +16,7 @@ public class ParseRemoveOperation : IParseFieldOperation public object Encode() => new Dictionary { {"__op", "Remove"}, - {"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)} + {nameof(objects), PointerOrLocalIdEncoder.Instance.Encode(objects)} }; public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) diff --git a/Parse/Modules/IParseModule.cs b/Parse/Modules/IParseModule.cs index d2a84da1..6d59806b 100644 --- a/Parse/Modules/IParseModule.cs +++ b/Parse/Modules/IParseModule.cs @@ -2,7 +2,7 @@ namespace Parse.Common.Internal { public interface IParseModule { - void OnModuleRegistered(); - void OnParseInitialized(); + void ExecuteModuleRegistrationHook(); + void ExecuteLibraryInitializationHook(); } } \ No newline at end of file diff --git a/Parse/Modules/ParseModuleController.cs b/Parse/Modules/ParseModuleController.cs index 7e75ac04..c1ce919f 100644 --- a/Parse/Modules/ParseModuleController.cs +++ b/Parse/Modules/ParseModuleController.cs @@ -11,12 +11,13 @@ namespace Parse.Common.Internal /// public class ParseModuleController { - public static ParseModuleController Instance { get; } = new ParseModuleController(); + public static ParseModuleController Instance { get; } = new ParseModuleController { }; - private readonly object mutex = new object(); - private readonly List modules = new List(); + object Mutex { get; } = new object { }; - private bool isParseInitialized = false; + List Modules { get; } = new List { }; + + bool Initialized { get; set; } = false; public void RegisterModule(IParseModule module) { @@ -25,36 +26,31 @@ public void RegisterModule(IParseModule module) return; } - lock (mutex) + lock (Mutex) { - modules.Add(module); - module.OnModuleRegistered(); + Modules.Add(module); + module.ExecuteModuleRegistrationHook(); - if (isParseInitialized) + if (Initialized) { - module.OnParseInitialized(); + module.ExecuteLibraryInitializationHook(); } } } public void ScanForModules() { - IEnumerable moduleTypes = Lister.AllAssemblies - .SelectMany(asm => asm.GetCustomAttributes()) - .Select(attr => attr.ModuleType) - .Where(type => type != null && type.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IParseModule))); + IEnumerable moduleTypes = Lister.AllAssemblies.SelectMany(asm => asm.GetCustomAttributes()).Select(attr => attr.ModuleType).Where(type => type != null && type.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IParseModule))); - lock (mutex) + lock (Mutex) { foreach (Type moduleType in moduleTypes) { try { - ConstructorInfo constructor = moduleType.FindConstructor(); - if (constructor != null) + if (moduleType.FindConstructor() is { } constructor) { - IParseModule module = constructor.Invoke(new object[] { }) as IParseModule; - RegisterModule(module); + RegisterModule(constructor.Invoke(new object[] { }) as IParseModule); } } catch (Exception) @@ -67,22 +63,23 @@ public void ScanForModules() public void Reset() { - lock (mutex) + lock (Mutex) { - modules.Clear(); - isParseInitialized = false; + Modules.Clear(); + Initialized = false; } } - public void ParseDidInitialize() + public void BroadcastParseInitialization() { - lock (mutex) + lock (Mutex) { - foreach (IParseModule module in modules) + foreach (IParseModule module in Modules) { - module.OnParseInitialized(); + module.ExecuteLibraryInitializationHook(); } - isParseInitialized = true; + + Initialized = true; } } } diff --git a/Parse/ObjectServiceExtensions.cs b/Parse/ObjectServiceExtensions.cs new file mode 100644 index 00000000..771adaf9 --- /dev/null +++ b/Parse/ObjectServiceExtensions.cs @@ -0,0 +1,510 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Parse.Abstractions.Library; +using Parse.Common.Internal; +using Parse.Core.Internal; +using Parse.Utilities; + +namespace Parse +{ + public static class ObjectServiceExtensions + { + /// + /// Registers a custom subclass type with the Parse SDK, enabling strong-typing of those ParseObjects whenever + /// they appear. Subclasses must specify the ParseClassName attribute, have a default constructor, and properties + /// backed by ParseObject fields should have ParseFieldName attributes supplied. + /// + /// The target instance. + /// The ParseObject subclass type to register. + public static void AddValidClass(this IServiceHub serviceHub) where T : ParseObject, new() => serviceHub.ClassController.AddValid(typeof(T)); + + /// + /// Registers a custom subclass type with the Parse SDK, enabling strong-typing of those ParseObjects whenever + /// they appear. Subclasses must specify the ParseClassName attribute, have a default constructor, and properties + /// backed by ParseObject fields should have ParseFieldName attributes supplied. + /// + /// The ParseObject subclass type to register. + /// The target instance. + public static void RegisterSubclass(this IServiceHub serviceHub, Type type) + { + if (typeof(ParseObject).IsAssignableFrom(type)) + { + serviceHub.ClassController.AddValid(type); + } + } + + /// + /// Unregisters a previously-registered sub-class of with the subclassing controller. + /// + /// + /// + public static void RemoveClass(this IServiceHub serviceHub) where T : ParseObject, new() => serviceHub.ClassController.RemoveClass(typeof(T)); + + /// + /// Unregisters a previously-registered sub-class of with the subclassing controller. + /// + /// + /// + public static void RemoveClass(this IParseObjectClassController subclassingController, Type type) + { + if (typeof(ParseObject).IsAssignableFrom(type)) + { + subclassingController.RemoveClass(type); + } + } + + /// + /// Creates a new ParseObject based upon a class name. If the class name is a special type (e.g. + /// for ), then the appropriate type of ParseObject is returned. + /// + /// The class of object to create. + /// A new ParseObject for the given class name. + public static ParseObject CreateObject(this IServiceHub serviceHub, string className) => serviceHub.ClassController.Instantiate(className); + + /// + /// Creates a new ParseObject based upon a given subclass type. + /// + /// A new ParseObject for the given class name. + public static T CreateObject(this IServiceHub serviceHub) where T : ParseObject => (T) serviceHub.ClassController.CreateObject(); + + /// + /// Creates a new ParseObject based upon a given subclass type. + /// + /// A new ParseObject for the given class name. + public static T CreateObject(this IParseObjectClassController classController) where T : ParseObject => (T) classController.Instantiate(classController.GetClassName(typeof(T))); + + /// + /// Creates a reference to an existing ParseObject for use in creating associations between + /// ParseObjects. 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 ParseObject without data. + public static ParseObject CreateObjectWithoutData(this IServiceHub serviceHub, string className, string objectId) => serviceHub.ClassController.CreateObjectWithoutData(className, objectId); + + /// + /// Creates a reference to an existing ParseObject for use in creating associations between + /// ParseObjects. 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 ParseObject without data. + public static ParseObject CreateObjectWithoutData(this IParseObjectClassController classController, string className, string objectId) + { + ParseObject.CreatingPointer.Value = true; + try + { + ParseObject result = classController.Instantiate(className); + result.ObjectId = objectId; + result.IsDirty = false; // Left in because the property setter might be doing something funky. + return result.IsDirty ? throw new InvalidOperationException("A ParseObject subclass default constructor must not make changes to the object that cause it to be dirty.") : result; + } + finally { ParseObject.CreatingPointer.Value = false; } + } + + /// + /// Creates a reference to an existing ParseObject for use in creating associations between + /// ParseObjects. 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 ParseObject without data. + public static T CreateObjectWithoutData(this IServiceHub serviceHub, string objectId) where T : ParseObject => (T) CreateObjectWithoutData(serviceHub, serviceHub.ClassController.GetClassName(typeof(T)), objectId); + + /// + /// Deletes each object in the provided list. + /// + /// The objects to delete. + public static Task DeleteObjectsAsync(this IServiceHub serviceHub, IEnumerable objects) where T : ParseObject => DeleteObjectsAsync(serviceHub, objects, CancellationToken.None); + + /// + /// Deletes each object in the provided list. + /// + /// The objects to delete. + /// The cancellation token. + public static Task DeleteObjectsAsync(this IServiceHub serviceHub, IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject + { + HashSet unique = new HashSet(objects.OfType().ToList(), new IdentityEqualityComparer { }); + + return EnqueueForAll(unique, toAwait => toAwait.OnSuccess(_ => Task.WhenAll(serviceHub.ObjectController.DeleteAllAsync(unique.Select(task => task.State).ToList(), serviceHub.GetCurrentSessionToken(), cancellationToken))).Unwrap().OnSuccess(task => + { + // Dirty all objects in memory. + + foreach (ParseObject obj in unique) + { + obj.IsDirty = true; + } + + return default(object); + }), cancellationToken); + } + + /// + /// Fetches all of the objects in the provided list. + /// + /// The objects to fetch. + /// The list passed in for convenience. + public static Task> FetchObjectsAsync(this IServiceHub serviceHub, IEnumerable objects) where T : ParseObject => FetchObjectsAsync(serviceHub, 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> FetchObjectsAsync(this IServiceHub serviceHub, IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject => EnqueueForAll(objects.Cast(), (Task toAwait) => serviceHub.FetchAllInternalAsync(objects, true, toAwait, cancellationToken), cancellationToken); + + /// + /// Fetches all of the objects that don't have data in the provided list. + /// + /// todo: describe objects parameter on FetchAllIfNeededAsync + /// The list passed in for convenience. + public static Task> FetchObjectsIfNeededAsync(this IServiceHub serviceHub, IEnumerable objects) where T : ParseObject => FetchObjectsIfNeededAsync(serviceHub, 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> FetchObjectsIfNeededAsync(this IServiceHub serviceHub, IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject => EnqueueForAll(objects.Cast(), (Task toAwait) => serviceHub.FetchAllInternalAsync(objects, false, toAwait, cancellationToken), cancellationToken); + + /// + /// Gets a for the type of object specified by + /// + /// + /// The class name of the object. + /// A new . + public static ParseQuery GetQuery(this IServiceHub serviceHub, string className) + { + // Since we can't return a ParseQuery (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 (serviceHub.ClassController.GetType(className) is { }) + { + throw new ArgumentException($"Use the class-specific query properties for class {className}", nameof(className)); + } + return new ParseQuery(serviceHub, className); + } + + /// + /// Saves each object in the provided list. + /// + /// The objects to save. + public static Task SaveObjectsAsync(this IServiceHub serviceHub, IEnumerable objects) where T : ParseObject => SaveObjectsAsync(serviceHub, objects, CancellationToken.None); + + /// + /// Saves each object in the provided list. + /// + /// The objects to save. + /// The cancellation token. + public static Task SaveObjectsAsync(this IServiceHub serviceHub, IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject => DeepSaveAsync(serviceHub, objects.ToList(), serviceHub.GetCurrentSessionToken(), cancellationToken); + + /// + /// 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 ParseObjects' children + /// Whether to include the root in the result + /// + internal static IEnumerable TraverseObjectDeep(this IServiceHub serviceHub, object root, bool traverseParseObjects = false, bool yieldRoot = false) + { + IEnumerable items = DeepTraversalInternal(serviceHub, root, traverseParseObjects, new HashSet(new IdentityEqualityComparer())); + return yieldRoot ? new[] { root }.Concat(items) : items; + } + + // TODO (hallucinogen): add unit test + internal static T GenerateObjectFromState(this IServiceHub serviceHub, IObjectState state, string defaultClassName) where T : ParseObject => serviceHub.ClassController.GenerateObjectFromState(state, defaultClassName); + + internal static T GenerateObjectFromState(this IParseObjectClassController classController, IObjectState state, string defaultClassName) where T : ParseObject + { + T obj = (T) classController.CreateObjectWithoutData(state.ClassName ?? defaultClassName, state.ObjectId); + obj.HandleFetchResult(state); + + return obj; + } + + internal static IDictionary GenerateJSONObjectForSaving(this IDictionary operations) + { + Dictionary result = new Dictionary(); + + foreach (KeyValuePair pair in operations) + { + result[pair.Key] = PointerOrLocalIdEncoder.Instance.Encode(pair.Value); + } + + return result; + } + + /// + /// Returns true if the given object can be serialized for saving as a value + /// that is pointed to by a ParseObject. + /// + internal static bool CanBeSerializedAsValue(this IServiceHub serviceHub, object value) => TraverseObjectDeep(serviceHub, value, yieldRoot: true).OfType().All(entity => entity.ObjectId is { }); + + static void CollectDirtyChildren(this IServiceHub serviceHub, object node, IList dirtyChildren, ICollection seen, ICollection seenNew) + { + foreach (ParseObject target in TraverseObjectDeep(serviceHub, 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 (target.ObjectId != null) + { + scopedSeenNew = new HashSet(new IdentityEqualityComparer()); + } + else + { + if (seenNew.Contains(target)) + { + throw new InvalidOperationException("Found a circular dependency while saving"); + } + + scopedSeenNew = new HashSet(seenNew, new IdentityEqualityComparer()) { target }; + } + + // 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(target)) + { + return; + } + + seen.Add(target); + + // 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(serviceHub, target.EstimatedData, dirtyChildren, seen, scopedSeenNew); + + if (target.CheckIsDirty(false)) + { + dirtyChildren.Add(target); + } + } + } + + /// + /// Helper version of CollectDirtyChildren so that callers don't have to add the internally + /// used parameters. + /// + static void CollectDirtyChildren(this IServiceHub serviceHub, object node, IList dirtyChildren) => CollectDirtyChildren(serviceHub, node, dirtyChildren, new HashSet(new IdentityEqualityComparer()), new HashSet(new IdentityEqualityComparer())); + + internal static Task DeepSaveAsync(this IServiceHub serviceHub, object target, string sessionToken, CancellationToken cancellationToken) + { + List objects = new List(); + CollectDirtyChildren(serviceHub, target, objects); + + HashSet uniqueObjects = new HashSet(objects, new IdentityEqualityComparer()); + List saveDirtyFileTasks = TraverseObjectDeep(serviceHub, target, true).OfType().Where(file => file.IsDirty).Select(file => file.SaveAsync(serviceHub, 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. + + List current = (from item in remaining where item.CanBeSerialized select item).ToList(), 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 ParseObject with a relation to a cycle."); + } + + // Save all of the objects in current. + + return EnqueueForAll(current, toAwait => toAwait.OnSuccess(__ => + { + List states = (from item in current select item.State).ToList(); + List> operationsList = (from item in current select item.StartSave()).ToList(); + + IList> saveTasks = serviceHub.ObjectController.SaveAllAsync(states, operationsList, sessionToken, cancellationToken); + + return Task.WhenAll(saveTasks).ContinueWith(task => + { + if (task.IsFaulted || task.IsCanceled) + { + foreach ((ParseObject item, IDictionary ops) pair in current.Zip(operationsList, (item, ops) => (item, ops))) + { + pair.item.HandleFailedSave(pair.ops); + } + } + else + { + IObjectState[] serverStates = task.Result; + foreach ((ParseObject item, IObjectState state) pair in current.Zip(serverStates, (item, state) => (item, state))) + { + pair.item.HandleSave(pair.state); + } + } + + cancellationToken.ThrowIfCancellationRequested(); + return task; + }).Unwrap(); + }).Unwrap().OnSuccess(t => (object) null), cancellationToken); + }); + }).Unwrap(); + } + + static IEnumerable DeepTraversalInternal(this IServiceHub serviceHub, object root, bool traverseParseObjects, ICollection seen) + { + seen.Add(root); + System.Collections.IEnumerable targets = ParseClient.IL2CPPCompiled ? null : null as IEnumerable; + + if (Conversion.As>(root) is { } rootDictionary) + { + targets = rootDictionary.Values; + } + else + { + if (Conversion.As>(root) is { } rootList) + { + targets = rootList; + } + else if (traverseParseObjects) + { + if (root is ParseObject entity) + { + targets = entity.Keys.ToList().Select(key => entity[key]); + } + } + } + + if (targets is { }) + { + foreach (object item in targets) + { + if (!seen.Contains(item)) + { + yield return item; + + foreach (object child in DeepTraversalInternal(serviceHub, item, traverseParseObjects, seen)) + { + yield return child; + } + } + } + } + } + + /// + /// Adds a task to the queue for all of the given objects. + /// + 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. + + TaskCompletionSource 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. + + LockSet 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. + + List childTasks = new List(); + foreach (ParseObject 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(default)); + return fullTask; + } + finally + { + lockSet.Exit(); + } + } + + /// + /// 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. + static Task> FetchAllInternalAsync(this IServiceHub serviceHub, IEnumerable objects, bool force, Task toAwait, CancellationToken cancellationToken) where T : ParseObject => toAwait.OnSuccess(_ => + { + if (objects.Any(obj => obj.State.ObjectId == null)) + { + throw new InvalidOperationException("You cannot fetch objects that haven't already been saved."); + } + + List 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. + + Dictionary>> findsByClass = (from obj in objectsToFetch group obj.ObjectId by obj.ClassName into classGroup where classGroup.Count() > 0 select (ClassName: classGroup.Key, FindTask: new ParseQuery(serviceHub, 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. + foreach ((T obj, ParseObject result) in from obj in objectsToFetch from result in findsByClass[obj.ClassName].Result where result.ObjectId == obj.ObjectId select (obj, result)) + { + obj.MergeFromObject(result); + obj.Fetched = true; + } + + return objects; + }); + }).Unwrap(); + + internal static string GetFieldForPropertyName(this IServiceHub serviceHub, string className, string propertyName) => serviceHub.ClassController.GetPropertyMappings(className).TryGetValue(propertyName, out string fieldName) ? fieldName : fieldName; + } +} diff --git a/Parse/Parse.csproj b/Parse/Parse.csproj index b70e01ad..f6f4e82a 100644 --- a/Parse/Parse.csproj +++ b/Parse/Parse.csproj @@ -38,4 +38,9 @@ + + + + + diff --git a/Parse/ParseClient.cs b/Parse/ParseClient.cs index 7406e3b3..d6192bc8 100644 --- a/Parse/ParseClient.cs +++ b/Parse/ParseClient.cs @@ -11,13 +11,17 @@ using Parse.Library; using Parse.Management; +#if DEBUG +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Parse.Test")] +#endif + namespace Parse { /// /// ParseClient contains static functions that handle global /// configuration for the Parse library. /// - public static partial class ParseClient + public class ParseClient : CustomServiceHub, IServiceHubComposer { /// /// Contains, in order, the official ISO date and time format strings, and two modified versions that account for the possibility that the server-side string processing mechanism removed trailing zeroes. @@ -29,14 +33,29 @@ public static partial class ParseClient "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'f'Z'", }; - static object Mutex { get; } = new object { }; + /// + /// Gets whether or not the assembly using the Parse SDK was compiled by IL2CPP. + /// + public static bool IL2CPPCompiled { get; set; } = AppDomain.CurrentDomain?.FriendlyName?.Equals("IL2CPP Root Domain") == true; + + /// + /// The configured default instance of to use. + /// + public static ParseClient Instance { get; private set; } /// /// The current configuration that parse has been initialized with. /// - public static Configuration Configuration { get; internal set; } + public IServerConnectionData Configuration => Services.ServerConnectionData; - internal static Version Version => new AssemblyName(typeof(ParseClient).GetTypeInfo().Assembly.FullName).Version; + internal static string Version => typeof(ParseClient)?.Assembly?.GetCustomAttribute()?.InformationalVersion ?? typeof(ParseClient)?.Assembly?.GetName()?.Version?.ToString(); + + /// + /// Services that provide essential functionality. + /// + public override IServiceHub Services { get; protected set; } + + // TODO: Implement IServiceHubMutator in all IServiceHub-implementing classes in Parse.Library and possibly require all implementations to do so as an efficiency improvement over instantiating an OrchestrationServiceHub, only for another one to be possibly instantiated when configurators are specified. /// /// Authenticates this client as belonging to your application. This must be @@ -48,7 +67,8 @@ public static partial class ParseClient /// /// The server URI provided in the Parse dashboard. /// - public static void Initialize(string identifier, string serverURI, ICacheLocationConfiguration storageConfiguration = default, IHostApplicationVersioningData hostVersioning = default, IParseCorePlugins plugins = default) => Initialize(new Configuration { ApplicationID = identifier, ServerURI = serverURI }, storageConfiguration, hostVersioning, plugins); + /// A service hub to override internal services and thereby make the Parse SDK operate in a custom manner. + public ParseClient(string identifier, string serverURI, IServiceHub serviceHub = default, params IServiceHubMutator[] configurators) : this(new ServerConnectionData { ApplicationID = identifier, ServerURI = serverURI }, serviceHub, configurators) { } /// /// Authenticates this client as belonging to your application. This must be @@ -58,66 +78,75 @@ public static partial class ParseClient /// /// The configuration to initialize Parse with. /// - public static void Initialize(Configuration configuration, ICacheLocationConfiguration cacheConfiguration = default, IHostApplicationVersioningData hostVersioning = default, IParseCorePlugins plugins = default) + /// A service hub to override internal services and thereby make the Parse SDK operate in a custom manner. + public ParseClient(IServerConnectionData configuration, IServiceHub serviceHub = default, params IServiceHubMutator[] configurators) + { + Services = serviceHub is { } ? new OrchestrationServiceHub { Custom = serviceHub, Default = new ServiceHub { ServerConnectionData = GenerateServerConnectionData() } } : new ServiceHub { ServerConnectionData = GenerateServerConnectionData() } as IServiceHub; + + IServerConnectionData GenerateServerConnectionData() => configuration switch + { + null => throw new ArgumentNullException(nameof(configuration)), + ServerConnectionData { Test: true } data => data, + { ServerURI: "https://api.parse.com/1/" } => throw new InvalidOperationException("Since the official parse server has shut down, you must specify a URI that points to a hosted instance."), + { ApplicationID: { }, ServerURI: { }, Key: { } } data => data, + _ => throw new InvalidOperationException("The IClientConfiguration implementation instance provided to the ParseClient constructor must be populated with configuration information.") + }; + + if (configurators is { Length: int length } && length > 0) + { + Services = BuildHub(default, Services, configurators); + } + } + + /// + /// Initializes a instance using the set on the 's implementation instance. + /// + public ParseClient() => Services = (Instance ?? throw new InvalidOperationException("A ParseClient instance with an initializer service must first be publicized in order for the default constructor to be used.")).Services.Cloner.BuildHub(Instance.Services, this); + + /// + /// Sets this instance as the template to create new instances from. + /// + ///// Declares that the current instance should be the publicly-accesible . + public void Publicize() { lock (Mutex) { - configuration.ServerURI ??= configuration.Test ? "https://api.parse.com/1/" : throw new ArgumentException("Since the official parse server has shut down, you must specify a URI that points to a hosted instance."); - plugins ??= new ParseCorePlugins { }; - - bool keepRelativeStoragePath = plugins is { StorageController: { } }, keepVersion = plugins is { MetadataController: { } }; - - if (plugins is ParseCorePlugins) - { - ParseCorePlugins.Instance = plugins; - } - - if (hostVersioning is { } && !keepVersion && plugins.MetadataController is MetadataController { } metadataController) - { - metadataController.HostVersioningData = hostVersioning switch - { - { CanBeUsedForInference: true } data => data, - { IsDefault: false } data => new HostApplicationVersioningData - { - BuildVersion = data.BuildVersion, - HostOSVersion = data.HostOSVersion, - DisplayVersion = HostApplicationVersioningData.Inferred.DisplayVersion - }, - _ => HostApplicationVersioningData.Inferred - }; - } - - if (cacheConfiguration is { } && !keepRelativeStoragePath && plugins.StorageController is StorageController { } storageController) - { - storageController.RelativeStorageFilePath = cacheConfiguration.GetRelativeStorageFilePath(plugins); - } - - Configuration = configuration; - - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); - - AppDomain.CurrentDomain.ProcessExit += (_, __) => plugins.StorageController.Clean(); + Instance = this; } } + static object Mutex { get; } = new object { }; + internal static string BuildQueryString(IDictionary parameters) => String.Join("&", (from pair in parameters let valueString = pair.Value as string select $"{Uri.EscapeDataString(pair.Key)}={Uri.EscapeDataString(String.IsNullOrEmpty(valueString) ? Json.Encode(pair.Value) : valueString)}").ToArray()); internal static IDictionary DecodeQueryString(string queryString) { - Dictionary dict = new Dictionary(); + Dictionary query = new Dictionary { }; + foreach (string pair in queryString.Split('&')) { string[] parts = pair.Split(new char[] { '=' }, 2); - dict[parts[0]] = parts.Length == 2 ? Uri.UnescapeDataString(parts[1].Replace("+", " ")) : null; + query[parts[0]] = parts.Length == 2 ? Uri.UnescapeDataString(parts[1].Replace("+", " ")) : null; } - return dict; + + return query; } internal static IDictionary DeserializeJsonString(string jsonData) => Json.Parse(jsonData) as IDictionary; internal static string SerializeJsonString(IDictionary jsonData) => Json.Encode(jsonData); + + public IServiceHub BuildHub(IMutableServiceHub target = default, IServiceHub extension = default, params IServiceHubMutator[] configurators) + { + OrchestrationServiceHub orchestrationServiceHub = new OrchestrationServiceHub { Custom = target ??= new MutableServiceHub { }, Default = extension ?? new ServiceHub { } }; + + foreach (IServiceHubMutator mutator in configurators.Where(configurator => configurator.Valid)) + { + mutator.Mutate(ref target, orchestrationServiceHub); + orchestrationServiceHub.Custom = target; + } + + return orchestrationServiceHub; + } } } diff --git a/Parse/Platform/Configuration/ParseConfig.cs b/Parse/ParseConfiguration.cs similarity index 51% rename from Parse/Platform/Configuration/ParseConfig.cs rename to Parse/ParseConfiguration.cs index f2decc8e..11b05e98 100644 --- a/Parse/Platform/Configuration/ParseConfig.cs +++ b/Parse/ParseConfiguration.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading; -using System.Threading.Tasks; using Parse.Common.Internal; using Parse.Core.Internal; using Parse.Management; @@ -10,57 +9,20 @@ namespace Parse { + /// /// The ParseConfig 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 ParseConfig : IJsonConvertible + public class ParseConfiguration : IJsonConvertible { - private IDictionary properties = new Dictionary(); - - /// - /// Gets the latest fetched ParseConfig. - /// - /// ParseConfig object - public static ParseConfig 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 IParseConfigController ConfigController => ParseCorePlugins.Instance.ConfigController; + IDictionary Properties { get; } = new Dictionary { }; - internal ParseConfig() - : base() - { - } + internal ParseConfiguration() { } - internal ParseConfig(IDictionary fetchedConfig) - { - IDictionary props = ParseDecoder.Instance.Decode(fetchedConfig["params"]) as IDictionary; - properties = props; - } + ParseConfiguration(IDictionary properties) => Properties = properties; - /// - /// Retrieves the ParseConfig asynchronously from the server. - /// - /// ParseConfig object that was fetched - public static Task GetAsync() => GetAsync(CancellationToken.None); - - /// - /// Retrieves the ParseConfig asynchronously from the server. - /// - /// The cancellation token. - /// ParseConfig object that was fetched - public static Task GetAsync(CancellationToken cancellationToken) => ConfigController.FetchConfigAsync(ParseUser.CurrentSessionToken, cancellationToken); + internal static ParseConfiguration Create(IDictionary configurationData, IParseDataDecoder decoder) => new ParseConfiguration(decoder.Decode(configurationData["params"]) as IDictionary); /// /// Gets a value for the key of a particular type. @@ -73,7 +35,7 @@ internal ParseConfig(IDictionary fetchedConfig) /// and is not found. /// The property under this /// key was found, but of a different type. - public T Get(string key) => Conversion.To(properties[key]); + public T Get(string key) => Conversion.To(Properties[key]); /// /// Populates result with the value for the key, if possible. @@ -85,20 +47,21 @@ internal ParseConfig(IDictionary fetchedConfig) /// true if the lookup and conversion succeeded, otherwise false. public bool TryGetValue(string key, out T result) { - if (properties.ContainsKey(key)) + if (Properties.ContainsKey(key)) { try { - T temp = Conversion.To(properties[key]); + T temp = Conversion.To(Properties[key]); result = temp; return true; } catch { - // Could not convert, do nothing + // Could not convert, do nothing. } } - result = default(T); + + result = default; return false; } @@ -109,10 +72,11 @@ public bool TryGetValue(string key, out T result) /// The property is /// retrieved and is not found. /// The value for the key. - virtual public object this[string key] => properties[key]; + virtual public object this[string key] => Properties[key]; - IDictionary IJsonConvertible.ToJSON() => new Dictionary { - { "params", NoObjectsEncoder.Instance.Encode(properties) } - }; + IDictionary IJsonConvertible.ConvertToJSON() => new Dictionary + { + ["params"] = NoObjectsEncoder.Instance.Encode(Properties) + }; } } diff --git a/Parse/ParseFile.cs b/Parse/ParseFile.cs index aca34a81..40309c81 100644 --- a/Parse/ParseFile.cs +++ b/Parse/ParseFile.cs @@ -5,12 +5,44 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; using Parse.Core.Internal; using Parse.Management; namespace Parse { + public static class FileServiceExtensions + { + /// + /// Saves the file to the Parse cloud. + /// + /// The cancellation token. + public static Task SaveFileAsync(this IServiceHub serviceHub, ParseFile file, CancellationToken cancellationToken = default) => serviceHub.SaveFileAsync(file, default, cancellationToken); + + /// + /// Saves the file to the Parse cloud. + /// + /// The progress callback. + /// The cancellation token. + public static Task SaveFileAsync(this IServiceHub serviceHub, ParseFile file, IProgress progress, CancellationToken cancellationToken = default) => file.TaskQueue.Enqueue(toAwait => serviceHub.FileController.SaveAsync(file.State, file.DataStream, serviceHub.GetCurrentSessionToken(), progress, cancellationToken), cancellationToken).OnSuccess(task => file.State = task.Result); + +#warning Make serviceHub null by default once dependents properly inject it when needed. + + /// + /// Saves the file to the Parse cloud. + /// + /// The cancellation token. + public static Task SaveAsync(this ParseFile file, IServiceHub serviceHub, CancellationToken cancellationToken = default) => serviceHub.SaveFileAsync(file, cancellationToken); + + /// + /// Saves the file to the Parse cloud. + /// + /// The progress callback. + /// The cancellation token. + public static Task SaveAsync(this ParseFile file, IServiceHub serviceHub, IProgress progress, CancellationToken cancellationToken = default) => serviceHub.SaveFileAsync(file, progress, cancellationToken); + } + /// /// ParseFile is a local representation of a file that is saved to the Parse cloud. /// @@ -29,17 +61,21 @@ namespace Parse /// public class ParseFile : IJsonConvertible { - private FileState state; - private readonly Stream dataStream; - private readonly TaskQueue taskQueue = new TaskQueue(); + internal FileState State { get; set; } + + internal Stream DataStream { get; } + + internal TaskQueue TaskQueue { get; } = new TaskQueue { }; #region Constructor - internal ParseFile(string name, Uri uri, string mimeType = null) => state = new FileState +#warning Make IServiceHub optionally null once all dependents are injecting it if necessary. + + internal ParseFile(string name, Uri uri, string mimeType = null) => State = new FileState { Name = name, - Url = uri, - MimeType = mimeType + Location = uri, + MediaType = mimeType }; /// @@ -51,8 +87,7 @@ public class ParseFile : IJsonConvertible /// The file's data. /// To specify the content-type used when uploading the /// file, provide this parameter. - public ParseFile(string name, byte[] data, string mimeType = null) - : this(name, new MemoryStream(data), mimeType) { } + public ParseFile(string name, byte[] data, string mimeType = null) : this(name, new MemoryStream(data), mimeType) { } /// /// Creates a new file from a stream and a name. @@ -65,12 +100,13 @@ public ParseFile(string name, byte[] data, string mimeType = null) /// file, provide this parameter. public ParseFile(string name, Stream data, string mimeType = null) { - state = new FileState + State = new FileState { Name = name, - MimeType = mimeType + MediaType = mimeType }; - dataStream = data; + + DataStream = data; } #endregion @@ -80,79 +116,44 @@ public ParseFile(string name, Stream data, string mimeType = null) /// /// Gets whether the file still needs to be saved. /// - public bool IsDirty => state.Url == null; + public bool IsDirty => State.Location == 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. /// [ParseFieldName("name")] - public string Name => state.Name; + public string Name => 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 => state.MimeType; + public string MimeType => State.MediaType; /// /// Gets the url of the file. It is only available after you save the file or after /// you get the file from a . /// [ParseFieldName("url")] - public Uri Url => state.SecureUrl; - - internal static IParseFileController FileController => ParseCorePlugins.Instance.FileController; + public Uri Url => State.SecureLocation; #endregion - IDictionary IJsonConvertible.ToJSON() + IDictionary IJsonConvertible.ConvertToJSON() { if (IsDirty) { - throw new InvalidOperationException( - "ParseFile must be saved before it can be serialized."); + throw new InvalidOperationException("ParseFile must be saved before it can be serialized."); } - return new Dictionary { - {"__type", "File"}, - {"name", Name}, - {"url", Url.AbsoluteUri} - }; - } - - #region Save - - /// - /// Saves the file to the Parse cloud. - /// - public Task SaveAsync() => SaveAsync(null, CancellationToken.None); - /// - /// Saves the file to the Parse cloud. - /// - /// The cancellation token. - public Task SaveAsync(CancellationToken cancellationToken) => SaveAsync(null, cancellationToken); - - /// - /// Saves the file to the Parse cloud. - /// - /// The progress callback. - public Task SaveAsync(IProgress progress) => SaveAsync(progress, CancellationToken.None); - - /// - /// Saves the file to the Parse cloud. - /// - /// The progress callback. - /// The cancellation token. - public Task SaveAsync(IProgress progress, - CancellationToken cancellationToken) => taskQueue.Enqueue( - toAwait => FileController.SaveAsync(state, dataStream, ParseUser.CurrentSessionToken, progress, cancellationToken), cancellationToken) - .OnSuccess(t => + return new Dictionary { - state = t.Result; - }); - - #endregion + ["__type"] = "File", + ["name"] = Name, + ["url"] = Url.AbsoluteUri + }; + } } } diff --git a/Parse/ParseGeoPoint.cs b/Parse/ParseGeoPoint.cs index 93f741ea..054d8fef 100644 --- a/Parse/ParseGeoPoint.cs +++ b/Parse/ParseGeoPoint.cs @@ -90,10 +90,10 @@ public ParseGeoDistance DistanceTo(ParseGeoPoint point) return new ParseGeoDistance(2 * Math.Asin(Math.Sqrt(a))); } - IDictionary IJsonConvertible.ToJSON() => new Dictionary { + IDictionary IJsonConvertible.ConvertToJSON() => new Dictionary { {"__type", "GeoPoint"}, - {"latitude", Latitude}, - {"longitude", Longitude} + {nameof(latitude), Latitude}, + {nameof(longitude), Longitude} }; } } diff --git a/Parse/ParseInstallation.cs b/Parse/ParseInstallation.cs index 89860001..65f5b31c 100644 --- a/Parse/ParseInstallation.cs +++ b/Parse/ParseInstallation.cs @@ -14,7 +14,6 @@ namespace Parse { - /// /// Represents this app installed on this device. Use this class to track information you want /// to sample from (i.e. if you update a field on app launch, you can issue a query to see @@ -23,13 +22,7 @@ namespace Parse [ParseClassName("_Installation")] public partial class ParseInstallation : ParseObject { - static readonly HashSet readOnlyKeys = new HashSet { "deviceType", "deviceUris", "installationId", "timeZone", "localeIdentifier", "parseVersion", "appName", "appIdentifier", "appVersion", "pushType" }; - - internal static IParseCurrentInstallationController CurrentInstallationController => ParsePushPlugins.Instance.CurrentInstallationController; - - internal static IDeviceInfoController DeviceInfoController => ParsePushPlugins.Instance.DeviceInfoController; - - internal static IMetadataController MetadataController => ParseCorePlugins.Instance.MetadataController; + static HashSet ImmutableKeys { get; } = new HashSet { "deviceType", "deviceUris", "installationId", "timeZone", "localeIdentifier", "parseVersion", "appName", "appIdentifier", "appVersion", "pushType" }; /// /// Constructs a new ParseInstallation. Generally, you should not need to construct @@ -37,39 +30,6 @@ public partial class ParseInstallation : ParseObject /// public ParseInstallation() : base() { } - /// - /// Gets the ParseInstallation representing this app on this device. - /// - public static ParseInstallation CurrentInstallation - { - get - { - Task task = CurrentInstallationController.GetAsync(CancellationToken.None); - // TODO (hallucinogen): this will absolutely break on Unity, but how should we resolve this? - task.Wait(); - return task.Result; - } - } - - internal static void ClearInMemoryInstallation() => CurrentInstallationController.ClearFromMemory(); - - /// - /// Constructs a for ParseInstallations. - /// - /// - /// Only the following types of queries are allowed for installations: - /// - /// - /// query.GetAsync(objectId) - /// query.WhereEqualTo(key, value) - /// query.WhereMatchesKeyInQuery<TOther>(key, keyInQuery, otherQuery) - /// - /// - /// You can add additional query conditions, but one of the above must appear as a top-level AND - /// clause in the query. - /// - public static ParseQuery Query => new ParseQuery(); - /// /// A GUID that uniquely names this app installed on this device. /// @@ -78,8 +38,9 @@ public Guid InstallationId { get { - string installationIdString = GetProperty("InstallationId"); + string installationIdString = GetProperty(nameof(InstallationId)); Guid? installationId = null; + try { installationId = new Guid(installationIdString); @@ -94,7 +55,7 @@ public Guid InstallationId internal set { Guid installationId = value; - SetProperty(installationId.ToString(), "InstallationId"); + SetProperty(installationId.ToString(), nameof(InstallationId)); } } @@ -104,8 +65,8 @@ internal set [ParseFieldName("deviceType")] public string DeviceType { - get => GetProperty("DeviceType"); - internal set => SetProperty(value, "DeviceType"); + get => GetProperty(nameof(DeviceType)); + internal set => SetProperty(value, nameof(DeviceType)); } /// @@ -114,8 +75,8 @@ public string DeviceType [ParseFieldName("appName")] public string AppName { - get => GetProperty("AppName"); - internal set => SetProperty(value, "AppName"); + get => GetProperty(nameof(AppName)); + internal set => SetProperty(value, nameof(AppName)); } /// @@ -124,8 +85,8 @@ public string AppName [ParseFieldName("appVersion")] public string AppVersion { - get => GetProperty("AppVersion"); - internal set => SetProperty(value, "AppVersion"); + get => GetProperty(nameof(AppVersion)); + internal set => SetProperty(value, nameof(AppVersion)); } /// @@ -136,8 +97,8 @@ public string AppVersion [ParseFieldName("appIdentifier")] public string AppIdentifier { - get => GetProperty("AppIdentifier"); - internal set => SetProperty(value, "AppIdentifier"); + get => GetProperty(nameof(AppIdentifier)); + internal set => SetProperty(value, nameof(AppIdentifier)); } /// @@ -150,8 +111,8 @@ public string AppIdentifier [ParseFieldName("timeZone")] public string TimeZone { - get => GetProperty("TimeZone"); - private set => SetProperty(value, "TimeZone"); + get => GetProperty(nameof(TimeZone)); + private set => SetProperty(value, nameof(TimeZone)); } /// @@ -161,8 +122,8 @@ public string TimeZone [ParseFieldName("localeIdentifier")] public string LocaleIdentifier { - get => GetProperty("LocaleIdentifier"); - private set => SetProperty(value, "LocaleIdentifier"); + get => GetProperty(nameof(LocaleIdentifier)); + private set => SetProperty(value, nameof(LocaleIdentifier)); } /// @@ -173,6 +134,7 @@ private string GetLocaleIdentifier() { string languageCode = null; string countryCode = null; + if (CultureInfo.CurrentCulture != null) { languageCode = CultureInfo.CurrentCulture.TwoLetterISOLanguageName; @@ -199,7 +161,7 @@ public Version ParseVersion { get { - string versionString = GetProperty("ParseVersion"); + string versionString = GetProperty(nameof(ParseVersion)); Version version = null; try { @@ -215,7 +177,7 @@ public Version ParseVersion private set { Version version = value; - SetProperty(version.ToString(), "ParseVersion"); + SetProperty(version.ToString(), nameof(ParseVersion)); } } @@ -226,46 +188,34 @@ private set [ParseFieldName("channels")] public IList Channels { - get => GetProperty>("Channels"); - set => SetProperty(value, "Channels"); + get => GetProperty>(nameof(Channels)); + set => SetProperty(value, nameof(Channels)); } - protected override bool IsKeyMutable(string key) => !readOnlyKeys.Contains(key); + protected override bool CheckKeyMutable(string key) => !ImmutableKeys.Contains(key); protected override Task SaveAsync(Task toAwait, CancellationToken cancellationToken) { Task platformHookTask = null; - if (CurrentInstallationController.IsCurrent(this)) - { - Configuration configuration = ParseClient.Configuration; - // 'this' is required in order for the extension method to be used. - SetIfDifferent("deviceType", DeviceInfoController.DeviceType); - SetIfDifferent("timeZone", DeviceInfoController.DeviceTimeZone); + if (Client.CurrentInstallationController.IsCurrent(this)) + { + SetIfDifferent("deviceType", Client.MetadataController.EnvironmentData.Platform); + SetIfDifferent("timeZone", Client.MetadataController.EnvironmentData.TimeZone); SetIfDifferent("localeIdentifier", GetLocaleIdentifier()); - SetIfDifferent("parseVersion", GetParseVersion().ToString()); - SetIfDifferent("appVersion", MetadataController.HostVersioningData.BuildVersion ?? DeviceInfoController.AppBuildVersion); - SetIfDifferent("appIdentifier", DeviceInfoController.AppIdentifier); - SetIfDifferent("appName", DeviceInfoController.AppName); + SetIfDifferent("parseVersion", ParseClient.Version); + SetIfDifferent("appVersion", Client.MetadataController.HostManifestData.Version); + SetIfDifferent("appIdentifier", Client.MetadataController.HostManifestData.Identifier); + SetIfDifferent("appName", Client.MetadataController.HostManifestData.Name); - platformHookTask = DeviceInfoController.ExecuteParseInstallationSaveHookAsync(this); +#warning InstallationDataFinalizer needs to be injected here somehow or removed. + + //platformHookTask = Client.InstallationDataFinalizer.FinalizeAsync(this); } - return platformHookTask.Safe().OnSuccess(_ => - { - return base.SaveAsync(toAwait, cancellationToken); - }).Unwrap().OnSuccess(_ => - { - if (CurrentInstallationController.IsCurrent(this)) - { - return Task.FromResult(0); - } - return CurrentInstallationController.SetAsync(this, cancellationToken); - }).Unwrap(); + return platformHookTask.Safe().OnSuccess(_ => base.SaveAsync(toAwait, cancellationToken)).Unwrap().OnSuccess(_ => Client.CurrentInstallationController.IsCurrent(this) ? Task.CompletedTask : Client.CurrentInstallationController.SetAsync(this, cancellationToken)).Unwrap(); } - private Version GetParseVersion() => new AssemblyName(typeof(ParseInstallation).GetTypeInfo().Assembly.FullName).Version; - /// /// This mapping of Windows names to a standard everyone else uses is maintained /// by the Unicode consortium, which makes this officially the first helpful diff --git a/Parse/ParseObject.cs b/Parse/ParseObject.cs index 5579560c..c35ba5f1 100644 --- a/Parse/ParseObject.cs +++ b/Parse/ParseObject.cs @@ -30,45 +30,16 @@ namespace Parse /// public class ParseObject : IEnumerable>, INotifyPropertyChanged { - private static readonly string AutoClassName = "_Automatic"; + internal static string AutoClassName { get; } = "_Automatic"; - private static readonly bool isCompiledByIL2CPP = false; + internal static ThreadLocal CreatingPointer { get; } = new ThreadLocal(() => false); - 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(); - } - } - - internal IObjectState State => state; - - internal static IParseObjectController ObjectController => ParseCorePlugins.Instance.ObjectController; - - internal static IObjectSubclassingController SubclassingController => ParseCorePlugins.Instance.SubclassingController; - - #region ParseObject Creation + internal TaskQueue taskQueue = new TaskQueue { }; /// - /// Constructor for use in ParseObject subclasses. Subclasses must specify a ParseClassName attribute. + /// The instance being targeted. /// - protected ParseObject() : this(AutoClassName) { } + internal ParseClient Client { get; } /// /// Constructs a new ParseObject with no data in it. A ParseObject constructed in this way will @@ -77,660 +48,370 @@ protected ParseObject() : this(AutoClassName) { } /// /// /// Class names must be alphanumerical plus underscore, and start with a letter. It is recommended - /// to name classes in CamelCaseLikeThis. + /// to name classes in PascalCase. /// /// The className for this ParseObject. - public ParseObject(string className) + /// The instance to target for any resources. + public ParseObject(string className, ParseClient client = default) { // 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? - bool isPointer = isCreatingPointer.Value; - isCreatingPointer.Value = false; - if (className == null) - throw new ArgumentException("You must specify a Parse class name when creating a new ParseObject."); - if (AutoClassName.Equals(className)) - className = SubclassingController.GetClassName(GetType()); + bool isPointer = CreatingPointer.Value; + CreatingPointer.Value = false; + + Client = client ?? ParseClient.Instance ?? throw new InvalidOperationException("A ParseClient needs to be initialized as the configured default instance before any ParseObjects can be instantiated."); + + if (AutoClassName.Equals(className ?? throw new ArgumentException("You must specify a Parse class name when creating a new ParseObject."))) + { + className = GetType().GetParseClassName(); + } + // If this is supposed to be created by a factory but wasn't, throw an exception - if (!SubclassingController.IsTypeValid(className, GetType())) + + if (!Client.Services.ClassController.GetClassMatch(className, GetType())) + { throw new ArgumentException("You must create this type of ParseObject using ParseObject.Create() or the proper subclass."); - state = new MutableObjectState { ClassName = className }; - OnPropertyChanged("ClassName"); + } + + State = new MutableObjectState { ClassName = className }; + OnPropertyChanged(nameof(ClassName)); + + OperationSetQueue.AddLast(new Dictionary()); - operationSetQueue.AddLast(new Dictionary()); if (!isPointer) { - hasBeenFetched = true; + Fetched = true; IsDirty = true; + SetDefaultValues(); } else { IsDirty = false; - hasBeenFetched = false; + Fetched = false; } } + #region ParseObject Creation + /// - /// Creates a new ParseObject based upon a class name. If the class name is a special type (e.g. - /// for ), then the appropriate type of ParseObject is returned. + /// Constructor for use in ParseObject subclasses. Subclasses must specify a ParseClassName attribute. /// - /// The class of object to create. - /// A new ParseObject for the given class name. - public static ParseObject Create(string className) => SubclassingController.Instantiate(className); + protected ParseObject(ParseClient client = default) : this(AutoClassName, client) { } /// - /// Creates a reference to an existing ParseObject for use in creating associations between - /// ParseObjects. Calling on this object will return - /// false until has been called. - /// No network request will be made. + /// Occurs when a property value changes. /// - /// The object's class. - /// The object id for the referenced object. - /// A ParseObject without data. - public static ParseObject CreateWithoutData(string className, string objectId) + public event PropertyChangedEventHandler PropertyChanged { - isCreatingPointer.Value = true; - try + add + { + PropertyChangedHandler.Add(value); + } + remove { - var result = SubclassingController.Instantiate(className); - result.ObjectId = objectId; - result.IsDirty = false; // Left in because the property setter might be doing something funky. - return result.IsDirty ? throw new InvalidOperationException("A ParseObject subclass default constructor must not make changes to the object that cause it to be dirty.") : result; + PropertyChangedHandler.Remove(value); } - finally { isCreatingPointer.Value = false; } } /// - /// Creates a new ParseObject based upon a given subclass type. - /// - /// A new ParseObject for the given class name. - public static T Create() where T : ParseObject => (T) SubclassingController.Instantiate(SubclassingController.GetClassName(typeof(T))); - - /// - /// Creates a reference to an existing ParseObject for use in creating associations between - /// ParseObjects. Calling on this object will return - /// false until has been called. - /// No network request will be made. + /// Gets or sets the ParseACL governing this object. /// - /// The object id for the referenced object. - /// A ParseObject without data. - public static T CreateWithoutData(string objectId) where T : ParseObject => (T) CreateWithoutData(SubclassingController.GetClassName(typeof(T)), objectId); - - // TODO (hallucinogen): add unit test - internal static T FromState(IObjectState state, string defaultClassName) where T : ParseObject + [ParseFieldName("ACL")] + public ParseACL ACL { - T obj = (T) CreateWithoutData(state.ClassName ?? defaultClassName, state.ObjectId); - obj.HandleFetchResult(state); - return obj; + get => GetProperty(null, nameof(ACL)); + set => SetProperty(value, nameof(ACL)); } - #endregion - - private static string GetFieldForPropertyName(string className, string propertyName) => SubclassingController.GetPropertyMappings(className).TryGetValue(propertyName, out string fieldName) ? fieldName : fieldName; - - /// - /// Sets the value of a property based upon its associated ParseFieldName attribute. - /// - /// The new value. - /// The name of the property. - /// The type for the property. - protected void SetProperty(T value, [CallerMemberName] string propertyName = null) => this[GetFieldForPropertyName(ClassName, propertyName)] = value; - - /// - /// Gets a relation for a property based upon its associated ParseFieldName attribute. - /// - /// The ParseRelation for the property. - /// The name of the property. - /// The ParseObject subclass type of the ParseRelation. - protected ParseRelation GetRelationProperty([CallerMemberName] string propertyName = null) where T : ParseObject => GetRelation(GetFieldForPropertyName(ClassName, propertyName)); - - /// - /// Gets the value of a property based upon its associated ParseFieldName attribute. - /// - /// The value of the property. - /// The name of the property. - /// The return type of the property. - protected T GetProperty([CallerMemberName] string propertyName = null) => GetProperty(default(T), propertyName); - - /// - /// Gets the value of a property based upon its associated ParseFieldName attribute. - /// - /// The value of the property. - /// The value to return if the property is not present on the ParseObject. - /// The name of the property. - /// The return type of the property. - protected T GetProperty(T defaultValue, [CallerMemberName] string propertyName = null) => TryGetValue(GetFieldForPropertyName(ClassName, propertyName), out T result) ? result : defaultValue; - /// - /// Allows subclasses to set values for non-pointer construction. + /// Gets the class name for the ParseObject. /// - internal virtual void SetDefaultValues() { } + public string ClassName => State.ClassName; /// - /// Registers a custom subclass type with the Parse SDK, enabling strong-typing of those ParseObjects whenever - /// they appear. Subclasses must specify the ParseClassName attribute, have a default constructor, and properties - /// backed by ParseObject fields should have ParseFieldName attributes supplied. + /// Gets the first time this object was saved as the server sees it, so that if you create a + /// ParseObject, 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. /// - /// The ParseObject subclass type to register. - public static void RegisterDerivative() where T : ParseObject, new() => SubclassingController.RegisterSubclass(typeof(T)); + [ParseFieldName("createdAt")] + public DateTime? CreatedAt => State.CreatedAt; /// - /// Registers a custom subclass type with the Parse SDK, enabling strong-typing of those ParseObjects whenever - /// they appear. Subclasses must specify the ParseClassName attribute, have a default constructor, and properties - /// backed by ParseObject fields should have ParseFieldName attributes supplied. + /// Gets whether the ParseObject has been fetched. /// - /// The ParseObject subclass type to register. - public static void RegisterSubclass(Type type) { if (typeof(ParseObject).IsAssignableFrom(type)) SubclassingController.RegisterSubclass(type); } - - internal static void UnregisterSubclass() where T : ParseObject, new() => SubclassingController.UnregisterSubclass(typeof(T)); - internal static void UnregisterSubclass(Type type) { if (typeof(ParseObject).IsAssignableFrom(type)) SubclassingController.UnregisterSubclass(type); } - - + public bool IsDataAvailable + { + get + { + lock (Mutex) + { + return Fetched; + } + } + } /// - /// Clears any changes to this object made since the last call to . + /// Indicates whether this ParseObject has unsaved changes. /// - public void Revert() + public bool IsDirty { - lock (mutex) + get { - bool wasDirty = CurrentOperations.Count > 0; - if (wasDirty) + lock (Mutex) { - CurrentOperations.Clear(); - RebuildEstimatedData(); - OnPropertyChanged("IsDirty"); + return CheckIsDirty(true); + } + } + internal set + { + lock (Mutex) + { + Dirty = value; + OnPropertyChanged(nameof(IsDirty)); } } } - internal virtual void HandleFetchResult(IObjectState serverState) + /// + /// Returns true if this object was created by the Parse server when the + /// object might have already been there (e.g. in the case of a Facebook + /// login) + /// + public bool IsNew { - lock (mutex) + get => State.IsNew; + internal set { - MergeFromServer(serverState); + MutateState(mutableClone => mutableClone.IsNew = value); + OnPropertyChanged(nameof(IsNew)); } } - internal void HandleFailedSave(IDictionary operationsBeforeSave) + /// + /// 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 { - lock (mutex) + get { - LinkedListNode> opNode = operationSetQueue.Find(operationsBeforeSave); - IDictionary nextOperations = opNode.Next.Value; - bool wasDirty = nextOperations.Count > 0; - operationSetQueue.Remove(opNode); - // Merge the data from the failed save into the next save. - foreach (KeyValuePair pair in operationsBeforeSave) + lock (Mutex) { - IParseFieldOperation operation1 = pair.Value; - nextOperations.TryGetValue(pair.Key, out IParseFieldOperation operation2); - if (operation2 != null) - operation2 = operation2.MergeWithPrevious(operation1); - else - operation2 = operation1; - nextOperations[pair.Key] = operation2; + return EstimatedData.Keys; } - if (!wasDirty && nextOperations == CurrentOperations && operationsBeforeSave.Count > 0) - OnPropertyChanged("IsDirty"); } } - internal virtual void HandleSave(IObjectState serverState) + /// + /// 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. + /// + [ParseFieldName("objectId")] + public string ObjectId { - lock (mutex) + get => State.ObjectId; + set { - IDictionary 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)); - MergeFromServer(serverState); + IsDirty = true; + SetObjectIdInternal(value); } } - internal virtual void MergeFromServer(IObjectState serverState) - { - // Make a new serverData with fetched values. - Dictionary newServerData = serverState.ToDictionary(t => t.Key, t => t.Value); + /// + /// Gets the last time this object was updated as the server sees it, so that if you make changes + /// to a ParseObject, 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. + /// + [ParseFieldName("updatedAt")] + public DateTime? UpdatedAt => State.UpdatedAt; - lock (mutex) + public IDictionary CurrentOperations + { + get { - // Trigger handler based on serverState - if (serverState.ObjectId != null) + lock (Mutex) { - // If the objectId is being merged in, consider this object to be fetched. - hasBeenFetched = true; - OnPropertyChanged("IsDataAvailable"); + return OperationSetQueue.Last.Value; } - 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(); + internal object Mutex { get; } = new object { }; - foreach (KeyValuePair pair in serverState) + public IObjectState State { get; private set; } + + internal 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) { - object value = pair.Value; - if (value is ParseObject) - { - // Resolve fetched object. - ParseObject parseObject = value as ParseObject; - if (fetchedObject.ContainsKey(parseObject.ObjectId)) - value = fetchedObject[parseObject.ObjectId]; - } - newServerData[pair.Key] = value; + return Client.CanBeSerializedAsValue(EstimatedData); } - - IsDirty = false; - serverState = serverState.MutatedClone(mutableClone => mutableClone.ServerData = newServerData); - MutateState(mutableClone => mutableClone.Apply(serverState)); } } - internal void MergeFromObject(ParseObject other) - { - // If they point to the same instance, we don't need to merge - lock (mutex) - if (this == other) - return; + bool Dirty { get; set; } - // Clear out any changes on this object. - if (operationSetQueue.Count != 1) - throw new InvalidOperationException("Attempt to MergeFromObject during save."); - operationSetQueue.Clear(); - foreach (IDictionary operationSet in other.operationSetQueue) - operationSetQueue.AddLast(operationSet.ToDictionary(entry => entry.Key, entry => entry.Value)); + internal IDictionary EstimatedData { get; } = new Dictionary { }; - lock (mutex) - state = other.State; - RebuildEstimatedData(); - } + internal bool Fetched { get; set; } - private bool HasDirtyChildren + bool HasDirtyChildren { get { - lock (mutex) + lock (Mutex) + { return FindUnsavedChildren().FirstOrDefault() != null; + } } } + LinkedList> OperationSetQueue { get; } = new LinkedList>(); + + SynchronizedEventHandler PropertyChangedHandler { get; } = new SynchronizedEventHandler(); + /// - /// Flattens dictionaries and lists into a single enumerable of all contained objects - /// that can then be queried over. + /// Gets or sets a value on the object. It is recommended to name + /// keys in partialCamelCaseLikeThis. /// - /// The root of the traversal - /// Whether to traverse into ParseObjects' children - /// Whether to include the root in the result - /// - internal static IEnumerable DeepTraversal(object root, bool traverseParseObjects = false, bool yieldRoot = false) - { - IEnumerable items = DeepTraversalInternal(root, traverseParseObjects, new HashSet(new IdentityEqualityComparer())); - if (yieldRoot) - return new[] { root }.Concat(items); - else - return items; - } - - private static IEnumerable DeepTraversalInternal(object root, bool traverseParseObjects, ICollection seen) + /// 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] { - seen.Add(root); - System.Collections.IEnumerable itemsToVisit = isCompiledByIL2CPP ? null : (IEnumerable) null; - IDictionary dict = Conversion.As>(root); - if (dict != null) - { - itemsToVisit = dict.Values; - } - else + get { - IList list = Conversion.As>(root); - if (list != null) + lock (Mutex) { - itemsToVisit = list; - } - else if (traverseParseObjects) - { - if (root is ParseObject obj) + CheckGetAccess(key); + object value = EstimatedData[key]; + + // A relation may be deserialized without a parent or key. Either way, + // make sure it's consistent. + + if (value is ParseRelationBase relation) { - itemsToVisit = obj.Keys.ToList().Select(k => obj[k]); + relation.EnsureParentAndKey(this, key); } + + return value; } } - if (itemsToVisit != null) + set { - foreach (object i in itemsToVisit) + lock (Mutex) { - if (!seen.Contains(i)) - { - yield return i; - IEnumerable children = DeepTraversalInternal(i, traverseParseObjects, seen); - foreach (object child in children) - { - yield return child; - } - } + CheckKeyIsMutable(key); + Set(key, value); } } } - private IEnumerable FindUnsavedChildren() => 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 ParseObject which have been fetched. - private IDictionary CollectFetchedObjects() => DeepTraversal(estimatedData).OfType().Where(o => o.ObjectId != null && o.IsDataAvailable).GroupBy(o => o.ObjectId).ToDictionary(group => group.Key, group => group.Last()); - - internal static IDictionary ToJSONObjectForSaving(IDictionary operations) - { - Dictionary result = new Dictionary(); - foreach (KeyValuePair pair in operations) - result[pair.Key] = PointerOrLocalIdEncoder.Instance.Encode(pair.Value); - return result; - } - - internal IDictionary ServerDataToJSONObjectForSerialization() => 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. + /// Adds a value for the given key, throwing an Exception if the key + /// already has a value. /// - internal IDictionary StartSave() - { - lock (mutex) - { - IDictionary currentOperations = CurrentOperations; - operationSetQueue.AddLast(new Dictionary()); - OnPropertyChanged("IsDirty"); - return currentOperations; - } - } - - protected virtual Task SaveAsync(Task toAwait, CancellationToken cancellationToken) + /// + /// This allows you to use collection initialization syntax when creating ParseObjects, + /// such as: + /// + /// var obj = new ParseObject("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) { - IDictionary currentOperations = null; - if (!IsDirty) - return Task.FromResult(0); - - Task deepSaveTask; - string sessionToken; - lock (mutex) + lock (Mutex) { - // Get the JSON representation of the object. - currentOperations = StartSave(); - - sessionToken = ParseUser.CurrentSessionToken; + if (ContainsKey(key)) + { + throw new ArgumentException("Key already exists", key); + } - deepSaveTask = DeepSaveAsync(estimatedData, sessionToken, cancellationToken); + this[key] = value; } - - return deepSaveTask.OnSuccess(_ => toAwait).Unwrap().OnSuccess(_ => ObjectController.SaveAsync(state, currentOperations, sessionToken, cancellationToken)).Unwrap().ContinueWith(t => - { - if (t.IsFaulted || t.IsCanceled) - HandleFailedSave(currentOperations); - else - HandleSave(t.Result); - return t; - }).Unwrap(); } /// - /// Saves this object to the server. - /// - public Task SaveAsync() => SaveAsync(CancellationToken.None); - - /// - /// Saves this object to the server. + /// Atomically adds objects to the end of the list associated with the given key. /// - /// The cancellation token. - public Task SaveAsync(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => SaveAsync(toAwait, cancellationToken), cancellationToken); - - internal virtual Task FetchAsyncInternal(Task toAwait, CancellationToken cancellationToken) => toAwait.OnSuccess(_ => ObjectId == null ? throw new InvalidOperationException("Cannot refresh an object that hasn't been saved to the server.") : ObjectController.FetchAsync(state, ParseUser.CurrentSessionToken, cancellationToken)).Unwrap().OnSuccess(t => - { - HandleFetchResult(t.Result); - return this; - }); - - private static Task DeepSaveAsync(object obj, string sessionToken, CancellationToken cancellationToken) + /// The key. + /// The objects to add. + public void AddRangeToList(string key, IEnumerable values) { - List objects = new List(); - CollectDirtyChildren(obj, objects); - - HashSet uniqueObjects = new HashSet(objects, new IdentityEqualityComparer()); - - List saveDirtyFileTasks = DeepTraversal(obj, true).OfType().Where(f => f.IsDirty).Select(f => f.SaveAsync(cancellationToken)).ToList(); - - return Task.WhenAll(saveDirtyFileTasks).OnSuccess(_ => + lock (Mutex) { - 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. - List current = (from item in remaining where item.CanBeSerialized select item).ToList(); - List 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 ParseObject with a relation to a cycle."); - } - - // Save all of the objects in current. - return EnqueueForAll(current, toAwait => - { - return toAwait.OnSuccess(__ => - { - List states = (from item in current - select item.state).ToList(); - List> operationsList = (from item in current - select item.StartSave()).ToList(); - - IList> 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 - { - IObjectState[] 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(); + CheckKeyIsMutable(key); + PerformOperation(key, new ParseAddOperation(values.Cast())); + } } /// - /// Saves each object in the provided list. - /// - /// The objects to save. - public static Task SaveAllAsync(IEnumerable objects) where T : ParseObject => 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 : ParseObject => DeepSaveAsync(objects.ToList(), ParseUser.CurrentSessionToken, cancellationToken); - - #endregion - - #region Fetch Object(s) - - /// - /// Fetches this object with the data from the server. + /// 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 cancellation token. - internal Task FetchAsyncInternal(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => FetchAsyncInternal(toAwait, cancellationToken), - cancellationToken); - - internal Task FetchIfNeededAsyncInternal( - Task toAwait, CancellationToken cancellationToken) + /// The key. + /// The objects to add. + public void AddRangeUniqueToList(string key, IEnumerable values) { - if (!IsDataAvailable) + lock (Mutex) { - return FetchAsyncInternal(toAwait, cancellationToken); + CheckKeyIsMutable(key); + PerformOperation(key, new ParseAddUniqueOperation(values.Cast())); } - return Task.FromResult(this); } - /// - /// If this ParseObject 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) => 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 : ParseObject => 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 : ParseObject => EnqueueForAll(objects.Cast(), (Task toAwait) => - { - return FetchAllInternalAsync(objects, false, toAwait, cancellationToken); - }, cancellationToken); + #endregion /// - /// Fetches all of the objects in the provided list. + /// Atomically adds an object to the end of the list associated with the given key. /// - /// The objects to fetch. - /// The list passed in for convenience. - public static Task> FetchAllAsync( - IEnumerable objects) where T : ParseObject => FetchAllAsync(objects, CancellationToken.None); + /// The key. + /// The object to add. + public void AddToList(string key, object value) => AddRangeToList(key, new[] { value }); /// - /// Fetches all of the objects in the provided list. + /// 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 objects to fetch. - /// The cancellation token. - /// The list passed in for convenience. - public static Task> FetchAllAsync( - IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject => EnqueueForAll(objects.Cast(), (Task toAwait) => - { - return FetchAllInternalAsync(objects, true, toAwait, cancellationToken); - }, cancellationToken); + /// The key. + /// The object to add. + public void AddUniqueToList(string key, object value) => AddRangeUniqueToList(key, new object[] { value }); /// - /// Fetches all of the objects in the list. + /// Returns whether this object has a particular key. /// - /// 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 : ParseObject => toAwait.OnSuccess(_ => - { - if (objects.Any(obj => { return obj.state.ObjectId == null; })) - { - throw new InvalidOperationException("You cannot fetch objects that haven't already been saved."); - } - - List 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. - Dictionary>> findsByClass = - (from obj in objectsToFetch - group obj.ObjectId by obj.ClassName into classGroup - where classGroup.Count() > 0 - select new - { - ClassName = classGroup.Key, - FindTask = new ParseQuery(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) + /// The key to check for + public bool ContainsKey(string key) { - if (ObjectId == null) + lock (Mutex) { - return Task.FromResult(0); + return EstimatedData.ContainsKey(key); } - - string sessionToken = ParseUser.CurrentSessionToken; - - return toAwait.OnSuccess(_ => - { - return ObjectController.DeleteAsync(State, sessionToken, cancellationToken); - }).Unwrap().OnSuccess(_ => IsDirty = true); } /// @@ -742,172 +423,92 @@ internal Task DeleteAsync(Task toAwait, CancellationToken cancellationToken) /// Deletes this object on the server. /// /// The cancellation token. - public Task DeleteAsync(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => DeleteAsync(toAwait, cancellationToken), - cancellationToken); + public Task DeleteAsync(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => DeleteAsync(toAwait, cancellationToken), cancellationToken); /// - /// Deletes each object in the provided list. + /// Gets a value for the key of a particular type. + /// The type to convert the value to. Supported types are + /// ParseObject and its descendents, Parse types such as ParseRelation and ParseGeopoint, + /// 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 objects to delete. - public static Task DeleteAllAsync(IEnumerable objects) where T : ParseObject => DeleteAllAsync(objects, CancellationToken.None); + public T Get(string key) => Conversion.To(this[key]); /// - /// Deletes each object in the provided list. + /// Access or create a Relation value for a key. /// - /// The objects to delete. - /// The cancellation token. - public static Task DeleteAllAsync( - IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject + /// The type of object to create a relation for. + /// The key for the relation field. + /// A ParseRelation for the key. + public ParseRelation GetRelation(string key) where T : ParseObject { - HashSet uniqueObjects = new HashSet(objects.OfType().ToList(), - new IdentityEqualityComparer()); - - return EnqueueForAll(uniqueObjects, toAwait => - { - List states = uniqueObjects.Select(t => t.state).ToList(); - return toAwait.OnSuccess(_ => - { - IList deleteTasks = ObjectController.DeleteAllAsync(states, - ParseUser.CurrentSessionToken, - cancellationToken); - - return Task.WhenAll(deleteTasks); - }).Unwrap().OnSuccess(t => - { - // Dirty all objects in memory. - foreach (ParseObject obj in uniqueObjects) - { - obj.IsDirty = true; - } + // All the sanity checking is done when add or remove is called. - return (object) null; - }); - }, cancellationToken); + TryGetValue(key, out ParseRelation relation); + return relation ?? new ParseRelation(this, key); } - #endregion - - private static void CollectDirtyChildren(object node, - IList dirtyChildren, - ICollection seen, - ICollection seenNew) + /// + /// A helper function for checking whether two ParseObjects point to + /// the same object in the cloud. + /// + public bool HasSameId(ParseObject other) { - foreach (ParseObject obj in DeepTraversal(node).OfType()) + lock (Mutex) { - 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()) { 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); - } + return other is { } && Equals(ClassName, other.ClassName) && Equals(ObjectId, other.ObjectId); } } + #region Atomic Increment + /// - /// Helper version of CollectDirtyChildren so that callers don't have to add the internally - /// used parameters. + /// Atomically increments the given key by 1. /// - private static void CollectDirtyChildren(object node, IList dirtyChildren) => CollectDirtyChildren(node, - dirtyChildren, - new HashSet(new IdentityEqualityComparer()), - new HashSet(new IdentityEqualityComparer())); + /// The key to increment. + public void Increment(string key) => Increment(key, 1); /// - /// Returns true if the given object can be serialized for saving as a value - /// that is pointed to by a ParseObject. + /// Atomically increments the given key by the given number. /// - private static bool CanBeSerializedAsValue(object value) => DeepTraversal(value, yieldRoot: true) - .OfType() - .All(o => o.ObjectId != null); - - private bool CanBeSerialized + /// The key to increment. + /// The amount to increment by. + public void Increment(string key, long amount) { - get + lock (Mutex) { - // 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); - } + CheckKeyIsMutable(key); + PerformOperation(key, new ParseIncrementOperation(amount)); } } /// - /// Adds a task to the queue for all of the given objects. + /// Atomically increments the given key by the given number. /// - private static Task EnqueueForAll(IEnumerable objects, - Func> taskStart, CancellationToken cancellationToken) + /// The key to increment. + /// The amount to increment by. + public void Increment(string key, double amount) { - // The task that will be complete when all of the child queues indicate they're ready to start. - TaskCompletionSource 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. - LockSet lockSet = new LockSet(objects.Select(o => o.taskQueue.Mutex)); - - lockSet.Enter(); - try + lock (Mutex) { - - // 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. - List childTasks = new List(); - foreach (ParseObject 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; + CheckKeyIsMutable(key); + PerformOperation(key, new ParseIncrementOperation(amount)); } - finally + } + + /// + /// Indicates whether key is unsaved for this ParseObject. + /// + /// 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) { - lockSet.Exit(); + return CurrentOperations.ContainsKey(key); } } @@ -917,698 +518,604 @@ private static Task EnqueueForAll(IEnumerable objects, /// The key to remove. public virtual void Remove(string key) { - lock (mutex) + lock (Mutex) { CheckKeyIsMutable(key); - PerformOperation(key, ParseDeleteOperation.Instance); } } - - private void ApplyOperations(IDictionary operations, - IDictionary map) + /// + /// 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) + lock (Mutex) { - foreach (KeyValuePair pair in operations) - { - map.TryGetValue(pair.Key, out object oldValue); - object newValue = pair.Value.Apply(oldValue, pair.Key); - if (newValue != ParseDeleteOperation.DeleteToken) - { - map[pair.Key] = newValue; - } - else - { - map.Remove(pair.Key); - } - } + CheckKeyIsMutable(key); + PerformOperation(key, new ParseRemoveOperation(values.Cast())); } } /// - /// Regenerates the estimatedData map from the serverData and operations. + /// Clears any changes to this object made since the last call to . /// - internal void RebuildEstimatedData() + public void Revert() { - lock (mutex) + lock (Mutex) { - estimatedData.Clear(); - foreach (KeyValuePair item in state) - { - estimatedData.Add(item); - } - foreach (IDictionary operations in operationSetQueue) + bool wasDirty = CurrentOperations.Count > 0; + if (wasDirty) { - ApplyOperations(operations, estimatedData); + CurrentOperations.Clear(); + RebuildEstimatedData(); + OnPropertyChanged(nameof(IsDirty)); } - // 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(null); } } /// - /// PerformOperation is like setting a value at an index, but instead of - /// just taking a new value, it takes a ParseFieldOperation that modifies the value. + /// Saves this object to the server. /// - internal void PerformOperation(string key, IParseFieldOperation operation) + public Task SaveAsync() => SaveAsync(CancellationToken.None); + + /// + /// Saves this object to the server. + /// + /// The cancellation token. + public Task SaveAsync(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => SaveAsync(toAwait, cancellationToken), cancellationToken); + + /// + /// 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) { - lock (mutex) + lock (Mutex) { - estimatedData.TryGetValue(key, out object oldValue); - object newValue = operation.Apply(oldValue, key); - if (newValue != ParseDeleteOperation.DeleteToken) - { - estimatedData[key] = newValue; - } - else - { - estimatedData.Remove(key); - } - - bool wasDirty = CurrentOperations.Count > 0; - CurrentOperations.TryGetValue(key, out IParseFieldOperation oldOperation); - IParseFieldOperation newOperation = operation.MergeWithPrevious(oldOperation); - CurrentOperations[key] = newOperation; - if (!wasDirty) + if (ContainsKey(key)) { - OnPropertyChanged("IsDirty"); + try + { + T temp = Conversion.To(this[key]); + result = temp; + return true; + } + catch + { + result = default; + return false; + } } - OnFieldsChanged(new[] { key }); + result = default; + return false; } } - /// - /// 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) + #endregion + + #region Delete Object + + internal Task DeleteAsync(Task toAwait, CancellationToken cancellationToken) { - if (key == null) + if (ObjectId == null) { - throw new ArgumentNullException("key"); + return Task.FromResult(0); } + + string sessionToken = Client.GetCurrentSessionToken(); + + return toAwait.OnSuccess(_ => Client.ObjectController.DeleteAsync(State, sessionToken, cancellationToken)).Unwrap().OnSuccess(_ => IsDirty = true); } + internal virtual Task FetchAsyncInternal(Task toAwait, CancellationToken cancellationToken) => toAwait.OnSuccess(_ => ObjectId == null ? throw new InvalidOperationException("Cannot refresh an object that hasn't been saved to the server.") : Client.ObjectController.FetchAsync(State, Client.GetCurrentSessionToken(), cancellationToken)).Unwrap().OnSuccess(task => + { + HandleFetchResult(task.Result); + return this; + }); + + #endregion + + #region Fetch Object(s) + /// - /// Gets or sets a value on the object. It is recommended to name - /// keys in partialCamelCaseLikeThis. + /// Fetches this object with the data from the server. /// - /// 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] + /// The cancellation token. + internal Task FetchAsyncInternal(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => FetchAsyncInternal(toAwait, cancellationToken), cancellationToken); + + internal Task FetchIfNeededAsyncInternal(Task toAwait, CancellationToken cancellationToken) => !IsDataAvailable ? FetchAsyncInternal(toAwait, cancellationToken) : Task.FromResult(this); + + /// + /// If this ParseObject 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) => taskQueue.Enqueue(toAwait => FetchIfNeededAsyncInternal(toAwait, cancellationToken), cancellationToken); + + internal void HandleFailedSave(IDictionary operationsBeforeSave) { - get + lock (Mutex) { - lock (mutex) - { - CheckGetAccess(key); + LinkedListNode> opNode = OperationSetQueue.Find(operationsBeforeSave); + IDictionary nextOperations = opNode.Next.Value; + bool wasDirty = nextOperations.Count > 0; + OperationSetQueue.Remove(opNode); - object value = estimatedData[key]; + // Merge the data from the failed save into the next save. - // A relation may be deserialized without a parent or key. Either way, - // make sure it's consistent. - if (value is ParseRelationBase relation) - { - relation.EnsureParentAndKey(this, key); - } + foreach (KeyValuePair pair in operationsBeforeSave) + { + IParseFieldOperation operation1 = pair.Value; - return value; + nextOperations.TryGetValue(pair.Key, out IParseFieldOperation operation2); + nextOperations[pair.Key] = operation2 is { } ? operation2.MergeWithPrevious(operation1) : operation1; } - } - set - { - lock (mutex) - { - CheckKeyIsMutable(key); - Set(key, value); + if (!wasDirty && nextOperations == CurrentOperations && operationsBeforeSave.Count > 0) + { + OnPropertyChanged(nameof(IsDirty)); } } } - /// - /// 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) + public virtual void HandleFetchResult(IObjectState serverState) { - lock (mutex) + lock (Mutex) { - OnSettingValue(ref key, ref value); + MergeFromServer(serverState); + } + } - if (!ParseEncoder.IsValidType(value)) - { - throw new ArgumentException("Invalid type for value: " + value.GetType().ToString()); - } + internal virtual void HandleSave(IObjectState serverState) + { + lock (Mutex) + { + IDictionary operationsBeforeSave = OperationSetQueue.First.Value; + OperationSetQueue.RemoveFirst(); - PerformOperation(key, new ParseSetOperation(value)); + // Merge the data from the save and the data from the server into serverData. + + MutateState(mutableClone => mutableClone.Apply(operationsBeforeSave)); + MergeFromServer(serverState); } } - internal void SetIfDifferent(string key, T value) + internal void MergeFromObject(ParseObject other) { - bool hasCurrent = TryGetValue(key, out T current); - if (value == null) + // If they point to the same instance, we don't need to merge + + lock (Mutex) { - if (hasCurrent) + if (this == other) { - PerformOperation(key, ParseDeleteOperation.Instance); + return; } - return; } - if (!hasCurrent || !value.Equals(current)) + + // Clear out any changes on this object. + + if (OperationSetQueue.Count != 1) { - Set(key, value); + throw new InvalidOperationException("Attempt to MergeFromObject during save."); } - } - - #region Atomic Increment - /// - /// Atomically increments the given key by 1. - /// - /// The key to increment. - public void Increment(string key) => Increment(key, 1); + OperationSetQueue.Clear(); - /// - /// 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) + foreach (IDictionary operationSet in other.OperationSetQueue) { - CheckKeyIsMutable(key); + OperationSetQueue.AddLast(operationSet.ToDictionary(entry => entry.Key, entry => entry.Value)); + } - PerformOperation(key, new ParseIncrementOperation(amount)); + lock (Mutex) + { + State = other.State; } + + RebuildEstimatedData(); } - /// - /// 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) + internal virtual void MergeFromServer(IObjectState serverState) { - lock (mutex) + // Make a new serverData with fetched values. + + Dictionary newServerData = serverState.ToDictionary(t => t.Key, t => t.Value); + + lock (Mutex) { - CheckKeyIsMutable(key); + // Trigger handler based on serverState - PerformOperation(key, new ParseIncrementOperation(amount)); - } - } + if (serverState.ObjectId != null) + { + // If the objectId is being merged in, consider this object to be fetched. - #endregion + Fetched = true; + OnPropertyChanged(nameof(IsDataAvailable)); + } - /// - /// 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 }); + if (serverState.UpdatedAt != null) + { + OnPropertyChanged(nameof(UpdatedAt)); + } - /// - /// 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); + if (serverState.CreatedAt != null) + { + OnPropertyChanged(nameof(CreatedAt)); + } - PerformOperation(key, new ParseAddOperation(values.Cast())); - } - } + // We cache the fetched object because subsequent Save operation might flush the fetched objects into Pointers. - /// - /// 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 }); + IDictionary fetchedObject = CollectFetchedObjects(); - /// - /// 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); + foreach (KeyValuePair pair in serverState) + { + object value = pair.Value; - PerformOperation(key, new ParseAddUniqueOperation(values.Cast())); + if (value is ParseObject) + { + // Resolve fetched object. + + ParseObject entity = value as ParseObject; + + if (fetchedObject.ContainsKey(entity.ObjectId)) + { + value = fetchedObject[entity.ObjectId]; + } + } + newServerData[pair.Key] = value; + } + + IsDirty = false; + MutateState(mutableClone => mutableClone.Apply(serverState.MutatedClone(mutableClone => mutableClone.ServerData = newServerData))); } } - /// - /// 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) + internal void MutateState(Action mutator) { - lock (mutex) + lock (Mutex) { - CheckKeyIsMutable(key); + State = State.MutatedClone(mutator); - PerformOperation(key, new ParseRemoveOperation(values.Cast())); + // Refresh the estimated data. + + RebuildEstimatedData(); } } /// - /// Returns whether this object has a particular key. + /// Override to run validations on key/value pairs. Make sure to still + /// call the base version. /// - /// The key to check for - public bool ContainsKey(string key) + internal virtual void OnSettingValue(ref string key, ref object value) { - lock (mutex) + if (key is null) { - return estimatedData.ContainsKey(key); + throw new ArgumentNullException(nameof(key)); } } /// - /// Gets a value for the key of a particular type. - /// The type to convert the value to. Supported types are - /// ParseObject and its descendents, Parse types such as ParseRelation and ParseGeopoint, - /// 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) => 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 ParseRelation for the key. - public ParseRelation GetRelation(string key) where T : ParseObject - { - // All the sanity checking is done when add or remove is called. - TryGetValue(key, out ParseRelation relation); - return relation ?? new ParseRelation(this, key); - } - - /// - /// Populates result with the value for the key, if possible. + /// PerformOperation is like setting a value at an index, but instead of + /// just taking a new value, it takes a ParseFieldOperation that modifies the value. /// - /// 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) + internal void PerformOperation(string key, IParseFieldOperation operation) { - lock (mutex) + lock (Mutex) { - if (ContainsKey(key)) + EstimatedData.TryGetValue(key, out object oldValue); + object newValue = operation.Apply(oldValue, key); + + if (newValue != ParseDeleteOperation.Token) { - try - { - T temp = Conversion.To(this[key]); - result = temp; - return true; - } - catch - { - result = default(T); - return false; - } + EstimatedData[key] = newValue; + } + else + { + EstimatedData.Remove(key); + } + + bool wasDirty = CurrentOperations.Count > 0; + CurrentOperations.TryGetValue(key, out IParseFieldOperation oldOperation); + IParseFieldOperation newOperation = operation.MergeWithPrevious(oldOperation); + CurrentOperations[key] = newOperation; + + if (!wasDirty) + { + OnPropertyChanged(nameof(IsDirty)); } - result = default(T); - return false; + + OnFieldsChanged(new[] { key }); } } /// - /// Gets whether the ParseObject has been fetched. + /// Regenerates the estimatedData map from the serverData and operations. /// - public bool IsDataAvailable + internal void RebuildEstimatedData() { - get + lock (Mutex) { - lock (mutex) + EstimatedData.Clear(); + + foreach (KeyValuePair item in State) { - return hasBeenFetched; + EstimatedData.Add(item); } - } - } - - private bool CheckIsDataAvailable(string key) - { - lock (mutex) - { - return IsDataAvailable || estimatedData.ContainsKey(key); - } - } - - private void CheckGetAccess(string key) - { - lock (mutex) - { - if (!CheckIsDataAvailable(key)) + foreach (IDictionary operations in OperationSetQueue) { - throw new InvalidOperationException( - "ParseObject has no data for this key. Call FetchIfNeededAsync() to get the data."); + ApplyOperations(operations, EstimatedData); } - } - } - private void CheckKeyIsMutable(string key) - { - if (!IsKeyMutable(key)) - { - throw new InvalidOperationException( - "Cannot change the `" + key + "` property of a `" + ClassName + "` object."); + // 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(default); } } - protected virtual bool IsKeyMutable(string key) => true; + public IDictionary ServerDataToJSONObjectForSerialization() => PointerOrLocalIdEncoder.Instance.Encode(State.ToDictionary(pair => pair.Key, t => t.Value)) as IDictionary; /// - /// A helper function for checking whether two ParseObjects point to - /// the same object in the cloud. + /// Perform Set internally which is not gated by mutability check. /// - public bool HasSameId(ParseObject other) + /// key for the object. + /// the value for the key. + public void Set(string key, object value) { - lock (mutex) + lock (Mutex) { - return other != null && - Equals(ClassName, other.ClassName) && - Equals(ObjectId, other.ObjectId); - } - } + OnSettingValue(ref key, ref value); - internal IDictionary CurrentOperations - { - get - { - lock (mutex) + if (!ParseDataEncoder.Validate(value)) { - return operationSetQueue.Last.Value; + throw new ArgumentException("Invalid type for value: " + value.GetType().ToString()); } + + PerformOperation(key, new ParseSetOperation(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. + /// Allows subclasses to set values for non-pointer construction. /// - public ICollection Keys + internal virtual void SetDefaultValues() { } + + public void SetIfDifferent(string key, T value) { - get + bool hasCurrent = TryGetValue(key, out T current); + + if (value == null) { - lock (mutex) + if (hasCurrent) { - return estimatedData.Keys; + PerformOperation(key, ParseDeleteOperation.Instance); } + return; } - } - /// - /// Gets or sets the ParseACL governing this object. - /// - [ParseFieldName("ACL")] - public ParseACL ACL - { - get => GetProperty(null, "ACL"); - set => SetProperty(value, "ACL"); + if (!hasCurrent || !value.Equals(current)) + { + Set(key, value); + } } + #endregion + + #region Save Object(s) + /// - /// Returns true if this object was created by the Parse server when the - /// object might have already been there (e.g. in the case of a Facebook - /// login) + /// Pushes new operations onto the queue and returns the current set of operations. /// -#if !UNITY - public -#else - internal -#endif - bool IsNew + internal IDictionary StartSave() { - get => state.IsNew; -#if !UNITY - internal -#endif - set + lock (Mutex) { - MutateState(mutableClone => - { - mutableClone.IsNew = value; - }); - OnPropertyChanged("IsNew"); + IDictionary currentOperations = CurrentOperations; + OperationSetQueue.AddLast(new Dictionary()); + OnPropertyChanged(nameof(IsDirty)); + return currentOperations; } } + #endregion + /// - /// Gets the last time this object was updated as the server sees it, so that if you make changes - /// to a ParseObject, 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. + /// Gets the value of a property based upon its associated ParseFieldName attribute. /// - [ParseFieldName("updatedAt")] - public DateTime? UpdatedAt => state.UpdatedAt; + /// The value of the property. + /// The name of the property. + /// The return type of the property. + protected T GetProperty([CallerMemberName] string propertyName = null) => GetProperty(default(T), propertyName); /// - /// Gets the first time this object was saved as the server sees it, so that if you create a - /// ParseObject, 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. + /// Gets the value of a property based upon its associated ParseFieldName attribute. /// - [ParseFieldName("createdAt")] - public DateTime? CreatedAt => state.CreatedAt; + /// The value of the property. + /// The value to return if the property is not present on the ParseObject. + /// The name of the property. + /// The return type of the property. + protected T GetProperty(T defaultValue, [CallerMemberName] string propertyName = null) => TryGetValue(Client.GetFieldForPropertyName(ClassName, propertyName), out T result) ? result : defaultValue; /// - /// Indicates whether this ParseObject has unsaved changes. + /// Gets a relation for a property based upon its associated ParseFieldName attribute. /// - public bool IsDirty + /// The ParseRelation for the property. + /// The name of the property. + /// The ParseObject subclass type of the ParseRelation. + protected ParseRelation GetRelationProperty([CallerMemberName] string propertyName = null) where T : ParseObject => GetRelation(Client.GetFieldForPropertyName(ClassName, propertyName)); + + protected virtual bool CheckKeyMutable(string key) => true; + + /// + /// 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 void OnFieldsChanged(IEnumerable fields) { - get - { - lock (mutex) - { return CheckIsDirty(true); } - } - internal set + IDictionary mappings = Client.ClassController.GetPropertyMappings(ClassName); + + foreach (string property in mappings is { } ? fields is { } ? from mapping in mappings join field in fields on mapping.Value equals field select mapping.Key : mappings.Keys : Enumerable.Empty()) { - lock (mutex) - { - dirty = value; - OnPropertyChanged("IsDirty"); - } + OnPropertyChanged(property); } + + OnPropertyChanged("Item[]"); } /// - /// Indicates whether key is unsaved for this ParseObject. + /// 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) /// - /// The key to check for. - /// true if the key has been altered and not saved yet, otherwise - /// false. - public bool IsKeyDirty(string key) + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChangedHandler.Invoke(this, new PropertyChangedEventArgs(propertyName)); + + protected virtual Task SaveAsync(Task toAwait, CancellationToken cancellationToken) { - lock (mutex) + IDictionary currentOperations = null; + + if (!IsDirty) { - return CurrentOperations.ContainsKey(key); + return Task.CompletedTask; } - } - private bool CheckIsDirty(bool considerChildren) - { - lock (mutex) + Task deepSaveTask; + string sessionToken; + + lock (Mutex) { - return (dirty || CurrentOperations.Count > 0 || (considerChildren && HasDirtyChildren)); + // Get the JSON representation of the object. + + currentOperations = StartSave(); + sessionToken = Client.GetCurrentSessionToken(); + deepSaveTask = Client.DeepSaveAsync(EstimatedData, sessionToken, cancellationToken); } - } - /// - /// 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. - /// - [ParseFieldName("objectId")] - public string ObjectId - { - get => state.ObjectId; - set + return deepSaveTask.OnSuccess(_ => toAwait).Unwrap().OnSuccess(_ => Client.ObjectController.SaveAsync(State, currentOperations, sessionToken, cancellationToken)).Unwrap().ContinueWith(task => { - IsDirty = true; - SetObjectIdInternal(value); - } + if (task.IsFaulted || task.IsCanceled) + { + HandleFailedSave(currentOperations); + } + else + { + HandleSave(task.Result); + } + + return task; + }).Unwrap(); } + /// - /// Sets the objectId without marking dirty. + /// Sets the value of a property based upon its associated ParseFieldName attribute. /// - /// The new objectId - private void SetObjectIdInternal(string objectId) + /// The new value. + /// The name of the property. + /// The type for the property. + protected void SetProperty(T value, [CallerMemberName] string propertyName = null) => this[Client.GetFieldForPropertyName(ClassName, propertyName)] = value; + + void ApplyOperations(IDictionary operations, IDictionary map) { - lock (mutex) + lock (Mutex) { - MutateState(mutableClone => + foreach (KeyValuePair pair in operations) { - mutableClone.ObjectId = objectId; - }); - OnPropertyChanged("ObjectId"); + map.TryGetValue(pair.Key, out object oldValue); + object newValue = pair.Value.Apply(oldValue, pair.Key); + + if (newValue != ParseDeleteOperation.Token) + { + map[pair.Key] = newValue; + } + else + { + map.Remove(pair.Key); + } + } } } - /// - /// Gets the class name for the ParseObject. - /// - public string ClassName => 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 ParseObjects, - /// such as: - /// - /// var obj = new ParseObject("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) + void CheckGetAccess(string key) { - lock (mutex) + lock (Mutex) { - if (ContainsKey(key)) + if (!CheckIsDataAvailable(key)) { - throw new ArgumentException("Key already exists", key); + throw new InvalidOperationException("ParseObject has no data for this key. Call FetchIfNeededAsync() to get the data."); } - this[key] = value; } } - IEnumerator> IEnumerable> - .GetEnumerator() + bool CheckIsDataAvailable(string key) { - lock (mutex) + lock (Mutex) { - return estimatedData.GetEnumerator(); + return IsDataAvailable || EstimatedData.ContainsKey(key); } } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + internal bool CheckIsDirty(bool considerChildren) { - lock (mutex) + lock (Mutex) { - return ((IEnumerable>) this).GetEnumerator(); + return Dirty || CurrentOperations.Count > 0 || considerChildren && HasDirtyChildren; } } - /// - /// Gets a for the type of object specified by - /// - /// - /// The class name of the object. - /// A new . - public static ParseQuery GetQuery(string className) + void CheckKeyIsMutable(string key) { - // Since we can't return a ParseQuery (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) + if (!CheckKeyMutable(key)) { - throw new ArgumentException( - "Use the class-specific query properties for class " + className, "className"); + throw new InvalidOperationException($@"Cannot change the ""{key}"" property of a ""{ClassName}"" object."); } - return new ParseQuery(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) + /// 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. /// - protected void OnFieldsChanged(IEnumerable fieldNames) - { - IDictionary mappings = SubclassingController.GetPropertyMappings(ClassName); - IEnumerable properties; + /// Map of objectId to ParseObject which have been fetched. + IDictionary CollectFetchedObjects() => Client.TraverseObjectDeep(EstimatedData).OfType().Where(o => o.ObjectId != null && o.IsDataAvailable).GroupBy(o => o.ObjectId).ToDictionary(group => group.Key, group => group.Last()); - 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 + IEnumerable FindUnsavedChildren() => Client.TraverseObjectDeep(EstimatedData).OfType().Where(o => o.IsDirty); + + IEnumerator> IEnumerable>.GetEnumerator() + { + lock (Mutex) { - properties = Enumerable.Empty(); + return EstimatedData.GetEnumerator(); } + } - foreach (string property in properties) + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + lock (Mutex) { - OnPropertyChanged(property); + return ((IEnumerable>) this).GetEnumerator(); } - 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 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. + /// Sets the objectId without marking dirty. /// - public event PropertyChangedEventHandler PropertyChanged + /// The new objectId + void SetObjectIdInternal(string objectId) { - add - { - propertyChanged.Add(value); - } - remove + lock (Mutex) { - propertyChanged.Remove(value); + MutateState(mutableClone => mutableClone.ObjectId = objectId); + OnPropertyChanged(nameof(ObjectId)); } } } diff --git a/Parse/ParsePush.cs b/Parse/ParsePush.cs index ab410621..93363b47 100644 --- a/Parse/ParsePush.cs +++ b/Parse/ParsePush.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Common.Internal; +using Parse.Abstractions.Library; using Parse.Push.Internal; namespace Parse @@ -14,20 +14,21 @@ namespace Parse /// public partial class ParsePush { - private object mutex; - private IPushState state; + object Mutex { get; } = new object { }; + + IPushState State { get; set; } + + IServiceHub Services { get; } + +#warning Make default(IServiceHub) the default value of serviceHub once all dependents properly inject it. /// /// Creates a push which will target every device. The Data field must be set before calling SendAsync. /// - public ParsePush() + public ParsePush(IServiceHub serviceHub) { - mutex = new object(); - // Default to everyone. - state = new MutablePushState - { - Query = ParseInstallation.Query - }; + Services = serviceHub ?? ParseClient.Instance; + State = new MutablePushState { Query = Services.GetInstallationQuery() }; } #region Properties @@ -39,15 +40,16 @@ public ParsePush() /// public ParseQuery Query { - get => state.Query; - set => MutateState(s => - { - if (s.Channels != null && value != null && value.GetConstraint("channels") != null) - { - throw new InvalidOperationException("A push may not have both Channels and a Query with a channels constraint"); - } - s.Query = value; - }); + get => State.Query; + set => MutateState(state => + { + if (state.Channels is { } && value is { } && value.GetConstraint("channels") is { }) + { + throw new InvalidOperationException("A push may not have both Channels and a Query with a channels constraint."); + } + + state.Query = value; + }); } /// @@ -63,15 +65,16 @@ public ParseQuery Query /// public IEnumerable Channels { - get => state.Channels; - set => MutateState(s => - { - if (value != null && s.Query != null && s.Query.GetConstraint("channels") != null) - { - throw new InvalidOperationException("A push may not have both Channels and a Query with a channels constraint"); - } - s.Channels = value; - }); + get => State.Channels; + set => MutateState(state => + { + if (value is { } && state.Query is { } && state.Query.GetConstraint("channels") is { }) + { + throw new InvalidOperationException("A push may not have both Channels and a Query with a channels constraint."); + } + + state.Channels = value; + }); } /// @@ -79,15 +82,16 @@ public IEnumerable Channels /// public DateTime? Expiration { - get => state.Expiration; - set => MutateState(s => - { - if (s.ExpirationInterval != null) - { - throw new InvalidOperationException("Cannot set Expiration after setting ExpirationInterval"); - } - s.Expiration = value; - }); + get => State.Expiration; + set => MutateState(state => + { + if (state.ExpirationInterval is { }) + { + throw new InvalidOperationException("Cannot set Expiration after setting ExpirationInterval."); + } + + state.Expiration = value; + }); } /// @@ -95,16 +99,18 @@ public DateTime? Expiration /// public DateTime? PushTime { - get => state.PushTime; - set => MutateState(s => - { - DateTime now = DateTime.Now; - if (value < now || value > now.AddDays(14)) - { - throw new InvalidOperationException("Cannot set PushTime in the past or more than two weeks later than now"); - } - s.PushTime = value; - }); + get => State.PushTime; + set => MutateState(state => + { + DateTime now = DateTime.Now; + + if (value < now || value > now.AddDays(14)) + { + throw new InvalidOperationException("Cannot set PushTime in the past or more than two weeks later than now."); + } + + state.PushTime = value; + }); } /// @@ -112,15 +118,16 @@ public DateTime? PushTime /// public TimeSpan? ExpirationInterval { - get => state.ExpirationInterval; - set => MutateState(s => - { - if (s.Expiration != null) - { - throw new InvalidOperationException("Cannot set ExpirationInterval after setting Expiration"); - } - s.ExpirationInterval = value; - }); + get => State.ExpirationInterval; + set => MutateState(state => + { + if (state.Expiration is { }) + { + throw new InvalidOperationException("Cannot set ExpirationInterval after setting Expiration."); + } + + state.ExpirationInterval = value; + }); } /// @@ -136,15 +143,16 @@ public TimeSpan? ExpirationInterval /// public IDictionary Data { - get => state.Data; - set => MutateState(s => - { - if (s.Alert != null && value != null) - { - throw new InvalidOperationException("A push may not have both an Alert and Data"); - } - s.Data = value; - }); + get => State.Data; + set => MutateState(state => + { + if (state.Alert is { } && value is { }) + { + throw new InvalidOperationException("A push may not have both an Alert and Data."); + } + + state.Data = value; + }); } /// @@ -158,33 +166,30 @@ public IDictionary Data /// public string Alert { - get => state.Alert; - set => MutateState(s => - { - if (s.Data != null && value != null) - { - throw new InvalidOperationException("A push may not have both an Alert and Data"); - } - s.Alert = value; - }); + get => State.Alert; + set => MutateState(state => + { + if (state.Data is { } && value is { }) + { + throw new InvalidOperationException("A push may not have both an Alert and Data."); + } + + state.Alert = value; + }); } #endregion - internal IDictionary Encode() => ParsePushEncoder.Instance.Encode(state); + internal IDictionary Encode() => ParsePushEncoder.Instance.Encode(State); - private void MutateState(Action func) + void MutateState(Action func) { - lock (mutex) + lock (Mutex) { - state = state.MutatedClone(func); + State = State.MutatedClone(func); } } - private static IParsePushController PushController => ParsePushPlugins.Instance.PushController; - - private static IParsePushChannelsController PushChannelsController => ParsePushPlugins.Instance.PushChannelsController; - #region Sending Push /// @@ -203,302 +208,7 @@ private void MutateState(Action func) /// console on http://parse.com /// /// CancellationToken to cancel the current operation. - public Task SendAsync(CancellationToken cancellationToken) => PushController.SendPushNotificationAsync(state, cancellationToken); - - /// - /// Pushes a simple message to every device. This is shorthand for: - /// - /// - /// var push = new ParsePush(); - /// push.Data = new Dictionary<string, object>{{"alert", alert}}; - /// return push.SendAsync(); - /// - /// - /// The alert message to send. - public static Task SendAlertAsync(string alert) - { - ParsePush push = new ParsePush - { - Alert = alert - }; - return push.SendAsync(); - } - - /// - /// Pushes a simple message to every device subscribed to channel. This is shorthand for: - /// - /// - /// var push = new ParsePush(); - /// push.Channels = new List<string> { channel }; - /// push.Data = new Dictionary<string, object>{{"alert", alert}}; - /// return push.SendAsync(); - /// - /// - /// The alert message to send. - /// An Installation must be subscribed to channel to receive this Push Notification. - public static Task SendAlertAsync(string alert, string channel) - { - ParsePush push = new ParsePush - { - Channels = new List { channel }, - Alert = alert - }; - return push.SendAsync(); - } - - /// - /// Pushes a simple message to every device subscribed to any of channels. This is shorthand for: - /// - /// - /// var push = new ParsePush(); - /// push.Channels = channels; - /// push.Data = new Dictionary<string, object>{{"alert", alert}}; - /// return push.SendAsync(); - /// - /// - /// The alert message to send. - /// An Installation must be subscribed to any of channels to receive this Push Notification. - public static Task SendAlertAsync(string alert, IEnumerable channels) - { - ParsePush push = new ParsePush - { - Channels = channels, - Alert = alert - }; - return push.SendAsync(); - } - - /// - /// Pushes a simple message to every device matching the target query. This is shorthand for: - /// - /// - /// var push = new ParsePush(); - /// push.Query = query; - /// push.Data = new Dictionary<string, object>{{"alert", alert}}; - /// return push.SendAsync(); - /// - /// - /// The alert message to send. - /// A query filtering the devices which should receive this Push Notification. - public static Task SendAlertAsync(string alert, ParseQuery query) - { - ParsePush push = new ParsePush - { - Query = query, - Alert = alert - }; - return push.SendAsync(); - } - - /// - /// Pushes an arbitrary payload to every device. This is shorthand for: - /// - /// - /// var push = new ParsePush(); - /// push.Data = data; - /// return push.SendAsync(); - /// - /// - /// A push payload. See the ParsePush.Data property for more information. - public static Task SendDataAsync(IDictionary data) - { - ParsePush push = new ParsePush - { - Data = data - }; - return push.SendAsync(); - } - - /// - /// Pushes an arbitrary payload to every device subscribed to channel. This is shorthand for: - /// - /// - /// var push = new ParsePush(); - /// push.Channels = new List<string> { channel }; - /// push.Data = data; - /// return push.SendAsync(); - /// - /// - /// A push payload. See the ParsePush.Data property for more information. - /// An Installation must be subscribed to channel to receive this Push Notification. - public static Task SendDataAsync(IDictionary data, string channel) - { - ParsePush push = new ParsePush - { - Channels = new List { channel }, - Data = data - }; - return push.SendAsync(); - } - - /// - /// Pushes an arbitrary payload to every device subscribed to any of channels. This is shorthand for: - /// - /// - /// var push = new ParsePush(); - /// push.Channels = channels; - /// push.Data = data; - /// return push.SendAsync(); - /// - /// - /// A push payload. See the ParsePush.Data property for more information. - /// An Installation must be subscribed to any of channels to receive this Push Notification. - public static Task SendDataAsync(IDictionary data, IEnumerable channels) - { - ParsePush push = new ParsePush - { - Channels = channels, - Data = data - }; - return push.SendAsync(); - } - - /// - /// Pushes an arbitrary payload to every device matching target. This is shorthand for: - /// - /// - /// var push = new ParsePush(); - /// push.Query = query - /// push.Data = data; - /// return push.SendAsync(); - /// - /// - /// A push payload. See the ParsePush.Data property for more information. - /// A query filtering the devices which should receive this Push Notification. - public static Task SendDataAsync(IDictionary data, ParseQuery query) - { - ParsePush push = new ParsePush - { - Query = query, - Data = data - }; - return push.SendAsync(); - } - - #endregion - - #region Receiving Push - - /// - /// An event fired when a push notification is received. - /// - public static event EventHandler ParsePushNotificationReceived - { - add - { - parsePushNotificationReceived.Add(value); - } - remove - { - parsePushNotificationReceived.Remove(value); - } - } - - internal static readonly SynchronizedEventHandler parsePushNotificationReceived = new SynchronizedEventHandler(); - - #endregion - - #region Push Subscription - - /// - /// Subscribe the current installation to this channel. This is shorthand for: - /// - /// - /// var installation = ParseInstallation.CurrentInstallation; - /// installation.AddUniqueToList("channels", channel); - /// installation.SaveAsync(); - /// - /// - /// The channel to which this installation should subscribe. - public static Task SubscribeAsync(string channel) => SubscribeAsync(new List { channel }, CancellationToken.None); - - /// - /// Subscribe the current installation to this channel. This is shorthand for: - /// - /// - /// var installation = ParseInstallation.CurrentInstallation; - /// installation.AddUniqueToList("channels", channel); - /// installation.SaveAsync(cancellationToken); - /// - /// - /// The channel to which this installation should subscribe. - /// CancellationToken to cancel the current operation. - public static Task SubscribeAsync(string channel, CancellationToken cancellationToken) => SubscribeAsync(new List { channel }, cancellationToken); - - /// - /// Subscribe the current installation to these channels. This is shorthand for: - /// - /// - /// var installation = ParseInstallation.CurrentInstallation; - /// installation.AddRangeUniqueToList("channels", channels); - /// installation.SaveAsync(); - /// - /// - /// The channels to which this installation should subscribe. - public static Task SubscribeAsync(IEnumerable channels) => SubscribeAsync(channels, CancellationToken.None); - - /// - /// Subscribe the current installation to these channels. This is shorthand for: - /// - /// - /// var installation = ParseInstallation.CurrentInstallation; - /// installation.AddRangeUniqueToList("channels", channels); - /// installation.SaveAsync(cancellationToken); - /// - /// - /// The channels to which this installation should subscribe. - /// CancellationToken to cancel the current operation. - public static Task SubscribeAsync(IEnumerable channels, CancellationToken cancellationToken) => PushChannelsController.SubscribeAsync(channels, cancellationToken); - - /// - /// Unsubscribe the current installation from this channel. This is shorthand for: - /// - /// - /// var installation = ParseInstallation.CurrentInstallation; - /// installation.Remove("channels", channel); - /// installation.SaveAsync(); - /// - /// - /// The channel from which this installation should unsubscribe. - public static Task UnsubscribeAsync(string channel) => UnsubscribeAsync(new List { channel }, CancellationToken.None); - - /// - /// Unsubscribe the current installation from this channel. This is shorthand for: - /// - /// - /// var installation = ParseInstallation.CurrentInstallation; - /// installation.Remove("channels", channel); - /// installation.SaveAsync(cancellationToken); - /// - /// - /// The channel from which this installation should unsubscribe. - /// CancellationToken to cancel the current operation. - public static Task UnsubscribeAsync(string channel, CancellationToken cancellationToken) => UnsubscribeAsync(new List { channel }, cancellationToken); - - /// - /// Unsubscribe the current installation from these channels. This is shorthand for: - /// - /// - /// var installation = ParseInstallation.CurrentInstallation; - /// installation.RemoveAllFromList("channels", channels); - /// installation.SaveAsync(); - /// - /// - /// The channels from which this installation should unsubscribe. - public static Task UnsubscribeAsync(IEnumerable channels) => UnsubscribeAsync(channels, CancellationToken.None); - - /// - /// Unsubscribe the current installation from these channels. This is shorthand for: - /// - /// - /// var installation = ParseInstallation.CurrentInstallation; - /// installation.RemoveAllFromList("channels", channels); - /// installation.SaveAsync(cancellationToken); - /// - /// - /// The channels from which this installation should unsubscribe. - /// CancellationToken to cancel the current operation. - public static Task UnsubscribeAsync(IEnumerable channels, CancellationToken cancellationToken) => PushChannelsController.UnsubscribeAsync(channels, cancellationToken); + public Task SendAsync(CancellationToken cancellationToken) => Services.PushController.SendPushNotificationAsync(State, cancellationToken); #endregion } diff --git a/Parse/ParseQuery.cs b/Parse/ParseQuery.cs index e714dd32..8e620935 100644 --- a/Parse/ParseQuery.cs +++ b/Parse/ParseQuery.cs @@ -1,16 +1,15 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. 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 Parse.Abstractions.Library; using Parse.Common.Internal; using Parse.Core.Internal; -using Parse.Management; namespace Parse { @@ -48,106 +47,145 @@ namespace Parse /// public class ParseQuery where T : ParseObject { - private readonly string className; - private readonly Dictionary where; - private readonly ReadOnlyCollection orderBy; - private readonly ReadOnlyCollection includes; - private readonly ReadOnlyCollection selectedKeys; - private readonly String redirectClassNameForKey; - private readonly int? skip; - private readonly int? limit; + /// + /// Serialized clauses. + /// + Dictionary Filters { get; } + + /// + /// Serialized clauses. + /// + ReadOnlyCollection Orderings { get; } + + /// + /// Serialized related data query merging request (data inclusion) clauses. + /// + ReadOnlyCollection Includes { get; } + + /// + /// Serialized key selections. + /// + ReadOnlyCollection KeySelections { get; } - internal string ClassName => className; + string RedirectClassNameForKey { get; } - internal static IParseQueryController QueryController => ParseCorePlugins.Instance.QueryController; + int? SkipAmount { get; } - internal static IObjectSubclassingController SubclassingController => ParseCorePlugins.Instance.SubclassingController; + int? LimitAmount { get; } + + internal string ClassName { get; } + + internal IServiceHub Services { get; } /// /// 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 ParseQuery(ParseQuery 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) + internal ParseQuery(ParseQuery 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; - orderBy = replacementOrderBy is null ? source.orderBy : new ReadOnlyCollection(replacementOrderBy.ToList()); - this.skip = skip is null ? source.skip : (source.skip ?? 0) + skip; // 0 could be handled differently from null - this.limit = limit ?? source.limit; - this.includes = source.includes; - this.selectedKeys = source.selectedKeys; - this.redirectClassNameForKey = redirectClassNameForKey ?? source.redirectClassNameForKey; - - if (thenBy != null) { - List newOrderBy = new List(orderBy ?? throw new ArgumentException("You must call OrderBy before calling ThenBy.")); + throw new ArgumentNullException(nameof(source)); + } + + Services = source.Services; + ClassName = source.ClassName; + Filters = source.Filters; + Orderings = replacementOrderBy is null ? source.Orderings : new ReadOnlyCollection(replacementOrderBy.ToList()); + + // 0 could be handled differently from null. + + SkipAmount = skip is null ? source.SkipAmount : (source.SkipAmount ?? 0) + skip; + LimitAmount = limit ?? source.LimitAmount; + Includes = source.Includes; + KeySelections = source.KeySelections; + RedirectClassNameForKey = redirectClassNameForKey ?? source.RedirectClassNameForKey; + + if (thenBy is { }) + { + List newOrderBy = new List(Orderings ?? throw new ArgumentException("You must call OrderBy before calling ThenBy.")); newOrderBy.AddRange(thenBy); - orderBy = new ReadOnlyCollection(newOrderBy); + Orderings = new ReadOnlyCollection(newOrderBy); } // Remove duplicates. - if (orderBy != null) - orderBy = new ReadOnlyCollection(new HashSet(orderBy).ToList()); - if (where != null) - this.where = new Dictionary(MergeWhereClauses(where)); + if (Orderings is { }) + { + Orderings = new ReadOnlyCollection(new HashSet(Orderings).ToList()); + } - if (includes != null) - this.includes = new ReadOnlyCollection(MergeIncludes(includes).ToList()); + if (where is { }) + { + Filters = new Dictionary(MergeWhereClauses(where)); + } + + if (includes is { }) + { + Includes = new ReadOnlyCollection(MergeIncludes(includes).ToList()); + } - if (selectedKeys != null) - this.selectedKeys = new ReadOnlyCollection(MergeSelectedKeys(selectedKeys).ToList()); + if (selectedKeys is { }) + { + KeySelections = new ReadOnlyCollection(MergeSelectedKeys(selectedKeys).ToList()); + } } - private HashSet MergeIncludes(IEnumerable includes) + HashSet MergeIncludes(IEnumerable includes) { - if (this.includes == null) + if (Includes is null) + { return new HashSet(includes); - HashSet newIncludes = new HashSet(this.includes); + } + + HashSet newIncludes = new HashSet(Includes); + foreach (string item in includes) + { newIncludes.Add(item); + } + return newIncludes; } - private HashSet MergeSelectedKeys(IEnumerable selectedKeys) - { - if (this.selectedKeys == null) - return new HashSet(selectedKeys); - HashSet newSelectedKeys = new HashSet(this.selectedKeys); - foreach (string item in selectedKeys) - newSelectedKeys.Add(item); - return newSelectedKeys; - } + HashSet MergeSelectedKeys(IEnumerable selectedKeys) => new HashSet((KeySelections ?? Enumerable.Empty()).Concat(selectedKeys)); - private IDictionary MergeWhereClauses(IDictionary where) + IDictionary MergeWhereClauses(IDictionary where) { - if (this.where == null) + if (Filters is null) + { return where; - Dictionary newWhere = new Dictionary(this.where); + } + + Dictionary newWhere = new Dictionary(Filters); foreach (KeyValuePair pair in where) { - IDictionary condition = pair.Value as IDictionary; if (newWhere.ContainsKey(pair.Key)) { - IDictionary oldCondition = newWhere[pair.Key] as IDictionary; - if (oldCondition == null || condition == null) + if (!(newWhere[pair.Key] is IDictionary oldCondition) || !(pair.Value is IDictionary condition)) + { throw new ArgumentException("More than one where clause for the given key provided."); + } + Dictionary newCondition = new Dictionary(oldCondition); foreach (KeyValuePair 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; } @@ -155,41 +193,14 @@ private IDictionary MergeWhereClauses(IDictionary /// Constructs a query based upon the ParseObject subclass used as the generic parameter for the ParseQuery. /// - public ParseQuery() : this(SubclassingController.GetClassName(typeof(T))) { } + public ParseQuery(IServiceHub serviceHub) : this(serviceHub, serviceHub.ClassController.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 ParseObjects for. - public ParseQuery(string className) => this.className = className ?? throw new ArgumentNullException("className", "Must specify a ParseObject class name when creating a ParseQuery."); - - /// - /// Constructs a query that is the or of the given queries. - /// - /// The list of ParseQueries to 'or' together. - /// A ParseQquery that is the 'or' of the passed in queries. - public static ParseQuery Or(IEnumerable> queries) - { - string className = null; - List> orValue = new List>(); - // We need to cast it to non-generic IEnumerable because of AOT-limitation - IEnumerable nonGenericQueries = queries; - foreach (object obj in nonGenericQueries) - { - ParseQuery q = obj as ParseQuery; - 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; - IDictionary parameters = q.BuildParameters(); - if (parameters.Count == 0) - continue; - if (!parameters.TryGetValue("where", out object 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 ParseQuery(new ParseQuery(className), where: new Dictionary { { "$or", orValue } }); - } + public ParseQuery(IServiceHub serviceHub, string className) => (ClassName, Services) = (className ?? throw new ArgumentNullException(nameof(className), "Must specify a ParseObject class name when creating a ParseQuery."), serviceHub); #region Order By @@ -270,7 +281,7 @@ public static ParseQuery Or(IEnumerable> queries) /// A new query with the additional constraint. public ParseQuery Limit(int count) => new ParseQuery(this, limit: count); - internal ParseQuery RedirectClassName(String key) => new ParseQuery(this, redirectClassNameForKey: key); + internal ParseQuery RedirectClassName(string key) => new ParseQuery(this, redirectClassNameForKey: key); #region Where @@ -431,7 +442,7 @@ public static ParseQuery Or(IEnumerable> queries) /// The key in the objects from the subquery to look in. /// The subquery to run /// A new query with the additional constraint. - public ParseQuery WhereMatchesKeyInQuery(string key, string keyInQuery, ParseQuery query) where TOther : ParseObject => new ParseQuery(this, where: new Dictionary { { key, new Dictionary { { "$select", new Dictionary { { "query", query.BuildParameters(true) }, { "key", keyInQuery } } } } } }); + public ParseQuery WhereMatchesKeyInQuery(string key, string keyInQuery, ParseQuery query) where TOther : ParseObject => new ParseQuery(this, where: new Dictionary { { key, new Dictionary { { "$select", new Dictionary { { nameof(query), query.BuildParameters(true) }, { nameof(key), keyInQuery } } } } } }); /// /// Adds a constraint to the query that requires a particular key's value @@ -441,7 +452,7 @@ public static ParseQuery Or(IEnumerable> queries) /// The key in the objects from the subquery to look in. /// The subquery to run /// A new query with the additional constraint. - public ParseQuery WhereDoesNotMatchesKeyInQuery(string key, string keyInQuery, ParseQuery query) where TOther : ParseObject => new ParseQuery(this, where: new Dictionary { { key, new Dictionary { { "$dontSelect", new Dictionary { { "query", query.BuildParameters(true) }, { "key", keyInQuery } } } } } }); + public ParseQuery WhereDoesNotMatchesKeyInQuery(string key, string keyInQuery, ParseQuery query) where TOther : ParseObject => new ParseQuery(this, where: new Dictionary { { key, new Dictionary { { "$dontSelect", new Dictionary { { nameof(query), query.BuildParameters(true) }, { nameof(key), keyInQuery } } } } } }); /// /// Adds a constraint to the query that requires that a particular key's value @@ -509,7 +520,7 @@ public static ParseQuery Or(IEnumerable> queries) /// A new query with the additional constraint. public ParseQuery WhereWithinDistance(string key, ParseGeoPoint point, ParseGeoDistance maxDistance) => new ParseQuery(WhereNear(key, point), where: new Dictionary { { key, new Dictionary { { "$maxDistance", maxDistance.Radians } } } }); - internal ParseQuery WhereRelatedTo(ParseObject parent, string key) => new ParseQuery(this, where: new Dictionary { { "$relatedTo", new Dictionary { { "object", parent }, { "key", key } } } }); + internal ParseQuery WhereRelatedTo(ParseObject parent, string key) => new ParseQuery(this, where: new Dictionary { { "$relatedTo", new Dictionary { { "object", parent }, { nameof(key), key } } } }); #endregion @@ -527,7 +538,7 @@ public static ParseQuery Or(IEnumerable> queries) public Task> FindAsync(CancellationToken cancellationToken) { EnsureNotInstallationQuery(); - return QueryController.FindAsync(this, ParseUser.CurrentUser, cancellationToken).OnSuccess(t => from state in t.Result select ParseObject.FromState(state, ClassName)); + return Services.QueryController.FindAsync(this, Services.GetCurrentUser(), cancellationToken).OnSuccess(task => from state in task.Result select Services.GenerateObjectFromState(state, ClassName)); } /// @@ -544,14 +555,14 @@ public Task> FindAsync(CancellationToken cancellationToken) public Task FirstOrDefaultAsync(CancellationToken cancellationToken) { EnsureNotInstallationQuery(); - return QueryController.FirstAsync(this, ParseUser.CurrentUser, cancellationToken).OnSuccess(t => t.Result is IObjectState state && state != null ? ParseObject.FromState(state, ClassName) : default(T)); + return Services.QueryController.FirstAsync(this, Services.GetCurrentUser(), cancellationToken).OnSuccess(task => task.Result is IObjectState state && state is { } ? Services.GenerateObjectFromState(state, ClassName) : default); } /// /// Retrieves at most one ParseObject that satisfies this query. /// /// A single ParseObject that satisfies this query. - /// If no results match the query. + /// If no results match the query. public Task FirstAsync() => FirstAsync(CancellationToken.None); /// @@ -559,8 +570,8 @@ public Task FirstOrDefaultAsync(CancellationToken cancellationToken) /// /// The cancellation token. /// A single ParseObject that satisfies this query. - /// If no results match the query. - public Task FirstAsync(CancellationToken cancellationToken) => FirstOrDefaultAsync(cancellationToken).OnSuccess(t => t.Result ?? throw new ParseException(ParseException.ErrorCode.ObjectNotFound, "No results matched the query.")); + /// If no results match the query. + public Task FirstAsync(CancellationToken cancellationToken) => FirstOrDefaultAsync(cancellationToken).OnSuccess(task => task.Result ?? throw new ParseFailureException(ParseFailureException.ErrorCode.ObjectNotFound, "No results matched the query.")); /// /// Counts the number of objects that match this query. @@ -576,7 +587,7 @@ public Task FirstOrDefaultAsync(CancellationToken cancellationToken) public Task CountAsync(CancellationToken cancellationToken) { EnsureNotInstallationQuery(); - return QueryController.CountAsync(this, ParseUser.CurrentUser, cancellationToken); + return Services.QueryController.CountAsync(this, Services.GetCurrentUser(), cancellationToken); } /// @@ -596,38 +607,38 @@ public Task CountAsync(CancellationToken cancellationToken) /// The ParseObject for the given objectId. public Task GetAsync(string objectId, CancellationToken cancellationToken) { - ParseQuery singleItemQuery = new ParseQuery(className).WhereEqualTo("objectId", objectId); - singleItemQuery = new ParseQuery(singleItemQuery, includes: includes, selectedKeys: selectedKeys, limit: 1); - return singleItemQuery.FindAsync(cancellationToken).OnSuccess(t => t.Result.FirstOrDefault() ?? throw new ParseException(ParseException.ErrorCode.ObjectNotFound, "Object with the given objectId not found.")); + ParseQuery singleItemQuery = new ParseQuery(Services, ClassName).WhereEqualTo(nameof(objectId), objectId); + singleItemQuery = new ParseQuery(singleItemQuery, includes: Includes, selectedKeys: KeySelections, limit: 1); + return singleItemQuery.FindAsync(cancellationToken).OnSuccess(t => t.Result.FirstOrDefault() ?? throw new ParseFailureException(ParseFailureException.ErrorCode.ObjectNotFound, "Object with the given objectId not found.")); } - internal object GetConstraint(string key) => where?.GetOrDefault(key, null); + internal object GetConstraint(string key) => Filters?.GetOrDefault(key, null); internal 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 (Filters != null) + result[nameof(Filters)] = PointerOrLocalIdEncoder.Instance.Encode(Filters); + if (Orderings != null) + result["order"] = String.Join(",", Orderings.ToArray()); + if (SkipAmount != null) + result[nameof(SkipAmount)] = SkipAmount.Value; + if (LimitAmount != null) + result[nameof(LimitAmount)] = LimitAmount.Value; + if (Includes != null) + result["include"] = String.Join(",", Includes.ToArray()); + if (KeySelections != null) + result["keys"] = String.Join(",", KeySelections.ToArray()); if (includeClassName) - result["className"] = className; - if (redirectClassNameForKey != null) - result["redirectClassNameForKey"] = redirectClassNameForKey; + result[nameof(ClassName)] = ClassName; + if (RedirectClassNameForKey != null) + result[nameof(RedirectClassNameForKey)] = RedirectClassNameForKey; return result; } - private string RegexQuote(string input) => "\\Q" + input.Replace("\\E", "\\E\\\\E\\Q") + "\\E"; + string RegexQuote(string input) => "\\Q" + input.Replace("\\E", "\\E\\\\E\\Q") + "\\E"; - private string GetRegexOptions(Regex regex, string modifiers) + string GetRegexOptions(Regex regex, string modifiers) { string result = modifiers ?? ""; if (regex.Options.HasFlag(RegexOptions.IgnoreCase) && !modifiers.Contains("i")) @@ -637,7 +648,7 @@ private string GetRegexOptions(Regex regex, string modifiers) return result; } - private IDictionary EncodeRegex(Regex regex, string modifiers) + IDictionary EncodeRegex(Regex regex, string modifiers) { string options = GetRegexOptions(regex, modifiers); Dictionary dict = new Dictionary { ["$regex"] = regex.ToString() }; @@ -646,10 +657,10 @@ private IDictionary EncodeRegex(Regex regex, string modifiers) return dict; } - private void EnsureNotInstallationQuery() + void EnsureNotInstallationQuery() { // The ParseInstallation class is not accessible from this project; using string literal. - if (className.Equals("_Installation")) + if (ClassName.Equals("_Installation")) throw new InvalidOperationException("Cannot directly query the Installation class."); } @@ -658,7 +669,7 @@ private void EnsureNotInstallationQuery() /// /// 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) => obj == null || !(obj is ParseQuery other) ? false : Equals(className, other.ClassName) && where.CollectionsEqual(other.where) && orderBy.CollectionsEqual(other.orderBy) && includes.CollectionsEqual(other.includes) && selectedKeys.CollectionsEqual(other.selectedKeys) && Equals(skip, other.skip) && Equals(limit, other.limit); + public override bool Equals(object obj) => obj == null || !(obj is ParseQuery other) ? false : Equals(ClassName, other.ClassName) && Filters.CollectionsEqual(other.Filters) && Orderings.CollectionsEqual(other.Orderings) && Includes.CollectionsEqual(other.Includes) && KeySelections.CollectionsEqual(other.KeySelections) && Equals(SkipAmount, other.SkipAmount) && Equals(LimitAmount, other.LimitAmount); /// /// Serves as the default hash function. diff --git a/Parse/ParseRelation.cs b/Parse/ParseRelation.cs index 5b7eb720..5e53891c 100644 --- a/Parse/ParseRelation.cs +++ b/Parse/ParseRelation.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; +using Parse.Abstractions.Library; using Parse.Common.Internal; using Parse.Core.Internal; using Parse.Management; @@ -18,84 +19,59 @@ namespace Parse [EditorBrowsable(EditorBrowsableState.Never)] public abstract class ParseRelationBase : IJsonConvertible { - private ParseObject parent; - private string key; - private string targetClassName; + ParseObject Parent { get; set; } - internal ParseRelationBase(ParseObject parent, string key) => EnsureParentAndKey(parent, key); + string Key { get; set; } - internal ParseRelationBase(ParseObject parent, string key, string targetClassName) - : this(parent, key) => this.targetClassName = targetClassName; + internal ParseRelationBase(ParseObject parent, string key) => EnsureParentAndKey(parent, key); - internal static IObjectSubclassingController SubclassingController => ParseCorePlugins.Instance.SubclassingController; + internal ParseRelationBase(ParseObject parent, string key, string targetClassName) : this(parent, key) => TargetClassName = targetClassName; internal void EnsureParentAndKey(ParseObject parent, string key) { - this.parent ??= parent; - 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"); - } + Parent ??= parent; + Key ??= key; - internal void Add(ParseObject obj) - { - ParseRelationOperation change = new ParseRelationOperation(new[] { obj }, null); - parent.PerformOperation(key, change); - targetClassName = change.TargetClassName; + Debug.Assert(Parent == parent, "Relation retrieved from two different objects"); + Debug.Assert(Key == key, "Relation retrieved from two different keys"); } - internal void Remove(ParseObject obj) + internal void Add(ParseObject entity) { - ParseRelationOperation change = new ParseRelationOperation(null, new[] { obj }); - parent.PerformOperation(key, change); - targetClassName = change.TargetClassName; - } + ParseRelationOperation change = new ParseRelationOperation(Parent.Client.ClassController, new[] { entity }, default); - IDictionary IJsonConvertible.ToJSON() => new Dictionary { - {"__type", "Relation"}, - {"className", targetClassName} - }; + Parent.PerformOperation(Key, change); + TargetClassName = change.TargetClassName; + } - internal ParseQuery GetQuery() where T : ParseObject + internal void Remove(ParseObject entity) { - if (targetClassName != null) - { - return new ParseQuery(targetClassName) - .WhereRelatedTo(parent, key); - } - - return new ParseQuery(parent.ClassName) - .RedirectClassName(key) - .WhereRelatedTo(parent, key); + ParseRelationOperation change = new ParseRelationOperation(Parent.Client.ClassController, default, new[] { entity }); + + Parent.PerformOperation(Key, change); + TargetClassName = change.TargetClassName; } - internal string TargetClassName + IDictionary IJsonConvertible.ConvertToJSON() => new Dictionary { - get => targetClassName; - set => targetClassName = value; - } + ["__type"] = "Relation", + ["className"] = TargetClassName + }; + + internal ParseQuery GetQuery() where T : ParseObject => TargetClassName is { } ? new ParseQuery(Parent.Client, TargetClassName).WhereRelatedTo(Parent, Key) : new ParseQuery(Parent.Client, Parent.ClassName).RedirectClassName(Key).WhereRelatedTo(Parent, Key); + + internal string TargetClassName { get; set; } /// /// Produces the proper ParseRelation<T> instance for the given classname. /// - internal static ParseRelationBase CreateRelation(ParseObject parent, - string key, - string targetClassName) + internal static ParseRelationBase CreateRelation(ParseObject parent, string key, string targetClassName) { - Type targetType = SubclassingController.GetType(targetClassName) ?? typeof(ParseObject); - - Expression>> createRelationExpr = - () => CreateRelation(parent, key, targetClassName); - MethodInfo createRelationMethod = - ((MethodCallExpression) createRelationExpr.Body) - .Method - .GetGenericMethodDefinition() - .MakeGenericMethod(targetType); - return (ParseRelationBase) createRelationMethod.Invoke(null, new object[] { parent, key, targetClassName }); + Expression>> createRelationExpr = () => CreateRelation(parent, key, targetClassName); + return (createRelationExpr.Body as MethodCallExpression).Method.GetGenericMethodDefinition().MakeGenericMethod(parent.Client.ClassController.GetType(targetClassName) ?? typeof(ParseObject)).Invoke(default, new object[] { parent, key, targetClassName }) as ParseRelationBase; } - private static ParseRelation CreateRelation(ParseObject parent, string key, string targetClassName) - where T : ParseObject => new ParseRelation(parent, key, targetClassName); + static ParseRelation CreateRelation(ParseObject parent, string key, string targetClassName) where T : ParseObject => new ParseRelation(parent, key, targetClassName); } /// @@ -105,11 +81,9 @@ private static ParseRelation CreateRelation(ParseObject parent, string key /// The type of the child objects. public sealed class ParseRelation : ParseRelationBase where T : ParseObject { - internal ParseRelation(ParseObject parent, string key) : base(parent, key) { } - internal ParseRelation(ParseObject parent, string key, string targetClassName) - : base(parent, key, targetClassName) { } + internal ParseRelation(ParseObject parent, string key, string targetClassName) : base(parent, key, targetClassName) { } /// /// Adds an object to this relation. The object must already have been saved. diff --git a/Parse/ParseRole.cs b/Parse/ParseRole.cs index ea745f59..b978fff1 100644 --- a/Parse/ParseRole.cs +++ b/Parse/ParseRole.cs @@ -30,8 +30,7 @@ public ParseRole() : base() { } /// /// The name of the role to create. /// The ACL for this role. Roles must have an ACL. - public ParseRole(string name, ParseACL acl) - : this() + public ParseRole(string name, ParseACL acl) : this() { Name = name; ACL = acl; @@ -43,8 +42,8 @@ public ParseRole(string name, ParseACL acl) [ParseFieldName("name")] public string Name { - get => GetProperty("Name"); - set => SetProperty(value, "Name"); + get => GetProperty(nameof(Name)); + set => SetProperty(value, nameof(Name)); } /// @@ -77,20 +76,13 @@ internal override void OnSettingValue(ref string key, ref object value) } if (!(value is string)) { - throw new ArgumentException("A role's name must be a string.", "value"); + 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.", - "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 ParseQuery Query => new ParseQuery(); } } diff --git a/Parse/ParseSession.cs b/Parse/ParseSession.cs index 37c038bf..3bbe176b 100644 --- a/Parse/ParseSession.cs +++ b/Parse/ParseSession.cs @@ -2,9 +2,6 @@ using System.Collections.Generic; using System.Threading; -using System.Threading.Tasks; -using Parse.Common.Internal; -using Parse.Core.Internal; using Parse.Management; namespace Parse @@ -15,76 +12,14 @@ namespace Parse [ParseClassName("_Session")] public class ParseSession : ParseObject { - private static readonly HashSet readOnlyKeys = new HashSet { - "sessionToken", "createdWith", "restricted", "user", "expiresAt", "installationId" - }; + static HashSet ImmutableKeys { get; } = new HashSet { "sessionToken", "createdWith", "restricted", "user", "expiresAt", "installationId" }; - protected override bool IsKeyMutable(string key) => !readOnlyKeys.Contains(key); + protected override bool CheckKeyMutable(string key) => !ImmutableKeys.Contains(key); /// /// Gets the session token for a user, if they are logged in. /// [ParseFieldName("sessionToken")] - public string SessionToken => GetProperty(null, "SessionToken"); - - /// - /// Constructs a for ParseSession. - /// - public static ParseQuery Query => new ParseQuery(); - - internal static IParseSessionController SessionController => ParseCorePlugins.Instance.SessionController; - - /// - /// Gets the current object related to the current user. - /// - public static Task GetCurrentSessionAsync() => GetCurrentSessionAsync(CancellationToken.None); - - /// - /// Gets the current object related to the current user. - /// - /// The cancellation token - public static Task GetCurrentSessionAsync(CancellationToken cancellationToken) => ParseUser.GetCurrentUserAsync().OnSuccess(t1 => - { - ParseUser user = t1.Result; - if (user == null) - { - return Task.FromResult((ParseSession) null); - } - - string sessionToken = user.SessionToken; - if (sessionToken == null) - { - return Task.FromResult((ParseSession) null); - } - - return SessionController.GetSessionAsync(sessionToken, cancellationToken).OnSuccess(t => - { - ParseSession session = 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 => - { - ParseSession session = FromState(t.Result, "_Session"); - return session.SessionToken; - }); - } + public string SessionToken => GetProperty(default, "SessionToken"); } } diff --git a/Parse/ParseUser.cs b/Parse/ParseUser.cs index 5eff7e56..ea172663 100644 --- a/Parse/ParseUser.cs +++ b/Parse/ParseUser.cs @@ -16,14 +16,6 @@ namespace Parse [ParseClassName("_User")] public class ParseUser : ParseObject { - private static readonly IDictionary authProviders = new Dictionary(); - - private static readonly HashSet readOnlyKeys = new HashSet { "sessionToken", "isNew" }; - - internal static IParseUserController UserController => ParseCorePlugins.Instance.UserController; - - internal static IParseCurrentUserController CurrentUserController => ParseCorePlugins.Instance.CurrentUserController; - /// /// Whether the ParseUser has been authenticated on this device. Only an authenticated /// ParseUser can be saved and deleted. @@ -32,9 +24,9 @@ public bool IsAuthenticated { get { - lock (mutex) + lock (Mutex) { - return SessionToken != null && CurrentUser != null && CurrentUser.ObjectId == ObjectId; + return SessionToken is { } && Client.GetCurrentUser() is { } user && user.ObjectId == ObjectId; } } } @@ -48,12 +40,13 @@ public override void Remove(string key) { if (key == "username") { - throw new ArgumentException("Cannot remove the username key."); + throw new InvalidOperationException("Cannot remove the username key."); } + base.Remove(key); } - protected override bool IsKeyMutable(string key) => !readOnlyKeys.Contains(key); + protected override bool CheckKeyMutable(string key) => !ImmutableKeys.Contains(key); internal override void HandleSave(IObjectState serverState) { @@ -67,25 +60,12 @@ internal override void HandleSave(IObjectState serverState) public string SessionToken => State.ContainsKey("sessionToken") ? State["sessionToken"] as string : null; - internal static string CurrentSessionToken - { - get - { - Task sessionTokenTask = GetCurrentSessionTokenAsync(); - sessionTokenTask.Wait(); - return sessionTokenTask.Result; - } - } - - internal static Task GetCurrentSessionTokenAsync(CancellationToken cancellationToken = default) => CurrentUserController.GetCurrentSessionTokenAsync(cancellationToken); - internal Task SetSessionTokenAsync(string newSessionToken) => SetSessionTokenAsync(newSessionToken, CancellationToken.None); internal Task SetSessionTokenAsync(string newSessionToken, CancellationToken cancellationToken) { MutateState(mutableClone => mutableClone.ServerData["sessionToken"] = newSessionToken); - - return SaveCurrentUserAsync(this); + return Client.SaveCurrentUserAsync(this); } /// @@ -94,8 +74,8 @@ internal Task SetSessionTokenAsync(string newSessionToken, CancellationToken can [ParseFieldName("username")] public string Username { - get => GetProperty(null, "Username"); - set => SetProperty(value, "Username"); + get => GetProperty(null, nameof(Username)); + set => SetProperty(value, nameof(Username)); } /// @@ -104,8 +84,8 @@ public string Username [ParseFieldName("password")] public string Password { - private get => GetProperty(null, "Password"); - set => SetProperty(value, "Password"); + get => GetProperty(null, nameof(Password)); + set => SetProperty(value, nameof(Password)); } /// @@ -114,8 +94,8 @@ public string Password [ParseFieldName("email")] public string Email { - get => GetProperty(null, "Email"); - set => SetProperty(value, "Email"); + get => GetProperty(null, nameof(Email)); + set => SetProperty(value, nameof(Email)); } internal Task SignUpAsync(Task toAwait, CancellationToken cancellationToken) @@ -145,7 +125,7 @@ internal Task SignUpAsync(Task toAwait, CancellationToken cancellationToken) IDictionary currentOperations = StartSave(); - return toAwait.OnSuccess(_ => UserController.SignUpAsync(State, currentOperations, cancellationToken)).Unwrap().ContinueWith(t => + return toAwait.OnSuccess(_ => Client.UserController.SignUpAsync(State, currentOperations, cancellationToken)).Unwrap().ContinueWith(t => { if (t.IsFaulted || t.IsCanceled) { @@ -156,7 +136,7 @@ internal Task SignUpAsync(Task toAwait, CancellationToken cancellationToken) HandleSave(t.Result); } return t; - }).Unwrap().OnSuccess(_ => SaveCurrentUserAsync(this)).Unwrap(); + }).Unwrap().OnSuccess(_ => Client.SaveCurrentUserAsync(this)).Unwrap(); } /// @@ -174,110 +154,21 @@ internal Task SignUpAsync(Task toAwait, CancellationToken cancellationToken) /// The cancellation token. public Task SignUpAsync(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => SignUpAsync(toAwait, cancellationToken), cancellationToken); - /// - /// 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) => 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) => UserController.LogInAsync(username, password, cancellationToken).OnSuccess(t => - { - ParseUser user = 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) => 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) => UserController.GetUserAsync(sessionToken, cancellationToken).OnSuccess(t => - { - ParseUser user = FromState(t.Result, "_User"); - return SaveCurrentUserAsync(user).OnSuccess(_ => user); - }).Unwrap(); - protected override Task SaveAsync(Task toAwait, CancellationToken cancellationToken) { - lock (mutex) + lock (Mutex) { - if (ObjectId == null) + if (ObjectId is 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(); + + return base.SaveAsync(toAwait, cancellationToken).OnSuccess(_ => Client.CurrentUserController.IsCurrent(this) ? Client.SaveCurrentUserAsync(this) : Task.CompletedTask).Unwrap(); } } // If this is already the current user, refresh its state on disk. - internal override Task FetchAsyncInternal(Task toAwait, CancellationToken cancellationToken) => base.FetchAsyncInternal(toAwait, cancellationToken).OnSuccess(t => !CurrentUserController.IsCurrent(this) ? Task.FromResult(t.Result) : 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() => LogOutAsync().Wait(); // TODO (hallucinogen): this will without a doubt fail in Unity. But what else can we do? - - /// - /// 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() => 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) => GetCurrentUserAsync().OnSuccess(t => - { - LogOutWithProviders(); - - ParseUser user = t.Result; - if (user == null) - { - return Task.FromResult(0); - } - - return user.taskQueue.Enqueue(toAwait => user.LogOutAsync(toAwait, cancellationToken), cancellationToken); - }).Unwrap(); + internal override Task FetchAsyncInternal(Task toAwait, CancellationToken cancellationToken) => base.FetchAsyncInternal(toAwait, cancellationToken).OnSuccess(t => !Client.CurrentUserController.IsCurrent(this) ? Task.FromResult(t.Result) : Client.SaveCurrentUserAsync(this).OnSuccess(_ => t.Result)).Unwrap(); internal Task LogOutAsync(Task toAwait, CancellationToken cancellationToken) { @@ -288,169 +179,44 @@ internal Task LogOutAsync(Task toAwait, CancellationToken cancellationToken) } // Cleanup in-memory session. - MutateState(mutableClone => - { - mutableClone.ServerData.Remove("sessionToken"); - }); - Task revokeSessionTask = ParseSession.RevokeAsync(oldSessionToken, cancellationToken); - return Task.WhenAll(revokeSessionTask, CurrentUserController.LogOutAsync(cancellationToken)); - } - - private static void LogOutWithProviders() - { - foreach (IParseAuthenticationProvider provider in authProviders.Values) - { - provider.Deauthenticate(); - } - } - - /// - /// Gets the currently logged in ParseUser with a valid session, either from memory or disk - /// if necessary. - /// - public static ParseUser CurrentUser - { - get - { - Task userTask = GetCurrentUserAsync(); - // TODO (hallucinogen): this will without a doubt fail in Unity. How should we fix it? - userTask.Wait(); - return userTask.Result; - } - } - - /// - /// Gets the currently logged in ParseUser with a valid session, either from memory or disk - /// if necessary, asynchronously. - /// - internal static Task GetCurrentUserAsync() => GetCurrentUserAsync(CancellationToken.None); - - /// - /// Gets the currently logged in ParseUser with a valid session, either from memory or disk - /// if necessary, asynchronously. - /// - internal static Task GetCurrentUserAsync(CancellationToken cancellationToken) => CurrentUserController.GetAsync(cancellationToken); - - private static Task SaveCurrentUserAsync(ParseUser user) => SaveCurrentUserAsync(user, CancellationToken.None); - - private static Task SaveCurrentUserAsync(ParseUser user, CancellationToken cancellationToken) => CurrentUserController.SetAsync(user, cancellationToken); - - internal static void ClearInMemoryUser() => CurrentUserController.ClearFromMemory(); - - /// - /// Constructs a for ParseUsers. - /// - public static ParseQuery Query => new ParseQuery(); - - #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() => 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 => - { - ParseUser user = t.Result; - return user.UpgradeToRevocableSessionAsync(cancellationToken); - }); - } - - internal static void DisableRevocableSession() - { - lock (isRevocableSessionEnabledMutex) - { - isRevocableSessionEnabled = false; - } - } - - internal static bool IsRevocableSessionEnabled - { - get - { - lock (isRevocableSessionEnabledMutex) - { - return isRevocableSessionEnabled; - } - } + MutateState(mutableClone => mutableClone.ServerData.Remove("sessionToken")); + Task revokeSessionTask = Client.RevokeSessionAsync(oldSessionToken, cancellationToken); + return Task.WhenAll(revokeSessionTask, Client.CurrentUserController.LogOutAsync(cancellationToken)); } internal Task UpgradeToRevocableSessionAsync() => UpgradeToRevocableSessionAsync(CancellationToken.None); - internal Task UpgradeToRevocableSessionAsync(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => UpgradeToRevocableSessionAsync(toAwait, cancellationToken), - cancellationToken); + internal Task UpgradeToRevocableSessionAsync(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => UpgradeToRevocableSessionAsync(toAwait, cancellationToken), cancellationToken); internal Task UpgradeToRevocableSessionAsync(Task toAwait, CancellationToken cancellationToken) { string sessionToken = SessionToken; - return toAwait.OnSuccess(_ => - { - return ParseSession.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken); - }).Unwrap().OnSuccess(t => - { - return SetSessionTokenAsync(t.Result); - }).Unwrap(); + return toAwait.OnSuccess(_ => Client.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken)).Unwrap().OnSuccess(task => SetSessionTokenAsync(task.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 Parse site. - /// - /// The email address associated with the user that forgot their password. - public static Task RequestPasswordResetAsync(string email) => 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 Parse site. - /// - /// The email address associated with the user that forgot their password. - /// The cancellation token. - public static Task RequestPasswordResetAsync(string email, CancellationToken cancellationToken) => UserController.RequestPasswordResetAsync(email, cancellationToken); - /// /// Gets the authData for this user. /// - internal IDictionary> AuthData + public IDictionary> AuthData { get => TryGetValue("authData", out IDictionary> authData) ? authData : null; - private set => this["authData"] = value; + set => this["authData"] = value; } - private static IParseAuthenticationProvider GetProvider(string providerName) => authProviders.TryGetValue(providerName, out IParseAuthenticationProvider provider) ? provider : null; - /// /// Removes null values from authData (which exist temporarily for unlinking) /// - private void CleanupAuthData() + void CleanupAuthData() { - lock (mutex) + lock (Mutex) { - if (!CurrentUserController.IsCurrent(this)) + if (!Client.CurrentUserController.IsCurrent(this)) { return; } + IDictionary> authData = AuthData; if (authData == null) @@ -468,12 +234,20 @@ private void CleanupAuthData() } } +#warning Check if the following properties should be injected via IServiceHub.UserController (except for ImmutableKeys). + + internal static IParseAuthenticationProvider GetProvider(string providerName) => Authenticators.TryGetValue(providerName, out IParseAuthenticationProvider provider) ? provider : null; + + internal static IDictionary Authenticators { get; } = new Dictionary { }; + + internal static HashSet ImmutableKeys { get; } = new HashSet { "sessionToken", "isNew" }; + /// /// Synchronizes authData for all providers. /// - private void SynchronizeAllAuthData() + internal void SynchronizeAllAuthData() { - lock (mutex) + lock (Mutex) { IDictionary> authData = AuthData; @@ -489,16 +263,19 @@ private void SynchronizeAllAuthData() } } - private void SynchronizeAuthData(IParseAuthenticationProvider provider) + internal void SynchronizeAuthData(IParseAuthenticationProvider provider) { bool restorationSuccess = false; - lock (mutex) + + lock (Mutex) { IDictionary> authData = AuthData; + if (authData == null || provider == null) { return; } + if (authData.TryGetValue(provider.AuthType, out IDictionary data)) { restorationSuccess = provider.RestoreAuthentication(data); @@ -514,12 +291,15 @@ private void SynchronizeAuthData(IParseAuthenticationProvider provider) internal Task LinkWithAsync(string authType, IDictionary data, CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => { IDictionary> authData = AuthData; + if (authData == null) { authData = AuthData = new Dictionary>(); } + authData[authType] = data; AuthData = authData; + return SaveAsync(cancellationToken); }, cancellationToken); @@ -539,48 +319,10 @@ internal Task LinkWithAsync(string authType, CancellationToken cancellationToken /// internal bool IsLinked(string authType) { - lock (mutex) + lock (Mutex) { return AuthData != null && AuthData.ContainsKey(authType) && AuthData[authType] != null; } } - - internal static Task LogInWithAsync(string authType, IDictionary data, CancellationToken cancellationToken) - { - ParseUser user = null; - - return UserController.LogInAsync(authType, data, cancellationToken).OnSuccess(t => - { - user = 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) - { - IParseAuthenticationProvider provider = GetProvider(authType); - return provider.AuthenticateAsync(cancellationToken).OnSuccess(authData => LogInWithAsync(authType, authData.Result, cancellationToken)).Unwrap(); - } - - internal static void RegisterProvider(IParseAuthenticationProvider provider) - { - authProviders[provider.AuthType] = provider; - ParseUser curUser = CurrentUser; - if (curUser != null) - { - curUser.SynchronizeAuthData(provider); - } - } } } diff --git a/Parse/Platform/Analytics/ParseAnalyticsController.cs b/Parse/Platform/Analytics/ParseAnalyticsController.cs index 9840f1ba..8ffd73d9 100644 --- a/Parse/Platform/Analytics/ParseAnalyticsController.cs +++ b/Parse/Platform/Analytics/ParseAnalyticsController.cs @@ -34,12 +34,12 @@ public Task TrackEventAsync(string name, IDictionary dimensions, IDictionary data = new Dictionary { ["at"] = DateTime.Now, - ["name"] = name, + [nameof(name)] = name, }; if (dimensions != null) { - data["dimensions"] = dimensions; + data[nameof(dimensions)] = dimensions; } return Runner.RunCommandAsync(new ParseCommand("events/" + name, "POST", sessionToken, data: PointerOrLocalIdEncoder.Instance.Encode(data) as IDictionary), cancellationToken: cancellationToken); diff --git a/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs b/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs index ea3fd790..f3fd6be9 100644 --- a/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs +++ b/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs @@ -1,83 +1,83 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. +//// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Abstractions.Management; -using Parse.Management; +//using Parse.Abstractions.Management; +//using Parse.Management; -namespace Parse.Analytics.Internal -{ - public class ParseAnalyticsPlugins : IParseAnalyticsPlugins - { - private static readonly object instanceMutex = new object(); - private static IParseAnalyticsPlugins instance; - public static IParseAnalyticsPlugins Instance - { - get - { - lock (instanceMutex) - { - instance ??= new ParseAnalyticsPlugins(); - return instance; - } - } - set - { - lock (instanceMutex) - { - instance = value; - } - } - } +//namespace Parse.Analytics.Internal +//{ +// public class ParseAnalyticsPlugins : IParseAnalyticsPlugins +// { +// private static readonly object instanceMutex = new object(); +// private static IParseAnalyticsPlugins instance; +// public static IParseAnalyticsPlugins Instance +// { +// get +// { +// lock (instanceMutex) +// { +// instance ??= new ParseAnalyticsPlugins(); +// return instance; +// } +// } +// set +// { +// lock (instanceMutex) +// { +// instance = value; +// } +// } +// } - private readonly object mutex = new object(); +// private readonly object mutex = new object(); - private IParseCorePlugins corePlugins; - private IParseAnalyticsController analyticsController; +// private IParseCorePlugins corePlugins; +// private IParseAnalyticsController analyticsController; - public void Reset() - { - lock (mutex) - { - CorePlugins = null; - AnalyticsController = null; - } - } +// public void Reset() +// { +// lock (mutex) +// { +// CorePlugins = null; +// AnalyticsController = null; +// } +// } - public IParseCorePlugins CorePlugins - { - get - { - lock (mutex) - { - corePlugins ??= ParseCorePlugins.Instance; - return corePlugins; - } - } - set - { - lock (mutex) - { - corePlugins = value; - } - } - } +// public IParseCorePlugins CorePlugins +// { +// get +// { +// lock (mutex) +// { +// corePlugins ??= ParseCorePlugins.Instance; +// return corePlugins; +// } +// } +// set +// { +// lock (mutex) +// { +// corePlugins = value; +// } +// } +// } - public IParseAnalyticsController AnalyticsController - { - get - { - lock (mutex) - { - analyticsController ??= new ParseAnalyticsController(CorePlugins.CommandRunner); - return analyticsController; - } - } - set - { - lock (mutex) - { - analyticsController = value; - } - } - } - } -} \ No newline at end of file +// public IParseAnalyticsController AnalyticsController +// { +// get +// { +// lock (mutex) +// { +// analyticsController ??= new ParseAnalyticsController(CorePlugins.CommandRunner); +// return analyticsController; +// } +// } +// set +// { +// lock (mutex) +// { +// analyticsController = value; +// } +// } +// } +// } +//} \ No newline at end of file diff --git a/Parse/Platform/Code/ParseCloudCodeController.cs b/Parse/Platform/Code/ParseCloudCodeController.cs index 6b7d2912..9937d7b8 100644 --- a/Parse/Platform/Code/ParseCloudCodeController.cs +++ b/Parse/Platform/Code/ParseCloudCodeController.cs @@ -11,28 +11,20 @@ namespace Parse.Core.Internal { public class ParseCloudCodeController : IParseCloudCodeController { - private readonly IParseCommandRunner commandRunner; + IParseCommandRunner CommandRunner { get; } - public ParseCloudCodeController(IParseCommandRunner commandRunner) => this.commandRunner = commandRunner; + IParseDataDecoder Decoder { get; } - public Task CallFunctionAsync(string name, - IDictionary parameters, - string sessionToken, - CancellationToken cancellationToken) + public ParseCloudCodeController(IParseCommandRunner commandRunner, IParseDataDecoder decoder) => (CommandRunner, Decoder) = (commandRunner, decoder); + + public Task CallFunctionAsync(string name, IDictionary parameters, string sessionToken, CancellationToken cancellationToken) { - ParseCommand command = new ParseCommand(String.Format("functions/{0}", Uri.EscapeUriString(name)), - method: "POST", - sessionToken: sessionToken, - data: NoObjectsEncoder.Instance.Encode(parameters) as IDictionary); + ParseCommand command = new ParseCommand($"functions/{Uri.EscapeUriString(name)}", method: "POST", sessionToken: sessionToken, data: NoObjectsEncoder.Instance.Encode(parameters) as IDictionary); - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => + return CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(task => { - IDictionary decoded = ParseDecoder.Instance.Decode(t.Result.Item2) as IDictionary; - if (!decoded.ContainsKey("result")) - { - return default(T); - } - return Conversion.To(decoded["result"]); + IDictionary decoded = Decoder.Decode(task.Result.Item2) as IDictionary; + return !decoded.ContainsKey("result") ? default : Conversion.To(decoded["result"]); }); } } diff --git a/Parse/Platform/Configuration/ParseConfigController.cs b/Parse/Platform/Configuration/ParseConfigController.cs deleted file mode 100644 index 4ad365b4..00000000 --- a/Parse/Platform/Configuration/ParseConfigController.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Threading; -using System.Threading.Tasks; -using Parse.Common.Internal; - -namespace Parse.Core.Internal -{ - /// - /// Config controller. - /// - internal class ParseConfigController : IParseConfigController - { - private readonly IParseCommandRunner commandRunner; - - /// - /// Initializes a new instance of the class. - /// - public ParseConfigController(IParseCommandRunner commandRunner, IStorageController storageController) - { - this.commandRunner = commandRunner; - CurrentConfigController = new ParseCurrentConfigController(storageController); - } - - public IParseCommandRunner CommandRunner { get; internal set; } - public IParseCurrentConfigController CurrentConfigController { get; internal set; } - - public Task FetchConfigAsync(string sessionToken, CancellationToken cancellationToken) - { - ParseCommand command = new ParseCommand("config", - method: "GET", - sessionToken: sessionToken, - data: null); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(task => - { - cancellationToken.ThrowIfCancellationRequested(); - return new ParseConfig(task.Result.Item2); - }).OnSuccess(task => - { - cancellationToken.ThrowIfCancellationRequested(); - CurrentConfigController.SetCurrentConfigAsync(task.Result); - return task; - }).Unwrap(); - } - } -} diff --git a/Parse/Platform/Configuration/ParseConfigurationController.cs b/Parse/Platform/Configuration/ParseConfigurationController.cs new file mode 100644 index 00000000..f3aead09 --- /dev/null +++ b/Parse/Platform/Configuration/ParseConfigurationController.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Threading; +using System.Threading.Tasks; +using Parse.Common.Internal; + +namespace Parse.Core.Internal +{ + /// + /// Config controller. + /// + internal class ParseConfigurationController : IParseConfigurationController + { + IParseCommandRunner CommandRunner { get; } + + IParseDataDecoder Decoder { get; } + + public IParseCurrentConfigurationController CurrentConfigurationController { get; } + + /// + /// Initializes a new instance of the class. + /// + public ParseConfigurationController(IParseCommandRunner commandRunner, IStorageController storageController, IParseDataDecoder decoder) + { + CommandRunner = commandRunner; + CurrentConfigurationController = new ParseCurrentConfigurationController(storageController, decoder); + Decoder = decoder; + } + + public Task FetchConfigAsync(string sessionToken, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand("config", method: "GET", sessionToken: sessionToken, data: default), cancellationToken: cancellationToken).OnSuccess(task => + { + cancellationToken.ThrowIfCancellationRequested(); + return Decoder.BuildConfiguration(task.Result.Item2); + }).OnSuccess(task => + { + cancellationToken.ThrowIfCancellationRequested(); + CurrentConfigurationController.SetCurrentConfigAsync(task.Result); + return task; + }).Unwrap(); + } +} diff --git a/Parse/Platform/Configuration/ParseCurrentConfigController.cs b/Parse/Platform/Configuration/ParseCurrentConfigController.cs deleted file mode 100644 index 53226c47..00000000 --- a/Parse/Platform/Configuration/ParseCurrentConfigController.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Parse.Common.Internal; - -namespace Parse.Core.Internal -{ - /// - /// Parse current config controller. - /// - internal class ParseCurrentConfigController : IParseCurrentConfigController - { - private const string CurrentConfigKey = "CurrentConfig"; - - private readonly TaskQueue taskQueue; - private ParseConfig currentConfig; - - private IStorageController storageController; - - /// - /// Initializes a new instance of the class. - /// - public ParseCurrentConfigController(IStorageController storageController) - { - this.storageController = storageController; - - taskQueue = new TaskQueue(); - } - - public Task GetCurrentConfigAsync() => taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => - { - if (currentConfig == null) - { - return storageController.LoadAsync().OnSuccess(t => - { - t.Result.TryGetValue(CurrentConfigKey, out object tmp); - - string propertiesString = tmp as string; - if (propertiesString != null) - { - IDictionary dictionary = ParseClient.DeserializeJsonString(propertiesString); - currentConfig = new ParseConfig(dictionary); - } - else - { - currentConfig = new ParseConfig(); - } - - return currentConfig; - }); - } - - return Task.FromResult(currentConfig); - }), CancellationToken.None).Unwrap(); - - public Task SetCurrentConfigAsync(ParseConfig config) => taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => - { - currentConfig = config; - - IDictionary jsonObject = ((IJsonConvertible) config).ToJSON(); - string jsonString = ParseClient.SerializeJsonString(jsonObject); - - return storageController.LoadAsync().OnSuccess(t => t.Result.AddAsync(CurrentConfigKey, jsonString)); - }).Unwrap().Unwrap(), CancellationToken.None); - - public Task ClearCurrentConfigAsync() => taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => - { - currentConfig = null; - - return storageController.LoadAsync().OnSuccess(t => t.Result.RemoveAsync(CurrentConfigKey)); - }).Unwrap().Unwrap(), CancellationToken.None); - - public Task ClearCurrentConfigInMemoryAsync() => taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => - { - currentConfig = null; - }), CancellationToken.None); - } -} diff --git a/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs b/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs new file mode 100644 index 00000000..1f0bfe8e --- /dev/null +++ b/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Parse.Common.Internal; + +namespace Parse.Core.Internal +{ + /// + /// Parse current config controller. + /// + internal class ParseCurrentConfigurationController : IParseCurrentConfigurationController + { + static string CurrentConfigurationKey { get; } = "CurrentConfig"; + + TaskQueue TaskQueue { get; } + + ParseConfiguration CurrentConfiguration { get; set; } + + IStorageController StorageController { get; } + + IParseDataDecoder Decoder { get; } + + /// + /// Initializes a new instance of the class. + /// + public ParseCurrentConfigurationController(IStorageController storageController, IParseDataDecoder decoder) + { + StorageController = storageController; + Decoder = decoder; + TaskQueue = new TaskQueue { }; + } + + public Task GetCurrentConfigAsync() => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => CurrentConfiguration is { } ? Task.FromResult(CurrentConfiguration) : StorageController.LoadAsync().OnSuccess(task => + { + task.Result.TryGetValue(CurrentConfigurationKey, out object data); + return CurrentConfiguration = data is string { } configuration ? Decoder.BuildConfiguration(ParseClient.DeserializeJsonString(configuration)) : new ParseConfiguration { }; + })), CancellationToken.None).Unwrap(); + + public Task SetCurrentConfigAsync(ParseConfiguration target) => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => + { + CurrentConfiguration = target; + return StorageController.LoadAsync().OnSuccess(task => task.Result.AddAsync(CurrentConfigurationKey, ParseClient.SerializeJsonString(((IJsonConvertible) target).ConvertToJSON()))); + }).Unwrap().Unwrap(), CancellationToken.None); + + public Task ClearCurrentConfigAsync() => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => + { + CurrentConfiguration = null; + return StorageController.LoadAsync().OnSuccess(task => task.Result.RemoveAsync(CurrentConfigurationKey)); + }).Unwrap().Unwrap(), CancellationToken.None); + + public Task ClearCurrentConfigInMemoryAsync() => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => CurrentConfiguration = null), CancellationToken.None); + } +} diff --git a/Parse/Platform/Files/FileState.cs b/Parse/Platform/Files/FileState.cs index 3c0403df..12e9eff2 100644 --- a/Parse/Platform/Files/FileState.cs +++ b/Parse/Platform/Files/FileState.cs @@ -6,27 +6,27 @@ namespace Parse.Core.Internal { public class FileState { - private const string ParseFileSecureScheme = "https"; - private const string ParseFileSecureDomain = "files.parsetfss.com"; + static string SecureHyperTextTransferScheme { get; } = "https"; public string Name { get; set; } - public string MimeType { get; set; } - public Uri Url { get; set; } - public Uri SecureUrl + + public string MediaType { get; set; } + + public Uri Location { get; set; } + + public Uri SecureLocation => Location switch { - get +#warning Investigate if the first branch of this swhich expression should be removed or an explicit failure case when not testing. + + { Host: "files.parsetfss.com" } location => new UriBuilder(location) { - Uri uri = Url; - if (uri != null && uri.Host == ParseFileSecureDomain) - { - return new UriBuilder(uri) - { - Scheme = ParseFileSecureScheme, - Port = -1, // This makes URIBuilder assign the default port for the URL scheme. - }.Uri; - } - return uri; - } - } + Scheme = SecureHyperTextTransferScheme, + + // This makes URIBuilder assign the default port for the URL scheme. + + Port = -1, + }.Uri, + _ => Location + }; } } diff --git a/Parse/Platform/Files/ParseFileController.cs b/Parse/Platform/Files/ParseFileController.cs index ce7c72a3..1958129a 100644 --- a/Parse/Platform/Files/ParseFileController.cs +++ b/Parse/Platform/Files/ParseFileController.cs @@ -1,7 +1,9 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; +using System.Collections.Generic; using System.IO; +using System.Net; using System.Threading; using System.Threading.Tasks; using Parse.Common.Internal; @@ -10,59 +12,49 @@ namespace Parse.Core.Internal { public class ParseFileController : IParseFileController { - private readonly IParseCommandRunner commandRunner; + IParseCommandRunner CommandRunner { get; } - public ParseFileController(IParseCommandRunner commandRunner) => this.commandRunner = commandRunner; + public ParseFileController(IParseCommandRunner commandRunner) => CommandRunner = commandRunner; - public Task SaveAsync(FileState state, - Stream dataStream, - string sessionToken, - IProgress progress, - CancellationToken cancellationToken = default(CancellationToken)) + public Task SaveAsync(FileState state, Stream dataStream, string sessionToken, IProgress progress, CancellationToken cancellationToken = default) { - if (state.Url != null) + if (state.Location != null) { // !isDirty + return Task.FromResult(state); } if (cancellationToken.IsCancellationRequested) { - TaskCompletionSource tcs = new TaskCompletionSource(); - tcs.TrySetCanceled(); - return tcs.Task; + return Task.FromCanceled(cancellationToken); } long oldPosition = dataStream.Position; - ParseCommand command = new ParseCommand("files/" + state.Name, - method: "POST", - sessionToken: sessionToken, - contentType: state.MimeType, - stream: dataStream); - return commandRunner.RunCommandAsync(command, - uploadProgress: progress, - cancellationToken: cancellationToken).OnSuccess(uploadTask => + return CommandRunner.RunCommandAsync(new ParseCommand($"files/{state.Name}", method: "POST", sessionToken: sessionToken, contentType: state.MediaType, stream: dataStream), uploadProgress: progress, cancellationToken: cancellationToken).OnSuccess(uploadTask => + { + Tuple> result = uploadTask.Result; + IDictionary jsonData = result.Item2; + cancellationToken.ThrowIfCancellationRequested(); + + return new FileState { - Tuple> result = uploadTask.Result; - System.Collections.Generic.IDictionary jsonData = result.Item2; - cancellationToken.ThrowIfCancellationRequested(); + Name = jsonData["name"] as string, + Location = new Uri(jsonData["url"] as string, UriKind.Absolute), + MediaType = state.MediaType + }; + }).ContinueWith(task => + { + // Rewind the stream on failure or cancellation (if possible). - return new FileState - { - Name = jsonData["name"] as string, - Url = new Uri(jsonData["url"] as string, UriKind.Absolute), - MimeType = state.MimeType - }; - }).ContinueWith(t => + if ((task.IsFaulted || task.IsCanceled) && dataStream.CanSeek) { - // Rewind the stream on failure or cancellation (if possible) - if ((t.IsFaulted || t.IsCanceled) && dataStream.CanSeek) - { - dataStream.Seek(oldPosition, SeekOrigin.Begin); - } - return t; - }).Unwrap(); + dataStream.Seek(oldPosition, SeekOrigin.Begin); + } + + return task; + }).Unwrap(); } } } diff --git a/Parse/Platform/Installation/InstallationIdController.cs b/Parse/Platform/Installation/InstallationIdController.cs deleted file mode 100644 index 0beb1ad2..00000000 --- a/Parse/Platform/Installation/InstallationIdController.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.Threading.Tasks; -using Parse.Common.Internal; - -namespace Parse.Core.Internal -{ - public class ParseInstallationController : IParseInstallationController - { - private const string InstallationIdKey = "InstallationId"; - private readonly object mutex = new object(); - private Guid? installationId; - - private readonly IStorageController storageController; - public ParseInstallationController(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(s => - { - s.Result.TryGetValue(InstallationIdKey, out object id); - try - { - lock (mutex) - { - installationId = new Guid((string) id); - return Task.FromResult(installationId); - } - } - catch (Exception) - { - Guid newInstallationId = Guid.NewGuid(); - return SetAsync(newInstallationId).OnSuccess(_ => newInstallationId); - } - }) - .Unwrap(); - } - - public Task ClearAsync() => SetAsync(null); - } -} diff --git a/Parse/Platform/Installation/ParseCurrentInstallationController.cs b/Parse/Platform/Installation/ParseCurrentInstallationController.cs index 5791c492..df270f2b 100644 --- a/Parse/Platform/Installation/ParseCurrentInstallationController.cs +++ b/Parse/Platform/Installation/ParseCurrentInstallationController.cs @@ -5,66 +5,61 @@ using System.Threading.Tasks; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Library; namespace Parse.Push.Internal { internal class ParseCurrentInstallationController : IParseCurrentInstallationController { - private const string ParseInstallationKey = "CurrentInstallation"; + static string ParseInstallationKey { get; } = nameof(CurrentInstallation); - private readonly object mutex = new object(); - private readonly TaskQueue taskQueue = new TaskQueue(); - private readonly IParseInstallationController installationIdController; - private readonly IStorageController storageController; - private readonly IParseInstallationCoder installationCoder; + object Mutex { get; } = new object { }; - public ParseCurrentInstallationController(IParseInstallationController installationIdController, IStorageController storageController, IParseInstallationCoder installationCoder) + TaskQueue TaskQueue { get; } = new TaskQueue { }; + + IParseInstallationController InstallationController { get; } + + IStorageController StorageController { get; } + + IParseInstallationCoder InstallationCoder { get; } + + IParseObjectClassController ClassController { get; } + + public ParseCurrentInstallationController(IParseInstallationController installationIdController, IStorageController storageController, IParseInstallationCoder installationCoder, IParseObjectClassController classController) { - this.installationIdController = installationIdController; - this.storageController = storageController; - this.installationCoder = installationCoder; + InstallationController = installationIdController; + StorageController = storageController; + InstallationCoder = installationCoder; + ClassController = classController; } - private ParseInstallation currentInstallation; + ParseInstallation CurrentInstallationValue { get; set; } + internal ParseInstallation CurrentInstallation { get { - lock (mutex) + lock (Mutex) { - return currentInstallation; + return CurrentInstallationValue; } } set { - lock (mutex) + lock (Mutex) { - currentInstallation = value; + CurrentInstallationValue = value; } } } - public Task SetAsync(ParseInstallation installation, CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - Task saveTask = storageController.LoadAsync().OnSuccess(storage => - { - if (installation == null) - { - return storage.Result.RemoveAsync(ParseInstallationKey); - } - else - { - IDictionary data = installationCoder.Encode(installation); - return storage.Result.AddAsync(ParseInstallationKey, Json.Encode(data)); - } - }).Unwrap(); - - CurrentInstallation = installation; - return saveTask; - }).Unwrap(); - }, cancellationToken); + public Task SetAsync(ParseInstallation installation, CancellationToken cancellationToken) => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => + { + Task saveTask = StorageController.LoadAsync().OnSuccess(storage => installation is { } ? storage.Result.AddAsync(ParseInstallationKey, Json.Encode(InstallationCoder.Encode(installation))) : storage.Result.RemoveAsync(ParseInstallationKey)).Unwrap(); + CurrentInstallation = installation; + + return saveTask; + }).Unwrap(), cancellationToken); public Task GetAsync(CancellationToken cancellationToken) { @@ -76,37 +71,28 @@ public Task GetAsync(CancellationToken cancellationToken) return Task.FromResult(cachedCurrent); } - return taskQueue.Enqueue(toAwait => + return TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => StorageController.LoadAsync().OnSuccess(stroage => { - return toAwait.ContinueWith(_ => + Task fetchTask; + stroage.Result.TryGetValue(ParseInstallationKey, out object temp); + ParseInstallation installation = default; + + if (temp is string installationDataString) { - return storageController.LoadAsync().OnSuccess(stroage => - { - Task fetchTask; - stroage.Result.TryGetValue(ParseInstallationKey, out object temp); - string installationDataString = temp as string; - ParseInstallation installation = null; - if (installationDataString != null) - { - IDictionary installationData = Json.Parse(installationDataString) as IDictionary; - installation = installationCoder.Decode(installationData); - - fetchTask = Task.FromResult(null); - } - else - { - installation = ParseObject.Create(); - fetchTask = installationIdController.GetAsync().ContinueWith(t => - { - installation.SetIfDifferent("installationId", t.Result.ToString()); - }); - } - - CurrentInstallation = installation; - return fetchTask.ContinueWith(t => installation); - }); - }).Unwrap().Unwrap(); - }, cancellationToken); + IDictionary installationData = Json.Parse(installationDataString) as IDictionary; + installation = InstallationCoder.Decode(installationData); + + fetchTask = Task.FromResult(null); + } + else + { + installation = ClassController.CreateObject(); + fetchTask = InstallationController.GetAsync().ContinueWith(t => installation.SetIfDifferent("installationId", t.Result.ToString())); + } + + CurrentInstallation = installation; + return fetchTask.ContinueWith(task => installation); + })).Unwrap().Unwrap(), cancellationToken); } public Task ExistsAsync(CancellationToken cancellationToken) @@ -116,13 +102,7 @@ public Task ExistsAsync(CancellationToken cancellationToken) return Task.FromResult(true); } - return taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - return storageController.LoadAsync().OnSuccess(s => s.Result.ContainsKey(ParseInstallationKey)); - }).Unwrap(); - }, cancellationToken); + return TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => StorageController.LoadAsync().OnSuccess(s => s.Result.ContainsKey(ParseInstallationKey))).Unwrap(), cancellationToken); } public bool IsCurrent(ParseInstallation installation) => CurrentInstallation == installation; @@ -133,13 +113,7 @@ public void ClearFromDisk() { ClearFromMemory(); - taskQueue.Enqueue(toAwait => - { - return toAwait.ContinueWith(_ => - { - return storageController.LoadAsync().OnSuccess(storage => storage.Result.RemoveAsync(ParseInstallationKey)); - }).Unwrap().Unwrap(); - }, CancellationToken.None); + TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => StorageController.LoadAsync().OnSuccess(storage => storage.Result.RemoveAsync(ParseInstallationKey))).Unwrap().Unwrap(), CancellationToken.None); } } } diff --git a/Parse/Platform/Installation/ParseInstallationCoder.cs b/Parse/Platform/Installation/ParseInstallationCoder.cs index a302c6e9..95d8d433 100644 --- a/Parse/Platform/Installation/ParseInstallationCoder.cs +++ b/Parse/Platform/Installation/ParseInstallationCoder.cs @@ -6,30 +6,34 @@ namespace Parse.Push.Internal { public class ParseInstallationCoder : IParseInstallationCoder { - private static readonly ParseInstallationCoder instance = new ParseInstallationCoder(); - public static ParseInstallationCoder Instance => instance; - private const string ISO8601Format = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'"; + IParseDataDecoder Decoder { get; } + + IParseObjectClassController ClassController { get; } + + public ParseInstallationCoder(IParseDataDecoder decoder, IParseObjectClassController classController) => Decoder = decoder; public IDictionary Encode(ParseInstallation installation) { - IObjectState state = installation.GetState(); + IObjectState state = installation.State; IDictionary data = PointerOrLocalIdEncoder.Instance.Encode(state.ToDictionary(x => x.Key, x => x.Value)) as IDictionary; + data["objectId"] = state.ObjectId; - if (state.CreatedAt != null) + + // The following operations use the date and time serialization format defined by ISO standard 8601. + + if (state.CreatedAt is { }) { - data["createdAt"] = state.CreatedAt.Value.ToString(ISO8601Format); + data["createdAt"] = state.CreatedAt.Value.ToString(ParseClient.DateFormatStrings[0]); } - if (state.UpdatedAt != null) + + if (state.UpdatedAt is { }) { - data["updatedAt"] = state.UpdatedAt.Value.ToString(ISO8601Format); + data["updatedAt"] = state.UpdatedAt.Value.ToString(ParseClient.DateFormatStrings[0]); } + return data; } - public ParseInstallation Decode(IDictionary data) - { - IObjectState state = ParseObjectCoder.Instance.Decode(data, ParseDecoder.Instance); - return ParseObjectExtensions.FromState(state, "_Installation"); - } + public ParseInstallation Decode(IDictionary data) => ClassController.GenerateObjectFromState(ParseObjectCoder.Instance.Decode(data, Decoder), "_Installation"); } } \ No newline at end of file diff --git a/Parse/Platform/Installation/ParseInstallationController.cs b/Parse/Platform/Installation/ParseInstallationController.cs new file mode 100644 index 00000000..15702d00 --- /dev/null +++ b/Parse/Platform/Installation/ParseInstallationController.cs @@ -0,0 +1,66 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.Threading.Tasks; +using Parse.Common.Internal; + +namespace Parse.Core.Internal +{ + public class ParseInstallationController : IParseInstallationController + { + static string InstallationIdKey { get; } = "InstallationId"; + + object Mutex { get; } = new object { }; + + Guid? InstallationId { get; set; } + + IStorageController StorageController { get; } + + public ParseInstallationController(IStorageController storageController) => StorageController = storageController; + + public Task SetAsync(Guid? installationId) + { + lock (Mutex) + { +#warning Should refactor here if this operates correctly. + + Task saveTask = installationId is { } ? StorageController.LoadAsync().OnSuccess(storage => storage.Result.AddAsync(InstallationIdKey, installationId.ToString())).Unwrap() : StorageController.LoadAsync().OnSuccess(storage => storage.Result.RemoveAsync(InstallationIdKey)).Unwrap(); + + InstallationId = installationId; + return saveTask; + } + } + + public Task GetAsync() + { + lock (Mutex) + { + if (InstallationId is { }) + { + return Task.FromResult(InstallationId); + } + } + + return StorageController.LoadAsync().OnSuccess(storageTask => + { + storageTask.Result.TryGetValue(InstallationIdKey, out object id); + + try + { + lock (Mutex) + { + return Task.FromResult(InstallationId = new Guid(id as string)); + } + } + catch (Exception) + { + Guid newInstallationId = Guid.NewGuid(); + return SetAsync(newInstallationId).OnSuccess(_ => newInstallationId); + } + }) + .Unwrap(); + } + + public Task ClearAsync() => SetAsync(null); + } +} diff --git a/Parse/Platform/Installation/ParseInstallationDataFinalizer.cs b/Parse/Platform/Installation/ParseInstallationDataFinalizer.cs new file mode 100644 index 00000000..704c0b73 --- /dev/null +++ b/Parse/Platform/Installation/ParseInstallationDataFinalizer.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading.Tasks; + +namespace Parse.Push.Internal +{ + /// + /// Controls the device information. + /// + public class ParseInstallationDataFinalizer : IParseInstallationDataFinalizer + { + public Task FinalizeAsync(ParseInstallation installation) => Task.FromResult(null); + + public void Initialize() { } + } +} \ No newline at end of file diff --git a/Parse/Platform/Notifications/ParsePushChannelsController.cs b/Parse/Platform/Notifications/ParsePushChannelsController.cs index f8c9ea20..70e9f7d0 100644 --- a/Parse/Platform/Notifications/ParsePushChannelsController.cs +++ b/Parse/Platform/Notifications/ParsePushChannelsController.cs @@ -3,23 +3,26 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Library; namespace Parse.Push.Internal { internal class ParsePushChannelsController : IParsePushChannelsController { - public Task SubscribeAsync(IEnumerable channels, CancellationToken cancellationToken) + IParseCurrentInstallationController CurrentInstallationController { get; } + + public ParsePushChannelsController(IParseCurrentInstallationController currentInstallationController) => CurrentInstallationController = currentInstallationController; + + public Task SubscribeAsync(IEnumerable channels, CancellationToken cancellationToken) => CurrentInstallationController.GetAsync(cancellationToken).ContinueWith(task => { - ParseInstallation installation = ParseInstallation.CurrentInstallation; - installation.AddRangeUniqueToList("channels", channels); - return installation.SaveAsync(cancellationToken); - } + task.Result.AddRangeUniqueToList(nameof(channels), channels); + return task.Result.SaveAsync(cancellationToken); + }); - public Task UnsubscribeAsync(IEnumerable channels, CancellationToken cancellationToken) + public Task UnsubscribeAsync(IEnumerable channels, CancellationToken cancellationToken) => CurrentInstallationController.GetAsync().ContinueWith(task => { - ParseInstallation installation = ParseInstallation.CurrentInstallation; - installation.RemoveAllFromList("channels", channels); - return installation.SaveAsync(cancellationToken); - } + task.Result.RemoveAllFromList(nameof(channels), channels); + return task.Result.SaveAsync(cancellationToken); + }); } } diff --git a/Parse/Platform/Notifications/ParsePushEncoder.cs b/Parse/Platform/Notifications/ParsePushEncoder.cs index c3d46ef2..410e1b73 100644 --- a/Parse/Platform/Notifications/ParsePushEncoder.cs +++ b/Parse/Platform/Notifications/ParsePushEncoder.cs @@ -9,32 +9,42 @@ namespace Parse.Push.Internal { public class ParsePushEncoder { - private static readonly ParsePushEncoder instance = new ParsePushEncoder(); - public static ParsePushEncoder Instance => instance; + public static ParsePushEncoder Instance { get; } = new ParsePushEncoder { }; private ParsePushEncoder() { } public IDictionary Encode(IPushState state) { - if (state.Alert == null && state.Data == null) + if (state.Alert is null && state.Data is null) { throw new InvalidOperationException("A push must have either an Alert or Data"); } - if (state.Channels == null && state.Query == null) + + if (state.Channels is null && state.Query is null) { throw new InvalidOperationException("A push must have either Channels or a Query"); } - IDictionary data = state.Data ?? new Dictionary { { "alert", state.Alert } }; - var query = state.Query ?? ParseInstallation.Query; + IDictionary data = state.Data ?? new Dictionary + { + ["alert"] = state.Alert + }; + +#warning Verify that it is fine to instantiate a ParseQuery here with a default(IServiceHub). + + ParseQuery query = state.Query ?? new ParseQuery(default, "_Installation") { }; + if (state.Channels != null) { query = query.WhereContainedIn("channels", state.Channels); } - Dictionary payload = new Dictionary { - { "data", data }, - { "where", query.BuildParameters().GetOrDefault("where", new Dictionary()) }, - }; + + Dictionary payload = new Dictionary + { + [nameof(data)] = data, + ["where"] = query.BuildParameters().GetOrDefault("where", new Dictionary { }), + }; + if (state.Expiration.HasValue) { payload["expiration_time"] = state.Expiration.Value.ToString("yyyy-MM-ddTHH:mm:ssZ"); @@ -43,6 +53,7 @@ public IDictionary Encode(IPushState state) { payload["expiration_interval"] = state.ExpirationInterval.Value.TotalSeconds; } + if (state.PushTime.HasValue) { payload["push_time"] = state.PushTime.Value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"); diff --git a/Parse/Platform/Notifications/ParsePushModule.cs b/Parse/Platform/Notifications/ParsePushModule.cs index b29980f0..f383c93a 100644 --- a/Parse/Platform/Notifications/ParsePushModule.cs +++ b/Parse/Platform/Notifications/ParsePushModule.cs @@ -3,22 +3,21 @@ namespace Parse.Push.Internal { - public class ParsePushModule : IParseModule - { - public void OnModuleRegistered() - { - } +#warning Check if the logic in this class is necessary to run. - public void OnParseInitialized() - { - ParseObject.RegisterDerivative(); + //public class ParsePushModule : IParseModule + //{ + // public void ExecuteModuleRegistrationHook() + // { + // } - ParseCorePlugins.Instance.SubclassingController.AddRegisterHook(typeof(ParseInstallation), () => - { - ParsePushPlugins.Instance.CurrentInstallationController.ClearFromMemory(); - }); + // public void ExecuteLibraryInitializationHook() + // { + // ParseObject.RegisterDerivative(); - ParsePushPlugins.Instance.DeviceInfoController.Initialize(); - } - } + // ParseCorePlugins.Instance.SubclassingController.AddRegisterHook(typeof(ParseInstallation), () => ParsePushPlugins.Instance.CurrentInstallationController.ClearFromMemory()); + + // ParsePushPlugins.Instance.DeviceInfoController.Initialize(); + // } + //} } \ No newline at end of file diff --git a/Parse/Platform/Notifications/ParsePushPlugins.cs b/Parse/Platform/Notifications/ParsePushPlugins.cs index efa248fc..bc2bd030 100644 --- a/Parse/Platform/Notifications/ParsePushPlugins.cs +++ b/Parse/Platform/Notifications/ParsePushPlugins.cs @@ -1,143 +1,143 @@ -using Parse.Abstractions.Management; -using Parse.Management; +//using Parse.Abstractions.Management; +//using Parse.Management; -namespace Parse.Push.Internal -{ - public class ParsePushPlugins : IParsePushPlugins - { - private static readonly object instanceMutex = new object(); - private static IParsePushPlugins instance; - public static IParsePushPlugins Instance - { - get - { - instance ??= new ParsePushPlugins(); - return instance; - } - set - { - lock (instanceMutex) - { - instance = value; - } - } - } +//namespace Parse.Push.Internal +//{ +// public class ParsePushPlugins : IParsePushPlugins +// { +// private static readonly object instanceMutex = new object(); +// private static IParsePushPlugins instance; +// public static IParsePushPlugins Instance +// { +// get +// { +// instance ??= new ParsePushPlugins(); +// return instance; +// } +// set +// { +// lock (instanceMutex) +// { +// instance = value; +// } +// } +// } - private readonly object mutex = new object(); +// private readonly object mutex = new object(); - private IParseCorePlugins corePlugins; - private IParsePushChannelsController pushChannelsController; - private IParsePushController pushController; - private IParseCurrentInstallationController currentInstallationController; - private IDeviceInfoController deviceInfoController; +// private IParseCorePlugins corePlugins; +// private IParsePushChannelsController pushChannelsController; +// private IParsePushController pushController; +// private IParseCurrentInstallationController currentInstallationController; +// private IInstallationDataFinalizer deviceInfoController; - public void Reset() - { - lock (mutex) - { - CorePlugins = null; - PushChannelsController = null; - PushController = null; - CurrentInstallationController = null; - DeviceInfoController = null; - } - } +// public void Reset() +// { +// lock (mutex) +// { +// CorePlugins = null; +// PushChannelsController = null; +// PushController = null; +// CurrentInstallationController = null; +// DeviceInfoController = null; +// } +// } - public IParseCorePlugins CorePlugins - { - get - { - lock (mutex) - { - corePlugins ??= ParseCorePlugins.Instance; - return corePlugins; - } - } - set - { - lock (mutex) - { - corePlugins = value; - } - } - } +// public IParseCorePlugins CorePlugins +// { +// get +// { +// lock (mutex) +// { +// corePlugins ??= ParseCorePlugins.Instance; +// return corePlugins; +// } +// } +// set +// { +// lock (mutex) +// { +// corePlugins = value; +// } +// } +// } - public IParsePushChannelsController PushChannelsController - { - get - { - lock (mutex) - { - pushChannelsController ??= new ParsePushChannelsController(); - return pushChannelsController; - } - } - set - { - lock (mutex) - { - pushChannelsController = value; - } - } - } +// public IParsePushChannelsController PushChannelsController +// { +// get +// { +// lock (mutex) +// { +// pushChannelsController ??= new ParsePushChannelsController(); +// return pushChannelsController; +// } +// } +// set +// { +// lock (mutex) +// { +// pushChannelsController = value; +// } +// } +// } - public IParsePushController PushController - { - get - { - lock (mutex) - { - pushController ??= new ParsePushController(CorePlugins.CommandRunner, CorePlugins.CurrentUserController); - return pushController; - } - } - set - { - lock (mutex) - { - pushController = value; - } - } - } +// public IParsePushController PushController +// { +// get +// { +// lock (mutex) +// { +// pushController ??= new ParsePushController(CorePlugins.CommandRunner, CorePlugins.CurrentUserController); +// return pushController; +// } +// } +// set +// { +// lock (mutex) +// { +// pushController = value; +// } +// } +// } - public IParseCurrentInstallationController CurrentInstallationController - { - get - { - lock (mutex) - { - currentInstallationController ??= new ParseCurrentInstallationController( - CorePlugins.InstallationController, CorePlugins.StorageController, ParseInstallationCoder.Instance - ); - return currentInstallationController; - } - } - set - { - lock (mutex) - { - currentInstallationController = value; - } - } - } +// public IParseCurrentInstallationController CurrentInstallationController +// { +// get +// { +// lock (mutex) +// { +// currentInstallationController ??= new ParseCurrentInstallationController( +// CorePlugins.InstallationController, CorePlugins.StorageController, ParseInstallationCoder.Instance +// ); +// return currentInstallationController; +// } +// } +// set +// { +// lock (mutex) +// { +// currentInstallationController = value; +// } +// } +// } - public IDeviceInfoController DeviceInfoController - { - get - { - lock (mutex) - { - deviceInfoController ??= new DeviceInfoController(); - return deviceInfoController; - } - } - set - { - lock (mutex) - { - deviceInfoController = value; - } - } - } - } -} +// public IInstallationDataFinalizer DeviceInfoController +// { +// get +// { +// lock (mutex) +// { +// deviceInfoController ??= new InstallationDataFinalizer(); +// return deviceInfoController; +// } +// } +// set +// { +// lock (mutex) +// { +// deviceInfoController = value; +// } +// } +// } +// } +//} diff --git a/Parse/Platform/Security/ParseACL.cs b/Parse/Platform/Security/ParseACL.cs index dbfc3163..c858d177 100644 --- a/Parse/Platform/Security/ParseACL.cs +++ b/Parse/Platform/Security/ParseACL.cs @@ -53,7 +53,7 @@ public ParseACL(ParseUser owner) SetWriteAccess(owner, true); } - IDictionary IJsonConvertible.ToJSON() + IDictionary IJsonConvertible.ConvertToJSON() { Dictionary result = new Dictionary(); foreach (string user in readers.Union(writers)) diff --git a/Parse/Platform/Session/ParseSessionController.cs b/Parse/Platform/Session/ParseSessionController.cs index bd3bf726..3076d2fe 100644 --- a/Parse/Platform/Session/ParseSessionController.cs +++ b/Parse/Platform/Session/ParseSessionController.cs @@ -9,45 +9,17 @@ namespace Parse.Core.Internal { public class ParseSessionController : IParseSessionController { - private readonly IParseCommandRunner commandRunner; - - public ParseSessionController(IParseCommandRunner commandRunner) => this.commandRunner = commandRunner; - - public Task GetSessionAsync(string sessionToken, CancellationToken cancellationToken) - { - ParseCommand command = new ParseCommand("sessions/me", - method: "GET", - sessionToken: sessionToken, - data: null); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - return ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); - }); - } - - public Task RevokeAsync(string sessionToken, CancellationToken cancellationToken) - { - ParseCommand command = new ParseCommand("logout", - method: "POST", - sessionToken: sessionToken, - data: new Dictionary()); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken); - } - - public Task UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) - { - ParseCommand command = new ParseCommand("upgradeToRevocableSession", - method: "POST", - sessionToken: sessionToken, - data: new Dictionary()); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => - { - return ParseObjectCoder.Instance.Decode(t.Result.Item2, ParseDecoder.Instance); - }); - } + IParseCommandRunner CommandRunner { get; } + + IParseDataDecoder Decoder { get; } + + public ParseSessionController(IParseCommandRunner commandRunner, IParseDataDecoder decoder) => (CommandRunner, Decoder) = (commandRunner, decoder); + + public Task GetSessionAsync(string sessionToken, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand("sessions/me", method: "GET", sessionToken: sessionToken, data: null), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder)); + + public Task RevokeAsync(string sessionToken, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand("logout", method: "POST", sessionToken: sessionToken, data: new Dictionary { }), cancellationToken: cancellationToken); + + public Task UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand("upgradeToRevocableSession", method: "POST", sessionToken: sessionToken, data: new Dictionary()), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder)); public bool IsRevocableSessionToken(string sessionToken) => sessionToken.Contains("r:"); } diff --git a/Parse/PushServiceExtensions.cs b/Parse/PushServiceExtensions.cs new file mode 100644 index 00000000..4bbf7197 --- /dev/null +++ b/Parse/PushServiceExtensions.cs @@ -0,0 +1,201 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Parse.Abstractions.Library; +using Parse.Common.Internal; + +namespace Parse +{ + public static class PushServiceExtensions + { + /// + /// Pushes a simple message to every device. This is shorthand for: + /// + /// + /// var push = new ParsePush(); + /// push.Data = new Dictionary<string, object>{{"alert", alert}}; + /// return push.SendAsync(); + /// + /// + /// The alert message to send. + public static Task SendPushAlertAsync(this IServiceHub serviceHub, string alert) => new ParsePush(serviceHub) { Alert = alert }.SendAsync(); + + /// + /// Pushes a simple message to every device subscribed to channel. This is shorthand for: + /// + /// + /// var push = new ParsePush(); + /// push.Channels = new List<string> { channel }; + /// push.Data = new Dictionary<string, object>{{"alert", alert}}; + /// return push.SendAsync(); + /// + /// + /// The alert message to send. + /// An Installation must be subscribed to channel to receive this Push Notification. + public static Task SendPushAlertAsync(this IServiceHub serviceHub, string alert, string channel) => new ParsePush(serviceHub) { Channels = new List { channel }, Alert = alert }.SendAsync(); + + /// + /// Pushes a simple message to every device subscribed to any of channels. This is shorthand for: + /// + /// + /// var push = new ParsePush(); + /// push.Channels = channels; + /// push.Data = new Dictionary<string, object>{{"alert", alert}}; + /// return push.SendAsync(); + /// + /// + /// The alert message to send. + /// An Installation must be subscribed to any of channels to receive this Push Notification. + public static Task SendPushAlertAsync(this IServiceHub serviceHub, string alert, IEnumerable channels) => new ParsePush(serviceHub) { Channels = channels, Alert = alert }.SendAsync(); + + /// + /// Pushes a simple message to every device matching the target query. This is shorthand for: + /// + /// + /// var push = new ParsePush(); + /// push.Query = query; + /// push.Data = new Dictionary<string, object>{{"alert", alert}}; + /// return push.SendAsync(); + /// + /// + /// The alert message to send. + /// A query filtering the devices which should receive this Push Notification. + public static Task SendPushAlertAsync(this IServiceHub serviceHub, string alert, ParseQuery query) => new ParsePush(serviceHub) { Query = query, Alert = alert }.SendAsync(); + + /// + /// Pushes an arbitrary payload to every device. This is shorthand for: + /// + /// + /// var push = new ParsePush(); + /// push.Data = data; + /// return push.SendAsync(); + /// + /// + /// A push payload. See the ParsePush.Data property for more information. + public static Task SendPushDataAsync(this IServiceHub serviceHub, IDictionary data) => new ParsePush(serviceHub) { Data = data }.SendAsync(); + + /// + /// Pushes an arbitrary payload to every device subscribed to channel. This is shorthand for: + /// + /// + /// var push = new ParsePush(); + /// push.Channels = new List<string> { channel }; + /// push.Data = data; + /// return push.SendAsync(); + /// + /// + /// A push payload. See the ParsePush.Data property for more information. + /// An Installation must be subscribed to channel to receive this Push Notification. + public static Task SendPushDataAsync(this IServiceHub serviceHub, IDictionary data, string channel) => new ParsePush(serviceHub) { Channels = new List { channel }, Data = data }.SendAsync(); + + /// + /// Pushes an arbitrary payload to every device subscribed to any of channels. This is shorthand for: + /// + /// + /// var push = new ParsePush(); + /// push.Channels = channels; + /// push.Data = data; + /// return push.SendAsync(); + /// + /// + /// A push payload. See the ParsePush.Data property for more information. + /// An Installation must be subscribed to any of channels to receive this Push Notification. + public static Task SendPushDataAsync(this IServiceHub serviceHub, IDictionary data, IEnumerable channels) => new ParsePush(serviceHub) { Channels = channels, Data = data }.SendAsync(); + + /// + /// Pushes an arbitrary payload to every device matching target. This is shorthand for: + /// + /// + /// var push = new ParsePush(); + /// push.Query = query + /// push.Data = data; + /// return push.SendAsync(); + /// + /// + /// A push payload. See the ParsePush.Data property for more information. + /// A query filtering the devices which should receive this Push Notification. + public static Task SendPushDataAsync(this IServiceHub serviceHub, IDictionary data, ParseQuery query) => new ParsePush(serviceHub) { Query = query, Data = data }.SendAsync(); + + #region Receiving Push + +#warning Check if this should be moved into IParsePushController. + + /// + /// An event fired when a push notification is received. + /// + public static event EventHandler ParsePushNotificationReceived + { + add + { + parsePushNotificationReceived.Add(value); + } + remove + { + parsePushNotificationReceived.Remove(value); + } + } + + internal static readonly SynchronizedEventHandler parsePushNotificationReceived = new SynchronizedEventHandler(); + + #endregion + + #region Push Subscription + + /// + /// Subscribe the current installation to this channel. This is shorthand for: + /// + /// + /// var installation = ParseInstallation.CurrentInstallation; + /// installation.AddUniqueToList("channels", channel); + /// installation.SaveAsync(cancellationToken); + /// + /// + /// The channel to which this installation should subscribe. + /// CancellationToken to cancel the current operation. + public static Task SubscribeToPushChannelAsync(this IServiceHub serviceHub, string channel, CancellationToken cancellationToken = default) => SubscribeToPushChannelsAsync(serviceHub, new List { channel }, cancellationToken); + + /// + /// Subscribe the current installation to these channels. This is shorthand for: + /// + /// + /// var installation = ParseInstallation.CurrentInstallation; + /// installation.AddRangeUniqueToList("channels", channels); + /// installation.SaveAsync(cancellationToken); + /// + /// + /// The channels to which this installation should subscribe. + /// CancellationToken to cancel the current operation. + public static Task SubscribeToPushChannelsAsync(this IServiceHub serviceHub, IEnumerable channels, CancellationToken cancellationToken = default) => serviceHub.PushChannelsController.SubscribeAsync(channels, cancellationToken); + + /// + /// Unsubscribe the current installation from this channel. This is shorthand for: + /// + /// + /// var installation = ParseInstallation.CurrentInstallation; + /// installation.Remove("channels", channel); + /// installation.SaveAsync(cancellationToken); + /// + /// + /// The channel from which this installation should unsubscribe. + /// CancellationToken to cancel the current operation. + public static Task UnsubscribeToPushChannelAsync(this IServiceHub serviceHub, string channel, CancellationToken cancellationToken = default) => UnsubscribeToPushChannelsAsync(serviceHub, new List { channel }, cancellationToken); + + /// + /// Unsubscribe the current installation from these channels. This is shorthand for: + /// + /// + /// var installation = ParseInstallation.CurrentInstallation; + /// installation.RemoveAllFromList("channels", channels); + /// installation.SaveAsync(cancellationToken); + /// + /// + /// The channels from which this installation should unsubscribe. + /// CancellationToken to cancel the current operation. + public static Task UnsubscribeToPushChannelsAsync(this IServiceHub serviceHub, IEnumerable channels, CancellationToken cancellationToken = default) => serviceHub.PushChannelsController.UnsubscribeAsync(channels, cancellationToken); + + #endregion + } +} diff --git a/Parse/Query/ParseQueryController.cs b/Parse/Query/ParseQueryController.cs index cf518f5b..2cbeb16c 100644 --- a/Parse/Query/ParseQueryController.cs +++ b/Parse/Query/ParseQueryController.cs @@ -11,11 +11,13 @@ namespace Parse.Core.Internal { internal class ParseQueryController : IParseQueryController { - private readonly IParseCommandRunner commandRunner; + IParseCommandRunner CommandRunner { get; } - public ParseQueryController(IParseCommandRunner commandRunner) => this.commandRunner = commandRunner; + IParseDataDecoder Decoder { get; } - public Task> FindAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject => FindAsync(query.ClassName, query.BuildParameters(), user?.SessionToken, cancellationToken).OnSuccess(t => (from item in t.Result["results"] as IList select ParseObjectCoder.Instance.Decode(item as IDictionary, ParseDecoder.Instance))); + public ParseQueryController(IParseCommandRunner commandRunner, IParseDataDecoder decoder) => (CommandRunner, Decoder) = (commandRunner, decoder); + + public Task> FindAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject => FindAsync(query.ClassName, query.BuildParameters(), user?.SessionToken, cancellationToken).OnSuccess(t => (from item in t.Result["results"] as IList select ParseObjectCoder.Instance.Decode(item as IDictionary, Decoder))); public Task CountAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject { @@ -23,7 +25,7 @@ public Task CountAsync(ParseQuery query, ParseUser user, Cancellation parameters["limit"] = 0; parameters["count"] = 1; - return FindAsync(query.ClassName, parameters, user?.SessionToken, cancellationToken).OnSuccess(t => Convert.ToInt32(t.Result["count"])); + return FindAsync(query.ClassName, parameters, user?.SessionToken, cancellationToken).OnSuccess(task => Convert.ToInt32(task.Result["count"])); } public Task FirstAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject @@ -31,9 +33,9 @@ public Task FirstAsync(ParseQuery query, ParseUser user, Can IDictionary parameters = query.BuildParameters(); parameters["limit"] = 1; - return FindAsync(query.ClassName, parameters, user?.SessionToken, cancellationToken).OnSuccess(t => (t.Result["results"] as IList).FirstOrDefault() as IDictionary is Dictionary item && item != null ? ParseObjectCoder.Instance.Decode(item, ParseDecoder.Instance) : null); + return FindAsync(query.ClassName, parameters, user?.SessionToken, cancellationToken).OnSuccess(task => (task.Result["results"] as IList).FirstOrDefault() as IDictionary is Dictionary item && item != null ? ParseObjectCoder.Instance.Decode(item, Decoder) : null); } - private Task> FindAsync(string className, IDictionary parameters, string sessionToken, CancellationToken cancellationToken) => commandRunner.RunCommandAsync(new ParseCommand($"classes/{Uri.EscapeDataString(className)}?{ParseClient.BuildQueryString(parameters)}", method: "GET", sessionToken: sessionToken, data: null), cancellationToken: cancellationToken).OnSuccess(t => t.Result.Item2); + Task> FindAsync(string className, IDictionary parameters, string sessionToken, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand($"classes/{Uri.EscapeDataString(className)}?{ParseClient.BuildQueryString(parameters)}", method: "GET", sessionToken: sessionToken, data: null), cancellationToken: cancellationToken).OnSuccess(t => t.Result.Item2); } } diff --git a/Parse/QueryServiceExtensions.cs b/Parse/QueryServiceExtensions.cs new file mode 100644 index 00000000..b5d6ef1f --- /dev/null +++ b/Parse/QueryServiceExtensions.cs @@ -0,0 +1,67 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Parse.Abstractions.Library; + +namespace Parse +{ + public static class QueryServiceExtensions + { + public static ParseQuery GetQuery(this IServiceHub serviceHub) where T : ParseObject => new ParseQuery(serviceHub); + + // ALTERNATE NAME: BuildOrQuery + + /// + /// Constructs a query that is the or of the given queries. + /// + /// The type of ParseObject being queried. + /// An initial query to 'or' with additional queries. + /// The list of ParseQueries to 'or' together. + /// A query that is the or of the given queries. + public static ParseQuery ConstructOrQuery(this IServiceHub serviceHub, ParseQuery source, params ParseQuery[] queries) where T : ParseObject => serviceHub.ConstructOrQuery(queries.Concat(new[] { source })); + + /// + /// Constructs a query that is the or of the given queries. + /// + /// The list of ParseQueries to 'or' together. + /// A ParseQquery that is the 'or' of the passed in queries. + public static ParseQuery ConstructOrQuery(this IServiceHub serviceHub, IEnumerable> queries) where T : ParseObject + { + string className = default; + List> orValue = new List> { }; + + // We need to cast it to non-generic IEnumerable because of AOT-limitation + + IEnumerable nonGenericQueries = queries; + foreach (object obj in nonGenericQueries) + { + ParseQuery query = obj as ParseQuery; + + if (className is { } && query.ClassName != className) + { + throw new ArgumentException("All of the queries in an or query must be on the same class."); + } + + className = query.ClassName; + IDictionary parameters = query.BuildParameters(); + + if (parameters.Count == 0) + { + continue; + } + + if (!parameters.TryGetValue("where", out object 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 ParseQuery(new ParseQuery(serviceHub, className), where: new Dictionary { ["$or"] = orValue }); + } + } +} diff --git a/Parse/RoleServiceExtensions.cs b/Parse/RoleServiceExtensions.cs new file mode 100644 index 00000000..09b4ef6a --- /dev/null +++ b/Parse/RoleServiceExtensions.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using Parse.Abstractions.Library; + +namespace Parse +{ + public static class RoleServiceExtensions + { + /// + /// Gets a over the Role collection. + /// + public static ParseQuery GetRoleQuery(this IServiceHub serviceHub) => serviceHub.GetQuery(); + } +} diff --git a/Parse/SessionsServiceExtensions.cs b/Parse/SessionsServiceExtensions.cs new file mode 100644 index 00000000..8783c755 --- /dev/null +++ b/Parse/SessionsServiceExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Threading; +using System.Threading.Tasks; +using Parse.Abstractions.Library; +using Parse.Common.Internal; +using Parse.Core.Internal; + +namespace Parse +{ + public static class SessionsServiceExtensions + { + /// + /// Constructs a for ParseSession. + /// + public static ParseQuery GetSessionQuery(this IServiceHub serviceHub) => serviceHub.GetQuery(); + + /// + /// Gets the current object related to the current user. + /// + public static Task GetCurrentSessionAsync(this IServiceHub serviceHub) => GetCurrentSessionAsync(serviceHub, CancellationToken.None); + + /// + /// Gets the current object related to the current user. + /// + /// The cancellation token + public static Task GetCurrentSessionAsync(this IServiceHub serviceHub, CancellationToken cancellationToken) => serviceHub.GetCurrentUserAsync().OnSuccess(task => task.Result switch + { + null => Task.FromResult(default), + { SessionToken: null } => Task.FromResult(default), + { SessionToken: { } sessionToken } => serviceHub.SessionController.GetSessionAsync(sessionToken, cancellationToken).OnSuccess(successTask => serviceHub.GenerateObjectFromState(successTask.Result, "_Session")) + }).Unwrap(); + + public static Task RevokeSessionAsync(this IServiceHub serviceHub, string sessionToken, CancellationToken cancellationToken) => sessionToken is null || !serviceHub.SessionController.IsRevocableSessionToken(sessionToken) ? Task.CompletedTask : serviceHub.SessionController.RevokeAsync(sessionToken, cancellationToken); + + public static Task UpgradeToRevocableSessionAsync(this IServiceHub serviceHub, string sessionToken, CancellationToken cancellationToken) => sessionToken is null || serviceHub.SessionController.IsRevocableSessionToken(sessionToken) ? Task.FromResult(sessionToken) : serviceHub.SessionController.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken).OnSuccess(task => serviceHub.GenerateObjectFromState(task.Result, "_Session").SessionToken); + } +} diff --git a/Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs b/Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs index 4e3b25e4..f275ac48 100644 --- a/Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs +++ b/Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using Parse.Abstractions.Library; using Parse.Abstractions.Management; using Parse.Abstractions.Storage; @@ -26,20 +27,20 @@ public struct IdentifierBasedCacheLocationConfiguration : ICacheLocationConfigur /// The corresponding relative path generated by this . /// /// This will cause a .cachefile file extension to be added to the cache file in order to prevent the creation of files with unwanted extensions due to the value of containing periods. - public string GetRelativeStorageFilePath(IParseCorePlugins plugins) + public string GetRelativeStorageFilePath(IServiceHub serviceHub) { FileInfo file; - while ((file = plugins.StorageController.GetWrapperForRelativePersistentStorageFilePath(GeneratePath())).Exists && IsFallback) + while ((file = serviceHub.StorageController.GetWrapperForRelativePersistentStorageFilePath(GeneratePath())).Exists && IsFallback) ; return file.FullName; } /// - /// Generates a path for use in the method. + /// Generates a path for use in the method. /// /// A potential path to the cachefile - string GeneratePath() => Path.Combine("Parse", IsFallback ? "_fallback" : "_global", $"{(IsFallback ? new Random { }.Next().ToString() : Identifier)}.cachefile"); + string GeneratePath() => Path.Combine(nameof(Parse), IsFallback ? "_fallback" : "_global", $"{(IsFallback ? new Random { }.Next().ToString() : Identifier)}.cachefile"); } } diff --git a/Parse/Storage/MetadataBasedCacheLocationConfiguration.cs b/Parse/Storage/MetadataBasedCacheLocationConfiguration.cs index 06e7a68e..ee3e27ad 100644 --- a/Parse/Storage/MetadataBasedCacheLocationConfiguration.cs +++ b/Parse/Storage/MetadataBasedCacheLocationConfiguration.cs @@ -1,8 +1,10 @@ using System; using System.IO; using System.Reflection; +using Parse.Abstractions.Library; using Parse.Abstractions.Management; using Parse.Abstractions.Storage; +using Parse.Library; namespace Parse.Storage { @@ -12,28 +14,28 @@ namespace Parse.Storage public struct MetadataBasedCacheLocationConfiguration : ICacheLocationConfiguration { /// - /// An instance of with inferred values based on the entry assembly. Should be used with . + /// An instance of with inferred values based on the entry assembly. Should be used with and . /// /// Should not be used with Unity. - public static MetadataBasedCacheLocationConfiguration NoCompanyInferred { get; } = new MetadataBasedCacheLocationConfiguration + public static MetadataBasedCacheLocationConfiguration Inferred => new MetadataBasedCacheLocationConfiguration { - CompanyName = Assembly.GetEntryAssembly().GetName().Name, - ProductName = String.Empty + Company = Assembly.GetExecutingAssembly()?.GetCustomAttribute()?.Company, + Product = Assembly.GetEntryAssembly()?.GetCustomAttribute()?.Product ?? Assembly.GetEntryAssembly()?.GetName()?.Name }; /// - /// The name of the company that owns the product specified by . + /// The name of the company that owns the product specified by . /// - public string CompanyName { get; set; } + public string Company { get; set; } /// /// The name of the product that is using the Parse .NET SDK. /// - public string ProductName { get; set; } + public string Product { get; set; } /// /// The corresponding relative path generated by this . /// - public string GetRelativeStorageFilePath(IParseCorePlugins plugins) => Path.Combine(CompanyName ?? "Parse", ProductName ?? "_global", $"{plugins.MetadataController.HostVersioningData.DisplayVersion ?? "1.0.0.0"}.cachefile"); + public string GetRelativeStorageFilePath(IServiceHub serviceHub) => Path.Combine(Company ?? nameof(Parse), Product ?? "_global", $"{serviceHub.MetadataController.HostManifestData.ShortVersion ?? "1.0.0.0"}.pc"); } } diff --git a/Parse/Storage/StorageController.cs b/Parse/Storage/StorageController.cs index e6eaaff3..e719b82a 100644 --- a/Parse/Storage/StorageController.cs +++ b/Parse/Storage/StorageController.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Parse.Core.Internal; using Parse.Internal.Utilities; +using Parse.Library; using Parse.Management; using Parse.Storage; @@ -190,7 +191,7 @@ public void Clean() /// /// Gets the calculated persistent storage file fallback path for this app execution. /// - public string FallbackPersistentStorageFilePath => StoredFallbackPersistentStorageFilePath ??= IdentifierBasedCacheLocationConfiguration.Fallback.GetRelativeStorageFilePath(new LightParseCorePlugins { StorageController = this }); + public string FallbackPersistentStorageFilePath => StoredFallbackPersistentStorageFilePath ??= IdentifierBasedCacheLocationConfiguration.Fallback.GetRelativeStorageFilePath(new MutableServiceHub { StorageController = this }); string StoredFallbackPersistentStorageFilePath { get; set; } diff --git a/Parse/UserServiceExtensions.cs b/Parse/UserServiceExtensions.cs new file mode 100644 index 00000000..91010748 --- /dev/null +++ b/Parse/UserServiceExtensions.cs @@ -0,0 +1,232 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Parse.Abstractions.Library; +using Parse.Common.Internal; +using Parse.Core.Internal; + +namespace Parse +{ + public static class UserServiceExtensions + { + internal static string GetCurrentSessionToken(this IServiceHub serviceHub) + { + Task sessionTokenTask = GetCurrentSessionTokenAsync(serviceHub); + sessionTokenTask.Wait(); + return sessionTokenTask.Result; + } + + internal static Task GetCurrentSessionTokenAsync(this IServiceHub serviceHub, CancellationToken cancellationToken = default) => serviceHub.CurrentUserController.GetCurrentSessionTokenAsync(cancellationToken); + + /// + /// 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(this IServiceHub serviceHub, string username, string password) => LogInAsync(serviceHub, 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(this IServiceHub serviceHub, string username, string password, CancellationToken cancellationToken) => serviceHub.UserController.LogInAsync(username, password, cancellationToken).OnSuccess(task => + { + ParseUser user = serviceHub.GenerateObjectFromState(task.Result, "_User"); + return SaveCurrentUserAsync(serviceHub, 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(this IServiceHub serviceHub, string sessionToken) => BecomeAsync(serviceHub, 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(this IServiceHub serviceHub, string sessionToken, CancellationToken cancellationToken) => serviceHub.UserController.GetUserAsync(sessionToken, cancellationToken).OnSuccess(t => + { + ParseUser user = serviceHub.GenerateObjectFromState(t.Result, "_User"); + return SaveCurrentUserAsync(serviceHub, user).OnSuccess(_ => user); + }).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(this IServiceHub serviceHub) => LogOutAsync(serviceHub).Wait(); // TODO (hallucinogen): this will without a doubt fail in Unity. But what else can we do? + + /// + /// 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(this IServiceHub serviceHub) => LogOutAsync(serviceHub, 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(this IServiceHub serviceHub, CancellationToken cancellationToken) => GetCurrentUserAsync(serviceHub).OnSuccess(task => + { + LogOutWithProviders(); + return task.Result is { } user ? user.taskQueue.Enqueue(toAwait => user.LogOutAsync(toAwait, cancellationToken), cancellationToken) : Task.CompletedTask; + }).Unwrap(); + + static void LogOutWithProviders() + { + foreach (IParseAuthenticationProvider provider in ParseUser.Authenticators.Values) + { + provider.Deauthenticate(); + } + } + + /// + /// Gets the currently logged in ParseUser with a valid session, either from memory or disk + /// if necessary. + /// + public static ParseUser GetCurrentUser(this IServiceHub serviceHub) + { + Task userTask = GetCurrentUserAsync(serviceHub); + + // TODO (hallucinogen): this will without a doubt fail in Unity. How should we fix it? + + userTask.Wait(); + return userTask.Result; + } + + /// + /// Gets the currently logged in ParseUser with a valid session, either from memory or disk + /// if necessary, asynchronously. + /// + internal static Task GetCurrentUserAsync(this IServiceHub serviceHub, CancellationToken cancellationToken = default) => serviceHub.CurrentUserController.GetAsync(cancellationToken); + + internal static Task SaveCurrentUserAsync(this IServiceHub serviceHub, ParseUser user, CancellationToken cancellationToken = default) => serviceHub.CurrentUserController.SetAsync(user, cancellationToken); + + internal static void ClearInMemoryUser(this IServiceHub serviceHub) => serviceHub.CurrentUserController.ClearFromMemory(); + + /// + /// Constructs a for s. + /// + public static ParseQuery GetUserQuery(this IServiceHub serviceHub) => serviceHub.GetQuery(); + + #region Legacy / Revocable Session Tokens + + /// + /// 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(this IServiceHub serviceHub, CancellationToken cancellationToken = default) + { + lock (serviceHub.UserController.RevocableSessionEnabledMutex) + { + serviceHub.UserController.RevocableSessionEnabled = true; + } + + return GetCurrentUserAsync(serviceHub, cancellationToken).OnSuccess(task => task.Result.UpgradeToRevocableSessionAsync(cancellationToken)); + } + + internal static void DisableRevocableSession(this IServiceHub serviceHub) + { + lock (serviceHub.UserController.RevocableSessionEnabledMutex) + { + serviceHub.UserController.RevocableSessionEnabled = false; + } + } + + internal static bool GetIsRevocableSessionEnabled(this IServiceHub serviceHub) + { + lock (serviceHub.UserController.RevocableSessionEnabledMutex) + { + return serviceHub.UserController.RevocableSessionEnabled; + } + } + + #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 Parse site. + /// + /// The email address associated with the user that forgot their password. + public static Task RequestPasswordResetAsync(this IServiceHub serviceHub, string email) => RequestPasswordResetAsync(serviceHub, 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 Parse site. + /// + /// The email address associated with the user that forgot their password. + /// The cancellation token. + public static Task RequestPasswordResetAsync(this IServiceHub serviceHub, string email, CancellationToken cancellationToken) => serviceHub.UserController.RequestPasswordResetAsync(email, cancellationToken); + + public static Task LogInWithAsync(this IServiceHub serviceHub, string authType, IDictionary data, CancellationToken cancellationToken) + { + ParseUser user = null; + + return serviceHub.UserController.LogInAsync(authType, data, cancellationToken).OnSuccess(task => + { + user = serviceHub.GenerateObjectFromState(task.Result, "_User"); + + lock (user.Mutex) + { + if (user.AuthData == null) + { + user.AuthData = new Dictionary>(); + } + + user.AuthData[authType] = data; + +#warning Check if SynchronizeAllAuthData should accept an IServiceHub for consistency on which actions take place on which IServiceHub implementation instance. + + user.SynchronizeAllAuthData(); + } + + return SaveCurrentUserAsync(serviceHub, user); + }).Unwrap().OnSuccess(t => user); + } + + public static Task LogInWithAsync(this IServiceHub serviceHub, string authType, CancellationToken cancellationToken) + { + IParseAuthenticationProvider provider = ParseUser.GetProvider(authType); + return provider.AuthenticateAsync(cancellationToken).OnSuccess(authData => LogInWithAsync(serviceHub, authType, authData.Result, cancellationToken)).Unwrap(); + } + + internal static void RegisterProvider(this IServiceHub serviceHub, IParseAuthenticationProvider provider) + { + ParseUser.Authenticators[provider.AuthType] = provider; + ParseUser curUser = GetCurrentUser(serviceHub); + + if (curUser != null) + { +#warning Check if SynchronizeAllAuthData should accept an IServiceHub for consistency on which actions take place on which IServiceHub implementation instance. + + curUser.SynchronizeAuthData(provider); + } + } + } +} diff --git a/Parse/Utilities/Conversion.cs b/Parse/Utilities/Conversion.cs index 8e0686f1..ce2319d3 100644 --- a/Parse/Utilities/Conversion.cs +++ b/Parse/Utilities/Conversion.cs @@ -6,6 +6,8 @@ namespace Parse.Utilities { +#warning Possibly should be refactored. + /// /// A set of utilities for converting generic types between each other. /// @@ -52,38 +54,28 @@ internal static object ConvertTo(object value) return value; } - if (ReflectionHelpers.IsPrimitive(typeof(T))) + if (typeof(T).IsPrimitive) { return (T) Convert.ChangeType(value, typeof(T), System.Globalization.CultureInfo.InvariantCulture); } - if (ReflectionHelpers.IsConstructedGenericType(typeof(T))) + if (typeof(T).IsConstructedGenericType) { // Add lifting for nullables. Only supports conversions between primitives. - if (ReflectionHelpers.IsNullable(typeof(T))) + + if (ReflectionHelpers.CheckWrappedWithNullable(typeof(T)) && typeof(T).GenericTypeArguments[0] is { IsPrimitive: true } innerType) { - Type innerType = ReflectionHelpers.GetGenericTypeArguments(typeof(T))[0]; - if (ReflectionHelpers.IsPrimitive(innerType)) - { - return (T) Convert.ChangeType(value, innerType, System.Globalization.CultureInfo.InvariantCulture); - } + return (T) Convert.ChangeType(value, innerType, System.Globalization.CultureInfo.InvariantCulture); } - Type listType = GetInterfaceType(value.GetType(), typeof(IList<>)); - if (listType != null && - typeof(T).GetGenericTypeDefinition() == typeof(IList<>)) + + if (GetInterfaceType(value.GetType(), typeof(IList<>)) is { } listType && typeof(T).GetGenericTypeDefinition() == typeof(IList<>)) { - Type wrapperType = typeof(FlexibleListWrapper<,>) - .MakeGenericType(ReflectionHelpers.GetGenericTypeArguments(typeof(T))[0], - ReflectionHelpers.GetGenericTypeArguments(listType)[0]); - return Activator.CreateInstance(wrapperType, value); + return Activator.CreateInstance(typeof(FlexibleListWrapper<,>).MakeGenericType(typeof(T).GenericTypeArguments[0], listType.GenericTypeArguments[0]), value); } - Type dictType = GetInterfaceType(value.GetType(), typeof(IDictionary<,>)); - if (dictType != null && typeof(T).GetGenericTypeDefinition() == typeof(IDictionary<,>)) + + if (GetInterfaceType(value.GetType(), typeof(IDictionary<,>)) is { } dictType && typeof(T).GetGenericTypeDefinition() == typeof(IDictionary<,>)) { - Type wrapperType = typeof(FlexibleDictionaryWrapper<,>) - .MakeGenericType(ReflectionHelpers.GetGenericTypeArguments(typeof(T))[1], - ReflectionHelpers.GetGenericTypeArguments(dictType)[1]); - return Activator.CreateInstance(wrapperType, value); + return Activator.CreateInstance(typeof(FlexibleDictionaryWrapper<,>).MakeGenericType(typeof(T).GenericTypeArguments[1], dictType.GenericTypeArguments[1]), value); } } @@ -98,23 +90,26 @@ internal static object ConvertTo(object value) /// The map is: /// (object type, generic interface type) => constructed generic type /// - private static readonly Dictionary, Type> interfaceLookupCache = new Dictionary, Type>(); + static Dictionary, Type> InterfaceLookupCache { get; } = new Dictionary, Type>(); - private static Type GetInterfaceType(Type objType, Type genericInterfaceType) + static Type GetInterfaceType(Type objType, Type genericInterfaceType) { Tuple cacheKey = new Tuple(objType, genericInterfaceType); - if (interfaceLookupCache.ContainsKey(cacheKey)) + + if (InterfaceLookupCache.ContainsKey(cacheKey)) { - return interfaceLookupCache[cacheKey]; + return InterfaceLookupCache[cacheKey]; } - foreach (Type type in ReflectionHelpers.GetInterfaces(objType)) + + foreach (Type type in objType.GetInterfaces()) { - if (ReflectionHelpers.IsConstructedGenericType(type) && type.GetGenericTypeDefinition() == genericInterfaceType) + if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == genericInterfaceType) { - return interfaceLookupCache[cacheKey] = type; + return InterfaceLookupCache[cacheKey] = type; } } - return null; + + return default; } } } \ No newline at end of file diff --git a/Parse/Utilities/Encoding/IParseDataDecoder.cs b/Parse/Utilities/Encoding/IParseDataDecoder.cs new file mode 100644 index 00000000..bddd7f2a --- /dev/null +++ b/Parse/Utilities/Encoding/IParseDataDecoder.cs @@ -0,0 +1,9 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +namespace Parse.Core.Internal +{ + public interface IParseDataDecoder + { + object Decode(object data); + } +} \ No newline at end of file diff --git a/Parse/Utilities/Encoding/NoObjectsEncoder.cs b/Parse/Utilities/Encoding/NoObjectsEncoder.cs index 61bb8415..55a62c99 100644 --- a/Parse/Utilities/Encoding/NoObjectsEncoder.cs +++ b/Parse/Utilities/Encoding/NoObjectsEncoder.cs @@ -5,17 +5,16 @@ namespace Parse.Core.Internal { + // This class isn't really a Singleton, but since it has no state, it's more efficient to get the default instance. + /// - /// A that throws an exception if it attempts to encode + /// A that throws an exception if it attempts to encode /// a /// - public class NoObjectsEncoder : ParseEncoder + public class NoObjectsEncoder : ParseDataEncoder { - // 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 => instance; + public static NoObjectsEncoder Instance { get; } = new NoObjectsEncoder(); - protected override IDictionary EncodeParseObject(ParseObject value) => throw new ArgumentException("ParseObjects not allowed here."); + protected override IDictionary EncodeObject(ParseObject value) => throw new ArgumentException("ParseObjects not allowed here."); } } diff --git a/Parse/Utilities/Encoding/ParseDataDecoder.cs b/Parse/Utilities/Encoding/ParseDataDecoder.cs new file mode 100644 index 00000000..1c8f5bfb --- /dev/null +++ b/Parse/Utilities/Encoding/ParseDataDecoder.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Parse.Abstractions.Library; +using Parse.Utilities; + +namespace Parse.Core.Internal +{ + public class ParseDataDecoder : IParseDataDecoder + { + // Prevent default constructor. + + IParseObjectClassController ClassController { get; } + + public ParseDataDecoder(IParseObjectClassController classController) => ClassController = classController; + + static string[] Types { get; } = { "Date", "Bytes", "Pointer", "File", "GeoPoint", "Object", "Relation" }; + + public object Decode(object data) => data switch + { + null => default, + IDictionary { } dictionary when dictionary.ContainsKey("__op") => ParseFieldOperations.Decode(dictionary), + IDictionary { } dictionary when dictionary.TryGetValue("__type", out object type) && Types.Contains(type) => type switch + { + "Date" => ParseDate(dictionary["iso"] as string), + "Bytes" => Convert.FromBase64String(dictionary["base64"] as string), + "Pointer" => DecodePointer(dictionary["className"] as string, dictionary["objectId"] as string), + "File" => new ParseFile(dictionary["name"] as string, new Uri(dictionary["url"] as string)), + "GeoPoint" => new ParseGeoPoint(Conversion.To(dictionary["latitude"]), Conversion.To(dictionary["longitude"])), + "Object" => ClassController.GenerateObjectFromState(ParseObjectCoder.Instance.Decode(dictionary, this), dictionary["className"] as string), + "Relation" => ParseRelationBase.CreateRelation(null, null, dictionary["className"] as string) + }, + IDictionary { } dictionary => dictionary.ToDictionary(pair => pair.Key, pair => Decode(pair.Value)), + IList { } list => list.Select(Decode), + _ => data + }; + + protected virtual object DecodePointer(string className, string objectId) => ClassController.CreateObjectWithoutData(className, objectId); + + // TODO(hallucinogen): Figure out if we should be more flexible with the date formats we accept. + + public static DateTime ParseDate(string input) => DateTime.ParseExact(input, ParseClient.DateFormatStrings, CultureInfo.InvariantCulture, DateTimeStyles.None); + } +} diff --git a/Parse/Utilities/Encoding/ParseDataEncoder.cs b/Parse/Utilities/Encoding/ParseDataEncoder.cs new file mode 100644 index 00000000..201da820 --- /dev/null +++ b/Parse/Utilities/Encoding/ParseDataEncoder.cs @@ -0,0 +1,72 @@ +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Parse.Common.Internal; +using Parse.Utilities; + +namespace Parse.Core.Internal +{ + /// + /// A ParseEncoder can be used to transform objects such as into JSON + /// data structures. + /// + /// + public abstract class ParseDataEncoder + { + public static bool Validate(object value) => value is null || value.GetType().IsPrimitive || value is string || value is ParseObject || value is ParseACL || value is ParseFile || value is ParseGeoPoint || value is ParseRelationBase || value is DateTime || value is byte[] || Conversion.As>(value) is { } || Conversion.As>(value) is { }; + + // If this object has a special encoding, encode it and return the encoded object. Otherwise, just return the original object. + + public object Encode(object value) => value switch + { + DateTime { } date => new Dictionary + { + ["iso"] = date.ToString(ParseClient.DateFormatStrings.First(), CultureInfo.InvariantCulture), + ["__type"] = "Date" + }, + byte[] { } bytes => new Dictionary + { + ["__type"] = "Bytes", + ["base64"] = Convert.ToBase64String(bytes) + }, + ParseObject { } entity => EncodeObject(entity), + IJsonConvertible { } jsonConvertible => jsonConvertible.ConvertToJSON(), + { } when Conversion.As>(value) is { } dictionary => dictionary.ToDictionary(pair => pair.Key, pair => Encode(pair.Value)), + { } when Conversion.As>(value) is { } list => EncodeList(list), + + // TODO (hallucinogen): convert IParseFieldOperation to IJsonConvertible + + IParseFieldOperation { } fieldOperation => fieldOperation.Encode(), + _ => value + }; + + protected abstract IDictionary EncodeObject(ParseObject value); + + object EncodeList(IList list) + { + List encoded = 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 (ParseClient.IL2CPPCompiled && list.GetType().IsArray) + { + list = new List(list); + } + + foreach (object item in list) + { + if (!Validate(item)) + { + throw new ArgumentException("Invalid type for value in an array"); + } + + encoded.Add(Encode(item)); + } + + return encoded; + } + } +} diff --git a/Parse/Utilities/Encoding/ParseDecoder.cs b/Parse/Utilities/Encoding/ParseDecoder.cs deleted file mode 100644 index 12c69b53..00000000 --- a/Parse/Utilities/Encoding/ParseDecoder.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Parse.Utilities; - -namespace Parse.Core.Internal -{ - public class ParseDecoder - { - // This class isn't really a Singleton, but since it has no state, it's more efficient to get the default instance. - public static ParseDecoder Instance { get; } = new ParseDecoder(); - - // Prevent default constructor. - private ParseDecoder() { } - - public object Decode(object data) - { - if (data == null) - { - return null; - } - - IDictionary dict = data as IDictionary; - if (dict != null) - { - if (dict.ContainsKey("__op")) - { - return ParseFieldOperations.Decode(dict); - } - - dict.TryGetValue("__type", out object type); - string typeString = type as string; - - if (typeString == null) - { - Dictionary newDict = new Dictionary(); - foreach (KeyValuePair 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") - { - return DecodePointer(dict["className"] as string, dict["objectId"] as string); - } - - if (typeString == "File") - { - return new ParseFile(dict["name"] as string, new Uri(dict["url"] as string)); - } - - if (typeString == "GeoPoint") - { - return new ParseGeoPoint(Conversion.To(dict["latitude"]), - Conversion.To(dict["longitude"])); - } - - if (typeString == "Object") - { - IObjectState state = ParseObjectCoder.Instance.Decode(dict, this); - return ParseObject.FromState(state, dict["className"] as string); - } - - if (typeString == "Relation") - { - return ParseRelationBase.CreateRelation(null, null, dict["className"] as string); - } - - Dictionary converted = new Dictionary(); - foreach (KeyValuePair pair in dict) - { - converted[pair.Key] = Decode(pair.Value); - } - return converted; - } - - IList 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) => ParseObject.CreateWithoutData(className, objectId); - - public static DateTime ParseDate(string input) => - // TODO(hallucinogen): Figure out if we should be more flexible with the date formats - // we accept. - DateTime.ParseExact(input, ParseClient.DateFormatStrings, CultureInfo.InvariantCulture, DateTimeStyles.None); - } -} diff --git a/Parse/Utilities/Encoding/ParseEncoder.cs b/Parse/Utilities/Encoding/ParseEncoder.cs deleted file mode 100644 index 37dbaff3..00000000 --- a/Parse/Utilities/Encoding/ParseEncoder.cs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Parse.Common.Internal; -using Parse.Utilities; - -namespace Parse.Core.Internal -{ - /// - /// A ParseEncoder can be used to transform objects such as into JSON - /// data structures. - /// - /// - public abstract class ParseEncoder - { -#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) => value == null || - ReflectionHelpers.IsPrimitive(value.GetType()) || - value is string || - value is ParseObject || - value is ParseACL || - value is ParseFile || - value is ParseGeoPoint || - value is ParseRelationBase || - 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).ToString(ParseClient.DateFormatStrings.First(), CultureInfo.InvariantCulture)}, - {"__type", "Date"} - }; - } - - byte[] bytes = value as byte[]; - if (bytes != null) - { - return new Dictionary { - {"__type", "Bytes"}, - {"base64", Convert.ToBase64String(bytes)} - }; - } - - ParseObject obj = value as ParseObject; - if (obj != null) - { - return EncodeParseObject(obj); - } - - IJsonConvertible jsonConvertible = value as IJsonConvertible; - if (jsonConvertible != null) - { - return jsonConvertible.ToJSON(); - } - - IDictionary dict = Conversion.As>(value); - if (dict != null) - { - Dictionary json = new Dictionary(); - foreach (KeyValuePair pair in dict) - { - json[pair.Key] = Encode(pair.Value); - } - return json; - } - - IList list = Conversion.As>(value); - if (list != null) - { - return EncodeList(list); - } - - // TODO (hallucinogen): convert IParseFieldOperation to IJsonConvertible - IParseFieldOperation operation = value as IParseFieldOperation; - if (operation != null) - { - return operation.Encode(); - } - - return value; - } - - protected abstract IDictionary EncodeParseObject(ParseObject value); - - private object EncodeList(IList list) - { - List 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 (object 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/Parse/Utilities/Encoding/ParseObjectCoder.cs b/Parse/Utilities/Encoding/ParseObjectCoder.cs index 12c6b208..7cb76726 100644 --- a/Parse/Utilities/Encoding/ParseObjectCoder.cs +++ b/Parse/Utilities/Encoding/ParseObjectCoder.cs @@ -6,14 +6,16 @@ namespace Parse.Core.Internal { // TODO: (richardross) refactor entire parse coder interfaces. + public class ParseObjectCoder { - public static ParseObjectCoder Instance { get; } = new ParseObjectCoder(); + public static ParseObjectCoder Instance { get; } = new ParseObjectCoder { }; // Prevent default constructor. - private ParseObjectCoder() { } - public IDictionary Encode(T state, IDictionary operations, ParseEncoder encoder) where T : IObjectState + ParseObjectCoder() { } + + public IDictionary Encode(T state, IDictionary operations, ParseDataEncoder encoder) where T : IObjectState { Dictionary result = new Dictionary(); foreach (KeyValuePair pair in operations) @@ -27,17 +29,16 @@ public IDictionary Encode(T state, IDictionary data, ParseDecoder decoder) + public IObjectState Decode(IDictionary data, IParseDataDecoder decoder) { - IDictionary serverData = new Dictionary(); - Dictionary mutableData = new Dictionary(data); - string objectId = extractFromDictionary(mutableData, "objectId", (obj) => obj as string); - DateTime? createdAt = extractFromDictionary(mutableData, "createdAt", (obj) => ParseDecoder.ParseDate(obj as string)); - DateTime? updatedAt = extractFromDictionary(mutableData, "updatedAt", (obj) => ParseDecoder.ParseDate(obj as string)); + IDictionary serverData = new Dictionary { }, mutableData = new Dictionary(data); + + string objectId = Extract(mutableData, "objectId", (obj) => obj as string); + DateTime? createdAt = Extract(mutableData, "createdAt", (obj) => ParseDataDecoder.ParseDate(obj as string)), updatedAt = Extract(mutableData, "updatedAt", (obj) => ParseDataDecoder.ParseDate(obj as string)); if (mutableData.ContainsKey("ACL")) { - serverData["ACL"] = extractFromDictionary(mutableData, "ACL", (obj) => new ParseACL(obj as IDictionary)); + serverData["ACL"] = Extract(mutableData, "ACL", (obj) => new ParseACL(obj as IDictionary)); } if (createdAt != null && updatedAt == null) @@ -46,6 +47,7 @@ public IObjectState Decode(IDictionary data, ParseDecoder decode } // Bring in the new server data. + foreach (KeyValuePair pair in mutableData) { if (pair.Key == "__type" || pair.Key == "className") @@ -53,8 +55,7 @@ public IObjectState Decode(IDictionary data, ParseDecoder decode continue; } - object value = pair.Value; - serverData[pair.Key] = decoder.Decode(value); + serverData[pair.Key] = decoder.Decode(pair.Value); } return new MutableObjectState @@ -66,9 +67,10 @@ public IObjectState Decode(IDictionary data, ParseDecoder decode }; } - private T extractFromDictionary(IDictionary data, string key, Func action) + T Extract(IDictionary data, string key, Func action) { T result = default; + if (data.ContainsKey(key)) { result = action(data[key]); diff --git a/Parse/Utilities/Encoding/PointerOrLocalIdEncoder.cs b/Parse/Utilities/Encoding/PointerOrLocalIdEncoder.cs index b42a05e0..37cd8208 100644 --- a/Parse/Utilities/Encoding/PointerOrLocalIdEncoder.cs +++ b/Parse/Utilities/Encoding/PointerOrLocalIdEncoder.cs @@ -6,29 +6,27 @@ namespace Parse.Core.Internal { /// - /// A that encode as pointers. If the object - /// does not have an , uses a local id. + /// A that encodes as pointers. If the object does not have an , uses a local id. /// - public class PointerOrLocalIdEncoder : ParseEncoder + public class PointerOrLocalIdEncoder : ParseDataEncoder { - // 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 => instance; + public static PointerOrLocalIdEncoder Instance { get; } = new PointerOrLocalIdEncoder { }; - protected override IDictionary EncodeParseObject(ParseObject value) + protected override IDictionary EncodeObject(ParseObject value) { - if (value.ObjectId == null) + if (value.ObjectId is null) { // TODO (hallucinogen): handle local id. For now we throw. - throw new ArgumentException("Cannot create a pointer to an object without an objectId"); + + throw new InvalidOperationException("Cannot create a pointer to an object without an objectId."); } - return new Dictionary { - {"__type", "Pointer"}, - {"className", value.ClassName}, - {"objectId", value.ObjectId} - }; + return new Dictionary + { + ["__type"] = "Pointer", + ["className"] = value.ClassName, + ["objectId"] = value.ObjectId + }; } } } diff --git a/Parse/Utilities/IJsonConvertible.cs b/Parse/Utilities/IJsonConvertible.cs index 081df368..8bcc731c 100644 --- a/Parse/Utilities/IJsonConvertible.cs +++ b/Parse/Utilities/IJsonConvertible.cs @@ -13,6 +13,6 @@ public interface IJsonConvertible /// Converts the object to a data structure that can be converted to JSON. /// /// An object to be JSONified. - IDictionary ToJSON(); + IDictionary ConvertToJSON(); } } diff --git a/Parse/Utilities/IHttpClient.cs b/Parse/Utilities/IWebClient.cs similarity index 66% rename from Parse/Utilities/IHttpClient.cs rename to Parse/Utilities/IWebClient.cs index 4c10303e..0fc12941 100644 --- a/Parse/Utilities/IHttpClient.cs +++ b/Parse/Utilities/IWebClient.cs @@ -10,17 +10,14 @@ namespace Parse.Common.Internal public interface IWebClient { /// - /// Executes HTTP request to a with HTTP verb - /// and . + /// 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); + Task> ExecuteAsync(WebRequest httpRequest, IProgress uploadProgress, IProgress downloadProgress, CancellationToken cancellationToken); } } diff --git a/Parse/Utilities/InternalExtensions.cs b/Parse/Utilities/InternalExtensions.cs index 86c404ee..eab6e299 100644 --- a/Parse/Utilities/InternalExtensions.cs +++ b/Parse/Utilities/InternalExtensions.cs @@ -42,8 +42,8 @@ public static TValue GetOrDefault(this IDictionary s } public static bool CollectionsEqual(this IEnumerable a, IEnumerable b) => Equals(a, b) || - (a != null && b != null && - a.SequenceEqual(b)); + a != null && b != null && + a.SequenceEqual(b); public static Task OnSuccess(this Task task, Func, TResult> continuation) => ((Task) task).OnSuccess(t => continuation((Task) t)); @@ -92,9 +92,7 @@ public static Task OnSuccess(this Task task, Action continuation) => task. public static Task WhileAsync(Func> predicate, Func body) { Func iterate = null; - iterate = () => - { - return predicate().OnSuccess(t => + iterate = () => predicate().OnSuccess(t => { if (!t.Result) { @@ -102,7 +100,6 @@ public static Task WhileAsync(Func> predicate, Func body) } return body().OnSuccess(_ => iterate()).Unwrap(); }).Unwrap(); - }; return iterate(); } } diff --git a/Parse/Utilities/Json.cs b/Parse/Utilities/Json.cs index e35badad..21ffd1f7 100644 --- a/Parse/Utilities/Json.cs +++ b/Parse/Utilities/Json.cs @@ -181,7 +181,7 @@ private bool ParseString(out object output) StringBuilder builder = new StringBuilder(contentCapture.Value); foreach (Capture escape in m.Groups["escape"].Captures) { - int index = (escape.Index - contentCapture.Index) - offset; + int index = escape.Index - contentCapture.Index - offset; offset += escape.Length - 1; builder.Remove(index + 1, escape.Length - 1); switch (escape.Value[1]) @@ -279,7 +279,7 @@ private bool Accept(char condition) ++currentStep; } - bool match = (currentStep < strLen) && (InputAsArray[currentStep] == condition); + bool match = currentStep < strLen && InputAsArray[currentStep] == condition; if (match) { ++step; @@ -332,7 +332,7 @@ private bool Accept(char[] condition) } } - bool match = (currentStep < strLen) && strMatch; + bool match = currentStep < strLen && strMatch; if (match) { Skip(step + condition.Length); diff --git a/Parse/Utilities/ParseConfigExtensions.cs b/Parse/Utilities/ParseConfigExtensions.cs deleted file mode 100644 index e5a927cb..00000000 --- a/Parse/Utilities/ParseConfigExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Collections.Generic; - -namespace Parse.Core.Internal -{ - /// - /// So here's the deal. We have a lot of internal APIs for ParseObject, ParseUser, 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 ParseConfigExtensions - { - public static ParseConfig Create(IDictionary fetchedConfig) => new ParseConfig(fetchedConfig); - } -} diff --git a/Parse/Utilities/ParseExtensions.cs b/Parse/Utilities/ParseExtensions.cs index b89860af..258a91df 100644 --- a/Parse/Utilities/ParseExtensions.cs +++ b/Parse/Utilities/ParseExtensions.cs @@ -4,7 +4,9 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; +using Parse.Library; namespace Parse { @@ -14,73 +16,6 @@ namespace Parse /// public static class ParseExtensions { - /// - /// Saves all of the ParseObjects in the enumeration. Equivalent to - /// calling . - /// - /// The objects to save. - public static Task SaveAllAsync(this IEnumerable objects) where T : ParseObject => ParseObject.SaveAllAsync(objects); - - /// - /// Saves all of the ParseObjects in the enumeration. Equivalent to - /// calling - /// . - /// - /// The objects to save. - /// The cancellation token. - public static Task SaveAllAsync( - this IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject => ParseObject.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 : ParseObject => ParseObject.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 : ParseObject => ParseObject.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 : ParseObject => ParseObject.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 : ParseObject => ParseObject.FetchAllIfNeededAsync(objects, cancellationToken); - - /// - /// Constructs a query that is the or of the given queries. - /// - /// The type of ParseObject being queried. - /// An initial query to 'or' with additional queries. - /// The list of ParseQueries to 'or' together. - /// A query that is the or of the given queries. - public static ParseQuery Or(this ParseQuery source, params ParseQuery[] queries) - where T : ParseObject => ParseQuery.Or(queries.Concat(new[] { source })); - /// /// Fetches this object with the data from the server. /// @@ -89,10 +24,9 @@ public static ParseQuery Or(this ParseQuery source, params ParseQuery /// Fetches this object with the data from the server. /// - /// The ParseObject to fetch. + /// The ParseObject to fetch. /// The cancellation token. - public static Task FetchAsync(this T obj, CancellationToken cancellationToken) - where T : ParseObject => obj.FetchAsyncInternal(cancellationToken).OnSuccess(t => (T) t.Result); + public static Task FetchAsync(this T target, CancellationToken cancellationToken) where T : ParseObject => target.FetchAsyncInternal(cancellationToken).OnSuccess(task => (T) task.Result); /// /// If this ParseObject has not been fetched (i.e. returns @@ -107,7 +41,6 @@ public static Task FetchAsync(this T obj, CancellationToken cancellationTo /// /// The ParseObject to fetch. /// The cancellation token. - public static Task FetchIfNeededAsync(this T obj, CancellationToken cancellationToken) - where T : ParseObject => obj.FetchIfNeededAsyncInternal(cancellationToken).OnSuccess(t => (T) t.Result); + public static Task FetchIfNeededAsync(this T obj, CancellationToken cancellationToken) where T : ParseObject => obj.FetchIfNeededAsyncInternal(cancellationToken).OnSuccess(t => (T) t.Result); } } diff --git a/Parse/Utilities/ParseObjectExtensions.cs b/Parse/Utilities/ParseObjectExtensions.cs deleted file mode 100644 index 1803bd6d..00000000 --- a/Parse/Utilities/ParseObjectExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Collections.Generic; - -namespace Parse.Core.Internal -{ - /// - /// So here's the deal. We have a lot of internal APIs for ParseObject, ParseUser, 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 ParseObjectExtensions - { - public static T FromState(IObjectState state, string defaultClassName) where T : ParseObject => ParseObject.FromState(state, defaultClassName); - - public static IObjectState GetState(this ParseObject obj) => obj.State; - - public static void HandleFetchResult(this ParseObject obj, IObjectState serverState) => obj.HandleFetchResult(serverState); - - public static IDictionary GetCurrentOperations(this ParseObject obj) => obj.CurrentOperations; - - public static IEnumerable DeepTraversal(object root, bool traverseParseObjects = false, bool yieldRoot = false) => ParseObject.DeepTraversal(root, traverseParseObjects, yieldRoot); - - public static void SetIfDifferent(this ParseObject obj, string key, T value) => obj.SetIfDifferent(key, value); - - public static IDictionary ServerDataToJSONObjectForSerialization(this ParseObject obj) => obj.ServerDataToJSONObjectForSerialization(); - - public static void Set(this ParseObject obj, string key, object value) => obj.Set(key, value); - } -} diff --git a/Parse/Utilities/ParseQueryExtensions.cs b/Parse/Utilities/ParseQueryExtensions.cs index 5ec38e6c..70f2b2e9 100644 --- a/Parse/Utilities/ParseQueryExtensions.cs +++ b/Parse/Utilities/ParseQueryExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -9,6 +10,8 @@ namespace Parse.Core.Internal { +#warning Fully refactor at some point. + /// /// So here's the deal. We have a lot of internal APIs for ParseObject, ParseUser, etc. /// @@ -21,89 +24,83 @@ namespace Parse.Core.Internal /// public static class ParseQueryExtensions { - 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 MethodInfo ParseObjectGetMethod { get; } + + static MethodInfo StringContainsMethod { get; } + + static MethodInfo StringStartsWithMethod { get; } + + static MethodInfo StringEndsWithMethod { get; } + + static MethodInfo ContainsMethod { get; } + + static MethodInfo NotContainsMethod { get; } + + static MethodInfo ContainsKeyMethod { get; } + + static MethodInfo NotContainsKeyMethod { get; } + + static Dictionary Mappings { get; } + static ParseQueryExtensions() { - 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 => ContainsStub(null, null)).GetGenericMethodDefinition(); - notContainsMethod = GetMethod( - o => NotContainsStub(null, null)) - .GetGenericMethodDefinition(); - - containsKeyMethod = GetMethod(o => ContainsKeyStub(null, null)); - notContainsKeyMethod = GetMethod( - o => NotContainsKeyStub(null, null)); + ParseObjectGetMethod = GetMethod(target => target.Get(null)).GetGenericMethodDefinition(); + StringContainsMethod = GetMethod(text => text.Contains(null)); + StringStartsWithMethod = GetMethod(text => text.StartsWith(null)); + StringEndsWithMethod = GetMethod(text => text.EndsWith(null)); + + Mappings = new Dictionary + { + [StringContainsMethod] = GetMethod>(query => query.WhereContains(null, null)), + [StringStartsWithMethod] = GetMethod>(query => query.WhereStartsWith(null, null)), + [StringEndsWithMethod] = GetMethod>(query => query.WhereEndsWith(null, null)), + }; + + ContainsMethod = GetMethod(o => ContainsStub(null, null)).GetGenericMethodDefinition(); + NotContainsMethod = GetMethod(o => NotContainsStub(null, null)).GetGenericMethodDefinition(); + + ContainsKeyMethod = GetMethod(o => ContainsKeyStub(null, null)); + NotContainsKeyMethod = GetMethod(o => NotContainsKeyStub(null, null)); } /// /// Gets a MethodInfo for a top-level method call. /// - private static MethodInfo GetMethod(Expression> expression) => (expression.Body as MethodCallExpression).Method; + static MethodInfo GetMethod(Expression> expression) => (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."); + 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."); + 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(ParseObject obj, string key) => throw new NotImplementedException( - "Exists only for expression translation as a placeholder."); + static bool ContainsKeyStub(ParseObject 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(ParseObject obj, string key) => throw new NotImplementedException( - "Exists only for expression translation as a placeholder."); + static bool NotContainsKeyStub(ParseObject 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) + static object GetValue(Expression exp) { try { - return Expression.Lambda( - typeof(Func<>).MakeGenericType(exp.Type), exp).Compile().DynamicInvoke(); + return Expression.Lambda(typeof(Func<>).MakeGenericType(exp.Type), exp).Compile().DynamicInvoke(); } catch (Exception e) { @@ -115,43 +112,30 @@ private static object GetValue(Expression exp) /// Checks whether the MethodCallExpression is a call to ParseObject.Get(), /// which is the call we normalize all indexing into the ParseObject to. /// - private static bool IsParseObjectGet(MethodCallExpression node) - { - if (node == null || node.Object == null) - { - return false; - } - if (!typeof(ParseObject).GetTypeInfo().IsAssignableFrom(node.Object.Type.GetTypeInfo())) - { - return false; - } - return node.Method.IsGenericMethod && node.Method.GetGenericMethodDefinition() == getMethod; - } - + static bool IsParseObjectGet(MethodCallExpression node) => node is { Object: { } } && typeof(ParseObject).GetTypeInfo().IsAssignableFrom(node.Object.Type.GetTypeInfo()) && node.Method.IsGenericMethod && node.Method.GetGenericMethodDefinition() == ParseObjectGetMethod; /// /// Visits an Expression, converting ParseObject.Get/ParseObject[]/ParseObject.Property, /// and nested indices into a single call to ParseObject.Get() with a "field path" like /// "foo.bar.baz" /// - private class ObjectNormalizer : ExpressionVisitor + class ObjectNormalizer : ExpressionVisitor { protected override Expression VisitIndex(IndexExpression node) { Expression visitedObject = Visit(node.Object); MethodCallExpression indexer = visitedObject as MethodCallExpression; + if (IsParseObjectGet(indexer)) { - string indexValue = GetValue(node.Arguments[0]) as string; - if (indexValue == null) + if (!(GetValue(node.Arguments[0]) is string indexValue)) { throw new InvalidOperationException("Index must be a string"); } - string newPath = GetValue(indexer.Arguments[0]) + "." + indexValue; - return Expression.Call(indexer.Object, - getMethod.MakeGenericMethod(node.Type), - Expression.Constant(newPath, typeof(string))); + + return Expression.Call(indexer.Object, ParseObjectGetMethod.MakeGenericMethod(node.Type), Expression.Constant($"{GetValue(indexer.Arguments[0])}.{indexValue}", typeof(string))); } + return base.VisitIndex(node); } @@ -159,19 +143,7 @@ protected override Expression VisitIndex(IndexExpression node) /// Check for a ParseFieldName attribute and use that as the path component, turning /// properties like foo.ObjectId into foo.Get("objectId") /// - protected override Expression VisitMember(MemberExpression node) - { - ParseFieldNameAttribute fieldName = node.Member.GetCustomAttribute(); - if (fieldName != null && - typeof(ParseObject).GetTypeInfo().IsAssignableFrom(node.Expression.Type.GetTypeInfo())) - { - string newPath = fieldName.FieldName; - return Expression.Call(node.Expression, - getMethod.MakeGenericMethod(node.Type), - Expression.Constant(newPath, typeof(string))); - } - return base.VisitMember(node); - } + protected override Expression VisitMember(MemberExpression node) => node.Member.GetCustomAttribute() is { } fieldName && typeof(ParseObject).GetTypeInfo().IsAssignableFrom(node.Expression.Type.GetTypeInfo()) ? Expression.Call(node.Expression, ParseObjectGetMethod.MakeGenericMethod(node.Type), Expression.Constant(fieldName.FieldName, typeof(string))) : base.VisitMember(node); /// /// If a ParseObject.Get() call has been cast, just change the generic parameter. @@ -179,46 +151,36 @@ protected override Expression VisitMember(MemberExpression node) protected override Expression VisitUnary(UnaryExpression node) { MethodCallExpression methodCall = Visit(node.Operand) as MethodCallExpression; - if ((node.NodeType == ExpressionType.Convert || - node.NodeType == ExpressionType.ConvertChecked) && - IsParseObjectGet(methodCall)) - { - return Expression.Call(methodCall.Object, - getMethod.MakeGenericMethod(node.Type), - methodCall.Arguments); - } - return base.VisitUnary(node); + return (node.NodeType == ExpressionType.Convert || node.NodeType == ExpressionType.ConvertChecked) && IsParseObjectGet(methodCall) ? Expression.Call(methodCall.Object, ParseObjectGetMethod.MakeGenericMethod(node.Type), methodCall.Arguments) : base.VisitUnary(node); } protected override Expression VisitMethodCall(MethodCallExpression node) { // Turn parseObject["foo"] into parseObject.Get("foo") + if (node.Method.Name == "get_Item" && node.Object is ParameterExpression) { - string indexPath = GetValue(node.Arguments[0]) as string; - return Expression.Call(node.Object, - getMethod.MakeGenericMethod(typeof(object)), - Expression.Constant(indexPath, typeof(string))); + return Expression.Call(node.Object, ParseObjectGetMethod.MakeGenericMethod(typeof(object)), Expression.Constant(GetValue(node.Arguments[0]) as string, typeof(string))); } // Turn parseObject.Get("foo")["bar"] into parseObject.Get("foo.bar") + if (node.Method.Name == "get_Item" || IsParseObjectGet(node)) { Expression visitedObject = Visit(node.Object); MethodCallExpression indexer = visitedObject as MethodCallExpression; + if (IsParseObjectGet(indexer)) { - string indexValue = GetValue(node.Arguments[0]) as string; - if (indexValue == null) + if (!(GetValue(node.Arguments[0]) is string indexValue)) { throw new InvalidOperationException("Index must be a string"); } - string newPath = GetValue(indexer.Arguments[0]) + "." + indexValue; - return Expression.Call(indexer.Object, - getMethod.MakeGenericMethod(node.Type), - Expression.Constant(newPath, typeof(string))); + + return Expression.Call(indexer.Object, ParseObjectGetMethod.MakeGenericMethod(node.Type), Expression.Constant($"{GetValue(indexer.Arguments[0])}.{indexValue}", typeof(string))); } } + return base.VisitMethodCall(node); } } @@ -226,7 +188,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) /// /// Normalizes Where expressions. /// - private class WhereNormalizer : ExpressionVisitor + class WhereNormalizer : ExpressionVisitor { /// @@ -236,13 +198,11 @@ private class WhereNormalizer : ExpressionVisitor /// protected override Expression VisitBinary(BinaryExpression node) { - MethodCallExpression leftTransformed = new ObjectNormalizer().Visit(node.Left) as MethodCallExpression; - MethodCallExpression rightTransformed = new ObjectNormalizer().Visit(node.Right) as MethodCallExpression; - - MethodCallExpression objectExpression; + MethodCallExpression rightTransformed = new ObjectNormalizer().Visit(node.Right) as MethodCallExpression, objectExpression; Expression filterExpression; bool inverted; - if (leftTransformed != null) + + if (new ObjectNormalizer().Visit(node.Left) is MethodCallExpression leftTransformed) { objectExpression = leftTransformed; filterExpression = node.Right; @@ -260,41 +220,13 @@ protected override Expression VisitBinary(BinaryExpression node) switch (node.NodeType) { case ExpressionType.GreaterThan: - if (inverted) - { - return Expression.LessThan(objectExpression, filterExpression); - } - else - { - return Expression.GreaterThan(objectExpression, filterExpression); - } + return inverted ? Expression.LessThan(objectExpression, filterExpression) : Expression.GreaterThan(objectExpression, filterExpression); case ExpressionType.GreaterThanOrEqual: - if (inverted) - { - return Expression.LessThanOrEqual(objectExpression, filterExpression); - } - else - { - return Expression.GreaterThanOrEqual(objectExpression, filterExpression); - } + return inverted ? Expression.LessThanOrEqual(objectExpression, filterExpression) : Expression.GreaterThanOrEqual(objectExpression, filterExpression); case ExpressionType.LessThan: - if (inverted) - { - return Expression.GreaterThan(objectExpression, filterExpression); - } - else - { - return Expression.LessThan(objectExpression, filterExpression); - } + return inverted ? Expression.GreaterThan(objectExpression, filterExpression) : Expression.LessThan(objectExpression, filterExpression); case ExpressionType.LessThanOrEqual: - if (inverted) - { - return Expression.GreaterThanOrEqual(objectExpression, filterExpression); - } - else - { - return Expression.LessThanOrEqual(objectExpression, filterExpression); - } + return inverted ? Expression.GreaterThanOrEqual(objectExpression, filterExpression) : Expression.LessThanOrEqual(objectExpression, filterExpression); case ExpressionType.Equal: return Expression.Equal(objectExpression, filterExpression); case ExpressionType.NotEqual: @@ -305,6 +237,7 @@ protected override Expression VisitBinary(BinaryExpression node) { throw new InvalidOperationException("Operation not supported: " + node); } + return base.VisitBinary(node); } @@ -314,12 +247,32 @@ protected override Expression VisitBinary(BinaryExpression node) /// protected override Expression VisitUnary(UnaryExpression node) { + // This is incorrect because control is supposed to be able to flow out of the binaryOperand case if the value of NodeType is not matched against an ExpressionType value, which it will not do. + // + // return node switch + // { + // { NodeType: ExpressionType.Not, Operand: var operand } => Visit(operand) switch + // { + // BinaryExpression { Left: var left, Right: var right, NodeType: var type } binaryOperand => type switch + // { + // ExpressionType.GreaterThan => Expression.LessThanOrEqual(left, right), + // ExpressionType.GreaterThanOrEqual => Expression.LessThan(left, right), + // ExpressionType.LessThan => Expression.GreaterThanOrEqual(left, right), + // ExpressionType.LessThanOrEqual => Expression.GreaterThan(left, right), + // ExpressionType.Equal => Expression.NotEqual(left, right), + // ExpressionType.NotEqual => Expression.Equal(left, right), + // }, + // _ => base.VisitUnary(node) + // }, + // _ => base.VisitUnary(node) + // }; + // Normalizes inversion + if (node.NodeType == ExpressionType.Not) { Expression visitedOperand = Visit(node.Operand); - BinaryExpression binaryOperand = visitedOperand as BinaryExpression; - if (binaryOperand != null) + if (visitedOperand is BinaryExpression binaryOperand) { switch (binaryOperand.NodeType) { @@ -338,31 +291,26 @@ protected override Expression VisitUnary(UnaryExpression node) } } - MethodCallExpression methodCallOperand = visitedOperand as MethodCallExpression; - if (methodCallOperand != null) + if (visitedOperand is MethodCallExpression methodCallOperand) { if (methodCallOperand.Method.IsGenericMethod) { - if (methodCallOperand.Method.GetGenericMethodDefinition() == containsMethod) + if (methodCallOperand.Method.GetGenericMethodDefinition() == ContainsMethod) { - MethodInfo genericNotContains = notContainsMethod.MakeGenericMethod( - methodCallOperand.Method.GetGenericArguments()); - return Expression.Call(genericNotContains, methodCallOperand.Arguments.ToArray()); + return Expression.Call(NotContainsMethod.MakeGenericMethod(methodCallOperand.Method.GetGenericArguments()), methodCallOperand.Arguments.ToArray()); } - if (methodCallOperand.Method.GetGenericMethodDefinition() == notContainsMethod) + if (methodCallOperand.Method.GetGenericMethodDefinition() == NotContainsMethod) { - MethodInfo genericContains = containsMethod.MakeGenericMethod( - methodCallOperand.Method.GetGenericArguments()); - return Expression.Call(genericContains, methodCallOperand.Arguments.ToArray()); + return Expression.Call(ContainsMethod.MakeGenericMethod(methodCallOperand.Method.GetGenericArguments()), methodCallOperand.Arguments.ToArray()); } } - if (methodCallOperand.Method == containsKeyMethod) + if (methodCallOperand.Method == ContainsKeyMethod) { - return Expression.Call(notContainsKeyMethod, methodCallOperand.Arguments.ToArray()); + return Expression.Call(NotContainsKeyMethod, methodCallOperand.Arguments.ToArray()); } - if (methodCallOperand.Method == notContainsKeyMethod) + if (methodCallOperand.Method == NotContainsKeyMethod) { - return Expression.Call(containsKeyMethod, methodCallOperand.Arguments.ToArray()); + return Expression.Call(ContainsKeyMethod, methodCallOperand.Arguments.ToArray()); } } } @@ -375,67 +323,56 @@ protected override Expression VisitUnary(UnaryExpression node) protected override Expression VisitMethodCall(MethodCallExpression node) { // Convert .Equals() into == - if (node.Method.Name == "Equals" && - node.Method.ReturnType == typeof(bool) && - node.Method.GetParameters().Length == 1) + + if (node.Method.Name == "Equals" && node.Method.ReturnType == typeof(bool) && node.Method.GetParameters().Length == 1) { - MethodCallExpression obj = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; - MethodCallExpression parameter = new ObjectNormalizer().Visit(node.Arguments[0]) as MethodCallExpression; - if ((IsParseObjectGet(obj) && (obj.Object is ParameterExpression)) || - (IsParseObjectGet(parameter) && (parameter.Object is ParameterExpression))) + MethodCallExpression obj = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression, parameter = new ObjectNormalizer().Visit(node.Arguments[0]) as MethodCallExpression; + + if (IsParseObjectGet(obj) && obj.Object is ParameterExpression || IsParseObjectGet(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) + + if (node.Method != StringContainsMethod && node.Method.Name == "Contains" && node.Method.ReturnType == typeof(bool) && node.Method.GetParameters().Length <= 2) { - Expression collection = node.Method.GetParameters().Length == 1 ? - node.Object : - node.Arguments[0]; + Expression collection = node.Method.GetParameters().Length == 1 ? node.Object : node.Arguments[0]; int parameterIndex = node.Method.GetParameters().Length - 1; - MethodCallExpression parameter = new ObjectNormalizer().Visit(node.Arguments[parameterIndex]) - as MethodCallExpression; - if (IsParseObjectGet(parameter) && (parameter.Object is ParameterExpression)) + + if (new ObjectNormalizer().Visit(node.Arguments[parameterIndex]) is MethodCallExpression { } parameter && IsParseObjectGet(parameter) && parameter.Object is ParameterExpression) { - MethodInfo genericContains = containsMethod.MakeGenericMethod(parameter.Type); - return Expression.Call(genericContains, collection, parameter); + return Expression.Call(ContainsMethod.MakeGenericMethod(parameter.Type), collection, parameter); } - MethodCallExpression target = new ObjectNormalizer().Visit(collection) as MethodCallExpression; - Expression element = node.Arguments[parameterIndex]; - if (IsParseObjectGet(target) && (target.Object is ParameterExpression)) + + if (new ObjectNormalizer().Visit(collection) is MethodCallExpression { } target && IsParseObjectGet(target) && target.Object is ParameterExpression) { - MethodInfo genericContains = containsMethod.MakeGenericMethod(element.Type); - return Expression.Call(genericContains, target, element); + Expression element = node.Arguments[parameterIndex]; + return Expression.Call(ContainsMethod.MakeGenericMethod(element.Type), 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) + // 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) { - MethodCallExpression getter = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; Expression target = null; string path = null; - if (IsParseObjectGet(getter) && getter.Object is ParameterExpression) + + if (new ObjectNormalizer().Visit(node.Object) is MethodCallExpression { } getter && IsParseObjectGet(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)); + return Expression.Call(ContainsKeyMethod, getter.Object, Expression.Constant($"{GetValue(getter.Arguments[0])}.{GetValue(node.Arguments[0])}")); } else if (node.Object is ParameterExpression) { target = node.Object; path = GetValue(node.Arguments[0]) as string; } - if (target != null && path != null) + + if (target is { } && path is { }) { - return Expression.Call(containsKeyMethod, target, Expression.Constant(path)); + return Expression.Call(ContainsKeyMethod, target, Expression.Constant(path)); } } return base.VisitMethodCall(node); @@ -445,44 +382,39 @@ protected override Expression VisitMethodCall(MethodCallExpression node) /// /// Converts a normalized method call expression into the appropriate ParseQuery clause. /// - private static ParseQuery WhereMethodCall( - this ParseQuery source, Expression> expression, MethodCallExpression node) - where T : ParseObject + static ParseQuery WhereMethodCall(this ParseQuery source, Expression> expression, MethodCallExpression node) where T : ParseObject { if (IsParseObjectGet(node) && (node.Type == typeof(bool) || node.Type == typeof(bool?))) { - // This is a raw boolean field access like 'where obj.Get("foo")' + // This is a raw boolean field access like 'where obj.Get("foo")'. + return source.WhereEqualTo(GetValue(node.Arguments[0]) as string, true); } - if (functionMappings.TryGetValue(node.Method, out MethodInfo translatedMethod)) + if (Mappings.TryGetValue(node.Method, out MethodInfo translatedMethod)) { MethodCallExpression objTransformed = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; - if (!(IsParseObjectGet(objTransformed) && - objTransformed.Object == expression.Parameters[0])) + + if (!(IsParseObjectGet(objTransformed) && objTransformed.Object == expression.Parameters[0])) { - throw new InvalidOperationException( - "The left-hand side of a supported function call must be a ParseObject field access."); + throw new InvalidOperationException("The left-hand side of a supported function call must be a ParseObject field access."); } - object fieldPath = GetValue(objTransformed.Arguments[0]); - object containedIn = GetValue(node.Arguments[0]); - Type 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 ParseQuery; + + return translatedMethod.DeclaringType.GetGenericTypeDefinition().MakeGenericType(typeof(T)).GetRuntimeMethod(translatedMethod.Name, translatedMethod.GetParameters().Select(parameter => parameter.ParameterType).ToArray()).Invoke(source, new[] { GetValue(objTransformed.Arguments[0]), GetValue(node.Arguments[0]) }) as ParseQuery; } if (node.Arguments[0] == expression.Parameters[0]) { // obj.ContainsKey("foo") --> query.WhereExists("foo") - if (node.Method == containsKeyMethod) + + if (node.Method == ContainsKeyMethod) { return source.WhereExists(GetValue(node.Arguments[1]) as string); } + // !obj.ContainsKey("foo") --> query.WhereDoesNotExist("foo") - if (node.Method == notContainsKeyMethod) + + if (node.Method == NotContainsKeyMethod) { return source.WhereDoesNotExist(GetValue(node.Arguments[1]) as string); } @@ -490,41 +422,37 @@ private static ParseQuery WhereMethodCall( if (node.Method.IsGenericMethod) { - if (node.Method.GetGenericMethodDefinition() == containsMethod) + if (node.Method.GetGenericMethodDefinition() == ContainsMethod) { // obj.Get>("path").Contains(someValue) + if (IsParseObjectGet(node.Arguments[0] as MethodCallExpression)) { - return source.WhereEqualTo( - GetValue(((MethodCallExpression) node.Arguments[0]).Arguments[0]) as string, - GetValue(node.Arguments[1])); + return source.WhereEqualTo(GetValue(((MethodCallExpression) node.Arguments[0]).Arguments[0]) as string, GetValue(node.Arguments[1])); } + // someList.Contains(obj.Get("path")) + if (IsParseObjectGet(node.Arguments[1] as MethodCallExpression)) { - System.Collections.IEnumerable collection = GetValue(node.Arguments[0]) as System.Collections.IEnumerable; - return source.WhereContainedIn( - GetValue(((MethodCallExpression) node.Arguments[1]).Arguments[0]) as string, - collection.Cast()); + return source.WhereContainedIn(GetValue(((MethodCallExpression) node.Arguments[1]).Arguments[0]) as string, (GetValue(node.Arguments[0]) as IEnumerable).Cast()); } } - if (node.Method.GetGenericMethodDefinition() == notContainsMethod) + if (node.Method.GetGenericMethodDefinition() == NotContainsMethod) { // !obj.Get>("path").Contains(someValue) + if (IsParseObjectGet(node.Arguments[0] as MethodCallExpression)) { - return source.WhereNotEqualTo( - GetValue(((MethodCallExpression) node.Arguments[0]).Arguments[0]) as string, - GetValue(node.Arguments[1])); + return source.WhereNotEqualTo(GetValue(((MethodCallExpression) node.Arguments[0]).Arguments[0]) as string, GetValue(node.Arguments[1])); } + // !someList.Contains(obj.Get("path")) + if (IsParseObjectGet(node.Arguments[1] as MethodCallExpression)) { - System.Collections.IEnumerable collection = GetValue(node.Arguments[0]) as System.Collections.IEnumerable; - return source.WhereNotContainedIn( - GetValue(((MethodCallExpression) node.Arguments[1]).Arguments[0]) as string, - collection.Cast()); + return source.WhereNotContainedIn(GetValue(((MethodCallExpression) node.Arguments[1]).Arguments[0]) as string, (GetValue(node.Arguments[0]) as IEnumerable).Cast()); } } } @@ -534,46 +462,33 @@ private static ParseQuery WhereMethodCall( /// /// Converts a normalized binary expression into the appropriate ParseQuery clause. /// - private static ParseQuery WhereBinaryExpression( - this ParseQuery source, Expression> expression, BinaryExpression node) - where T : ParseObject + static ParseQuery WhereBinaryExpression(this ParseQuery source, Expression> expression, BinaryExpression node) where T : ParseObject { MethodCallExpression leftTransformed = new ObjectNormalizer().Visit(node.Left) as MethodCallExpression; - if (!(IsParseObjectGet(leftTransformed) && - leftTransformed.Object == expression.Parameters[0])) + if (!(IsParseObjectGet(leftTransformed) && leftTransformed.Object == expression.Parameters[0])) { - throw new InvalidOperationException( - "Where expressions must have one side be a field operation on a ParseObject."); + throw new InvalidOperationException("Where expressions must have one side be a field operation on a ParseObject."); } string fieldPath = GetValue(leftTransformed.Arguments[0]) as string; object filterValue = GetValue(node.Right); - if (filterValue != null && !ParseEncoder.IsValidType(filterValue)) + if (filterValue != null && !ParseDataEncoder.Validate(filterValue)) { - throw new InvalidOperationException( - "Where clauses must use types compatible with ParseObjects."); + throw new InvalidOperationException("Where clauses must use types compatible with ParseObjects."); } - switch (node.NodeType) + return node.NodeType switch { - 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."); - } + ExpressionType.GreaterThan => source.WhereGreaterThan(fieldPath, filterValue), + ExpressionType.GreaterThanOrEqual => source.WhereGreaterThanOrEqualTo(fieldPath, filterValue), + ExpressionType.LessThan => source.WhereLessThan(fieldPath, filterValue), + ExpressionType.LessThanOrEqual => source.WhereLessThanOrEqualTo(fieldPath, filterValue), + ExpressionType.Equal => source.WhereEqualTo(fieldPath, filterValue), + ExpressionType.NotEqual => source.WhereNotEqualTo(fieldPath, filterValue), + _ => throw new InvalidOperationException("Where expressions do not support this operator."), + }; } /// @@ -587,81 +502,67 @@ private static ParseQuery WhereBinaryExpression( /// functions on ParseQuery /// A new ParseQuery whose results will match the given predicate as /// well as the source's filters. - public static ParseQuery Where( - this ParseQuery source, Expression> predicate) - where TSource : ParseObject + public static ParseQuery Where(this ParseQuery source, Expression> predicate) where TSource : ParseObject { // Handle top-level logic operators && and || - BinaryExpression binaryExpression = predicate.Body as BinaryExpression; - if (binaryExpression != null) + + if (predicate.Body is BinaryExpression binaryExpression) { if (binaryExpression.NodeType == ExpressionType.AndAlso) { - return source - .Where(Expression.Lambda>( - binaryExpression.Left, predicate.Parameters)) - .Where(Expression.Lambda>( - binaryExpression.Right, predicate.Parameters)); + return source.Where(Expression.Lambda>(binaryExpression.Left, predicate.Parameters)).Where(Expression.Lambda>(binaryExpression.Right, predicate.Parameters)); } + if (binaryExpression.NodeType == ExpressionType.OrElse) { - ParseQuery left = source.Where(Expression.Lambda>( - binaryExpression.Left, predicate.Parameters)); - ParseQuery right = source.Where(Expression.Lambda>( - binaryExpression.Right, predicate.Parameters)); - return left.Or(right); + return source.Services.ConstructOrQuery(source.Where(Expression.Lambda>(binaryExpression.Left, predicate.Parameters)), (ParseQuery) source.Where(Expression.Lambda>(binaryExpression.Right, predicate.Parameters))); } } Expression normalized = new WhereNormalizer().Visit(predicate.Body); - MethodCallExpression methodCallExpr = normalized as MethodCallExpression; - if (methodCallExpr != null) + if (normalized is MethodCallExpression methodCallExpr) { return source.WhereMethodCall(predicate, methodCallExpr); } - BinaryExpression binaryExpr = normalized as BinaryExpression; - if (binaryExpr != null) + if (normalized is BinaryExpression binaryExpr) { return source.WhereBinaryExpression(predicate, binaryExpr); } - UnaryExpression unaryExpr = normalized as UnaryExpression; - if (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Not) + if (normalized is UnaryExpression { NodeType: ExpressionType.Not, Operand: MethodCallExpression { } node, Type: var type } unaryExpr && IsParseObjectGet(node) && (type == typeof(bool) || type == typeof(bool?))) { - MethodCallExpression node = unaryExpr.Operand as MethodCallExpression; - if (IsParseObjectGet(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); - } + // 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 ParseQueries."); + throw new InvalidOperationException("Encountered an unsupported expression for ParseQueries."); } /// /// Normalizes an OrderBy's keySelector expression and then extracts the path /// from the ParseObject.Get() call. /// - private static string GetOrderByPath( - Expression> keySelector) + static string GetOrderByPath(Expression> keySelector) { string result = null; Expression normalized = new ObjectNormalizer().Visit(keySelector.Body); MethodCallExpression callExpr = normalized as MethodCallExpression; + if (IsParseObjectGet(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 ParseObject."); + throw new InvalidOperationException("OrderBy expression must be a field access on a ParseObject."); } + return result; } @@ -674,9 +575,7 @@ private static string GetOrderByPath( /// A function to extract a key from the ParseObject. /// A new ParseQuery based on source whose results will be ordered by /// the key specified in the keySelector. - public static ParseQuery OrderBy( - this ParseQuery source, Expression> keySelector) - where TSource : ParseObject => source.OrderBy(GetOrderByPath(keySelector)); + public static ParseQuery OrderBy(this ParseQuery source, Expression> keySelector) where TSource : ParseObject => source.OrderBy(GetOrderByPath(keySelector)); /// /// Orders a query based upon the key selector provided. @@ -687,9 +586,7 @@ public static ParseQuery OrderBy( /// A function to extract a key from the ParseObject. /// A new ParseQuery based on source whose results will be ordered by /// the key specified in the keySelector. - public static ParseQuery OrderByDescending( - this ParseQuery source, Expression> keySelector) - where TSource : ParseObject => source.OrderByDescending(GetOrderByPath(keySelector)); + public static ParseQuery OrderByDescending( this ParseQuery source, Expression> keySelector) where TSource : ParseObject => source.OrderByDescending(GetOrderByPath(keySelector)); /// /// Performs a subsequent ordering of a query based upon the key selector provided. @@ -700,9 +597,7 @@ public static ParseQuery OrderByDescending( /// A function to extract a key from the ParseObject. /// A new ParseQuery based on source whose results will be ordered by /// the key specified in the keySelector. - public static ParseQuery ThenBy( - this ParseQuery source, Expression> keySelector) - where TSource : ParseObject => source.ThenBy(GetOrderByPath(keySelector)); + public static ParseQuery ThenBy(this ParseQuery source, Expression> keySelector) where TSource : ParseObject => source.ThenBy(GetOrderByPath(keySelector)); /// /// Performs a subsequent ordering of a query based upon the key selector provided. @@ -713,9 +608,7 @@ public static ParseQuery ThenBy( /// A function to extract a key from the ParseObject. /// A new ParseQuery based on source whose results will be ordered by /// the key specified in the keySelector. - public static ParseQuery ThenByDescending( - this ParseQuery source, Expression> keySelector) - where TSource : ParseObject => source.ThenByDescending(GetOrderByPath(keySelector)); + public static ParseQuery ThenByDescending(this ParseQuery source, Expression> keySelector) where TSource : ParseObject => source.ThenByDescending(GetOrderByPath(keySelector)); /// /// Correlates the elements of two queries based on matching keys. @@ -736,65 +629,52 @@ public static ParseQuery ThenByDescending( /// result to determine which query is the base query. /// A new ParseQuery with a WhereMatchesQuery or WhereMatchesKeyInQuery /// clause based upon the query indicated in the . - public static ParseQuery Join( - this ParseQuery outer, - ParseQuery inner, - Expression> outerKeySelector, - Expression> innerKeySelector, - Expression> resultSelector) - where TOuter : ParseObject - where TInner : ParseObject - where TResult : ParseObject + public static ParseQuery Join(this ParseQuery outer, ParseQuery inner, Expression> outerKeySelector, Expression> innerKeySelector, Expression> resultSelector) where TOuter : ParseObject where TInner : ParseObject where TResult : ParseObject { - // resultSelector must select either the inner object or the outer object. If it's the inner - // object, reverse the query. + // 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 ParseQuery; + + return inner.Join(outer, innerKeySelector, outerKeySelector, (i, o) => i) as ParseQuery; } + 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; + Expression outerNormalized = new ObjectNormalizer().Visit(outerKeySelector.Body), innerNormalized = new ObjectNormalizer().Visit(innerKeySelector.Body); + MethodCallExpression outerAsGet = outerNormalized as MethodCallExpression, innerAsGet = innerNormalized as MethodCallExpression; + if (IsParseObjectGet(outerAsGet) && outerAsGet.Object == outerKeySelector.Parameters[0]) { string outerKey = GetValue(outerAsGet.Arguments[0]) as string; if (IsParseObjectGet(innerAsGet) && innerAsGet.Object == innerKeySelector.Parameters[0]) { - // Both are key accesses, so treat this as a WhereMatchesKeyInQuery - string innerKey = GetValue(innerAsGet.Arguments[0]) as string; - return outer.WhereMatchesKeyInQuery(outerKey, innerKey, inner) as ParseQuery; + // Both are key accesses, so treat this as a WhereMatchesKeyInQuery. + + return outer.WhereMatchesKeyInQuery(outerKey, GetValue(innerAsGet.Arguments[0]) as string, inner) as ParseQuery; } if (innerKeySelector.Body == innerKeySelector.Parameters[0]) { - // The inner selector is on the result of the query itself, so treat this as a - // WhereMatchesQuery + // The inner selector is on the result of the query itself, so treat this as a WhereMatchesQuery. + return outer.WhereMatchesQuery(outerKey, inner) as ParseQuery; } - throw new InvalidOperationException( - "The key for the joined object must be a ParseObject or a field access " + - "on the ParseObject."); + + throw new InvalidOperationException("The key for the joined object must be a ParseObject or a field access on the ParseObject."); } // 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 ParseObject."); + throw new InvalidOperationException("The key for the selected object must be a field access on the ParseObject."); } public static string GetClassName(this ParseQuery query) where T : ParseObject => query.ClassName; diff --git a/Parse/Utilities/ParseSessionExtensions.cs b/Parse/Utilities/ParseSessionExtensions.cs deleted file mode 100644 index 32296ed6..00000000 --- a/Parse/Utilities/ParseSessionExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Core.Internal -{ - /// - /// So here's the deal. We have a lot of internal APIs for ParseObject, ParseUser, 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 ParseSessionExtensions - { - public static Task UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) => ParseSession.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken); - - public static Task RevokeAsync(string sessionToken, CancellationToken cancellationToken) => ParseSession.RevokeAsync(sessionToken, cancellationToken); - } -} diff --git a/Parse/Utilities/ParseUserExtensions.cs b/Parse/Utilities/ParseUserExtensions.cs index 9d8e5d60..07694a61 100644 --- a/Parse/Utilities/ParseUserExtensions.cs +++ b/Parse/Utilities/ParseUserExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; namespace Parse.Core.Internal { @@ -18,14 +19,8 @@ namespace Parse.Core.Internal /// public static class ParseUserExtensions { - public static IDictionary> GetAuthData(this ParseUser user) => user.AuthData; - public static Task UnlinkFromAsync(this ParseUser user, string authType, CancellationToken cancellationToken) => user.UnlinkFromAsync(authType, cancellationToken); - public static Task LogInWithAsync(string authType, CancellationToken cancellationToken) => ParseUser.LogInWithAsync(authType, cancellationToken); - - public static Task LogInWithAsync(string authType, IDictionary data, CancellationToken cancellationToken) => ParseUser.LogInWithAsync(authType, data, cancellationToken); - public static Task LinkWithAsync(this ParseUser user, string authType, CancellationToken cancellationToken) => user.LinkWithAsync(authType, cancellationToken); public static Task LinkWithAsync(this ParseUser user, string authType, IDictionary data, CancellationToken cancellationToken) => user.LinkWithAsync(authType, data, cancellationToken); diff --git a/Parse/Utilities/ReflectionHelpers.cs b/Parse/Utilities/ReflectionHelpers.cs index 09299db1..47035152 100644 --- a/Parse/Utilities/ReflectionHelpers.cs +++ b/Parse/Utilities/ReflectionHelpers.cs @@ -9,71 +9,12 @@ namespace Parse.Common.Internal { public static class ReflectionHelpers { - public static IEnumerable GetProperties(Type type) => -#if MONO || UNITY - return type.GetProperties(); -#else - type.GetRuntimeProperties(); -#endif - - - public static MethodInfo GetMethod(Type type, string name, Type[] parameters) => -#if MONO || UNITY - return type.GetMethod(name, parameters); -#else - type.GetRuntimeMethod(name, parameters); -#endif - - - public static bool IsPrimitive(Type type) => -#if MONO || UNITY - return type.IsPrimitive; -#else - type.GetTypeInfo().IsPrimitive; -#endif - - - public static IEnumerable GetInterfaces(Type type) => -#if MONO || UNITY - return type.GetInterfaces(); -#else - type.GetTypeInfo().ImplementedInterfaces; -#endif - - - public static bool IsConstructedGenericType(Type type) => -#if UNITY - return type.IsGenericType && !type.IsGenericTypeDefinition; -#else - type.IsConstructedGenericType; -#endif - - - public static IEnumerable GetConstructors(Type type) => -#if UNITY - BindingFlags searchFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - return type.GetConstructors(searchFlags); -#else - type.GetTypeInfo().DeclaredConstructors - .Where(c => (c.Attributes & MethodAttributes.Static) == 0); -#endif - - - public static Type[] GetGenericTypeArguments(Type type) => -#if UNITY - return type.GetGenericArguments(); -#else - type.GenericTypeArguments; -#endif - - - public static PropertyInfo GetProperty(Type type, string name) => -#if MONO || UNITY - return type.GetProperty(name); -#else - type.GetRuntimeProperty(name); -#endif - + /// + /// Gets all of the defined constructors that aren't static on a given instance. + /// + /// + /// + public static IEnumerable GetInstanceConstructors(this Type type) => type.GetTypeInfo().DeclaredConstructors.Where(constructor => (constructor.Attributes & MethodAttributes.Static) == 0); /// /// This method helps simplify the process of getting a constructor for a type. @@ -83,34 +24,20 @@ public static PropertyInfo GetProperty(Type type, string name) => /// /// /// - public static ConstructorInfo FindConstructor(this Type self, params Type[] parameterTypes) - { - IEnumerable 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 ConstructorInfo FindConstructor(this Type self, params Type[] parameterTypes) => self.GetConstructors().Where(constructor => constructor.GetParameters().Select(parameter => parameter.ParameterType).SequenceEqual(parameterTypes)).SingleOrDefault(); - public static IEnumerable GetCustomAttributes(this Assembly assembly) where T : Attribute => -#if UNITY - return assembly.GetCustomAttributes(typeof(T), false).Select(attr => attr as T); -#else - CustomAttributeExtensions.GetCustomAttributes(assembly); -#endif + /// + /// Checks if a instance is another instance wrapped with . + /// + /// + /// + public static bool CheckWrappedWithNullable(this Type type) => type.IsConstructedGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)); + /// + /// Gets the value of if the type has a custom attribute of type . + /// + /// + /// + public static string GetParseClassName(this Type type) => type.GetCustomAttribute()?.ClassName; } } diff --git a/Parse/Utilities/HttpClient.Portable.cs b/Parse/Utilities/UniversalWebClient.cs similarity index 68% rename from Parse/Utilities/HttpClient.Portable.cs rename to Parse/Utilities/UniversalWebClient.cs index 3d8afeac..2c38aa41 100644 --- a/Parse/Utilities/HttpClient.Portable.cs +++ b/Parse/Utilities/UniversalWebClient.cs @@ -8,16 +8,16 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using BuiltInClient = System.Net.Http.HttpClient; +using BCLWebClient = System.Net.Http.HttpClient; namespace Parse.Common.Internal { /// - /// + /// A universal implementation of . /// public class UniversalWebClient : IWebClient { - static HashSet ContentHeaders = new HashSet + static HashSet ContentHeaders { get; } = new HashSet { { "Allow" }, { "Content-Disposition" }, @@ -32,60 +32,67 @@ public class UniversalWebClient : IWebClient { "Last-Modified" } }; - public UniversalWebClient() : this(new BuiltInClient { }) { } + public UniversalWebClient() : this(new BCLWebClient { }) { } - public UniversalWebClient(BuiltInClient client) => this.client = client; + public UniversalWebClient(BCLWebClient client) => Client = client; - private BuiltInClient client; + BCLWebClient Client { get; set; } - public Task> ExecuteAsync(HttpRequest httpRequest, IProgress uploadProgress, IProgress downloadProgress, CancellationToken cancellationToken) + public Task> ExecuteAsync(WebRequest httpRequest, IProgress uploadProgress, IProgress downloadProgress, CancellationToken cancellationToken) { - uploadProgress ??= new Progress { }; - downloadProgress ??= new Progress { }; + uploadProgress ??= new Progress { }; + downloadProgress ??= new Progress { }; - HttpRequestMessage message = new HttpRequestMessage(new HttpMethod(httpRequest.Method), httpRequest.Uri); + HttpRequestMessage message = new HttpRequestMessage(new HttpMethod(httpRequest.Method), httpRequest.Target); // Fill in zero-length data if method is post. - Stream data = httpRequest.Data == null && httpRequest.Method.ToLower().Equals("post") ? new MemoryStream(new byte[0]) : httpRequest.Data; - if (data != null) + if ((httpRequest.Data is null && httpRequest.Method.ToLower().Equals("post") ? new MemoryStream(new byte[0]) : httpRequest.Data) is Stream { } data) + { message.Content = new StreamContent(data); + } if (httpRequest.Headers != null) { foreach (KeyValuePair header in httpRequest.Headers) { if (ContentHeaders.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 ParseUploadProgressEventArgs { Progress = 0 }); - return client.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ContinueWith(httpMessageTask => + uploadProgress.Report(new DataTransferLevel { Amount = 0 }); + + return Client.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ContinueWith(httpMessageTask => { HttpResponseMessage response = httpMessageTask.Result; - uploadProgress.Report(new ParseUploadProgressEventArgs { Progress = 1 }); + uploadProgress.Report(new DataTransferLevel { Amount = 1 }); return response.Content.ReadAsStreamAsync().ContinueWith(streamTask => { - MemoryStream resultStream = new MemoryStream(); + MemoryStream resultStream = new MemoryStream { }; Stream responseStream = streamTask.Result; - int bufferSize = 4096; + int bufferSize = 4096, bytesRead = 0; byte[] buffer = new byte[bufferSize]; - int bytesRead = 0; - long totalLength = -1; - long readSoFar = 0; + long totalLength = -1, readSoFar = 0; try - { totalLength = responseStream.Length; } + { + totalLength = responseStream.Length; + } catch { }; return InternalExtensions.WhileAsync(() => responseStream.ReadAsync(buffer, 0, bufferSize, cancellationToken).OnSuccess(readTask => (bytesRead = readTask.Result) > 0), () => @@ -98,7 +105,9 @@ public Task> ExecuteAsync(HttpRequest httpRequest, readSoFar += bytesRead; if (totalLength > -1) - downloadProgress.Report(new ParseDownloadProgressEventArgs { Progress = 1.0 * readSoFar / totalLength }); + { + downloadProgress.Report(new DataTransferLevel { Amount = 1.0 * readSoFar / totalLength }); + } }); }).ContinueWith(_ => { @@ -107,11 +116,17 @@ public Task> ExecuteAsync(HttpRequest httpRequest, }).Unwrap().OnSuccess(_ => { // If getting stream size is not supported, then report download only once. + if (totalLength == -1) - downloadProgress.Report(new ParseDownloadProgressEventArgs { Progress = 1.0 }); + { + downloadProgress.Report(new DataTransferLevel { Amount = 1.0 }); + } + byte[] resultAsArray = resultStream.ToArray(); resultStream.Dispose(); + // Assume UTF-8 encoding. + return new Tuple(response.StatusCode, Encoding.UTF8.GetString(resultAsArray, 0, resultAsArray.Length)); }); }); diff --git a/Parse/Utilities/HttpRequest.cs b/Parse/Utilities/WebRequest.cs similarity index 83% rename from Parse/Utilities/HttpRequest.cs rename to Parse/Utilities/WebRequest.cs index f1c4fc14..61ce2a70 100644 --- a/Parse/Utilities/HttpRequest.cs +++ b/Parse/Utilities/WebRequest.cs @@ -9,9 +9,14 @@ namespace Parse.Common.Internal /// /// IHttpRequest is an interface that provides an API to execute HTTP request data. /// - public class HttpRequest + public class WebRequest { - public Uri Uri { get; set; } + public Uri Target => new Uri(new Uri(Resource), Path); + + public string Resource { get; set; } + + public string Path { get; set; } + public IList> Headers { get; set; } /// From 23e86eace91e2c6e6f1fbcb0edb6e23e0144738f Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Thu, 9 Apr 2020 03:38:20 -0700 Subject: [PATCH 07/24] Complete refactored usage of dependency injection via merged plugin container interface IServiceHub, and rewrite tests to account for the change. --- Parse.Test/ACLTests.cs | 15 +- Parse.Test/AnalyticsControllerTests.cs | 42 +- Parse.Test/AnalyticsTests.cs | 85 +- Parse.Test/CloudControllerTests.cs | 41 +- Parse.Test/CloudTests.cs | 32 +- Parse.Test/CommandTests.cs | 81 +- Parse.Test/ConfigTests.cs | 33 +- Parse.Test/CurrentUserControllerTests.cs | 109 ++- Parse.Test/DecoderTests.cs | 67 +- Parse.Test/EncoderTests.cs | 60 +- Parse.Test/FileControllerTests.cs | 8 +- Parse.Test/FileTests.cs | 16 +- Parse.Test/GeoPointTests.cs | 7 +- Parse.Test/InstallationIdControllerTests.cs | 8 +- Parse.Test/InstallationTests.cs | 56 +- Parse.Test/ObjectCoderTests.cs | 6 +- Parse.Test/ObjectControllerTests.cs | 155 ++-- Parse.Test/ObjectTests.cs | 195 +++-- Parse.Test/Parse.Test.csproj | 2 +- Parse.Test/ProgressTests.cs | 38 +- Parse.Test/PushTests.cs | 141 ++- Parse.Test/RelationTests.cs | 3 +- Parse.Test/SessionControllerTests.cs | 76 +- Parse.Test/SessionTests.cs | 145 +-- Parse.Test/UserControllerTests.cs | 79 +- Parse.Test/UserTests.cs | 825 +++++++++--------- .../Abstractions/Library/CustomServiceHub.cs | 2 +- .../Management/IParseCurrentUserController.cs | 5 +- .../Management/IParseObjectClassController.cs | 9 +- .../Management/IParseObjectController.cs | 11 +- .../IParseObjectCurrentController.cs | 3 +- .../Management/IParseUserController.cs | 11 +- .../Tracking/IParseFieldOperation.cs | 4 +- .../Analytics/IParseAnalyticsController.cs | 11 +- .../Code/IParseCloudCodeController.cs | 6 +- .../IParseConfigurationController.cs | 3 +- .../IParseCurrentConfigurationController.cs | 3 +- .../Installation/IParseInstallationCoder.cs | 3 +- .../IParsePushChannelsController.cs | 6 +- .../Notifications/IParsePushController.cs | 3 +- .../Session/IParseSessionController.cs | 7 +- .../Query/IParseQueryController.cs | 6 +- Parse/AnalyticsServiceExtensions.cs | 4 +- Parse/CloudCodeServiceExtensions.cs | 2 +- Parse/ConfigurationServiceExtensions.cs | 10 +- Parse/InstallationServiceExtensions.cs | 2 +- .../Library/ConcurrentUserServiceHubCloner.cs | 5 +- Parse/Library/MutableServiceHub.cs | 6 +- Parse/Library/ServiceHub.cs | 4 +- Parse/Library/Utilities/LateInitializer.cs | 36 +- Parse/Management/ObjectSubclassInfo.cs | 24 - .../Management/ParseCurrentUserController.cs | 9 +- Parse/Management/ParseObjectClass.cs | 23 + ...oller.cs => ParseObjectClassController.cs} | 52 +- Parse/Management/ParseObjectController.cs | 34 +- Parse/Management/ParseUserController.cs | 13 +- .../Management/Tracking/ParseAddOperation.cs | 53 +- .../Tracking/ParseAddUniqueOperation.cs | 72 +- .../Tracking/ParseDeleteOperation.cs | 3 +- .../Tracking/ParseIncrementOperation.cs | 215 ++--- .../Tracking/ParseRelationOperation.cs | 5 +- .../Tracking/ParseRemoveOperation.cs | 55 +- .../Management/Tracking/ParseSetOperation.cs | 4 +- Parse/ObjectServiceExtensions.cs | 38 +- Parse/ParseClient.cs | 8 +- Parse/ParseConfiguration.cs | 12 +- Parse/ParseInstallation.cs | 14 +- Parse/ParseObject.cs | 75 +- Parse/ParsePush.cs | 2 +- Parse/ParseQuery.cs | 2 +- Parse/ParseRelation.cs | 8 +- Parse/ParseUser.cs | 26 +- .../Analytics/ParseAnalyticsController.cs | 11 +- .../Platform/Code/ParseCloudCodeController.cs | 14 +- .../ParseConfigurationController.cs | 5 +- .../ParseCurrentConfigurationController.cs | 5 +- .../ParseCurrentInstallationController.cs | 7 +- .../Installation/ParseInstallationCoder.cs | 7 +- .../ParsePushChannelsController.cs | 10 +- .../Notifications/ParsePushController.cs | 20 +- .../Session/ParseSessionController.cs | 7 +- Parse/PushServiceExtensions.cs | 4 +- Parse/Query/ParseQueryController.cs | 14 +- Parse/SessionsServiceExtensions.cs | 4 +- Parse/UserServiceExtensions.cs | 29 +- Parse/Utilities/Encoding/IParseDataDecoder.cs | 15 +- Parse/Utilities/Encoding/ParseDataDecoder.cs | 12 +- Parse/Utilities/Encoding/ParseDataEncoder.cs | 13 +- Parse/Utilities/Encoding/ParseObjectCoder.cs | 9 +- Parse/Utilities/InternalExtensions.cs | 6 +- 90 files changed, 1750 insertions(+), 1676 deletions(-) delete mode 100644 Parse/Management/ObjectSubclassInfo.cs create mode 100644 Parse/Management/ParseObjectClass.cs rename Parse/Management/{ObjectSubclassingController.cs => ParseObjectClassController.cs} (69%) diff --git a/Parse.Test/ACLTests.cs b/Parse.Test/ACLTests.cs index 255d3f3e..919fa678 100644 --- a/Parse.Test/ACLTests.cs +++ b/Parse.Test/ACLTests.cs @@ -1,6 +1,7 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Parse.Core.Internal; +using Parse.Library; using Parse.Management; namespace Parse.Test @@ -8,15 +9,17 @@ namespace Parse.Test [TestClass] public class ACLTests { + ParseClient Client { get; set; } = new ParseClient(new ServerConnectionData { Test = true }); + [TestInitialize] - public void SetUp() + public void Initialize() { - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); + Client.AddValidClass(); + Client.AddValidClass(); } [TestCleanup] - public void TearDown() => ParseCorePlugins.Instance = null; + public void Clean() => (Client.Services as ServiceHub).Reset(); [TestMethod] public void TestCheckPermissionsWithParseUserConstructor() @@ -49,8 +52,8 @@ public void TestReadWriteMutationWithParseUserConstructor() } [TestMethod] - public void TestParseACLCreationWithNullObjectIdParseUser() => Assert.ThrowsException(() => new ParseACL(GenerateUser(null))); + public void TestParseACLCreationWithNullObjectIdParseUser() => Assert.ThrowsException(() => new ParseACL(GenerateUser(default))); - ParseUser GenerateUser(string objectID) => ParseObjectExtensions.FromState(new MutableObjectState { ObjectId = objectID }, "_User"); + ParseUser GenerateUser(string objectID) => Client.GenerateObjectFromState(new MutableObjectState { ObjectId = objectID }, "_User"); } } diff --git a/Parse.Test/AnalyticsControllerTests.cs b/Parse.Test/AnalyticsControllerTests.cs index 852ce1cb..0c31a4c4 100644 --- a/Parse.Test/AnalyticsControllerTests.cs +++ b/Parse.Test/AnalyticsControllerTests.cs @@ -15,8 +15,10 @@ namespace Parse.Test [TestClass] public class AnalyticsControllerTests { + ParseClient Client { get; set; } + [TestInitialize] - public void SetUp() => ParseClient.Initialize(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); + public void SetUp() => Client = new ParseClient(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(AnalyticsControllerTests))] @@ -24,11 +26,12 @@ public Task TestTrackEventWithEmptyDimensions() { Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary { })); - return new ParseAnalyticsController(mockRunner.Object).TrackEventAsync("SomeEvent", dimensions: null, sessionToken: null, cancellationToken: CancellationToken.None).ContinueWith(t => + return new ParseAnalyticsController(mockRunner.Object).TrackEventAsync("SomeEvent", dimensions: default, sessionToken: default, serviceHub: Client, cancellationToken: CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/events/SomeEvent"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/events/SomeEvent"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -38,13 +41,12 @@ public Task TestTrackEventWithNonEmptyDimensions() { Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary { })); - Dictionary dimensions = new Dictionary { ["njwerjk12"] = "5523dd" }; - - return new ParseAnalyticsController(mockRunner.Object).TrackEventAsync("SomeEvent", dimensions: dimensions, sessionToken: null, cancellationToken: CancellationToken.None).ContinueWith(t => + return new ParseAnalyticsController(mockRunner.Object).TrackEventAsync("SomeEvent", dimensions: new Dictionary { ["njwerjk12"] = "5523dd" }, sessionToken: default, serviceHub: Client, cancellationToken: CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath.Contains("/1/events/SomeEvent")), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath.Contains("/1/events/SomeEvent")), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -54,11 +56,12 @@ public Task TestTrackAppOpenedWithEmptyPushHash() { Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary())); - return new ParseAnalyticsController(mockRunner.Object).TrackAppOpenedAsync(null, sessionToken: null, cancellationToken: CancellationToken.None).ContinueWith(t => + return new ParseAnalyticsController(mockRunner.Object).TrackAppOpenedAsync(default, sessionToken: default, serviceHub: Client, cancellationToken: CancellationToken.None).ContinueWith(t => { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/events/AppOpened"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/events/AppOpened"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -68,20 +71,19 @@ public Task TestTrackAppOpenedWithNonEmptyPushHash() { Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary())); - string pushHash = "32j4hll12lkk"; - - return new ParseAnalyticsController(mockRunner.Object).TrackAppOpenedAsync(pushHash, sessionToken: null, cancellationToken: CancellationToken.None).ContinueWith(t => + return new ParseAnalyticsController(mockRunner.Object).TrackAppOpenedAsync("32j4hll12lkk", sessionToken: default, serviceHub: Client, cancellationToken: CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockRunner.Verify(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } Mock CreateMockRunner(Tuple> response) { Mock mockRunner = new Mock(); - mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); + mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); return mockRunner; } diff --git a/Parse.Test/AnalyticsTests.cs b/Parse.Test/AnalyticsTests.cs index 515ecb14..0a5471fa 100644 --- a/Parse.Test/AnalyticsTests.cs +++ b/Parse.Test/AnalyticsTests.cs @@ -4,41 +4,43 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Parse.Abstractions.Library; using Parse.Abstractions.Management; using Parse.Analytics.Internal; using Parse.Core.Internal; +using Parse.Library; namespace Parse.Test { [TestClass] public class AnalyticsTests { - [TestCleanup] - public void TearDown() => ParseAnalyticsPlugins.Instance.Reset(); +#warning Skipped post-test-evaluation cleaning method may be needed. + + // [TestCleanup] + // public void TearDown() => (Client.Services as ServiceHub).Reset(); [TestMethod] [AsyncStateMachine(typeof(AnalyticsTests))] public Task TestTrackEvent() { - Mock mockController = new Mock(); - Mock mockCorePlugins = new Mock(); - Mock mockCurrentUserController = new Mock(); + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); - mockCorePlugins.Setup(corePlugins => corePlugins.CurrentUserController).Returns(mockCurrentUserController.Object); + Mock mockController = new Mock { }; + Mock mockCurrentUserController = new Mock { }; - mockCurrentUserController.Setup(controller => controller.GetCurrentSessionTokenAsync(It.IsAny())).Returns(Task.FromResult("sessionToken")); + mockCurrentUserController.Setup(controller => controller.GetCurrentSessionTokenAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult("sessionToken")); - ParseAnalyticsPlugins.Instance = new ParseAnalyticsPlugins - { - AnalyticsController = mockController.Object, - CorePlugins = mockCorePlugins.Object - }; + hub.AnalyticsController = mockController.Object; + hub.CurrentUserController = mockCurrentUserController.Object; - return AnalyticsServiceExtensions.TrackEventAsync("SomeEvent").ContinueWith(t => + return client.TrackAnalyticsEventAsync("SomeEvent").ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockController.Verify(obj => obj.TrackEventAsync(It.Is(eventName => eventName == "SomeEvent"), It.Is>(dict => dict == null), It.IsAny(), It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockController.Verify(obj => obj.TrackEventAsync(It.Is(eventName => eventName == "SomeEvent"), It.Is>(dict => dict == null), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); }); } @@ -46,25 +48,22 @@ public Task TestTrackEvent() [AsyncStateMachine(typeof(AnalyticsTests))] public Task TestTrackEventWithDimension() { - Mock mockController = new Mock(); - Mock mockCorePlugins = new Mock(); - Mock mockCurrentUserController = new Mock(); + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); - mockCorePlugins.Setup(corePlugins => corePlugins.CurrentUserController).Returns(mockCurrentUserController.Object); + Mock mockController = new Mock { }; + Mock mockCurrentUserController = new Mock { }; - mockCurrentUserController.Setup(controller => controller.GetCurrentSessionTokenAsync(It.IsAny())).Returns(Task.FromResult("sessionToken")); + mockCurrentUserController.Setup(controller => controller.GetCurrentSessionTokenAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult("sessionToken")); - ParseAnalyticsPlugins.Instance = new ParseAnalyticsPlugins - { - AnalyticsController = mockController.Object, - CorePlugins = mockCorePlugins.Object - }; + hub.AnalyticsController = mockController.Object; + hub.CurrentUserController = mockCurrentUserController.Object; - return AnalyticsServiceExtensions.TrackEventAsync("SomeEvent", new Dictionary { ["facebook"] = "hq" }).ContinueWith(t => + return client.TrackAnalyticsEventAsync("SomeEvent", new Dictionary { ["facebook"] = "hq" }).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockController.Verify(obj => obj.TrackEventAsync(It.Is(eventName => eventName == "SomeEvent"), It.Is>(dict => dict != null && dict.Count == 1), It.IsAny(), It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + mockController.Verify(obj => obj.TrackEventAsync(It.Is(eventName => eventName == "SomeEvent"), It.Is>(dict => dict != null && dict.Count == 1), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); }); } @@ -72,25 +71,23 @@ public Task TestTrackEventWithDimension() [AsyncStateMachine(typeof(AnalyticsTests))] public Task TestTrackAppOpened() { - Mock mockController = new Mock(); - Mock mockCorePlugins = new Mock(); - Mock mockCurrentUserController = new Mock(); + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); - mockCorePlugins.Setup(corePlugins => corePlugins.CurrentUserController).Returns(mockCurrentUserController.Object); + Mock mockController = new Mock { }; + Mock mockCurrentUserController = new Mock { }; - mockCurrentUserController.Setup(controller => controller.GetCurrentSessionTokenAsync(It.IsAny())).Returns(Task.FromResult("sessionToken")); + mockCurrentUserController.Setup(controller => controller.GetCurrentSessionTokenAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult("sessionToken")); - ParseAnalyticsPlugins.Instance = new ParseAnalyticsPlugins - { - AnalyticsController = mockController.Object, - CorePlugins = mockCorePlugins.Object - }; + hub.AnalyticsController = mockController.Object; + hub.CurrentUserController = mockCurrentUserController.Object; - return AnalyticsServiceExtensions.TrackAppOpenedAsync().ContinueWith(t => + return client.TrackLaunchAsync().ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockController.Verify(obj => obj.TrackAppOpenedAsync(It.Is(pushHash => pushHash == null), It.IsAny(), It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockController.Verify(obj => obj.TrackAppOpenedAsync(It.Is(pushHash => pushHash == null), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); }); } } diff --git a/Parse.Test/CloudControllerTests.cs b/Parse.Test/CloudControllerTests.cs index 1156b9a3..ef80294e 100644 --- a/Parse.Test/CloudControllerTests.cs +++ b/Parse.Test/CloudControllerTests.cs @@ -11,18 +11,22 @@ namespace Parse.Test { +#warning Class refactoring requires completion. + [TestClass] public class CloudControllerTests { + ParseClient Client { get; set; } + [TestInitialize] - public void SetUp() => ParseClient.Initialize(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); + public void SetUp() => Client = new ParseClient(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(CloudControllerTests))] - public Task TestEmptyCallFunction() => new ParseCloudCodeController(CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, null)).Object).CallFunctionAsync("someFunction", null, null, CancellationToken.None).ContinueWith(t => + public Task TestEmptyCallFunction() => new ParseCloudCodeController(CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, default)).Object, Client.Decoder).CallFunctionAsync("someFunction", default, default, Client, CancellationToken.None).ContinueWith(task => { - Assert.IsTrue(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); + Assert.IsTrue(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); }); [TestMethod] @@ -33,38 +37,37 @@ public Task TestCallFunction() Tuple> response = new Tuple>(HttpStatusCode.Accepted, responseDict); Mock mockRunner = CreateMockRunner(response); - ParseCloudCodeController controller = new ParseCloudCodeController(mockRunner.Object); - return controller.CallFunctionAsync("someFunction", null, null, CancellationToken.None).ContinueWith(t => + return new ParseCloudCodeController(mockRunner.Object, Client.Decoder).CallFunctionAsync("someFunction", default, default, Client, CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - Assert.AreEqual("gogo", t.Result); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + Assert.AreEqual("gogo", task.Result); }); } [TestMethod] [AsyncStateMachine(typeof(CloudControllerTests))] - public Task TestCallFunctionWithComplexType() => new ParseCloudCodeController(CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary() { { "result", new Dictionary { { "fosco", "ben" }, { "list", new List { 1, 2, 3 } } } } })).Object).CallFunctionAsync>("someFunction", null, null, CancellationToken.None).ContinueWith(t => + public Task TestCallFunctionWithComplexType() => new ParseCloudCodeController(CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary() { { "result", new Dictionary { { "fosco", "ben" }, { "list", new List { 1, 2, 3 } } } } })).Object, Client.Decoder).CallFunctionAsync>("someFunction", default, default, Client, CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - Assert.IsInstanceOfType(t.Result, typeof(IDictionary)); - Assert.AreEqual("ben", t.Result["fosco"]); - Assert.IsInstanceOfType(t.Result["list"], typeof(IList)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + Assert.IsInstanceOfType(task.Result, typeof(IDictionary)); + Assert.AreEqual("ben", task.Result["fosco"]); + Assert.IsInstanceOfType(task.Result["list"], typeof(IList)); }); [TestMethod] [AsyncStateMachine(typeof(CloudControllerTests))] - public Task TestCallFunctionWithWrongType() => new ParseCloudCodeController(CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary() { { "result", "gogo" } })).Object).CallFunctionAsync("someFunction", null, null, CancellationToken.None).ContinueWith(t => + public Task TestCallFunctionWithWrongType() => new ParseCloudCodeController(CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary() { { "result", "gogo" } })).Object, Client.Decoder).CallFunctionAsync("someFunction", default, default, Client, CancellationToken.None).ContinueWith(task => { - Assert.IsTrue(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); + Assert.IsTrue(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); }); private Mock CreateMockRunner(Tuple> response) { Mock mockRunner = new Mock { }; - mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); + mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); return mockRunner; } diff --git a/Parse.Test/CloudTests.cs b/Parse.Test/CloudTests.cs index fe87768b..46b44f69 100644 --- a/Parse.Test/CloudTests.cs +++ b/Parse.Test/CloudTests.cs @@ -4,7 +4,9 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Parse.Abstractions.Library; using Parse.Core.Internal; +using Parse.Library; using Parse.Management; namespace Parse.Test @@ -12,29 +14,31 @@ namespace Parse.Test [TestClass] public class CloudTests { - [TestCleanup] - public void TearDown() => ParseCorePlugins.Instance.Reset(); +#warning Skipped post-test-evaluation cleaning method may be needed. + + // [TestCleanup] + // public void TearDown() => ParseCorePlugins.Instance.Reset(); [TestMethod] [AsyncStateMachine(typeof(CloudTests))] public Task TestCloudFunctions() { + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + Mock mockController = new Mock(); - mockController.Setup(obj => obj.CallFunctionAsync>(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())).Returns(Task.FromResult>(new Dictionary { ["fosco"] = "ben", ["list"] = new List { 1, 2, 3 } })); + mockController.Setup(obj => obj.CallFunctionAsync>(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult>(new Dictionary { ["fosco"] = "ben", ["list"] = new List { 1, 2, 3 } })); - ParseCorePlugins.Instance = new ParseCorePlugins - { - CloudCodeController = mockController.Object, - CurrentUserController = new Mock().Object - }; + hub.CloudCodeController = mockController.Object; + hub.CurrentUserController = new Mock { }.Object; - return CloudCodeServiceExtensions.CallFunctionAsync>("someFunction", null, CancellationToken.None).ContinueWith(t => + return client.CallCloudCodeFunctionAsync>("someFunction", null, CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - Assert.IsInstanceOfType(t.Result, typeof(IDictionary)); - Assert.AreEqual("ben", t.Result["fosco"]); - Assert.IsInstanceOfType(t.Result["list"], typeof(IList)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + Assert.IsInstanceOfType(task.Result, typeof(IDictionary)); + Assert.AreEqual("ben", task.Result["fosco"]); + Assert.IsInstanceOfType(task.Result["list"], typeof(IList)); }); } } diff --git a/Parse.Test/CommandTests.cs b/Parse.Test/CommandTests.cs index a9ccdd2e..bfe15773 100644 --- a/Parse.Test/CommandTests.cs +++ b/Parse.Test/CommandTests.cs @@ -15,25 +15,24 @@ namespace Parse.Test { +#warning Initialization and cleaning steps may be redundant for each test method. It may be possible to simply reset the required services before each run. +#warning Class refactoring requires completion. + [TestClass] public class CommandTests { - Mock MockMetadataController { get; } = new Mock { }; + ParseClient Client { get; set; } [TestInitialize] - public void SetUp() - { - ParseClient.Initialize(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); - MockMetadataController.Setup(metadata => metadata.HostManifestData).Returns(new HostManifestData { Version = "1", ShortVersion = "1", HostOSVersion = "1" }); - } + public void Initialize() => Client = new ParseClient(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestCleanup] - public void TearDown() => ParseCorePlugins.Instance.Reset(); + public void Clean() => (Client.Services as ServiceHub).Reset(); [TestMethod] public void TestMakeCommand() { - ParseCommand command = new ParseCommand("endpoint", method: "GET", sessionToken: "abcd", headers: null, data: null); + ParseCommand command = new ParseCommand("endpoint", method: "GET", sessionToken: "abcd", headers: default, data: default); Assert.AreEqual("/1/endpoint", command.Target.AbsolutePath); Assert.AreEqual("GET", command.Method); @@ -47,16 +46,16 @@ public Task TestRunCommand() Mock mockHttpClient = new Mock(); Mock mockInstallationIdController = new Mock(); Task> fakeResponse = Task.FromResult(new Tuple(HttpStatusCode.OK, "{}")); - mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(fakeResponse); + mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(fakeResponse); - mockInstallationIdController.Setup(i => i.GetAsync()).Returns(Task.FromResult(null)); + mockInstallationIdController.Setup(controller => controller.GetAsync()).Returns(Task.FromResult(default)); - return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, MockMetadataController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => + return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, Client.MetadataController, Client.ServerConnectionData, new Lazy(() => Client.UserController)).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: default)).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - Assert.IsInstanceOfType(t.Result.Item2, typeof(IDictionary)); - Assert.AreEqual(0, t.Result.Item2.Count); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + Assert.IsInstanceOfType(task.Result.Item2, typeof(IDictionary)); + Assert.AreEqual(0, task.Result.Item2.Count); }); } @@ -66,11 +65,11 @@ public Task TestRunCommandWithArrayResult() { Mock mockHttpClient = new Mock(); Mock mockInstallationIdController = new Mock(); - mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "[]"))); + mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "[]"))); - mockInstallationIdController.Setup(i => i.GetAsync()).Returns(Task.FromResult(null)); + mockInstallationIdController.Setup(controller => controller.GetAsync()).Returns(Task.FromResult(default)); - return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, MockMetadataController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => + return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, Client.MetadataController, Client.ServerConnectionData, new Lazy(() => Client.UserController)).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: default)).ContinueWith(t => { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); @@ -87,16 +86,16 @@ public Task TestRunCommandWithInvalidString() { Mock mockHttpClient = new Mock(); Mock mockInstallationIdController = new Mock(); - mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "invalid"))); + mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "invalid"))); - mockInstallationIdController.Setup(i => i.GetAsync()).Returns(Task.FromResult(null)); + mockInstallationIdController.Setup(controller => controller.GetAsync()).Returns(Task.FromResult(default)); - return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, MockMetadataController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => + return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, Client.MetadataController, Client.ServerConnectionData, new Lazy(() => Client.UserController)).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: default)).ContinueWith(task => { - Assert.IsTrue(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - Assert.IsInstanceOfType(t.Exception.InnerException, typeof(ParseFailureException)); - Assert.AreEqual(ParseFailureException.ErrorCode.OtherCause, (t.Exception.InnerException as ParseFailureException).Code); + Assert.IsTrue(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + Assert.IsInstanceOfType(task.Exception.InnerException, typeof(ParseFailureException)); + Assert.AreEqual(ParseFailureException.ErrorCode.OtherCause, (task.Exception.InnerException as ParseFailureException).Code); }); } @@ -105,17 +104,17 @@ public Task TestRunCommandWithInvalidString() public Task TestRunCommandWithErrorCode() { Mock mockHttpClient = new Mock(); - Mock mockInstallationIdController = new Mock(); - mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.NotFound, "{ \"code\": 101, \"error\": \"Object not found.\" }"))); + Mock mockInstallationController = new Mock(); + mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.NotFound, "{ \"code\": 101, \"error\": \"Object not found.\" }"))); - mockInstallationIdController.Setup(i => i.GetAsync()).Returns(Task.FromResult(null)); + mockInstallationController.Setup(controller => controller.GetAsync()).Returns(Task.FromResult(default)); - return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, MockMetadataController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => + return new ParseCommandRunner(mockHttpClient.Object, mockInstallationController.Object, Client.MetadataController, Client.ServerConnectionData, new Lazy(() => Client.UserController)).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: default)).ContinueWith(task => { - Assert.IsTrue(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - Assert.IsInstanceOfType(t.Exception.InnerException, typeof(ParseFailureException)); - ParseFailureException parseException = t.Exception.InnerException as ParseFailureException; + Assert.IsTrue(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + Assert.IsInstanceOfType(task.Exception.InnerException, typeof(ParseFailureException)); + ParseFailureException parseException = task.Exception.InnerException as ParseFailureException; Assert.AreEqual(ParseFailureException.ErrorCode.ObjectNotFound, parseException.Code); Assert.AreEqual("Object not found.", parseException.Message); }); @@ -126,17 +125,17 @@ public Task TestRunCommandWithErrorCode() public Task TestRunCommandWithInternalServerError() { Mock mockHttpClient = new Mock(); - Mock mockInstallationIdController = new Mock(); + Mock mockInstallationController = new Mock(); - mockHttpClient.Setup(client => client.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.InternalServerError, null))); - mockInstallationIdController.Setup(installationController => installationController.GetAsync()).Returns(Task.FromResult(null)); + mockHttpClient.Setup(client => client.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.InternalServerError, default))); + mockInstallationController.Setup(installationController => installationController.GetAsync()).Returns(Task.FromResult(default)); - return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, MockMetadataController.Object).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: null)).ContinueWith(t => + return new ParseCommandRunner(mockHttpClient.Object, mockInstallationController.Object, Client.MetadataController, Client.ServerConnectionData, new Lazy(() => Client.UserController)).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: default)).ContinueWith(task => { - Assert.IsTrue(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - Assert.IsInstanceOfType(t.Exception.InnerException, typeof(ParseFailureException)); - Assert.AreEqual(ParseFailureException.ErrorCode.InternalServerError, (t.Exception.InnerException as ParseFailureException).Code); + Assert.IsTrue(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + Assert.IsInstanceOfType(task.Exception.InnerException, typeof(ParseFailureException)); + Assert.AreEqual(ParseFailureException.ErrorCode.InternalServerError, (task.Exception.InnerException as ParseFailureException).Code); }); } } diff --git a/Parse.Test/ConfigTests.cs b/Parse.Test/ConfigTests.cs index bfacff0d..3974b4c6 100644 --- a/Parse.Test/ConfigTests.cs +++ b/Parse.Test/ConfigTests.cs @@ -5,8 +5,10 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Newtonsoft.Json; +using Parse.Abstractions.Library; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Library; using Parse.Management; namespace Parse.Test @@ -14,40 +16,42 @@ namespace Parse.Test [TestClass] public class ConfigTests { - private IParseConfigurationController MockedConfigController + ParseClient Client { get; } = new ParseClient(new ServerConnectionData { Test = true }, new MutableServiceHub { }); + + IParseConfigurationController MockedConfigController { get { Mock mockedConfigController = new Mock(); Mock mockedCurrentConfigController = new Mock(); - ParseConfiguration theConfig = ParseConfigExtensions.Create(new Dictionary { ["params"] = new Dictionary { ["testKey"] = "testValue" } }); + ParseConfiguration theConfig = Client.BuildConfiguration(new Dictionary { ["params"] = new Dictionary { ["testKey"] = "testValue" } }); - mockedCurrentConfigController.Setup(obj => obj.GetCurrentConfigAsync()).Returns(Task.FromResult(theConfig)); + mockedCurrentConfigController.Setup(obj => obj.GetCurrentConfigAsync(Client)).Returns(Task.FromResult(theConfig)); mockedConfigController.Setup(obj => obj.CurrentConfigurationController).Returns(mockedCurrentConfigController.Object); TaskCompletionSource tcs = new TaskCompletionSource(); tcs.TrySetCanceled(); - mockedConfigController.Setup(obj => obj.FetchConfigAsync(It.IsAny(), It.Is(ct => ct.IsCancellationRequested))).Returns(tcs.Task); + mockedConfigController.Setup(obj => obj.FetchConfigAsync(It.IsAny(), It.IsAny(), It.Is(ct => ct.IsCancellationRequested))).Returns(tcs.Task); - mockedConfigController.Setup(obj => obj.FetchConfigAsync(It.IsAny(), It.Is(ct => !ct.IsCancellationRequested))).Returns(Task.FromResult(theConfig)); + mockedConfigController.Setup(obj => obj.FetchConfigAsync(It.IsAny(), It.IsAny(), It.Is(ct => !ct.IsCancellationRequested))).Returns(Task.FromResult(theConfig)); return mockedConfigController.Object; } } [TestInitialize] - public void SetUp() => ParseCorePlugins.Instance = new ParseCorePlugins { ConfigController = MockedConfigController, CurrentUserController = new Mock().Object }; + public void SetUp() => (Client.Services as OrchestrationServiceHub).Custom = new MutableServiceHub { ConfigurationController = MockedConfigController, CurrentUserController = new Mock().Object }; [TestCleanup] - public void TearDown() => ParseCorePlugins.Instance = null; + public void TearDown() => ((Client.Services as OrchestrationServiceHub).Default as ServiceHub).Reset(); [TestMethod] public void TestCurrentConfig() { - ParseConfiguration config = ParseConfiguration.CurrentConfig; + ParseConfiguration config = Client.GetCurrentConfiguration(); Assert.AreEqual("testValue", config["testKey"]); Assert.AreEqual("testValue", config.Get("testKey")); @@ -57,24 +61,25 @@ public void TestCurrentConfig() public void TestToJSON() { IDictionary expectedJson = new Dictionary { { "params", new Dictionary { { "testKey", "testValue" } } } }; - Assert.AreEqual(JsonConvert.SerializeObject((ParseConfiguration.CurrentConfig as IJsonConvertible).ConvertToJSON()), JsonConvert.SerializeObject(expectedJson)); + Assert.AreEqual(JsonConvert.SerializeObject((Client.GetCurrentConfiguration() as IJsonConvertible).ConvertToJSON()), JsonConvert.SerializeObject(expectedJson)); } [TestMethod] [AsyncStateMachine(typeof(ConfigTests))] - public Task TestGetConfig() => ParseConfiguration.GetAsync().ContinueWith(t => + public Task TestGetConfig() => Client.GetConfigurationAsync().ContinueWith(task => { - Assert.AreEqual("testValue", t.Result["testKey"]); - Assert.AreEqual("testValue", t.Result.Get("testKey")); + Assert.AreEqual("testValue", task.Result["testKey"]); + Assert.AreEqual("testValue", task.Result.Get("testKey")); }); [TestMethod] [AsyncStateMachine(typeof(ConfigTests))] public Task TestGetConfigCancel() { - CancellationTokenSource tokenSource = new CancellationTokenSource(); + CancellationTokenSource tokenSource = new CancellationTokenSource { }; tokenSource.Cancel(); - return ParseConfiguration.GetAsync(tokenSource.Token).ContinueWith(t => Assert.IsTrue(t.IsCanceled)); + + return Client.GetConfigurationAsync(tokenSource.Token).ContinueWith(task => Assert.IsTrue(task.IsCanceled)); } } } diff --git a/Parse.Test/CurrentUserControllerTests.cs b/Parse.Test/CurrentUserControllerTests.cs index 9230c28c..3172bb46 100644 --- a/Parse.Test/CurrentUserControllerTests.cs +++ b/Parse.Test/CurrentUserControllerTests.cs @@ -7,6 +7,7 @@ using Moq; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Library; using Parse.Management; namespace Parse.Test @@ -14,25 +15,31 @@ namespace Parse.Test [TestClass] public class CurrentUserControllerTests { + ParseClient Client { get; } = new ParseClient(new ServerConnectionData { Test = true }); + [TestInitialize] - public void SetUp() => ParseObject.RegisterDerivative(); + public void SetUp() => Client.AddValidClass(); [TestCleanup] - public void TearDown() => ParseCorePlugins.Instance.Reset(); + public void TearDown() => (Client.Services as ServiceHub).Reset(); [TestMethod] - public void TestConstructor() => Assert.IsNull(new ParseCurrentUserController(new Mock().Object).CurrentUser); + public void TestConstructor() => Assert.IsNull(new ParseCurrentUserController(new Mock { }.Object, Client.ClassController, Client.Decoder).CurrentUser); [TestMethod] [AsyncStateMachine(typeof(CurrentUserControllerTests))] public Task TestGetSetAsync() { +#warning This method may need a fully custom ParseClient setup. + Mock storageController = new Mock(MockBehavior.Strict); Mock> mockedStorage = new Mock>(); - ParseCurrentUserController controller = new ParseCurrentUserController(storageController.Object); - ParseUser user = new ParseUser(); - storageController.Setup(s => s.LoadAsync()).Returns(Task.FromResult(mockedStorage.Object)); + ParseCurrentUserController controller = new ParseCurrentUserController(storageController.Object, Client.ClassController, Client.Decoder); + + ParseUser user = new ParseUser { }.Bind(Client) as ParseUser; + + storageController.Setup(storage => storage.LoadAsync()).Returns(Task.FromResult(mockedStorage.Object)); return controller.SetAsync(user, CancellationToken.None).OnSuccess(_ => { @@ -47,21 +54,19 @@ public Task TestGetSetAsync() }; #pragma warning restore IDE0039 // Use local function - mockedStorage.Verify(s => s.AddAsync("CurrentUser", Match.Create(predicate))); - mockedStorage.Setup(s => s.TryGetValue("CurrentUser", out jsonObject)).Returns(true); + mockedStorage.Verify(storage => storage.AddAsync("CurrentUser", Match.Create(predicate))); + mockedStorage.Setup(storage => storage.TryGetValue("CurrentUser", out jsonObject)).Returns(true); - return controller.GetAsync(CancellationToken.None); - }).Unwrap() - .OnSuccess(t => + return controller.GetAsync(Client, CancellationToken.None); + }).Unwrap().OnSuccess(task => { Assert.AreEqual(user, controller.CurrentUser); controller.ClearFromMemory(); Assert.AreNotEqual(user, controller.CurrentUser); - return controller.GetAsync(CancellationToken.None); - }).Unwrap() - .OnSuccess(t => + return controller.GetAsync(Client, CancellationToken.None); + }).Unwrap().OnSuccess(task => { Assert.AreNotSame(user, controller.CurrentUser); Assert.IsNotNull(controller.CurrentUser); @@ -74,41 +79,38 @@ public Task TestExistsAsync() { Mock storageController = new Mock(); Mock> mockedStorage = new Mock>(); - ParseCurrentUserController controller = new ParseCurrentUserController(storageController.Object); - ParseUser user = new ParseUser(); + ParseCurrentUserController controller = new ParseCurrentUserController(storageController.Object, Client.ClassController, Client.Decoder); + ParseUser user = new ParseUser { }.Bind(Client) as ParseUser; storageController.Setup(c => c.LoadAsync()).Returns(Task.FromResult(mockedStorage.Object)); bool contains = false; - mockedStorage.Setup(s => s.AddAsync("CurrentUser", It.IsAny())).Callback(() => contains = true).Returns(Task.FromResult(null)).Verifiable(); + mockedStorage.Setup(storage => storage.AddAsync("CurrentUser", It.IsAny())).Callback(() => contains = true).Returns(Task.FromResult(null)).Verifiable(); - mockedStorage.Setup(s => s.RemoveAsync("CurrentUser")).Callback(() => contains = false).Returns(Task.FromResult(null)).Verifiable(); + mockedStorage.Setup(storage => storage.RemoveAsync("CurrentUser")).Callback(() => contains = false).Returns(Task.FromResult(null)).Verifiable(); - mockedStorage.Setup(s => s.ContainsKey("CurrentUser")).Returns(() => contains); + mockedStorage.Setup(storage => storage.ContainsKey("CurrentUser")).Returns(() => contains); return controller.SetAsync(user, CancellationToken.None).OnSuccess(_ => { Assert.AreEqual(user, controller.CurrentUser); return controller.ExistsAsync(CancellationToken.None); - }).Unwrap() - .OnSuccess(t => + }).Unwrap().OnSuccess(task => { - Assert.IsTrue(t.Result); + Assert.IsTrue(task.Result); controller.ClearFromMemory(); return controller.ExistsAsync(CancellationToken.None); - }).Unwrap() - .OnSuccess(t => + }).Unwrap().OnSuccess(task => { - Assert.IsTrue(t.Result); + Assert.IsTrue(task.Result); controller.ClearFromDisk(); return controller.ExistsAsync(CancellationToken.None); - }).Unwrap() - .OnSuccess(t => + }).Unwrap().OnSuccess(task => { - Assert.IsFalse(t.Result); + Assert.IsFalse(task.Result); mockedStorage.Verify(); }); } @@ -118,13 +120,14 @@ public Task TestExistsAsync() public Task TestIsCurrent() { Mock storageController = new Mock(MockBehavior.Strict); - ParseCurrentUserController controller = new ParseCurrentUserController(storageController.Object); - ParseUser user = new ParseUser(); - ParseUser user2 = new ParseUser(); + ParseCurrentUserController controller = new ParseCurrentUserController(storageController.Object, Client.ClassController, Client.Decoder); - storageController.Setup(s => s.LoadAsync()).Returns(Task.FromResult(new Mock>().Object)); + ParseUser user = new ParseUser { }.Bind(Client) as ParseUser; + ParseUser user2 = new ParseUser { }.Bind(Client) as ParseUser; - return controller.SetAsync(user, CancellationToken.None).OnSuccess(t => + storageController.Setup(storage => storage.LoadAsync()).Returns(Task.FromResult(new Mock>().Object)); + + return controller.SetAsync(user, CancellationToken.None).OnSuccess(task => { Assert.IsTrue(controller.IsCurrent(user)); Assert.IsFalse(controller.IsCurrent(user2)); @@ -134,8 +137,7 @@ public Task TestIsCurrent() Assert.IsFalse(controller.IsCurrent(user)); return controller.SetAsync(user, CancellationToken.None); - }).Unwrap() - .OnSuccess(t => + }).Unwrap().OnSuccess(task => { Assert.IsTrue(controller.IsCurrent(user)); Assert.IsFalse(controller.IsCurrent(user2)); @@ -145,8 +147,7 @@ public Task TestIsCurrent() Assert.IsFalse(controller.IsCurrent(user)); return controller.SetAsync(user2, CancellationToken.None); - }).Unwrap() - .OnSuccess(t => + }).Unwrap().OnSuccess(task => { Assert.IsFalse(controller.IsCurrent(user)); Assert.IsTrue(controller.IsCurrent(user2)); @@ -159,47 +160,41 @@ public Task TestCurrentSessionToken() { Mock storageController = new Mock(); Mock> mockedStorage = new Mock>(); - ParseCurrentUserController controller = new ParseCurrentUserController(storageController.Object); + ParseCurrentUserController controller = new ParseCurrentUserController(storageController.Object, Client.ClassController, Client.Decoder); storageController.Setup(c => c.LoadAsync()).Returns(Task.FromResult(mockedStorage.Object)); - return controller.GetCurrentSessionTokenAsync(CancellationToken.None).OnSuccess(t => + return controller.GetCurrentSessionTokenAsync(Client, CancellationToken.None).OnSuccess(task => { - Assert.IsNull(t.Result); + Assert.IsNull(task.Result); // We should probably mock this. - ParseUser user = ParseObject.CreateWithoutData(null); + + ParseUser user = Client.CreateObjectWithoutData(default); user.HandleFetchResult(new MutableObjectState { ServerData = new Dictionary { ["sessionToken"] = "randomString" } }); return controller.SetAsync(user, CancellationToken.None); - }).Unwrap() - .OnSuccess(_ => controller.GetCurrentSessionTokenAsync(CancellationToken.None)).Unwrap() - .OnSuccess(t => Assert.AreEqual("randomString", t.Result)); + }).Unwrap().OnSuccess(_ => controller.GetCurrentSessionTokenAsync(Client, CancellationToken.None)).Unwrap().OnSuccess(task => Assert.AreEqual("randomString", task.Result)); } public Task TestLogOut() { - ParseCurrentUserController controller = new ParseCurrentUserController(new Mock(MockBehavior.Strict).Object); - ParseUser user = new ParseUser(); + ParseCurrentUserController controller = new ParseCurrentUserController(new Mock(MockBehavior.Strict).Object, Client.ClassController, Client.Decoder); + ParseUser user = new ParseUser { }.Bind(Client) as ParseUser; return controller.SetAsync(user, CancellationToken.None).OnSuccess(_ => { Assert.AreEqual(user, controller.CurrentUser); return controller.ExistsAsync(CancellationToken.None); - }).Unwrap() - .OnSuccess(t => + }).Unwrap().OnSuccess(task => { - Assert.IsTrue(t.Result); - - return controller.LogOutAsync(CancellationToken.None); - }).Unwrap().OnSuccess(_ => controller.GetAsync(CancellationToken.None)).Unwrap() - .OnSuccess(t => + Assert.IsTrue(task.Result); + return controller.LogOutAsync(Client, CancellationToken.None); + }).Unwrap().OnSuccess(_ => controller.GetAsync(Client, CancellationToken.None)).Unwrap().OnSuccess(task => { - Assert.IsNull(t.Result); - + Assert.IsNull(task.Result); return controller.ExistsAsync(CancellationToken.None); - }).Unwrap() - .OnSuccess(t => Assert.IsFalse(t.Result)); + }).Unwrap().OnSuccess(t => Assert.IsFalse(t.Result)); } } } diff --git a/Parse.Test/DecoderTests.cs b/Parse.Test/DecoderTests.cs index 8993cb72..9f5c11f3 100644 --- a/Parse.Test/DecoderTests.cs +++ b/Parse.Test/DecoderTests.cs @@ -2,16 +2,20 @@ using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using Parse.Core.Internal; +using Parse.Library; namespace Parse.Test { [TestClass] public class DecoderTests { + ParseClient Client { get; } = new ParseClient(new ServerConnectionData { Test = true }); + [TestMethod] public void TestParseDate() { - DateTime dateTime = (DateTime) ParseDataDecoder.Instance.Decode(ParseDataDecoder.ParseDate("1990-08-30T12:03:59.000Z")); + DateTime dateTime = (DateTime) Client.Decoder.Decode(ParseDataDecoder.ParseDate("1990-08-30T12:03:59.000Z"), Client); + Assert.AreEqual(1990, dateTime.Year); Assert.AreEqual(8, dateTime.Month); Assert.AreEqual(30, dateTime.Day); @@ -24,21 +28,22 @@ public void TestParseDate() [TestMethod] public void TestDecodePrimitives() { - Assert.AreEqual(1, ParseDataDecoder.Instance.Decode(1)); - Assert.AreEqual(0.3, ParseDataDecoder.Instance.Decode(0.3)); - Assert.AreEqual("halyosy", ParseDataDecoder.Instance.Decode("halyosy")); + Assert.AreEqual(1, Client.Decoder.Decode(1, Client)); + Assert.AreEqual(0.3, Client.Decoder.Decode(0.3, Client)); + Assert.AreEqual("halyosy", Client.Decoder.Decode("halyosy", Client)); - Assert.IsNull(ParseDataDecoder.Instance.Decode(null)); + Assert.IsNull(Client.Decoder.Decode(default, Client)); } [TestMethod] // Decoding ParseFieldOperation is not supported on .NET now. We only need this for LDS. - public void TestDecodeFieldOperation() => Assert.ThrowsException(() => ParseDataDecoder.Instance.Decode(new Dictionary() { { "__op", "Increment" }, { "amount", "322" } })); + public void TestDecodeFieldOperation() => Assert.ThrowsException(() => Client.Decoder.Decode(new Dictionary { { "__op", "Increment" }, { "amount", "322" } }, Client)); [TestMethod] public void TestDecodeDate() { - DateTime dateTime = (DateTime) ParseDataDecoder.Instance.Decode(new Dictionary() { { "__type", "Date" }, { "iso", "1990-08-30T12:03:59.000Z" } }); + DateTime dateTime = (DateTime) Client.Decoder.Decode(new Dictionary { { "__type", "Date" }, { "iso", "1990-08-30T12:03:59.000Z" } }, Client); + Assert.AreEqual(1990, dateTime.Year); Assert.AreEqual(8, dateTime.Month); Assert.AreEqual(30, dateTime.Day); @@ -55,7 +60,8 @@ public void TestDecodeImproperDate() for (int i = 0; i < 2; i++, value["iso"] = (value["iso"] as string).Substring(0, (value["iso"] as string).Length - 1) + "0Z") { - DateTime dateTime = (DateTime) ParseDataDecoder.Instance.Decode(value); + DateTime dateTime = (DateTime) Client.Decoder.Decode(value, Client); + Assert.AreEqual(1990, dateTime.Year); Assert.AreEqual(8, dateTime.Month); Assert.AreEqual(30, dateTime.Day); @@ -67,12 +73,13 @@ public void TestDecodeImproperDate() } [TestMethod] - public void TestDecodeBytes() => Assert.AreEqual("This is an encoded string", System.Text.Encoding.UTF8.GetString(ParseDataDecoder.Instance.Decode(new Dictionary() { { "__type", "Bytes" }, { "base64", "VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw==" } }) as byte[])); + public void TestDecodeBytes() => Assert.AreEqual("This is an encoded string", System.Text.Encoding.UTF8.GetString(Client.Decoder.Decode(new Dictionary { { "__type", "Bytes" }, { "base64", "VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw==" } }, Client) as byte[])); [TestMethod] public void TestDecodePointer() { - ParseObject obj = ParseDataDecoder.Instance.Decode(new Dictionary { ["__type"] = "Pointer", ["className"] = "Corgi", ["objectId"] = "lLaKcolnu" }) as ParseObject; + ParseObject obj = Client.Decoder.Decode(new Dictionary { ["__type"] = "Pointer", ["className"] = "Corgi", ["objectId"] = "lLaKcolnu" }, Client) as ParseObject; + Assert.IsFalse(obj.IsDataAvailable); Assert.AreEqual("Corgi", obj.ClassName); Assert.AreEqual("lLaKcolnu", obj.ObjectId); @@ -82,23 +89,25 @@ public void TestDecodePointer() public void TestDecodeFile() { - ParseFile file1 = ParseDataDecoder.Instance.Decode(new Dictionary { ["__type"] = "File", ["name"] = "Corgi.png", ["url"] = "http://corgi.xyz/gogo.png" }) as ParseFile; + ParseFile file1 = Client.Decoder.Decode(new Dictionary { ["__type"] = "File", ["name"] = "Corgi.png", ["url"] = "http://corgi.xyz/gogo.png" }, Client) as ParseFile; + Assert.AreEqual("Corgi.png", file1.Name); Assert.AreEqual("http://corgi.xyz/gogo.png", file1.Url.AbsoluteUri); Assert.IsFalse(file1.IsDirty); - Assert.ThrowsException(() => ParseDataDecoder.Instance.Decode(new Dictionary { ["__type"] = "File", ["name"] = "Corgi.png" })); + Assert.ThrowsException(() => Client.Decoder.Decode(new Dictionary { ["__type"] = "File", ["name"] = "Corgi.png" }, Client)); } [TestMethod] public void TestDecodeGeoPoint() { - ParseGeoPoint point1 = (ParseGeoPoint) ParseDataDecoder.Instance.Decode(new Dictionary { ["__type"] = "GeoPoint", ["latitude"] = 0.9, ["longitude"] = 0.3 }); + ParseGeoPoint point1 = (ParseGeoPoint) Client.Decoder.Decode(new Dictionary { ["__type"] = "GeoPoint", ["latitude"] = 0.9, ["longitude"] = 0.3 }, Client); + Assert.IsNotNull(point1); Assert.AreEqual(0.9, point1.Latitude); Assert.AreEqual(0.3, point1.Longitude); - Assert.ThrowsException(() => ParseDataDecoder.Instance.Decode(new Dictionary { ["__type"] = "GeoPoint", ["latitude"] = 0.9 })); + Assert.ThrowsException(() => Client.Decoder.Decode(new Dictionary { ["__type"] = "GeoPoint", ["latitude"] = 0.9 }, Client)); } [TestMethod] @@ -113,7 +122,8 @@ public void TestDecodeObject() ["updatedAt"] = "2015-06-22T22:06:41.733Z" }; - ParseObject obj = ParseDataDecoder.Instance.Decode(value) as ParseObject; + ParseObject obj = Client.Decoder.Decode(value, Client) as ParseObject; + Assert.IsTrue(obj.IsDataAvailable); Assert.AreEqual("Corgi", obj.ClassName); Assert.AreEqual("lLaKcolnu", obj.ObjectId); @@ -131,7 +141,8 @@ public void TestDecodeRelation() ["objectId"] = "lLaKcolnu" }; - ParseRelation relation = ParseDataDecoder.Instance.Decode(value) as ParseRelation; + ParseRelation relation = Client.Decoder.Decode(value, Client) as ParseRelation; + Assert.IsNotNull(relation); Assert.AreEqual("Corgi", relation.GetTargetClassName()); } @@ -160,7 +171,8 @@ public void TestDecodeDictionary() } }; - IDictionary dict = ParseDataDecoder.Instance.Decode(value) as IDictionary; + IDictionary dict = Client.Decoder.Decode(value, Client) as IDictionary; + Assert.AreEqual("luka", dict["megurine"]); Assert.IsTrue(dict["hatsune"] is ParseObject); Assert.IsTrue(dict["decodedGeoPoint"] is ParseGeoPoint); @@ -171,10 +183,11 @@ public void TestDecodeDictionary() IDictionary randomValue = new Dictionary() { ["ultimate"] = "elements", - [new ParseACL()] = "lLaKcolnu" + [new ParseACL { }] = "lLaKcolnu" }; - IDictionary randomDict = ParseDataDecoder.Instance.Decode(randomValue) as IDictionary; + IDictionary randomDict = Client.Decoder.Decode(randomValue, Client) as IDictionary; + Assert.AreEqual("elements", randomDict["ultimate"]); Assert.AreEqual(2, randomDict.Keys.Count); } @@ -182,18 +195,18 @@ [new ParseACL()] = "lLaKcolnu" [TestMethod] public void TestDecodeList() { - IList value = new List() + IList value = new List { - 1, new ParseACL(), "wiz", - new Dictionary() + 1, new ParseACL { }, "wiz", + new Dictionary { ["__type"] = "GeoPoint", ["latitude"] = 0.9, ["longitude"] = 0.3 }, - new List() + new List { - new Dictionary() + new Dictionary { ["__type"] = "GeoPoint", ["latitude"] = 0.9, @@ -202,7 +215,8 @@ public void TestDecodeList() } }; - IList list = ParseDataDecoder.Instance.Decode(value) as IList; + IList list = Client.Decoder.Decode(value, Client) as IList; + Assert.AreEqual(1, list[0]); Assert.IsTrue(list[1] is ParseACL); Assert.AreEqual("wiz", list[2]); @@ -215,9 +229,8 @@ public void TestDecodeList() [TestMethod] public void TestDecodeArray() { - int[] value = new int[] { 1, 2, 3, 4 }; + int[] value = new int[] { 1, 2, 3, 4 }, array = Client.Decoder.Decode(value, Client) as int[]; - int[] array = ParseDataDecoder.Instance.Decode(value) as int[]; Assert.AreEqual(4, array.Length); Assert.AreEqual(1, array[0]); Assert.AreEqual(2, array[1]); diff --git a/Parse.Test/EncoderTests.cs b/Parse.Test/EncoderTests.cs index b21f1726..f86fb406 100644 --- a/Parse.Test/EncoderTests.cs +++ b/Parse.Test/EncoderTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Parse.Core.Internal; +using Parse.Library; // TODO (hallucinogen): mock ParseACL, ParseObject, ParseUser once we have their Interfaces namespace Parse.Test @@ -11,13 +12,15 @@ namespace Parse.Test [TestClass] public class EncoderTests { + ParseClient Client { get; } = new ParseClient(new ServerConnectionData { Test = true }); + /// /// A that's used only for testing. This class is used to test /// 's base methods. /// - private class ParseEncoderTestClass : ParseDataEncoder + class ParseEncoderTestClass : ParseDataEncoder { - public static ParseEncoderTestClass Instance { get; } = new ParseEncoderTestClass(); + public static ParseEncoderTestClass Instance { get; } = new ParseEncoderTestClass { }; protected override IDictionary EncodeObject(ParseObject value) => null; } @@ -52,7 +55,9 @@ public void TestIsValidType() public void TestEncodeDate() { DateTime dateTime = new DateTime(1990, 8, 30, 12, 3, 59); - IDictionary value = ParseEncoderTestClass.Instance.Encode(dateTime) as IDictionary; + + IDictionary value = ParseEncoderTestClass.Instance.Encode(dateTime, Client) as IDictionary; + Assert.AreEqual("Date", value["__type"]); Assert.AreEqual("1990-08-30T12:03:59.000Z", value["iso"]); } @@ -61,7 +66,9 @@ public void TestEncodeDate() public void TestEncodeBytes() { byte[] bytes = new byte[] { 1, 2, 3, 4 }; - IDictionary value = ParseEncoderTestClass.Instance.Encode(bytes) as IDictionary; + + IDictionary value = ParseEncoderTestClass.Instance.Encode(bytes, Client) as IDictionary; + Assert.AreEqual("Bytes", value["__type"]); Assert.AreEqual(Convert.ToBase64String(new byte[] { 1, 2, 3, 4 }), value["base64"]); } @@ -70,7 +77,8 @@ public void TestEncodeBytes() public void TestEncodeParseObjectWithNoObjectsEncoder() { ParseObject obj = new ParseObject("Corgi"); - Assert.ThrowsException(() => NoObjectsEncoder.Instance.Encode(obj)); + + Assert.ThrowsException(() => NoObjectsEncoder.Instance.Encode(obj, Client)); } [TestMethod] @@ -83,20 +91,25 @@ public void TestEncodeParseObjectWithPointerOrLocalIdEncoder() public void TestEncodeParseFile() { ParseFile file1 = ParseFileExtensions.Create("Corgi.png", new Uri("http://corgi.xyz/gogo.png")); - IDictionary value = ParseEncoderTestClass.Instance.Encode(file1) as IDictionary; + + IDictionary value = ParseEncoderTestClass.Instance.Encode(file1, Client) as IDictionary; + Assert.AreEqual("File", value["__type"]); Assert.AreEqual("Corgi.png", value["name"]); Assert.AreEqual("http://corgi.xyz/gogo.png", value["url"]); ParseFile file2 = new ParseFile(null, new MemoryStream(new byte[] { 1, 2, 3, 4 })); - Assert.ThrowsException(() => ParseEncoderTestClass.Instance.Encode(file2)); + + Assert.ThrowsException(() => ParseEncoderTestClass.Instance.Encode(file2, Client)); } [TestMethod] public void TestEncodeParseGeoPoint() { ParseGeoPoint point = new ParseGeoPoint(3.22, 32.2); - IDictionary value = ParseEncoderTestClass.Instance.Encode(point) as IDictionary; + + IDictionary value = ParseEncoderTestClass.Instance.Encode(point, Client) as IDictionary; + Assert.AreEqual("GeoPoint", value["__type"]); Assert.AreEqual(3.22, value["latitude"]); Assert.AreEqual(32.2, value["longitude"]); @@ -106,7 +119,9 @@ public void TestEncodeParseGeoPoint() public void TestEncodeACL() { ParseACL acl1 = new ParseACL(); - IDictionary value1 = ParseEncoderTestClass.Instance.Encode(acl1) as IDictionary; + + IDictionary value1 = ParseEncoderTestClass.Instance.Encode(acl1, Client) as IDictionary; + Assert.IsNotNull(value1); Assert.AreEqual(0, value1.Keys.Count); @@ -115,7 +130,9 @@ public void TestEncodeACL() PublicReadAccess = true, PublicWriteAccess = true }; - IDictionary value2 = ParseEncoderTestClass.Instance.Encode(acl2) as IDictionary; + + IDictionary value2 = ParseEncoderTestClass.Instance.Encode(acl2, Client) as IDictionary; + Assert.AreEqual(1, value2.Keys.Count); IDictionary publicAccess = value2["*"] as IDictionary; Assert.AreEqual(2, publicAccess.Keys.Count); @@ -130,7 +147,9 @@ public void TestEncodeParseRelation() { ParseObject obj = new ParseObject("Corgi"); ParseRelation relation = ParseRelationExtensions.Create(obj, "nano", "Husky"); - IDictionary value = ParseEncoderTestClass.Instance.Encode(relation) as IDictionary; + + IDictionary value = ParseEncoderTestClass.Instance.Encode(relation, Client) as IDictionary; + Assert.AreEqual("Relation", value["__type"]); Assert.AreEqual("Husky", value["className"]); } @@ -139,10 +158,13 @@ public void TestEncodeParseRelation() public void TestEncodeParseFieldOperation() { ParseIncrementOperation incOps = new ParseIncrementOperation(1); - IDictionary value = ParseEncoderTestClass.Instance.Encode(incOps) as IDictionary; + + IDictionary value = ParseEncoderTestClass.Instance.Encode(incOps, Client) as IDictionary; + Assert.AreEqual("Increment", value["__op"]); Assert.AreEqual(1, value["amount"]); - // Other operations are tested in FieldOperationTests + + // Other operations are tested in FieldOperationTests. } [TestMethod] @@ -162,7 +184,8 @@ public void TestEncodeList() } }; - IList value = ParseEncoderTestClass.Instance.Encode(list) as IList; + IList value = ParseEncoderTestClass.Instance.Encode(list, Client) as IList; + IDictionary item0 = value[0] as IDictionary; Assert.AreEqual("GeoPoint", item0["__type"]); Assert.AreEqual(0.0, item0["latitude"]); @@ -190,22 +213,23 @@ public void TestEncodeDictionary() IDictionary dict = new Dictionary() { ["item"] = "random", - ["list"] = new List() { "vesperia", "abyss", "legendia" }, + ["list"] = new List { "vesperia", "abyss", "legendia" }, ["array"] = new int[] { 1, 2, 3 }, ["geo"] = new ParseGeoPoint(0, 0), ["validDict"] = new Dictionary { ["phantasia"] = "jbf" } }; - IDictionary value = ParseEncoderTestClass.Instance.Encode(dict) as IDictionary; + IDictionary value = ParseEncoderTestClass.Instance.Encode(dict, Client) as IDictionary; + Assert.AreEqual("random", value["item"]); Assert.IsTrue(value["list"] is IList); Assert.IsTrue(value["array"] is IList); Assert.IsTrue(value["geo"] is IDictionary); Assert.IsTrue(value["validDict"] is IDictionary); - Assert.ThrowsException(() => ParseEncoderTestClass.Instance.Encode(new Dictionary { })); + Assert.ThrowsException(() => ParseEncoderTestClass.Instance.Encode(new Dictionary { }, Client)); - Assert.ThrowsException(() => ParseEncoderTestClass.Instance.Encode(new Dictionary { ["validDict"] = new Dictionary { [new ParseACL()] = "jbf" } })); + Assert.ThrowsException(() => ParseEncoderTestClass.Instance.Encode(new Dictionary { ["validDict"] = new Dictionary { [new ParseACL()] = "jbf" } }, Client)); } } } diff --git a/Parse.Test/FileControllerTests.cs b/Parse.Test/FileControllerTests.cs index 9fdc3642..2b8480ce 100644 --- a/Parse.Test/FileControllerTests.cs +++ b/Parse.Test/FileControllerTests.cs @@ -12,11 +12,13 @@ namespace Parse.Test { +#warning Refactor this class. +#warning Skipped initialization step may be needed. + [TestClass] public class FileControllerTests { - [TestInitialize] - public void SetUp() => ParseClient.Initialize(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); + // public void SetUp() => Client = new ParseClient(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(FileControllerTests))] @@ -90,7 +92,7 @@ public Task TestFileControllerSave() private Mock CreateMockRunner(Tuple> response) { Mock mockRunner = new Mock(); - mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); + mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); return mockRunner; } diff --git a/Parse.Test/FileTests.cs b/Parse.Test/FileTests.cs index 503ae84b..a49af804 100644 --- a/Parse.Test/FileTests.cs +++ b/Parse.Test/FileTests.cs @@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Parse.Core.Internal; +using Parse.Library; using Parse.Management; namespace Parse.Test @@ -13,26 +14,25 @@ namespace Parse.Test [TestClass] public class FileTests { - [TestCleanup] - public void TearDown() => ParseCorePlugins.Instance = null; - [TestMethod] [AsyncStateMachine(typeof(FileTests))] public Task TestFileSave() { Mock mockController = new Mock(); - mockController.Setup(obj => obj.SaveAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new FileState { Name = "newBekti.png", Location = new Uri("https://www.parse.com/newBekti.png"), MediaType = "image/png" })); + mockController.Setup(obj => obj.SaveAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new FileState { Name = "newBekti.png", Location = new Uri("https://www.parse.com/newBekti.png"), MediaType = "image/png" })); Mock mockCurrentUserController = new Mock(); - ParseCorePlugins.Instance = new ParseCorePlugins { FileController = mockController.Object, CurrentUserController = mockCurrentUserController.Object }; - ParseFile file = new ParseFile("bekti.jpeg", new MemoryStream(), "image/jpeg"); + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, new MutableServiceHub { FileController = mockController.Object, CurrentUserController = mockCurrentUserController.Object }); + + ParseFile file = new ParseFile("bekti.jpeg", new MemoryStream { }, "image/jpeg"); + Assert.AreEqual("bekti.jpeg", file.Name); Assert.AreEqual("image/jpeg", file.MimeType); Assert.IsTrue(file.IsDirty); - return file.SaveAsync().ContinueWith(t => + return file.SaveAsync(client).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); + Assert.IsFalse(task.IsFaulted); Assert.AreEqual("newBekti.png", file.Name); Assert.AreEqual("image/png", file.MimeType); Assert.AreEqual("https://www.parse.com/newBekti.png", file.Url.AbsoluteUri); diff --git a/Parse.Test/GeoPointTests.cs b/Parse.Test/GeoPointTests.cs index fc3684ab..e43eaac0 100644 --- a/Parse.Test/GeoPointTests.cs +++ b/Parse.Test/GeoPointTests.cs @@ -5,12 +5,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Library; namespace Parse.Test { [TestClass] public class GeoPointTests { + ParseClient Client { get; } = new ParseClient(new ServerConnectionData { Test = true }); + [TestMethod] public void TestGeoPointCultureInvariantParsing() { @@ -19,8 +22,8 @@ public void TestGeoPointCultureInvariantParsing() { Thread.CurrentThread.CurrentCulture = c; ParseGeoPoint point = new ParseGeoPoint(1.234, 1.234); - string serialized = Json.Encode(new Dictionary { { nameof(point), NoObjectsEncoder.Instance.Encode(point) } }); - IDictionary deserialized = ParseDataDecoder.Instance.Decode(Json.Parse(serialized)) as IDictionary; + string serialized = Json.Encode(new Dictionary { { nameof(point), NoObjectsEncoder.Instance.Encode(point, Client) } }); + IDictionary deserialized = Client.Decoder.Decode(Json.Parse(serialized), Client) as IDictionary; ParseGeoPoint pointAgain = (ParseGeoPoint) deserialized[nameof(point)]; Assert.AreEqual(1.234, pointAgain.Latitude); Assert.AreEqual(1.234, pointAgain.Longitude); diff --git a/Parse.Test/InstallationIdControllerTests.cs b/Parse.Test/InstallationIdControllerTests.cs index 4b09a77c..ede7d2c3 100644 --- a/Parse.Test/InstallationIdControllerTests.cs +++ b/Parse.Test/InstallationIdControllerTests.cs @@ -5,15 +5,20 @@ using Moq; using Parse.Common.Internal; using Parse.Core.Internal; +using Parse.Library; using Parse.Management; namespace Parse.Test { +#warning Class refactoring may be required. + [TestClass] public class InstallationIdControllerTests { + ParseClient Client { get; } = new ParseClient(new ServerConnectionData { Test = true }); + [TestCleanup] - public void TearDown() => ParseCorePlugins.Instance = null; + public void TearDown() => (Client.Services as ServiceHub).Reset(); [TestMethod] public void TestConstructor() @@ -22,6 +27,7 @@ public void TestConstructor() ParseInstallationController controller = new ParseInstallationController(storageMock.Object); // Make sure it didn't touch storageMock. + storageMock.Verify(); } diff --git a/Parse.Test/InstallationTests.cs b/Parse.Test/InstallationTests.cs index 1b878f2f..b99d8399 100644 --- a/Parse.Test/InstallationTests.cs +++ b/Parse.Test/InstallationTests.cs @@ -4,7 +4,9 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Parse.Abstractions.Library; using Parse.Core.Internal; +using Parse.Library; using Parse.Management; using Parse.Push.Internal; @@ -13,25 +15,29 @@ namespace Parse.Test [TestClass] public class InstallationTests { + ParseClient Client { get; } = new ParseClient(new ServerConnectionData { Test = true }); + [TestInitialize] - public void SetUp() => ParseObject.RegisterDerivative(); + public void SetUp() => Client.AddValidClass(); [TestCleanup] - public void TearDown() => ParseCorePlugins.Instance = null; + public void TearDown() => (Client.Services as ServiceHub).Reset(); [TestMethod] - public void TestGetInstallationQuery() => Assert.IsInstanceOfType(ParseInstallation.Query, typeof(ParseQuery)); + public void TestGetInstallationQuery() => Assert.IsInstanceOfType(Client.GetInstallationQuery(), typeof(ParseQuery)); [TestMethod] public void TestInstallationIdGetterSetter() { Guid guid = Guid.NewGuid(); - ParseInstallation installation = ParseObjectExtensions.FromState(new MutableObjectState { ServerData = new Dictionary { ["installationId"] = guid.ToString() } }, "_Installation"); + ParseInstallation installation = Client.GenerateObjectFromState(new MutableObjectState { ServerData = new Dictionary { ["installationId"] = guid.ToString() } }, "_Installation"); + Assert.IsNotNull(installation); Assert.AreEqual(guid, installation.InstallationId); Guid newGuid = Guid.NewGuid(); Assert.ThrowsException(() => installation["installationId"] = newGuid); + installation.SetIfDifferent("installationId", newGuid.ToString()); Assert.AreEqual(newGuid, installation.InstallationId); } @@ -39,11 +45,13 @@ public void TestInstallationIdGetterSetter() [TestMethod] public void TestDeviceTypeGetterSetter() { - ParseInstallation installation = ParseObjectExtensions.FromState(new MutableObjectState { ServerData = new Dictionary { ["deviceType"] = "parseOS" } }, "_Installation"); + ParseInstallation installation = Client.GenerateObjectFromState(new MutableObjectState { ServerData = new Dictionary { ["deviceType"] = "parseOS" } }, "_Installation"); + Assert.IsNotNull(installation); Assert.AreEqual("parseOS", installation.DeviceType); Assert.ThrowsException(() => installation["deviceType"] = "gogoOS"); + installation.SetIfDifferent("deviceType", "gogoOS"); Assert.AreEqual("gogoOS", installation.DeviceType); } @@ -51,11 +59,13 @@ public void TestDeviceTypeGetterSetter() [TestMethod] public void TestAppNameGetterSetter() { - ParseInstallation installation = ParseObjectExtensions.FromState(new MutableObjectState { ServerData = new Dictionary { ["appName"] = "parseApp" } }, "_Installation"); + ParseInstallation installation = Client.GenerateObjectFromState(new MutableObjectState { ServerData = new Dictionary { ["appName"] = "parseApp" } }, "_Installation"); + Assert.IsNotNull(installation); Assert.AreEqual("parseApp", installation.AppName); Assert.ThrowsException(() => installation["appName"] = "gogoApp"); + installation.SetIfDifferent("appName", "gogoApp"); Assert.AreEqual("gogoApp", installation.AppName); } @@ -63,11 +73,13 @@ public void TestAppNameGetterSetter() [TestMethod] public void TestAppVersionGetterSetter() { - ParseInstallation installation = ParseObjectExtensions.FromState(new MutableObjectState { ServerData = new Dictionary { ["appVersion"] = "1.2.3" } }, "_Installation"); + ParseInstallation installation = Client.GenerateObjectFromState(new MutableObjectState { ServerData = new Dictionary { ["appVersion"] = "1.2.3" } }, "_Installation"); + Assert.IsNotNull(installation); Assert.AreEqual("1.2.3", installation.AppVersion); Assert.ThrowsException(() => installation["appVersion"] = "1.2.4"); + installation.SetIfDifferent("appVersion", "1.2.4"); Assert.AreEqual("1.2.4", installation.AppVersion); } @@ -75,11 +87,13 @@ public void TestAppVersionGetterSetter() [TestMethod] public void TestAppIdentifierGetterSetter() { - ParseInstallation installation = ParseObjectExtensions.FromState(new MutableObjectState { ServerData = new Dictionary { ["appIdentifier"] = "com.parse.app" } }, "_Installation"); + ParseInstallation installation = Client.GenerateObjectFromState(new MutableObjectState { ServerData = new Dictionary { ["appIdentifier"] = "com.parse.app" } }, "_Installation"); + Assert.IsNotNull(installation); Assert.AreEqual("com.parse.app", installation.AppIdentifier); Assert.ThrowsException(() => installation["appIdentifier"] = "com.parse.newapp"); + installation.SetIfDifferent("appIdentifier", "com.parse.newapp"); Assert.AreEqual("com.parse.newapp", installation.AppIdentifier); } @@ -87,7 +101,8 @@ public void TestAppIdentifierGetterSetter() [TestMethod] public void TestTimeZoneGetter() { - ParseInstallation installation = ParseObjectExtensions.FromState(new MutableObjectState { ServerData = new Dictionary { ["timeZone"] = "America/Los_Angeles" } }, "_Installation"); + ParseInstallation installation = Client.GenerateObjectFromState(new MutableObjectState { ServerData = new Dictionary { ["timeZone"] = "America/Los_Angeles" } }, "_Installation"); + Assert.IsNotNull(installation); Assert.AreEqual("America/Los_Angeles", installation.TimeZone); } @@ -95,7 +110,8 @@ public void TestTimeZoneGetter() [TestMethod] public void TestLocaleIdentifierGetter() { - ParseInstallation installation = ParseObjectExtensions.FromState(new MutableObjectState { ServerData = new Dictionary { ["localeIdentifier"] = "en-US" } }, "_Installation"); + ParseInstallation installation = Client.GenerateObjectFromState(new MutableObjectState { ServerData = new Dictionary { ["localeIdentifier"] = "en-US" } }, "_Installation"); + Assert.IsNotNull(installation); Assert.AreEqual("en-US", installation.LocaleIdentifier); } @@ -103,12 +119,14 @@ public void TestLocaleIdentifierGetter() [TestMethod] public void TestChannelGetterSetter() { - ParseInstallation installation = ParseObjectExtensions.FromState(new MutableObjectState { ServerData = new Dictionary { ["channels"] = new List { "the", "richard" } } }, "_Installation"); + ParseInstallation installation = Client.GenerateObjectFromState(new MutableObjectState { ServerData = new Dictionary { ["channels"] = new List { "the", "richard" } } }, "_Installation"); + Assert.IsNotNull(installation); Assert.AreEqual("the", installation.Channels[0]); Assert.AreEqual("richard", installation.Channels[1]); - installation.Channels = new List() { "mr", "kevin" }; + installation.Channels = new List { "mr", "kevin" }; + Assert.AreEqual("mr", installation.Channels[0]); Assert.AreEqual("kevin", installation.Channels[1]); } @@ -116,14 +134,20 @@ public void TestChannelGetterSetter() [TestMethod] public void TestGetCurrentInstallation() { + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + Guid guid = Guid.NewGuid(); - ParseInstallation installation = ParseObjectExtensions.FromState(new MutableObjectState { ServerData = new Dictionary { ["installationId"] = guid.ToString() } }, "_Installation"); + + ParseInstallation installation = client.GenerateObjectFromState(new MutableObjectState { ServerData = new Dictionary { ["installationId"] = guid.ToString() } }, "_Installation"); + Mock mockController = new Mock(); - mockController.Setup(obj => obj.GetAsync(It.IsAny())).Returns(Task.FromResult(installation)); + mockController.Setup(obj => obj.GetAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(installation)); + + hub.CurrentInstallationController = mockController.Object; - ParsePushPlugins.Instance = new ParsePushPlugins { CurrentInstallationController = mockController.Object }; + ParseInstallation currentInstallation = client.GetCurrentInstallation(); - ParseInstallation currentInstallation = ParseInstallation.CurrentInstallation; Assert.IsNotNull(currentInstallation); Assert.AreEqual(guid, currentInstallation.InstallationId); } diff --git a/Parse.Test/ObjectCoderTests.cs b/Parse.Test/ObjectCoderTests.cs index 5b1a3f2e..a0849618 100644 --- a/Parse.Test/ObjectCoderTests.cs +++ b/Parse.Test/ObjectCoderTests.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using Parse.Core.Internal; +using Parse.Library; namespace Parse.Test { @@ -21,9 +22,10 @@ public void TestACLCoding() }, ["*"] = new Dictionary { ["read"] = true } } - }, null); + }, default, new ServiceHub { }); + + ParseACL resultACL = default; - ParseACL resultACL = null; Assert.IsTrue(state.ContainsKey("ACL")); Assert.IsTrue((resultACL = state.ServerData["ACL"] as ParseACL) is ParseACL); Assert.IsTrue(resultACL.PublicReadAccess); diff --git a/Parse.Test/ObjectControllerTests.cs b/Parse.Test/ObjectControllerTests.cs index e12a7de0..1a79a472 100644 --- a/Parse.Test/ObjectControllerTests.cs +++ b/Parse.Test/ObjectControllerTests.cs @@ -12,11 +12,15 @@ namespace Parse.Test { +#warning Finish refactoring. + [TestClass] public class ObjectControllerTests { + ParseClient Client { get; set; } + [TestInitialize] - public void SetUp() => ParseClient.Initialize(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); + public void SetUp() => Client = new ParseClient(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(ObjectControllerTests))] @@ -24,14 +28,14 @@ public Task TestFetch() { Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary { ["__type"] = "Object", ["className"] = "Corgi", ["objectId"] = "st4nl3yW", ["doge"] = "isShibaInu", ["createdAt"] = "2015-09-18T18:11:28.943Z" })); - return new ParseObjectController(mockRunner.Object).FetchAsync(new MutableObjectState { ClassName = "Corgi", ObjectId = "st4nl3yW", ServerData = new Dictionary { ["corgi"] = "isNotDoge" } }, null, CancellationToken.None).ContinueWith(t => + return new ParseObjectController(mockRunner.Object, Client.Decoder).FetchAsync(new MutableObjectState { ClassName = "Corgi", ObjectId = "st4nl3yW", ServerData = new Dictionary { ["corgi"] = "isNotDoge" } }, default, Client, CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); - IObjectState newState = t.Result; + IObjectState newState = task.Result; Assert.AreEqual("isShibaInu", newState["doge"]); Assert.IsFalse(newState.ContainsKey("corgi")); Assert.IsNotNull(newState.CreatedAt); @@ -45,14 +49,14 @@ public Task TestSave() { Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary { ["__type"] = "Object", ["className"] = "Corgi", ["objectId"] = "st4nl3yW", ["doge"] = "isShibaInu", ["createdAt"] = "2015-09-18T18:11:28.943Z" })); - return new ParseObjectController(mockRunner.Object).SaveAsync(new MutableObjectState { ClassName = "Corgi", ObjectId = "st4nl3yW", ServerData = new Dictionary { ["corgi"] = "isNotDoge" } }, new Dictionary { ["gogo"] = new Mock { }.Object }, null, CancellationToken.None).ContinueWith(t => + return new ParseObjectController(mockRunner.Object, Client.Decoder).SaveAsync(new MutableObjectState { ClassName = "Corgi", ObjectId = "st4nl3yW", ServerData = new Dictionary { ["corgi"] = "isNotDoge" } }, new Dictionary { ["gogo"] = new Mock { }.Object }, default, Client, CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); - IObjectState newState = t.Result; + IObjectState newState = task.Result; Assert.AreEqual("isShibaInu", newState["doge"]); Assert.IsFalse(newState.ContainsKey("corgi")); Assert.IsFalse(newState.ContainsKey("gogo")); @@ -83,15 +87,15 @@ public Task TestSaveNewObject() Tuple> response = new Tuple>(HttpStatusCode.Created, responseDict); Mock mockRunner = CreateMockRunner(response); - ParseObjectController controller = new ParseObjectController(mockRunner.Object); - return controller.SaveAsync(state, operations, null, CancellationToken.None).ContinueWith(t => + ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder); + return controller.SaveAsync(state, operations, default, Client, CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); - IObjectState newState = t.Result; + IObjectState newState = task.Result; Assert.AreEqual("isShibaInu", newState["doge"]); Assert.IsFalse(newState.ContainsKey("corgi")); Assert.IsFalse(newState.ContainsKey("gogo")); @@ -107,6 +111,7 @@ public Task TestSaveNewObject() public Task TestSaveAll() { List states = new List(); + for (int i = 0; i < 30; ++i) { states.Add(new MutableObjectState @@ -116,11 +121,16 @@ public Task TestSaveAll() ServerData = new Dictionary { ["corgi"] = "isNotDoge" } }); } + List> operationsList = new List>(); + for (int i = 0; i < 30; ++i) + { operationsList.Add(new Dictionary { ["gogo"] = new Mock { }.Object }); + } List> results = new List>(); + for (int i = 0; i < 30; ++i) { results.Add(new Dictionary @@ -135,13 +145,14 @@ public Task TestSaveAll() } }); } + Dictionary responseDict = new Dictionary { [nameof(results)] = results }; Tuple> response = new Tuple>(HttpStatusCode.OK, responseDict); Mock mockRunner = CreateMockRunner(response); - ParseObjectController controller = new ParseObjectController(mockRunner.Object); - IList> tasks = controller.SaveAllAsync(states, operationsList, null, CancellationToken.None); + ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder); + IList> tasks = controller.SaveAllAsync(states, operationsList, default, Client, CancellationToken.None); return Task.WhenAll(tasks).ContinueWith(_ => { @@ -158,7 +169,7 @@ public Task TestSaveAll() Assert.IsNotNull(serverState.UpdatedAt); } - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -222,10 +233,10 @@ public Task TestSaveAllManyObjects() Tuple> response2 = new Tuple>(HttpStatusCode.OK, responseDict2); Mock mockRunner = new Mock { }; - mockRunner.SetupSequence(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)).Returns(Task.FromResult(response)).Returns(Task.FromResult(response2)); + mockRunner.SetupSequence(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)).Returns(Task.FromResult(response)).Returns(Task.FromResult(response2)); - ParseObjectController controller = new ParseObjectController(mockRunner.Object); - IList> tasks = controller.SaveAllAsync(states, operationsList, null, CancellationToken.None); + ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder); + IList> tasks = controller.SaveAllAsync(states, operationsList, default, Client, CancellationToken.None); return Task.WhenAll(tasks).ContinueWith(_ => { @@ -242,7 +253,7 @@ public Task TestSaveAllManyObjects() Assert.IsNotNull(serverState.UpdatedAt); } - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(3)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(3)); }); } @@ -257,19 +268,14 @@ public Task TestDelete() ServerData = new Dictionary { ["corgi"] = "isNotDoge" } }; - Tuple> response = new Tuple>(HttpStatusCode.OK, new Dictionary()); - Mock mockRunner = CreateMockRunner(response); + Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.OK, new Dictionary { })); - ParseObjectController controller = new ParseObjectController(mockRunner.Object); - return controller.DeleteAsync(state, null, CancellationToken.None).ContinueWith(t => + return new ParseObjectController(mockRunner.Object, Client.Decoder).DeleteAsync(state, default, CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), - It.IsAny>(), - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -291,21 +297,23 @@ public Task TestDeleteAll() List> results = new List>(); for (int i = 0; i < 30; ++i) + { results.Add(new Dictionary { ["success"] = null }); + } Dictionary responseDict = new Dictionary { [nameof(results)] = results }; Tuple> response = new Tuple>(HttpStatusCode.OK, responseDict); Mock mockRunner = CreateMockRunner(response); - ParseObjectController controller = new ParseObjectController(mockRunner.Object); - IList tasks = controller.DeleteAllAsync(states, null, CancellationToken.None); + ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder); + IList tasks = controller.DeleteAllAsync(states, default, CancellationToken.None); return Task.WhenAll(tasks).ContinueWith(_ => { Assert.IsTrue(tasks.All(task => task.IsCompleted && !task.IsCanceled && !task.IsFaulted)); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -324,11 +332,14 @@ public Task TestDeleteAllManyObjects() }); } - // Make multiple response since the batch will be splitted. + // Make multiple response since the batch will be split. + List> results = new List>(); for (int i = 0; i < 50; ++i) - results.Add(new Dictionary { ["success"] = null }); + { + results.Add(new Dictionary { ["success"] = default }); + } Dictionary responseDict = new Dictionary { [nameof(results)] = results }; Tuple> response = new Tuple>(HttpStatusCode.OK, responseDict); @@ -336,22 +347,24 @@ public Task TestDeleteAllManyObjects() List> results2 = new List>(); for (int i = 0; i < 2; ++i) - results2.Add(new Dictionary { ["success"] = null }); + { + results2.Add(new Dictionary { ["success"] = default }); + } Dictionary responseDict2 = new Dictionary { [nameof(results)] = results2 }; Tuple> response2 = new Tuple>(HttpStatusCode.OK, responseDict2); Mock mockRunner = new Mock(); - mockRunner.SetupSequence(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)).Returns(Task.FromResult(response)).Returns(Task.FromResult(response2)); + mockRunner.SetupSequence(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)).Returns(Task.FromResult(response)).Returns(Task.FromResult(response2)); - ParseObjectController controller = new ParseObjectController(mockRunner.Object); + ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder); IList tasks = controller.DeleteAllAsync(states, null, CancellationToken.None); return Task.WhenAll(tasks).ContinueWith(_ => { Assert.IsTrue(tasks.All(task => task.IsCompleted && !task.IsCanceled && !task.IsFaulted)); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(3)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(3)); }); } @@ -359,7 +372,8 @@ public Task TestDeleteAllManyObjects() [AsyncStateMachine(typeof(ObjectControllerTests))] public Task TestDeleteAllFailSome() { - List states = new List(); + List states = new List { }; + for (int i = 0; i < 30; ++i) { states.Add(new MutableObjectState @@ -370,7 +384,7 @@ public Task TestDeleteAllFailSome() }); } - List> results = new List>(); + List> results = new List> { }; for (int i = 0; i < 15; ++i) { @@ -386,15 +400,15 @@ public Task TestDeleteAllFailSome() }); } else - results.Add(new Dictionary { ["success"] = null }); + { + results.Add(new Dictionary { ["success"] = default }); + } } - Dictionary responseDict = new Dictionary { [nameof(results)] = results }; - Tuple> response = new Tuple>(HttpStatusCode.OK, responseDict); - Mock mockRunner = CreateMockRunner(response); + Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.OK, new Dictionary { [nameof(results)] = results })); - ParseObjectController controller = new ParseObjectController(mockRunner.Object); + ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder); IList tasks = controller.DeleteAllAsync(states, null, CancellationToken.None); return Task.WhenAll(tasks).ContinueWith(_ => @@ -415,10 +429,7 @@ public Task TestDeleteAllFailSome() } } - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), - It.IsAny>(), - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -426,34 +437,31 @@ public Task TestDeleteAllFailSome() [AsyncStateMachine(typeof(ObjectControllerTests))] public Task TestDeleteAllInconsistent() { - List states = new List(); + List states = new List { }; + for (int i = 0; i < 30; ++i) { states.Add(new MutableObjectState { ClassName = "Corgi", ObjectId = "st4nl3yW" + i, - ServerData = new Dictionary() { - { "corgi", "isNotDoge" }, - } + ServerData = new Dictionary + { + ["corgi"] = "isNotDoge" + } }); } - List> results = new List>(); + List> results = new List> { }; + for (int i = 0; i < 36; ++i) { - results.Add(new Dictionary() { - { "success", null } - }); + results.Add(new Dictionary { ["success"] = default }); } - Dictionary responseDict = new Dictionary() { - { nameof(results), results } - }; - Tuple> response = new Tuple>(HttpStatusCode.OK, responseDict); - Mock mockRunner = CreateMockRunner(response); + Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.OK, new Dictionary { [nameof(results)] = results })); - ParseObjectController controller = new ParseObjectController(mockRunner.Object); + ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder); IList tasks = controller.DeleteAllAsync(states, null, CancellationToken.None); return Task.WhenAll(tasks).ContinueWith(_ => @@ -461,21 +469,14 @@ public Task TestDeleteAllInconsistent() Assert.IsTrue(tasks.All(task => task.IsFaulted)); Assert.IsInstanceOfType(tasks[0].Exception.InnerException, typeof(InvalidOperationException)); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), - It.IsAny>(), - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } private Mock CreateMockRunner(Tuple> response) { Mock mockRunner = new Mock(); - mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), - It.IsAny>(), - It.IsAny>(), - It.IsAny())) - .Returns(Task.FromResult(response)); + mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); return mockRunner; } diff --git a/Parse.Test/ObjectTests.cs b/Parse.Test/ObjectTests.cs index 3c890792..14f14afc 100644 --- a/Parse.Test/ObjectTests.cs +++ b/Parse.Test/ObjectTests.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Parse.Core.Internal; +using Parse.Library; using Parse.Management; namespace Parse.Test @@ -13,13 +14,15 @@ namespace Parse.Test public class ObjectTests { [ParseClassName(nameof(SubClass))] - private class SubClass : ParseObject { } + class SubClass : ParseObject { } [ParseClassName(nameof(UnregisteredSubClass))] - private class UnregisteredSubClass : ParseObject { } + class UnregisteredSubClass : ParseObject { } + + ParseClient Client { get; } = new ParseClient(new ServerConnectionData { Test = true }); [TestCleanup] - public void TearDown() => ParseCorePlugins.Instance.Reset(); + public void TearDown() => (Client.Services as ServiceHub).Reset(); [TestMethod] public void TestParseObjectConstructor() @@ -34,13 +37,13 @@ public void TestParseObjectConstructor() [TestMethod] public void TestParseObjectCreate() { - ParseObject obj = ParseObject.Create("Corgi"); + ParseObject obj = Client.CreateObject("Corgi"); Assert.AreEqual("Corgi", obj.ClassName); Assert.IsNull(obj.CreatedAt); Assert.IsTrue(obj.IsDataAvailable); Assert.IsTrue(obj.IsDirty); - ParseObject obj2 = ParseObject.CreateWithoutData("Corgi", "waGiManPutr4Pet1r"); + ParseObject obj2 = Client.CreateObjectWithoutData("Corgi", "waGiManPutr4Pet1r"); Assert.AreEqual("Corgi", obj2.ClassName); Assert.AreEqual("waGiManPutr4Pet1r", obj2.ObjectId); Assert.IsNull(obj2.CreatedAt); @@ -51,15 +54,15 @@ public void TestParseObjectCreate() [TestMethod] public void TestParseObjectCreateWithGeneric() { - ParseObject.RegisterDerivative(); + Client.AddValidClass(); - ParseObject obj = ParseObject.Create(); + ParseObject obj = Client.CreateObject(); Assert.AreEqual(nameof(SubClass), obj.ClassName); Assert.IsNull(obj.CreatedAt); Assert.IsTrue(obj.IsDataAvailable); Assert.IsTrue(obj.IsDirty); - ParseObject obj2 = ParseObject.CreateWithoutData("waGiManPutr4Pet1r"); + ParseObject obj2 = Client.CreateObjectWithoutData("waGiManPutr4Pet1r"); Assert.AreEqual(nameof(SubClass), obj2.ClassName); Assert.AreEqual("waGiManPutr4Pet1r", obj2.ObjectId); Assert.IsNull(obj2.CreatedAt); @@ -68,7 +71,7 @@ public void TestParseObjectCreateWithGeneric() } [TestMethod] - public void TestParseObjectCreateWithGenericFailWithoutSubclass() => Assert.ThrowsException(() => ParseObject.Create()); + public void TestParseObjectCreateWithGenericFailWithoutSubclass() => Assert.ThrowsException(() => Client.CreateObject()); [TestMethod] public void TestFromState() @@ -77,13 +80,15 @@ public void TestFromState() { ObjectId = "waGiManPutr4Pet1r", ClassName = "Pagi", - CreatedAt = new DateTime(), - ServerData = new Dictionary() { - { "username", "kevin" }, - { "sessionToken", "se551onT0k3n" } - } + CreatedAt = new DateTime { }, + ServerData = new Dictionary + { + ["username"] = "kevin", + ["sessionToken"] = "se551onT0k3n" + } }; - ParseObject obj = ParseObjectExtensions.FromState(state, "Omitted"); + + ParseObject obj = Client.GenerateObjectFromState(state, "Omitted"); Assert.AreEqual("waGiManPutr4Pet1r", obj.ObjectId); Assert.AreEqual("Pagi", obj.ClassName); @@ -96,65 +101,70 @@ public void TestFromState() [TestMethod] public void TestRegisterSubclass() { - Assert.ThrowsException(() => ParseObject.Create()); + Assert.ThrowsException(() => Client.CreateObject()); try { - ParseObject.RegisterDerivative(); - ParseObject.Create(); + Client.AddValidClass(); + Client.CreateObject(); - ParseCorePlugins.Instance.SubclassingController.RemoveClass(typeof(UnregisteredSubClass)); - ParseObject.Create(); + Client.ClassController.RemoveClass(typeof(UnregisteredSubClass)); + Client.CreateObject(); } catch { Assert.Fail(); } - ParseCorePlugins.Instance.SubclassingController.RemoveClass(typeof(SubClass)); - Assert.ThrowsException(() => ParseObject.Create()); + Client.ClassController.RemoveClass(typeof(SubClass)); + Assert.ThrowsException(() => Client.CreateObject()); } [TestMethod] public void TestRevert() { - ParseObject obj = ParseObject.Create("Corgi"); + ParseObject obj = Client.CreateObject("Corgi"); obj["gogo"] = true; Assert.IsTrue(obj.IsDirty); - Assert.AreEqual(1, obj.GetCurrentOperations().Count); + Assert.AreEqual(1, obj.CurrentOperations.Count); Assert.IsTrue(obj.ContainsKey("gogo")); obj.Revert(); Assert.IsTrue(obj.IsDirty); - Assert.AreEqual(0, obj.GetCurrentOperations().Count); + Assert.AreEqual(0, obj.CurrentOperations.Count); Assert.IsFalse(obj.ContainsKey("gogo")); } [TestMethod] public void TestDeepTraversal() { - ParseObject obj = ParseObject.Create("Corgi"); - IDictionary someDict = new Dictionary() { - { "someList", new List() } - }; - obj[nameof(obj)] = ParseObject.Create("Pug"); - obj["obj2"] = ParseObject.Create("Pug"); + ParseObject obj = Client.CreateObject("Corgi"); + + IDictionary someDict = new Dictionary + { + ["someList"] = new List { } + }; + + obj[nameof(obj)] = Client.CreateObject("Pug"); + obj["obj2"] = Client.CreateObject("Pug"); obj["list"] = new List(); obj["dict"] = someDict; obj["someBool"] = true; obj["someInt"] = 23; - IEnumerable traverseResult = ParseObjectExtensions.DeepTraversal(obj, true, true); + IEnumerable traverseResult = Client.TraverseObjectDeep(obj, true, true); Assert.AreEqual(8, traverseResult.Count()); - // Don't traverse beyond the root (since root is ParseObject) - traverseResult = ParseObjectExtensions.DeepTraversal(obj, false, true); + // Don't traverse beyond the root (since root is ParseObject). + + traverseResult = Client.TraverseObjectDeep(obj, false, true); Assert.AreEqual(1, traverseResult.Count()); - traverseResult = ParseObjectExtensions.DeepTraversal(someDict, false, true); + traverseResult = Client.TraverseObjectDeep(someDict, false, true); Assert.AreEqual(2, traverseResult.Count()); - // Should ignore root - traverseResult = ParseObjectExtensions.DeepTraversal(obj, true, false); + // Should ignore root. + + traverseResult = Client.TraverseObjectDeep(obj, true, false); Assert.AreEqual(7, traverseResult.Count()); } @@ -162,7 +172,7 @@ public void TestDeepTraversal() [TestMethod] public void TestRemove() { - ParseObject obj = ParseObject.Create("Corgi"); + ParseObject obj = Client.CreateObject("Corgi"); obj["gogo"] = true; Assert.IsTrue(obj.ContainsKey("gogo")); @@ -173,14 +183,15 @@ public void TestRemove() { ObjectId = "waGiManPutr4Pet1r", ClassName = "Pagi", - CreatedAt = new DateTime(), - ServerData = new Dictionary() { - { "username", "kevin" }, - { "sessionToken", "se551onT0k3n" } - } + CreatedAt = new DateTime { }, + ServerData = new Dictionary + { + ["username"] = "kevin", + ["sessionToken"] = "se551onT0k3n" + } }; - obj = ParseObjectExtensions.FromState(state, "Corgi"); + obj = Client.GenerateObjectFromState(state, "Corgi"); Assert.IsTrue(obj.ContainsKey("username")); Assert.IsTrue(obj.ContainsKey("sessionToken")); @@ -192,7 +203,7 @@ public void TestRemove() [TestMethod] public void TestIndexGetterSetter() { - ParseObject obj = ParseObject.Create("Corgi"); + ParseObject obj = Client.CreateObject("Corgi"); obj["gogo"] = true; obj["list"] = new List(); obj["dict"] = new Dictionary(); @@ -220,18 +231,21 @@ public void TestIndexGetterSetter() [TestMethod] public void TestPropertiesGetterSetter() { - DateTime now = new DateTime(); + DateTime now = new DateTime { }; + IObjectState state = new MutableObjectState { ObjectId = "waGiManPutr4Pet1r", ClassName = "Pagi", CreatedAt = now, - ServerData = new Dictionary() { - { "username", "kevin" }, - { "sessionToken", "se551onT0k3n" } - } + ServerData = new Dictionary + { + ["username"] = "kevin", + ["sessionToken"] = "se551onT0k3n" + } }; - ParseObject obj = ParseObjectExtensions.FromState(state, "Omitted"); + + ParseObject obj = Client.GenerateObjectFromState(state, "Omitted"); Assert.AreEqual("Pagi", obj.ClassName); Assert.AreEqual(now, obj.CreatedAt); @@ -306,13 +320,14 @@ public void TestTryGetValue() { ObjectId = "waGiManPutr4Pet1r", ClassName = "Pagi", - CreatedAt = new DateTime(), - ServerData = new Dictionary() { - { "username", "kevin" }, - { "sessionToken", "se551onT0k3n" } - } + CreatedAt = new DateTime { }, + ServerData = new Dictionary() + { + ["username"] = "kevin", + ["sessionToken"] = "se551onT0k3n" + } }; - ParseObject obj = ParseObjectExtensions.FromState(state, "Omitted"); + ParseObject obj = Client.GenerateObjectFromState(state, "Omitted"); obj.TryGetValue("username", out string res); Assert.AreEqual("kevin", res); @@ -330,13 +345,14 @@ public void TestGet() { ObjectId = "waGiManPutr4Pet1r", ClassName = "Pagi", - CreatedAt = new DateTime(), - ServerData = new Dictionary() { - { "username", "kevin" }, - { "sessionToken", "se551onT0k3n" } - } + CreatedAt = new DateTime { }, + ServerData = new Dictionary() + { + ["username"] = "kevin", + ["sessionToken"] = "se551onT0k3n" + } }; - ParseObject obj = ParseObjectExtensions.FromState(state, "Omitted"); + ParseObject obj = Client.GenerateObjectFromState(state, "Omitted"); Assert.AreEqual("kevin", obj.Get("username")); Assert.ThrowsException(() => obj.Get("username")); Assert.ThrowsException(() => obj.Get("missingItem")); @@ -361,13 +377,14 @@ public void TestKeys() { ObjectId = "waGiManPutr4Pet1r", ClassName = "Pagi", - CreatedAt = new DateTime(), - ServerData = new Dictionary() { - { "username", "kevin" }, - { "sessionToken", "se551onT0k3n" } - } + CreatedAt = new DateTime { }, + ServerData = new Dictionary() + { + ["username"] = "kevin", + ["sessionToken"] = "se551onT0k3n" + } }; - ParseObject obj = ParseObjectExtensions.FromState(state, "Omitted"); + ParseObject obj = Client.GenerateObjectFromState(state, "Omitted"); Assert.AreEqual(2, obj.Keys.Count); obj["additional"] = true; @@ -384,13 +401,14 @@ public void TestAdd() { ObjectId = "waGiManPutr4Pet1r", ClassName = "Pagi", - CreatedAt = new DateTime(), - ServerData = new Dictionary() { - { "username", "kevin" }, - { "sessionToken", "se551onT0k3n" } - } + CreatedAt = new DateTime { }, + ServerData = new Dictionary() + { + ["username"] = "kevin", + ["sessionToken"] = "se551onT0k3n" + } }; - ParseObject obj = ParseObjectExtensions.FromState(state, "Omitted"); + ParseObject obj = Client.GenerateObjectFromState(state, "Omitted"); Assert.ThrowsException(() => obj.Add("username", "kevin")); obj.Add("zeus", "bewithyou"); @@ -404,43 +422,50 @@ public void TestEnumerator() { ObjectId = "waGiManPutr4Pet1r", ClassName = "Pagi", - CreatedAt = new DateTime(), - ServerData = new Dictionary() { - { "username", "kevin" }, - { "sessionToken", "se551onT0k3n" } - } + CreatedAt = new DateTime { }, + ServerData = new Dictionary() + { + ["username"] = "kevin", + ["sessionToken"] = "se551onT0k3n" + } }; - ParseObject obj = ParseObjectExtensions.FromState(state, "Omitted"); + ParseObject obj = Client.GenerateObjectFromState(state, "Omitted"); int count = 0; + foreach (KeyValuePair key in obj) { count++; } + Assert.AreEqual(2, count); obj["newDirtyItem"] = "newItem"; count = 0; + foreach (KeyValuePair key in obj) { count++; } + Assert.AreEqual(3, count); } [TestMethod] public void TestGetQuery() { - ParseObject.RegisterDerivative(); + Client.AddValidClass(); - ParseQuery query = ParseObject.GetQuery(nameof(UnregisteredSubClass)); + ParseQuery query = Client.GetQuery(nameof(UnregisteredSubClass)); Assert.AreEqual(nameof(UnregisteredSubClass), query.GetClassName()); - Assert.ThrowsException(() => ParseObject.GetQuery(nameof(SubClass))); + Assert.ThrowsException(() => Client.GetQuery(nameof(SubClass))); - ParseCorePlugins.Instance.SubclassingController.RemoveClass(typeof(SubClass)); + Client.ClassController.RemoveClass(typeof(SubClass)); } +#warning These tests are incomplete. + [TestMethod] public void TestPropertyChanged() { diff --git a/Parse.Test/Parse.Test.csproj b/Parse.Test/Parse.Test.csproj index b319c947..5ebd6543 100644 --- a/Parse.Test/Parse.Test.csproj +++ b/Parse.Test/Parse.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + netcoreapp3.1 false latest diff --git a/Parse.Test/ProgressTests.cs b/Parse.Test/ProgressTests.cs index cbaf8cc2..0eecff4d 100644 --- a/Parse.Test/ProgressTests.cs +++ b/Parse.Test/ProgressTests.cs @@ -4,13 +4,15 @@ namespace Parse.Test { +#warning Refactor if possible. + [TestClass] public class ProgressTests { [TestMethod] public void TestDownloadProgressEventGetterSetter() { - DataRecievalPresenter downloadProgressEvent = new DataRecievalPresenter { Amount = 0.5f }; + IDataTransferLevel downloadProgressEvent = new DataTransferLevel { Amount = 0.5f }; Assert.AreEqual(0.5f, downloadProgressEvent.Amount); downloadProgressEvent.Amount = 1.0f; @@ -20,7 +22,7 @@ public void TestDownloadProgressEventGetterSetter() [TestMethod] public void TestUploadProgressEventGetterSetter() { - DataRecievalPresenter uploadProgressEvent = new DataRecievalPresenter { Amount = 0.5f }; + IDataTransferLevel uploadProgressEvent = new DataTransferLevel { Amount = 0.5f }; Assert.AreEqual(0.5f, uploadProgressEvent.Amount); uploadProgressEvent.Amount = 1.0f; @@ -31,15 +33,15 @@ public void TestUploadProgressEventGetterSetter() public void TestObservingDownloadProgress() { int called = 0; - Mock> mockProgress = new Mock>(); - mockProgress.Setup(obj => obj.Report(It.IsAny())).Callback(() => called++); - IProgress progress = mockProgress.Object; + Mock> mockProgress = new Mock>(); + mockProgress.Setup(obj => obj.Report(It.IsAny())).Callback(() => called++); + IProgress progress = mockProgress.Object; - progress.Report(new DataRecievalPresenter { Amount = 0.2f }); - progress.Report(new DataRecievalPresenter { Amount = 0.42f }); - progress.Report(new DataRecievalPresenter { Amount = 0.53f }); - progress.Report(new DataRecievalPresenter { Amount = 0.68f }); - progress.Report(new DataRecievalPresenter { Amount = 0.88f }); + progress.Report(new DataTransferLevel { Amount = 0.2f }); + progress.Report(new DataTransferLevel { Amount = 0.42f }); + progress.Report(new DataTransferLevel { Amount = 0.53f }); + progress.Report(new DataTransferLevel { Amount = 0.68f }); + progress.Report(new DataTransferLevel { Amount = 0.88f }); Assert.AreEqual(5, called); } @@ -48,15 +50,15 @@ public void TestObservingDownloadProgress() public void TestObservingUploadProgress() { int called = 0; - Mock> mockProgress = new Mock>(); - mockProgress.Setup(obj => obj.Report(It.IsAny())).Callback(() => called++); - IProgress progress = mockProgress.Object; + Mock> mockProgress = new Mock>(); + mockProgress.Setup(obj => obj.Report(It.IsAny())).Callback(() => called++); + IProgress progress = mockProgress.Object; - progress.Report(new DataTransmissionAdvancementLevel { Amount = 0.2f }); - progress.Report(new DataTransmissionAdvancementLevel { Amount = 0.42f }); - progress.Report(new DataTransmissionAdvancementLevel { Amount = 0.53f }); - progress.Report(new DataTransmissionAdvancementLevel { Amount = 0.68f }); - progress.Report(new DataTransmissionAdvancementLevel { Amount = 0.88f }); + progress.Report(new DataTransferLevel { Amount = 0.2f }); + progress.Report(new DataTransferLevel { Amount = 0.42f }); + progress.Report(new DataTransferLevel { Amount = 0.53f }); + progress.Report(new DataTransferLevel { Amount = 0.68f }); + progress.Report(new DataTransferLevel { Amount = 0.88f }); Assert.AreEqual(5, called); } diff --git a/Parse.Test/PushTests.cs b/Parse.Test/PushTests.cs index 666bcfc4..0d673810 100644 --- a/Parse.Test/PushTests.cs +++ b/Parse.Test/PushTests.cs @@ -4,7 +4,9 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Parse.Abstractions.Library; using Parse.Common.Internal; +using Parse.Library; using Parse.Management; using Parse.Push.Internal; @@ -13,89 +15,71 @@ namespace Parse.Test [TestClass] public class PushTests { - private IParsePushController GetMockedPushController(IPushState expectedPushState) + ParseClient Client { get; } = new ParseClient(new ServerConnectionData { Test = true }); + + IParsePushController GetMockedPushController(IPushState expectedPushState) { Mock mockedController = new Mock(MockBehavior.Strict); - - mockedController.Setup( - obj => obj.SendPushNotificationAsync( - It.Is(s => s.Equals(expectedPushState)), - It.IsAny() - ) - ).Returns(Task.FromResult(false)); + mockedController.Setup(obj => obj.SendPushNotificationAsync(It.Is(s => s.Equals(expectedPushState)), It.IsAny(), It.IsAny())).Returns(Task.FromResult(false)); return mockedController.Object; } - private IParsePushChannelsController GetMockedPushChannelsController(IEnumerable channels) + IParsePushChannelsController GetMockedPushChannelsController(IEnumerable channels) { Mock mockedChannelsController = new Mock(MockBehavior.Strict); - - mockedChannelsController.Setup( - obj => obj.SubscribeAsync( - It.Is>(it => it.CollectionsEqual(channels)), - It.IsAny() - ) - ).Returns(Task.FromResult(false)); - - mockedChannelsController.Setup( - obj => obj.UnsubscribeAsync( - It.Is>(it => it.CollectionsEqual(channels)), - It.IsAny() - ) - ).Returns(Task.FromResult(false)); + mockedChannelsController.Setup(obj => obj.SubscribeAsync(It.Is>(it => it.CollectionsEqual(channels)), It.IsAny(), It.IsAny())).Returns(Task.FromResult(false)); + mockedChannelsController.Setup(obj => obj.UnsubscribeAsync(It.Is>(it => it.CollectionsEqual(channels)), It.IsAny(), It.IsAny())).Returns(Task.FromResult(false)); return mockedChannelsController.Object; } [TestCleanup] - public void TearDown() - { - ParseCorePlugins.Instance = null; - ParsePushPlugins.Instance = null; - } + public void TearDown() => (Client.Services as ServiceHub).Reset(); [TestMethod] [AsyncStateMachine(typeof(PushTests))] public Task TestSendPush() { + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + MutablePushState state = new MutablePushState { - Query = ParseInstallation.Query + Query = Client.GetInstallationQuery() }; - ParsePush thePush = new ParsePush(); - ParsePushPlugins.Instance = new ParsePushPlugins - { - PushController = GetMockedPushController(state) - }; + ParsePush thePush = new ParsePush(client); + + hub.PushController = GetMockedPushController(state); thePush.Alert = "Alert"; state.Alert = "Alert"; - return thePush.SendAsync().ContinueWith(t => + return thePush.SendAsync().ContinueWith(task => { - Assert.IsTrue(t.IsCompleted); - Assert.IsFalse(t.IsFaulted); + Assert.IsTrue(task.IsCompleted); + Assert.IsFalse(task.IsFaulted); thePush.Channels = new List { { "channel" } }; state.Channels = new List { { "channel" } }; return thePush.SendAsync(); - }).Unwrap().ContinueWith(t => + }).Unwrap().ContinueWith(task => { - Assert.IsTrue(t.IsCompleted); - Assert.IsFalse(t.IsFaulted); + Assert.IsTrue(task.IsCompleted); + Assert.IsFalse(task.IsFaulted); + + ParseQuery query = new ParseQuery(client, "aClass"); - ParseQuery query = new ParseQuery("aClass"); thePush.Query = query; state.Query = query; return thePush.SendAsync(); - }).Unwrap().ContinueWith(t => + }).Unwrap().ContinueWith(task => { - Assert.IsTrue(t.IsCompleted); - Assert.IsFalse(t.IsFaulted); + Assert.IsTrue(task.IsCompleted); + Assert.IsFalse(task.IsFaulted); }); } @@ -103,30 +87,30 @@ public Task TestSendPush() [AsyncStateMachine(typeof(PushTests))] public Task TestSubscribe() { - List channels = new List(); - ParsePushPlugins.Instance = new ParsePushPlugins - { - PushChannelsController = GetMockedPushChannelsController(channels) - }; + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + List channels = new List { }; + + hub.PushChannelsController = GetMockedPushChannelsController(channels); channels.Add("test"); - return ParsePush.SubscribeAsync("test").ContinueWith(t => + return Client.SubscribeToPushChannelAsync("test").ContinueWith(task => { - Assert.IsTrue(t.IsCompleted); - Assert.IsFalse(t.IsFaulted); + Assert.IsTrue(task.IsCompleted); + Assert.IsFalse(task.IsFaulted); - return ParsePush.SubscribeAsync(new List { { "test" } }); - }).Unwrap().ContinueWith(t => + return Client.SubscribeToPushChannelsAsync(new List { { "test" } }); + }).Unwrap().ContinueWith(task => { - Assert.IsTrue(t.IsCompleted); - Assert.IsFalse(t.IsFaulted); + Assert.IsTrue(task.IsCompleted); + Assert.IsFalse(task.IsFaulted); - CancellationTokenSource cts = new CancellationTokenSource(); - return ParsePush.SubscribeAsync(new List { { "test" } }, cts.Token); - }).Unwrap().ContinueWith(t => + return Client.SubscribeToPushChannelsAsync(new List { { "test" } }, new CancellationTokenSource { }.Token); + }).Unwrap().ContinueWith(task => { - Assert.IsTrue(t.IsCompleted); - Assert.IsFalse(t.IsFaulted); + Assert.IsTrue(task.IsCompleted); + Assert.IsFalse(task.IsFaulted); }); } @@ -134,30 +118,31 @@ public Task TestSubscribe() [AsyncStateMachine(typeof(PushTests))] public Task TestUnsubscribe() { - List channels = new List(); - ParsePushPlugins.Instance = new ParsePushPlugins - { - PushChannelsController = GetMockedPushChannelsController(channels) - }; + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + List channels = new List { }; + + hub.PushChannelsController = GetMockedPushChannelsController(channels); channels.Add("test"); - return ParsePush.UnsubscribeAsync("test").ContinueWith(t => + + return Client.UnsubscribeToPushChannelAsync("test").ContinueWith(task => { - Assert.IsTrue(t.IsCompleted); - Assert.IsFalse(t.IsFaulted); + Assert.IsTrue(task.IsCompleted); + Assert.IsFalse(task.IsFaulted); - return ParsePush.UnsubscribeAsync(new List { { "test" } }); - }).ContinueWith(t => + return Client.UnsubscribeToPushChannelsAsync(new List { { "test" } }); + }).ContinueWith(task => { - Assert.IsTrue(t.IsCompleted); - Assert.IsFalse(t.IsFaulted); + Assert.IsTrue(task.IsCompleted); + Assert.IsFalse(task.IsFaulted); - CancellationTokenSource cts = new CancellationTokenSource(); - return ParsePush.UnsubscribeAsync(new List { { "test" } }, cts.Token); - }).ContinueWith(t => + return Client.UnsubscribeToPushChannelsAsync(new List { { "test" } }, new CancellationTokenSource { }.Token); + }).ContinueWith(task => { - Assert.IsTrue(t.IsCompleted); - Assert.IsFalse(t.IsFaulted); + Assert.IsTrue(task.IsCompleted); + Assert.IsFalse(task.IsFaulted); }); } } diff --git a/Parse.Test/RelationTests.cs b/Parse.Test/RelationTests.cs index cebda342..584b73c9 100644 --- a/Parse.Test/RelationTests.cs +++ b/Parse.Test/RelationTests.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using Parse.Core.Internal; +using Parse.Library; namespace Parse.Test { @@ -10,7 +11,7 @@ public class RelationTests [TestMethod] public void TestRelationQuery() { - ParseObject parent = ParseObject.CreateWithoutData("Foo", "abcxyz"); + ParseObject parent = new ServiceHub { }.CreateObjectWithoutData("Foo", "abcxyz"); ParseRelation relation = parent.GetRelation("child"); ParseQuery query = relation.Query; diff --git a/Parse.Test/SessionControllerTests.cs b/Parse.Test/SessionControllerTests.cs index 00256cfa..d7ed9798 100644 --- a/Parse.Test/SessionControllerTests.cs +++ b/Parse.Test/SessionControllerTests.cs @@ -15,15 +15,19 @@ namespace Parse.Test [TestClass] public class SessionControllerTests { +#warning Check if reinitializing the client for every test method is really necessary. + + ParseClient Client { get; set; } + [TestInitialize] - public void SetUp() => ParseClient.Initialize(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); + public void SetUp() => Client = new ParseClient(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(SessionControllerTests))] - public Task TestGetSessionWithEmptyResult() => new ParseSessionController(CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, null)).Object).GetSessionAsync("S0m3Se551on", CancellationToken.None).ContinueWith(t => + public Task TestGetSessionWithEmptyResult() => new ParseSessionController(CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, null)).Object, Client.Decoder).GetSessionAsync("S0m3Se551on", Client, CancellationToken.None).ContinueWith(task => { - Assert.IsTrue(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); + Assert.IsTrue(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); }); [TestMethod] @@ -37,15 +41,17 @@ public Task TestGetSession() ["sessionToken"] = "S0m3Se551on", ["restricted"] = true }); + Mock mockRunner = CreateMockRunner(response); - return new ParseSessionController(mockRunner.Object).GetSessionAsync("S0m3Se551on", CancellationToken.None).ContinueWith(t => + return new ParseSessionController(mockRunner.Object, Client.Decoder).GetSessionAsync("S0m3Se551on", Client, CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/sessions/me"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/sessions/me"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); - IObjectState session = t.Result; + IObjectState session = task.Result; Assert.AreEqual(2, session.Count()); Assert.IsTrue((bool) session["restricted"]); Assert.AreEqual("S0m3Se551on", session["sessionToken"]); @@ -56,18 +62,14 @@ public Task TestGetSession() [AsyncStateMachine(typeof(SessionControllerTests))] public Task TestRevoke() { - Tuple> response = new Tuple>(HttpStatusCode.Accepted, null); - Mock mockRunner = CreateMockRunner(response); + Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, default)); - ParseSessionController controller = new ParseSessionController(mockRunner.Object); - return controller.RevokeAsync("S0m3Se551on", CancellationToken.None).ContinueWith(t => + return new ParseSessionController(mockRunner.Object, Client.Decoder).RevokeAsync("S0m3Se551on", CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/logout"), - It.IsAny>(), - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/logout"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -76,25 +78,23 @@ public Task TestRevoke() public Task TestUpgradeToRevocableSession() { Tuple> response = new Tuple>(HttpStatusCode.Accepted, - new Dictionary() { - { "__type", "Object" }, - { "className", "Session" }, - { "sessionToken", "S0m3Se551on" }, - { "restricted", true } + new Dictionary + { + ["__type"] = "Object", + ["className"] = "Session", + ["sessionToken"] = "S0m3Se551on", + ["restricted"] = true }); + Mock mockRunner = CreateMockRunner(response); - ParseSessionController controller = new ParseSessionController(mockRunner.Object); - return controller.UpgradeToRevocableSessionAsync("S0m3Se551on", CancellationToken.None).ContinueWith(t => + return new ParseSessionController(mockRunner.Object, Client.Decoder).UpgradeToRevocableSessionAsync("S0m3Se551on", Client, CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/upgradeToRevocableSession"), - It.IsAny>(), - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); - - IObjectState session = t.Result; + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/upgradeToRevocableSession"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + + IObjectState session = task.Result; Assert.AreEqual(2, session.Count()); Assert.IsTrue((bool) session["restricted"]); Assert.AreEqual("S0m3Se551on", session["sessionToken"]); @@ -104,7 +104,7 @@ public Task TestUpgradeToRevocableSession() [TestMethod] public void TestIsRevocableSessionToken() { - IParseSessionController sessionController = new ParseSessionController(Mock.Of()); + IParseSessionController sessionController = new ParseSessionController(Mock.Of(), Client.Decoder); Assert.IsTrue(sessionController.IsRevocableSessionToken("r:session")); Assert.IsTrue(sessionController.IsRevocableSessionToken("r:session:r:")); Assert.IsTrue(sessionController.IsRevocableSessionToken("session:r:")); @@ -117,11 +117,7 @@ public void TestIsRevocableSessionToken() private Mock CreateMockRunner(Tuple> response) { Mock mockRunner = new Mock(); - mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), - It.IsAny>(), - It.IsAny>(), - It.IsAny())) - .Returns(Task.FromResult(response)); + mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); return mockRunner; } diff --git a/Parse.Test/SessionTests.cs b/Parse.Test/SessionTests.cs index b9f6d590..f2eaa5e9 100644 --- a/Parse.Test/SessionTests.cs +++ b/Parse.Test/SessionTests.cs @@ -4,7 +4,9 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Parse.Abstractions.Library; using Parse.Core.Internal; +using Parse.Library; using Parse.Management; namespace Parse.Test @@ -12,23 +14,26 @@ namespace Parse.Test [TestClass] public class SessionTests { + ParseClient Client { get; } = new ParseClient(new ServerConnectionData { Test = true }); + [TestInitialize] public void SetUp() { - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); + Client.AddValidClass(); + Client.AddValidClass(); } [TestCleanup] - public void TearDown() => ParseCorePlugins.Instance.Reset(); + public void TearDown() => (Client.Services as ServiceHub).Reset(); [TestMethod] - public void TestGetSessionQuery() => Assert.IsInstanceOfType(ParseSession.Query, typeof(ParseQuery)); + public void TestGetSessionQuery() => Assert.IsInstanceOfType(Client.GetSessionQuery(), typeof(ParseQuery)); [TestMethod] public void TestGetSessionToken() { - ParseSession session = ParseObjectExtensions.FromState(new MutableObjectState { ServerData = new Dictionary() { { "sessionToken", "llaKcolnu" } } }, "_Session"); + ParseSession session = Client.GenerateObjectFromState(new MutableObjectState { ServerData = new Dictionary() { ["sessionToken"] = "llaKcolnu" } }, "_Session"); + Assert.IsNotNull(session); Assert.AreEqual("llaKcolnu", session.SessionToken); } @@ -37,41 +42,44 @@ public void TestGetSessionToken() [AsyncStateMachine(typeof(SessionTests))] public Task TestGetCurrentSession() { + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + IObjectState sessionState = new MutableObjectState { - ServerData = new Dictionary() { - { "sessionToken", "newllaKcolnu" } - } + ServerData = new Dictionary + { + ["sessionToken"] = "newllaKcolnu" + } }; + Mock mockController = new Mock(); - mockController.Setup(obj => obj.GetSessionAsync(It.IsAny(), - It.IsAny())).Returns(Task.FromResult(sessionState)); + mockController.Setup(obj => obj.GetSessionAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(sessionState)); IObjectState userState = new MutableObjectState { - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" } - } + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu" + } }; - ParseUser user = ParseObjectExtensions.FromState(userState, "_User"); + + ParseUser user = client.GenerateObjectFromState(userState, "_User"); + Mock mockCurrentUserController = new Mock(); - mockCurrentUserController.Setup(obj => obj.GetAsync(It.IsAny())) - .Returns(Task.FromResult(user)); - ParseCorePlugins.Instance = new ParseCorePlugins - { - SessionController = mockController.Object, - CurrentUserController = mockCurrentUserController.Object, - }; - ParseObject.RegisterDerivative(); + mockCurrentUserController.Setup(obj => obj.GetAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(user)); + + hub.SessionController = mockController.Object; + hub.CurrentUserController = mockCurrentUserController.Object; - return ParseSession.GetCurrentSessionAsync().ContinueWith(t => + return client.GetCurrentSessionAsync().ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockController.Verify(obj => obj.GetSessionAsync(It.Is(sessionToken => sessionToken == "llaKcolnu"), - It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); - ParseSession session = t.Result; + mockController.Verify(obj => obj.GetSessionAsync(It.Is(sessionToken => sessionToken == "llaKcolnu"), It.IsAny(),It.IsAny()), Times.Exactly(1)); + + ParseSession session = task.Result; Assert.AreEqual("newllaKcolnu", session.SessionToken); }); } @@ -80,19 +88,20 @@ public Task TestGetCurrentSession() [AsyncStateMachine(typeof(SessionTests))] public Task TestGetCurrentSessionWithNoCurrentUser() { + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + Mock mockController = new Mock(); Mock mockCurrentUserController = new Mock(); - ParseCorePlugins.Instance = new ParseCorePlugins - { - SessionController = mockController.Object, - CurrentUserController = mockCurrentUserController.Object, - }; - return ParseSession.GetCurrentSessionAsync().ContinueWith(t => + hub.SessionController = mockController.Object; + hub.CurrentUserController = mockCurrentUserController.Object; + + return client.GetCurrentSessionAsync().ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - Assert.IsNull(t.Result); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + Assert.IsNull(task.Result); }); } @@ -100,23 +109,21 @@ public Task TestGetCurrentSessionWithNoCurrentUser() [AsyncStateMachine(typeof(SessionTests))] public Task TestRevoke() { + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + Mock mockController = new Mock(); - mockController - .Setup(sessionController => sessionController.IsRevocableSessionToken(It.IsAny())) - .Returns(true); + mockController.Setup(sessionController => sessionController.IsRevocableSessionToken(It.IsAny())).Returns(true); - ParseCorePlugins.Instance = new ParseCorePlugins - { - SessionController = mockController.Object - }; + hub.SessionController = mockController.Object; - CancellationTokenSource source = new CancellationTokenSource(); - return ParseSessionExtensions.RevokeAsync("r:someSession", source.Token).ContinueWith(t => + CancellationTokenSource source = new CancellationTokenSource { }; + return client.RevokeSessionAsync("r:someSession", source.Token).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockController.Verify(obj => obj.RevokeAsync(It.Is(sessionToken => sessionToken == "r:someSession"), - source.Token), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockController.Verify(obj => obj.RevokeAsync(It.Is(sessionToken => sessionToken == "r:someSession"), source.Token), Times.Exactly(1)); }); } @@ -124,34 +131,34 @@ public Task TestRevoke() [AsyncStateMachine(typeof(SessionTests))] public Task TestUpgradeToRevocableSession() { + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + IObjectState state = new MutableObjectState { - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" } - } + ServerData = new Dictionary() + { + ["sessionToken"] = "llaKcolnu" + } }; + Mock mockController = new Mock(); - mockController.Setup(obj => obj.UpgradeToRevocableSessionAsync(It.IsAny(), - It.IsAny())).Returns(Task.FromResult(state)); + mockController.Setup(obj => obj.UpgradeToRevocableSessionAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(state)); Mock mockCurrentUserController = new Mock(); - ParseCorePlugins.Instance = new ParseCorePlugins - { - SessionController = mockController.Object, - CurrentUserController = mockCurrentUserController.Object, - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); - CancellationTokenSource source = new CancellationTokenSource(); - return ParseSessionExtensions.UpgradeToRevocableSessionAsync("someSession", source.Token).ContinueWith(t => + hub.SessionController = mockController.Object; + hub.CurrentUserController = mockCurrentUserController.Object; + + CancellationTokenSource source = new CancellationTokenSource { }; + return client.UpgradeToRevocableSessionAsync("someSession", source.Token).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockController.Verify(obj => obj.UpgradeToRevocableSessionAsync(It.Is(sessionToken => sessionToken == "someSession"), - source.Token), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockController.Verify(obj => obj.UpgradeToRevocableSessionAsync(It.Is(sessionToken => sessionToken == "someSession"), It.IsAny(), source.Token), Times.Exactly(1)); - Assert.AreEqual("llaKcolnu", t.Result); + Assert.AreEqual("llaKcolnu", task.Result); }); } } diff --git a/Parse.Test/UserControllerTests.cs b/Parse.Test/UserControllerTests.cs index 860c61eb..e2067dbc 100644 --- a/Parse.Test/UserControllerTests.cs +++ b/Parse.Test/UserControllerTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Parse.Abstractions.Library; using Parse.Core.Internal; using Parse.Library; @@ -14,8 +15,10 @@ namespace Parse.Test [TestClass] public class UserControllerTests { + ParseClient Client { get; set; } + [TestInitialize] - public void SetUp() => ParseClient.Initialize(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); + public void SetUp() => new ParseClient(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(UserControllerTests))] @@ -45,18 +48,16 @@ public Task TestSignUp() ["createdAt"] = "2015-09-18T18:11:28.943Z" }; - Tuple> response = new Tuple>(HttpStatusCode.Accepted, responseDict); - Mock mockRunner = CreateMockRunner(response); + Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, responseDict)); - ParseUserController controller = new ParseUserController(mockRunner.Object); - return controller.SignUpAsync(state, operations, CancellationToken.None).ContinueWith(t => + return new ParseUserController(mockRunner.Object, Client.Decoder).SignUpAsync(state, operations, Client, CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/_User"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/_User"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); - IObjectState newState = t.Result; + IObjectState newState = task.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); Assert.AreEqual("d3ImSh3ki", newState.ObjectId); Assert.IsNotNull(newState.CreatedAt); @@ -77,18 +78,16 @@ public Task TestLogInWithUsernamePassword() ["createdAt"] = "2015-09-18T18:11:28.943Z" }; - Tuple> response = new Tuple>(HttpStatusCode.Accepted, responseDict); - Mock mockRunner = CreateMockRunner(response); + Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, responseDict)); - ParseUserController controller = new ParseUserController(mockRunner.Object); - return controller.LogInAsync("grantland", "123grantland123", CancellationToken.None).ContinueWith(t => + return new ParseUserController(mockRunner.Object, Client.Decoder).LogInAsync("grantland", "123grantland123", Client, CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/login"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/login"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); - IObjectState newState = t.Result; + IObjectState newState = task.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); Assert.AreEqual("d3ImSh3ki", newState.ObjectId); Assert.IsNotNull(newState.CreatedAt); @@ -109,18 +108,16 @@ public Task TestLogInWithAuthData() ["createdAt"] = "2015-09-18T18:11:28.943Z" }; - Tuple> response = new Tuple>(HttpStatusCode.Accepted, responseDict); - Mock mockRunner = CreateMockRunner(response); + Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, responseDict)); - ParseUserController controller = new ParseUserController(mockRunner.Object); - return controller.LogInAsync("facebook", data: null, cancellationToken: CancellationToken.None).ContinueWith(t => + return new ParseUserController(mockRunner.Object, Client.Decoder).LogInAsync("facebook", data: null, serviceHub: Client, cancellationToken: CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/users"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/users"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); - IObjectState newState = t.Result; + IObjectState newState = task.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); Assert.AreEqual("d3ImSh3ki", newState.ObjectId); Assert.IsNotNull(newState.CreatedAt); @@ -132,7 +129,8 @@ public Task TestLogInWithAuthData() [AsyncStateMachine(typeof(UserControllerTests))] public Task TestGetUserFromSessionToken() { - Dictionary responseDict = new Dictionary() { + Dictionary responseDict = new Dictionary + { ["__type"] = "Object", ["className"] = "_User", ["objectId"] = "d3ImSh3ki", @@ -140,18 +138,16 @@ public Task TestGetUserFromSessionToken() ["createdAt"] = "2015-09-18T18:11:28.943Z" }; - Tuple> response = new Tuple>(HttpStatusCode.Accepted, responseDict); - Mock mockRunner = CreateMockRunner(response); + Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, responseDict)); - ParseUserController controller = new ParseUserController(mockRunner.Object); - return controller.GetUserAsync("s3ss10nt0k3n", CancellationToken.None).ContinueWith(t => + return new ParseUserController(mockRunner.Object, Client.Decoder).GetUserAsync("s3ss10nt0k3n", Client, CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/users/me"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/users/me"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); - IObjectState newState = t.Result; + IObjectState newState = task.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); Assert.AreEqual("d3ImSh3ki", newState.ObjectId); Assert.IsNotNull(newState.CreatedAt); @@ -163,24 +159,21 @@ public Task TestGetUserFromSessionToken() [AsyncStateMachine(typeof(UserControllerTests))] public Task TestRequestPasswordReset() { - Dictionary responseDict = new Dictionary(); - Tuple> response = new Tuple>(HttpStatusCode.Accepted, responseDict); - Mock mockRunner = CreateMockRunner(response); + Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary { })); - ParseUserController controller = new ParseUserController(mockRunner.Object); - return controller.RequestPasswordResetAsync("gogo@parse.com", CancellationToken.None).ContinueWith(t => + return new ParseUserController(mockRunner.Object, Client.Decoder).RequestPasswordResetAsync("gogo@parse.com", CancellationToken.None).ContinueWith(t => { Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/requestPasswordReset"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/requestPasswordReset"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } - private Mock CreateMockRunner(Tuple> response) + Mock CreateMockRunner(Tuple> response) { - Mock mockRunner = new Mock(); - mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); + Mock mockRunner = new Mock { }; + mockRunner.Setup(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)); return mockRunner; } diff --git a/Parse.Test/UserTests.cs b/Parse.Test/UserTests.cs index f7c940d6..e911a99d 100644 --- a/Parse.Test/UserTests.cs +++ b/Parse.Test/UserTests.cs @@ -5,39 +5,47 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Parse.Abstractions.Library; using Parse.Core.Internal; +using Parse.Library; using Parse.Management; namespace Parse.Test { +#warning Class refactoring requires completion. + [TestClass] public class UserTests { - [TestInitialize] - public void SetUp() - { - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); - } + ParseClient Client { get; set; } = new ParseClient(new ServerConnectionData { Test = true }); [TestCleanup] - public void TearDown() => ParseCorePlugins.Instance = null; + public void TearDown() => (Client.Services as ServiceHub).Reset(); [TestMethod] public void TestRemoveFields() { IObjectState state = new MutableObjectState { - ServerData = new Dictionary() { - { "username", "kevin" }, - { "name", "andrew" } - } + ServerData = new Dictionary + { + ["username"] = "kevin", + ["name"] = "andrew" + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); + + ParseUser user = Client.GenerateObjectFromState(state, "_User"); Assert.ThrowsException(() => user.Remove("username")); + try - { user.Remove("name"); } - catch { Assert.Fail(); } + { + user.Remove("name"); + } + catch + { + Assert.Fail(@"Removing ""name"" field on ParseUser should not throw an exception because ""name"" is not an immutable field and was defined on the object."); + } + Assert.IsFalse(user.ContainsKey("name")); } @@ -46,12 +54,14 @@ public void TestSessionTokenGetter() { IObjectState state = new MutableObjectState { - ServerData = new Dictionary() { - { "username", "kevin" }, - { "sessionToken", "se551onT0k3n" } - } + ServerData = new Dictionary + { + ["username"] = "kevin", + ["sessionToken"] = "se551onT0k3n" + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); + + ParseUser user = Client.GenerateObjectFromState(state, "_User"); Assert.AreEqual("se551onT0k3n", user.SessionToken); } @@ -60,11 +70,13 @@ public void TestUsernameGetterSetter() { IObjectState state = new MutableObjectState { - ServerData = new Dictionary() { - { "username", "kevin" }, - } + ServerData = new Dictionary + { + ["username"] = "kevin", + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); + + ParseUser user = Client.GenerateObjectFromState(state, "_User"); Assert.AreEqual("kevin", user.Username); user.Username = "ilya"; Assert.AreEqual("ilya", user.Username); @@ -75,15 +87,17 @@ public void TestPasswordGetterSetter() { IObjectState state = new MutableObjectState { - ServerData = new Dictionary() { - { "username", "kevin" }, - { "password", "hurrah" }, - } + ServerData = new Dictionary + { + ["username"] = "kevin", + ["password"] = "hurrah" + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); - Assert.AreEqual("hurrah", user.GetState()["password"]); + + ParseUser user = Client.GenerateObjectFromState(state, "_User"); + Assert.AreEqual("hurrah", user.State["password"]); user.Password = "david"; - Assert.IsNotNull(user.GetCurrentOperations()["password"]); + Assert.IsNotNull(user.CurrentOperations["password"]); } [TestMethod] @@ -91,13 +105,15 @@ public void TestEmailGetterSetter() { IObjectState state = new MutableObjectState { - ServerData = new Dictionary() { - { "email", "james@parse.com" }, - { "name", "andrew" }, - { "sessionToken", "se551onT0k3n" } - } + ServerData = new Dictionary + { + ["email"] = "james@parse.com", + ["name"] = "andrew", + ["sessionToken"] = "se551onT0k3n" + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); + + ParseUser user = Client.GenerateObjectFromState(state, "_User"); Assert.AreEqual("james@parse.com", user.Email); user.Email = "bryan@parse.com"; Assert.AreEqual("bryan@parse.com", user.Email); @@ -108,22 +124,26 @@ public void TestAuthDataGetter() { IObjectState state = new MutableObjectState { - ServerData = new Dictionary() { - { "email", "james@parse.com" }, - { "authData", new Dictionary() { - { "facebook", new Dictionary() { - { "sessionToken", "none" } - }} - }} - } + ServerData = new Dictionary + { + ["email"] = "james@parse.com", + ["authData"] = new Dictionary + { + ["facebook"] = new Dictionary + { + ["sessionToken"] = "none" + } + } + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); - Assert.AreEqual(1, user.GetAuthData().Count); - Assert.IsInstanceOfType(user.GetAuthData()["facebook"], typeof(IDictionary)); + + ParseUser user = Client.GenerateObjectFromState(state, "_User"); + Assert.AreEqual(1, user.AuthData.Count); + Assert.IsInstanceOfType(user.AuthData["facebook"], typeof(IDictionary)); } [TestMethod] - public void TestGetUserQuery() => Assert.IsInstanceOfType(ParseUser.Query, typeof(ParseQuery)); + public void TestGetUserQuery() => Assert.IsInstanceOfType(Client.GetUserQuery(), typeof(ParseQuery)); [TestMethod] public void TestIsAuthenticated() @@ -131,20 +151,21 @@ public void TestIsAuthenticated() IObjectState state = new MutableObjectState { ObjectId = "wagimanPutraPetir", - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" } - } - }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); - Mock mockCurrentUserController = new Mock(); - mockCurrentUserController.Setup(obj => obj.GetAsync(It.IsAny())) - .Returns(Task.FromResult(user)); - ParseCorePlugins.Instance = new ParseCorePlugins - { - CurrentUserController = mockCurrentUserController.Object + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu" + } }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); + + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + ParseUser user = client.GenerateObjectFromState(state, "_User"); + + Mock mockCurrentUserController = new Mock { }; + mockCurrentUserController.Setup(obj => obj.GetAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(user)); + + hub.CurrentUserController = mockCurrentUserController.Object; Assert.IsTrue(user.IsAuthenticated); } @@ -155,28 +176,31 @@ public void TestIsAuthenticatedWithOtherParseUser() IObjectState state = new MutableObjectState { ObjectId = "wagimanPutraPetir", - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" } - } + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu" + } }; + IObjectState state2 = new MutableObjectState { ObjectId = "wagimanPutraPetir2", - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" } - } + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu" + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); - ParseUser user2 = ParseObjectExtensions.FromState(state2, "_User"); - Mock mockCurrentUserController = new Mock(); - mockCurrentUserController.Setup(obj => obj.GetAsync(It.IsAny())) - .Returns(Task.FromResult(user)); - ParseCorePlugins.Instance = new ParseCorePlugins - { - CurrentUserController = mockCurrentUserController.Object - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); + + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + ParseUser user = client.GenerateObjectFromState(state, "_User"); + ParseUser user2 = client.GenerateObjectFromState(state2, "_User"); + + Mock mockCurrentUserController = new Mock { }; + mockCurrentUserController.Setup(obj => obj.GetAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(user)); + + hub.CurrentUserController = mockCurrentUserController.Object; Assert.IsFalse(user2.IsAuthenticated); } @@ -187,16 +211,18 @@ public Task TestSignUpWithInvalidServerData() { IObjectState state = new MutableObjectState { - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" } - } + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu" + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); - return user.SignUpAsync().ContinueWith(t => + ParseUser user = Client.GenerateObjectFromState(state, "_User"); + + return user.SignUpAsync().ContinueWith(task => { - Assert.IsTrue(t.IsFaulted); - Assert.IsInstanceOfType(t.Exception.InnerException, typeof(InvalidOperationException)); + Assert.IsTrue(task.IsFaulted); + Assert.IsInstanceOfType(task.Exception.InnerException, typeof(InvalidOperationException)); }); } @@ -206,38 +232,39 @@ public Task TestSignUp() { IObjectState state = new MutableObjectState { - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" }, - { "username", "ihave" }, - { "password", "adream" } - } + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu", + ["username"] = "ihave", + ["password"] = "adream" + } }; + IObjectState newState = new MutableObjectState { ObjectId = "some0neTol4v4" }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); - Mock mockController = new Mock(); - mockController.Setup(obj => obj.SignUpAsync(It.IsAny(), - It.IsAny>(), - It.IsAny())).Returns(Task.FromResult(newState)); - ParseCorePlugins.Instance = new ParseCorePlugins - { - UserController = mockController.Object - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); - return user.SignUpAsync().ContinueWith(t => + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + ParseUser user = client.GenerateObjectFromState(state, "_User"); + + Mock mockController = new Mock { }; + mockController.Setup(obj => obj.SignUpAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(newState)); + + hub.UserController = mockController.Object; + + return user.SignUpAsync().ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockController.Verify(obj => obj.SignUpAsync(It.IsAny(), - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockController.Verify(obj => obj.SignUpAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(user.IsDirty); Assert.AreEqual("ihave", user.Username); - Assert.IsFalse(user.GetState().ContainsKey("password")); + Assert.IsFalse(user.State.ContainsKey("password")); Assert.AreEqual("some0neTol4v4", user.ObjectId); }); } @@ -248,36 +275,35 @@ public Task TestLogIn() { IObjectState state = new MutableObjectState { - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" }, - { "username", "ihave" }, - { "password", "adream" } - } + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu", + ["username"] = "ihave", + ["password"] = "adream" + } }; + IObjectState newState = new MutableObjectState { ObjectId = "some0neTol4v4" }; - Mock mockController = new Mock(); - mockController.Setup(obj => obj.LogInAsync("ihave", - "adream", - It.IsAny())).Returns(Task.FromResult(newState)); - ParseCorePlugins.Instance = new ParseCorePlugins - { - UserController = mockController.Object - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); - return ParseUser.LogInAsync("ihave", "adream").ContinueWith(t => + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + Mock mockController = new Mock { }; + mockController.Setup(obj => obj.LogInAsync("ihave", "adream", It.IsAny(), It.IsAny())).Returns(Task.FromResult(newState)); + + hub.UserController = mockController.Object; + + return client.LogInAsync("ihave", "adream").ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockController.Verify(obj => obj.LogInAsync("ihave", - "adream", - It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); - ParseUser user = t.Result; + mockController.Verify(obj => obj.LogInAsync("ihave", "adream", It.IsAny(), It.IsAny()), Times.Exactly(1)); + + ParseUser user = task.Result; Assert.IsFalse(user.IsDirty); Assert.IsNull(user.Username); Assert.AreEqual("some0neTol4v4", user.ObjectId); @@ -293,22 +319,23 @@ public Task TestBecome() ObjectId = "some0neTol4v4", ServerData = new Dictionary { ["sessionToken"] = "llaKcolnu" } }; - Mock mockController = new Mock(); - mockController.Setup(obj => obj.GetUserAsync("llaKcolnu", It.IsAny())).Returns(Task.FromResult(state)); - ParseCorePlugins.Instance = new ParseCorePlugins - { - UserController = mockController.Object - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); - return ParseUser.BecomeAsync("llaKcolnu").ContinueWith(t => + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + Mock mockController = new Mock { }; + mockController.Setup(obj => obj.GetUserAsync("llaKcolnu", It.IsAny(), It.IsAny())).Returns(Task.FromResult(state)); + + hub.UserController = mockController.Object; + + return client.BecomeAsync("llaKcolnu").ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockController.Verify(obj => obj.GetUserAsync("llaKcolnu", It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockController.Verify(obj => obj.GetUserAsync("llaKcolnu", It.IsAny(), It.IsAny()), Times.Exactly(1)); - ParseUser user = t.Result; + ParseUser user = task.Result; Assert.AreEqual("some0neTol4v4", user.ObjectId); Assert.AreEqual("llaKcolnu", user.SessionToken); }); @@ -320,30 +347,32 @@ public Task TestLogOut() { IObjectState state = new MutableObjectState { - ServerData = new Dictionary() { - { "sessionToken", "r:llaKcolnu" } - } + ServerData = new Dictionary + { + ["sessionToken"] = "r:llaKcolnu" + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); - Mock mockCurrentUserController = new Mock(); - mockCurrentUserController.Setup(obj => obj.GetAsync(It.IsAny())) - .Returns(Task.FromResult(user)); + + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + ParseUser user = client.GenerateObjectFromState(state, "_User"); + + Mock mockCurrentUserController = new Mock { }; + mockCurrentUserController.Setup(obj => obj.GetAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(user)); + Mock mockSessionController = new Mock(); mockSessionController.Setup(c => c.IsRevocableSessionToken(It.IsAny())).Returns(true); - ParseCorePlugins.Instance = new ParseCorePlugins - { - CurrentUserController = mockCurrentUserController.Object, - SessionController = mockSessionController.Object - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); + hub.CurrentUserController = mockCurrentUserController.Object; + hub.SessionController = mockSessionController.Object; - return ParseUser.LogOutAsync().ContinueWith(t => + return client.LogOutAsync().ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockCurrentUserController.Verify(obj => obj.LogOutAsync(It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockCurrentUserController.Verify(obj => obj.LogOutAsync(It.IsAny(), It.IsAny()), Times.Exactly(1)); mockSessionController.Verify(obj => obj.RevokeAsync("r:llaKcolnu", It.IsAny()), Times.Exactly(1)); }); } @@ -353,71 +382,65 @@ public void TestCurrentUser() { IObjectState state = new MutableObjectState { - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" } - } - }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); - Mock mockCurrentUserController = new Mock(); - mockCurrentUserController.Setup(obj => obj.GetAsync(It.IsAny())) - .Returns(Task.FromResult(user)); - ParseCorePlugins.Instance = new ParseCorePlugins - { - CurrentUserController = mockCurrentUserController.Object, + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu" + } }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); - Assert.AreEqual(user, ParseUser.CurrentUser); - } + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); - [TestMethod] - public void TestCurrentUserWithEmptyResult() - { - Mock mockCurrentUserController = new Mock(); - ParseCorePlugins.Instance = new ParseCorePlugins - { - CurrentUserController = mockCurrentUserController.Object - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); + ParseUser user = client.GenerateObjectFromState(state, "_User"); + + Mock mockCurrentUserController = new Mock { }; + mockCurrentUserController.Setup(obj => obj.GetAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(user)); + + hub.CurrentUserController = mockCurrentUserController.Object; - Assert.IsNull(ParseUser.CurrentUser); + Assert.AreEqual(user, client.GetCurrentUser()); } + [TestMethod] + public void TestCurrentUserWithEmptyResult() => Assert.IsNull(new ParseClient(new ServerConnectionData { Test = true }, new MutableServiceHub { CurrentUserController = new Mock { }.Object }).GetCurrentUser()); + [TestMethod] [AsyncStateMachine(typeof(UserTests))] public Task TestRevocableSession() { IObjectState state = new MutableObjectState { - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" } - } + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu" + } }; + IObjectState newState = new MutableObjectState { - ServerData = new Dictionary() { - { "sessionToken", "r:llaKcolnu" } - } + ServerData = new Dictionary + { + ["sessionToken"] = "r:llaKcolnu" + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); + + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + ParseUser user = client.GenerateObjectFromState(state, "_User"); + Mock mockSessionController = new Mock(); - mockSessionController.Setup(obj => obj.UpgradeToRevocableSessionAsync("llaKcolnu", - It.IsAny())).Returns(Task.FromResult(newState)); - ParseCorePlugins.Instance = new ParseCorePlugins - { - SessionController = mockSessionController.Object - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); + mockSessionController.Setup(obj => obj.UpgradeToRevocableSessionAsync("llaKcolnu", It.IsAny(), It.IsAny())).Returns(Task.FromResult(newState)); + + hub.SessionController = mockSessionController.Object; - return user.UpgradeToRevocableSessionAsync(CancellationToken.None).ContinueWith(t => + return user.UpgradeToRevocableSessionAsync(CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockSessionController.Verify(obj => obj.UpgradeToRevocableSessionAsync("llaKcolnu", - It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockSessionController.Verify(obj => obj.UpgradeToRevocableSessionAsync("llaKcolnu", It.IsAny(), It.IsAny()), Times.Exactly(1)); + Assert.AreEqual("r:llaKcolnu", user.SessionToken); }); } @@ -426,20 +449,19 @@ public Task TestRevocableSession() [AsyncStateMachine(typeof(UserTests))] public Task TestRequestPasswordReset() { - Mock mockController = new Mock(); - ParseCorePlugins.Instance = new ParseCorePlugins - { - UserController = mockController.Object - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + Mock mockController = new Mock { }; - return ParseUser.RequestPasswordResetAsync("gogo@parse.com").ContinueWith(t => + hub.UserController = mockController.Object; + + return client.RequestPasswordResetAsync("gogo@parse.com").ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockController.Verify(obj => obj.RequestPasswordResetAsync("gogo@parse.com", - It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockController.Verify(obj => obj.RequestPasswordResetAsync("gogo@parse.com", It.IsAny()), Times.Exactly(1)); }); } @@ -450,44 +472,45 @@ public Task TestUserSave() IObjectState state = new MutableObjectState { ObjectId = "some0neTol4v4", - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" }, - { "username", "ihave" }, - { "password", "adream" } - } + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu", + ["username"] = "ihave", + ["password"] = "adream" + } }; + IObjectState newState = new MutableObjectState { - ServerData = new Dictionary() { - { "Alliance", "rekt" } - } + ServerData = new Dictionary + { + ["Alliance"] = "rekt" + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); + + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + ParseUser user = client.GenerateObjectFromState(state, "_User"); Mock mockObjectController = new Mock(); - mockObjectController.Setup(obj => obj.SaveAsync(It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny())).Returns(Task.FromResult(newState)); - ParseCorePlugins.Instance = new ParseCorePlugins - { - ObjectController = mockObjectController.Object, - CurrentUserController = new Mock().Object - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); + + mockObjectController.Setup(obj => obj.SaveAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(newState)); + + hub.ObjectController = mockObjectController.Object; + hub.CurrentUserController = new Mock { }.Object; + user["Alliance"] = "rekt"; - return user.SaveAsync().ContinueWith(t => + return user.SaveAsync().ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockObjectController.Verify(obj => obj.SaveAsync(It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockObjectController.Verify(obj => obj.SaveAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(user.IsDirty); Assert.AreEqual("ihave", user.Username); - Assert.IsFalse(user.GetState().ContainsKey("password")); + Assert.IsFalse(user.State.ContainsKey("password")); Assert.AreEqual("some0neTol4v4", user.ObjectId); Assert.AreEqual("rekt", user["Alliance"]); }); @@ -500,42 +523,45 @@ public Task TestUserFetch() IObjectState state = new MutableObjectState { ObjectId = "some0neTol4v4", - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" }, - { "username", "ihave" }, - { "password", "adream" } - } + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu", + ["username"] = "ihave", + ["password"] = "adream" + } }; + IObjectState newState = new MutableObjectState { - ServerData = new Dictionary() { - { "Alliance", "rekt" } - } + ServerData = new Dictionary + { + ["Alliance"] = "rekt" + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); + + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + ParseUser user = client.GenerateObjectFromState(state, "_User"); + Mock mockObjectController = new Mock(); - mockObjectController.Setup(obj => obj.FetchAsync(It.IsAny(), - It.IsAny(), - It.IsAny())).Returns(Task.FromResult(newState)); - ParseCorePlugins.Instance = new ParseCorePlugins - { - ObjectController = mockObjectController.Object, - CurrentUserController = new Mock().Object - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); + mockObjectController.Setup(obj => obj.FetchAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(newState)); + + hub.ObjectController = mockObjectController.Object; + hub.CurrentUserController = new Mock { }.Object; + user["Alliance"] = "rekt"; - return user.FetchAsync().ContinueWith(t => + return user.FetchAsync().ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockObjectController.Verify(obj => obj.FetchAsync(It.IsAny(), - It.IsAny(), - It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockObjectController.Verify(obj => obj.FetchAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); + Assert.IsTrue(user.IsDirty); Assert.AreEqual("ihave", user.Username); - Assert.IsTrue(user.GetState().ContainsKey("password")); + Assert.IsTrue(user.State.ContainsKey("password")); Assert.AreEqual("some0neTol4v4", user.ObjectId); Assert.AreEqual("rekt", user["Alliance"]); }); @@ -548,41 +574,41 @@ public Task TestLink() IObjectState state = new MutableObjectState { ObjectId = "some0neTol4v4", - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" } - } + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu" + } }; + IObjectState newState = new MutableObjectState { - ServerData = new Dictionary() { - { "garden", "ofWords" } - } + ServerData = new Dictionary + { + ["garden"] = "ofWords" + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); + + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + ParseUser user = client.GenerateObjectFromState(state, "_User"); + Mock mockObjectController = new Mock(); - mockObjectController.Setup(obj => obj.SaveAsync(It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny())).Returns(Task.FromResult(newState)); - ParseCorePlugins.Instance = new ParseCorePlugins - { - ObjectController = mockObjectController.Object, - CurrentUserController = new Mock().Object - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); + mockObjectController.Setup(obj => obj.SaveAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(newState)); - return user.LinkWithAsync("parse", new Dictionary(), CancellationToken.None).ContinueWith(t => + hub.ObjectController = mockObjectController.Object; + hub.CurrentUserController = new Mock { }.Object; + + return user.LinkWithAsync("parse", new Dictionary { }, CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockObjectController.Verify(obj => obj.SaveAsync(It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockObjectController.Verify(obj => obj.SaveAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(user.IsDirty); - Assert.IsNotNull(user.GetAuthData()); - Assert.IsNotNull(user.GetAuthData()["parse"]); + Assert.IsNotNull(user.AuthData); + Assert.IsNotNull(user.AuthData["parse"]); Assert.AreEqual("some0neTol4v4", user.ObjectId); Assert.AreEqual("ofWords", user["garden"]); }); @@ -595,46 +621,48 @@ public Task TestUnlink() IObjectState state = new MutableObjectState { ObjectId = "some0neTol4v4", - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" }, - { "authData", new Dictionary { - { "parse", new Dictionary() } - }} - } + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu", + ["authData"] = new Dictionary + { + ["parse"] = new Dictionary { } + } + } }; + IObjectState newState = new MutableObjectState { - ServerData = new Dictionary() { - { "garden", "ofWords" } - } + ServerData = new Dictionary + { + ["garden"] = "ofWords" + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); + + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + ParseUser user = client.GenerateObjectFromState(state, "_User"); + Mock mockObjectController = new Mock(); - mockObjectController.Setup(obj => obj.SaveAsync(It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny())).Returns(Task.FromResult(newState)); - Mock mockCurrentUserController = new Mock(); + mockObjectController.Setup(obj => obj.SaveAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(newState)); + + Mock mockCurrentUserController = new Mock { }; mockCurrentUserController.Setup(obj => obj.IsCurrent(user)).Returns(true); - ParseCorePlugins.Instance = new ParseCorePlugins - { - ObjectController = mockObjectController.Object, - CurrentUserController = mockCurrentUserController.Object, - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); - return user.UnlinkFromAsync("parse", CancellationToken.None).ContinueWith(t => + hub.ObjectController = mockObjectController.Object; + hub.CurrentUserController = mockCurrentUserController.Object; + + return user.UnlinkFromAsync("parse", CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockObjectController.Verify(obj => obj.SaveAsync(It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockObjectController.Verify(obj => obj.SaveAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(user.IsDirty); - Assert.IsNotNull(user.GetAuthData()); - Assert.IsFalse(user.GetAuthData().ContainsKey("parse")); + Assert.IsNotNull(user.AuthData); + Assert.IsFalse(user.AuthData.ContainsKey("parse")); Assert.AreEqual("some0neTol4v4", user.ObjectId); Assert.AreEqual("ofWords", user["garden"]); }); @@ -647,47 +675,49 @@ public Task TestUnlinkNonCurrentUser() IObjectState state = new MutableObjectState { ObjectId = "some0neTol4v4", - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" }, - { "authData", new Dictionary { - { "parse", new Dictionary() } - }} - } + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu", + ["authData"] = new Dictionary + { + ["parse"] = new Dictionary { } + } + } }; + IObjectState newState = new MutableObjectState { - ServerData = new Dictionary() { - { "garden", "ofWords" } - } + ServerData = new Dictionary + { + ["garden"] = "ofWords" + } }; - ParseUser user = ParseObjectExtensions.FromState(state, "_User"); + + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + ParseUser user = client.GenerateObjectFromState(state, "_User"); + Mock mockObjectController = new Mock(); - mockObjectController.Setup(obj => obj.SaveAsync(It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny())).Returns(Task.FromResult(newState)); - Mock mockCurrentUserController = new Mock(); + mockObjectController.Setup(obj => obj.SaveAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(newState)); + + Mock mockCurrentUserController = new Mock { }; mockCurrentUserController.Setup(obj => obj.IsCurrent(user)).Returns(false); - ParseCorePlugins.Instance = new ParseCorePlugins - { - ObjectController = mockObjectController.Object, - CurrentUserController = mockCurrentUserController.Object, - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); - return user.UnlinkFromAsync("parse", CancellationToken.None).ContinueWith(t => + hub.ObjectController = mockObjectController.Object; + hub.CurrentUserController = mockCurrentUserController.Object; + + return user.UnlinkFromAsync("parse", CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockObjectController.Verify(obj => obj.SaveAsync(It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockObjectController.Verify(obj => obj.SaveAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(user.IsDirty); - Assert.IsNotNull(user.GetAuthData()); - Assert.IsTrue(user.GetAuthData().ContainsKey("parse")); - Assert.IsNull(user.GetAuthData()["parse"]); + Assert.IsNotNull(user.AuthData); + Assert.IsTrue(user.AuthData.ContainsKey("parse")); + Assert.IsNull(user.AuthData["parse"]); Assert.AreEqual("some0neTol4v4", user.ObjectId); Assert.AreEqual("ofWords", user["garden"]); }); @@ -700,33 +730,31 @@ public Task TestLogInWith() IObjectState state = new MutableObjectState { ObjectId = "some0neTol4v4", - ServerData = new Dictionary() { - { "sessionToken", "llaKcolnu" } - } + ServerData = new Dictionary + { + ["sessionToken"] = "llaKcolnu" + } }; - Mock mockController = new Mock(); - mockController.Setup(obj => obj.LogInAsync("parse", - It.IsAny>(), - It.IsAny())).Returns(Task.FromResult(state)); - ParseCorePlugins.Instance = new ParseCorePlugins - { - UserController = mockController.Object - }; - ParseObject.RegisterDerivative(); - ParseObject.RegisterDerivative(); + MutableServiceHub hub = new MutableServiceHub { }; + ParseClient client = new ParseClient(new ServerConnectionData { Test = true }, hub); - return ParseUserExtensions.LogInWithAsync("parse", new Dictionary(), CancellationToken.None).ContinueWith(t => + Mock mockController = new Mock { }; + mockController.Setup(obj => obj.LogInAsync("parse", It.IsAny>(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(state)); + + hub.UserController = mockController.Object; + + return client.LogInWithAsync("parse", new Dictionary { }, CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - mockController.Verify(obj => obj.LogInAsync("parse", - It.IsAny>(), - It.IsAny()), Times.Exactly(1)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + + mockController.Verify(obj => obj.LogInAsync("parse", It.IsAny>(), It.IsAny(), It.IsAny()), Times.Exactly(1)); - ParseUser user = t.Result; - Assert.IsNotNull(user.GetAuthData()); - Assert.IsNotNull(user.GetAuthData()["parse"]); + ParseUser user = task.Result; + + Assert.IsNotNull(user.AuthData); + Assert.IsNotNull(user.AuthData["parse"]); Assert.AreEqual("some0neTol4v4", user.ObjectId); }); } @@ -735,34 +763,23 @@ public Task TestLogInWith() public void TestImmutableKeys() { ParseUser user = new ParseUser(); - string[] immutableKeys = new string[] { - "sessionToken", "isNew" - }; + string[] immutableKeys = new string[] { "sessionToken", "isNew" }; foreach (string key in immutableKeys) { - Assert.ThrowsException(() => - user[key] = "1234567890" - ); + Assert.ThrowsException(() => user[key] = "1234567890"); - Assert.ThrowsException(() => - user.Add(key, "1234567890") - ); + Assert.ThrowsException(() => user.Add(key, "1234567890")); - Assert.ThrowsException(() => - user.AddRangeUniqueToList(key, new string[] { "1234567890" }) - ); + Assert.ThrowsException(() => user.AddRangeUniqueToList(key, new string[] { "1234567890" })); - Assert.ThrowsException(() => - user.Remove(key) - ); + Assert.ThrowsException(() => user.Remove(key)); - Assert.ThrowsException(() => - user.RemoveAllFromList(key, new string[] { "1234567890" }) - ); + Assert.ThrowsException(() => user.RemoveAllFromList(key, new string[] { "1234567890" })); } - // Other special keys should be good + // Other special keys should be good. + user["username"] = "username"; user["password"] = "password"; } diff --git a/Parse/Abstractions/Library/CustomServiceHub.cs b/Parse/Abstractions/Library/CustomServiceHub.cs index cea2a8d2..a45fbc06 100644 --- a/Parse/Abstractions/Library/CustomServiceHub.cs +++ b/Parse/Abstractions/Library/CustomServiceHub.cs @@ -7,7 +7,7 @@ namespace Parse.Abstractions.Library { public abstract class CustomServiceHub : ICustomServiceHub { - public virtual IServiceHub Services { get; protected set; } + public virtual IServiceHub Services { get; internal set; } public virtual IServiceHubCloner Cloner => Services.Cloner; diff --git a/Parse/Abstractions/Management/IParseCurrentUserController.cs b/Parse/Abstractions/Management/IParseCurrentUserController.cs index c61b74ce..df9246d2 100644 --- a/Parse/Abstractions/Management/IParseCurrentUserController.cs +++ b/Parse/Abstractions/Management/IParseCurrentUserController.cs @@ -2,13 +2,14 @@ using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; namespace Parse.Core.Internal { public interface IParseCurrentUserController : IParseObjectCurrentController { - Task GetCurrentSessionTokenAsync(CancellationToken cancellationToken); + Task GetCurrentSessionTokenAsync(IServiceHub serviceHub, CancellationToken cancellationToken = default); - Task LogOutAsync(CancellationToken cancellationToken); + Task LogOutAsync(IServiceHub serviceHub, CancellationToken cancellationToken = default); } } diff --git a/Parse/Abstractions/Management/IParseObjectClassController.cs b/Parse/Abstractions/Management/IParseObjectClassController.cs index b63933ef..d8436434 100644 --- a/Parse/Abstractions/Management/IParseObjectClassController.cs +++ b/Parse/Abstractions/Management/IParseObjectClassController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Parse.Abstractions.Library; namespace Parse.Core.Internal { @@ -11,13 +12,13 @@ public interface IParseObjectClassController bool GetClassMatch(string className, Type type); - void AddValid(Type t); + void AddValid(Type type); - void RemoveClass(Type t); + void RemoveClass(Type type); - void AddRegisterHook(Type t, Action action); + void AddRegisterHook(Type type, Action action); - ParseObject Instantiate(string className); + ParseObject Instantiate(string className, IServiceHub serviceHub); IDictionary GetPropertyMappings(string className); diff --git a/Parse/Abstractions/Management/IParseObjectController.cs b/Parse/Abstractions/Management/IParseObjectController.cs index e0f95415..9ee6c31c 100644 --- a/Parse/Abstractions/Management/IParseObjectController.cs +++ b/Parse/Abstractions/Management/IParseObjectController.cs @@ -3,19 +3,20 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; namespace Parse.Core.Internal { public interface IParseObjectController { - Task FetchAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken); + Task FetchAsync(IObjectState state, string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default); - Task SaveAsync(IObjectState state, IDictionary operations, string sessionToken, CancellationToken cancellationToken); + Task SaveAsync(IObjectState state, IDictionary operations, string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default); - IList> SaveAllAsync(IList states, IList> operationsList, string sessionToken, CancellationToken cancellationToken); + IList> SaveAllAsync(IList states, IList> operationsList, string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default); - Task DeleteAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken); + Task DeleteAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken = default); - IList DeleteAllAsync(IList states, string sessionToken, CancellationToken cancellationToken); + IList DeleteAllAsync(IList states, string sessionToken, CancellationToken cancellationToken = default); } } diff --git a/Parse/Abstractions/Management/IParseObjectCurrentController.cs b/Parse/Abstractions/Management/IParseObjectCurrentController.cs index c1f0d608..c6dd3cd0 100644 --- a/Parse/Abstractions/Management/IParseObjectCurrentController.cs +++ b/Parse/Abstractions/Management/IParseObjectCurrentController.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; namespace Parse.Core.Internal { @@ -24,7 +25,7 @@ public interface IParseObjectCurrentController where T : ParseObject /// Gets the persisted current . /// /// The cancellation token. - Task GetAsync(CancellationToken cancellationToken = default); + Task GetAsync(IServiceHub serviceHub, CancellationToken cancellationToken = default); /// /// Returns a that resolves to true if current diff --git a/Parse/Abstractions/Management/IParseUserController.cs b/Parse/Abstractions/Management/IParseUserController.cs index 7a3b2411..0eed3e99 100644 --- a/Parse/Abstractions/Management/IParseUserController.cs +++ b/Parse/Abstractions/Management/IParseUserController.cs @@ -3,20 +3,21 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; namespace Parse.Core.Internal { public interface IParseUserController { - Task SignUpAsync(IObjectState state, IDictionary operations, CancellationToken cancellationToken); + Task SignUpAsync(IObjectState state, IDictionary operations, IServiceHub serviceHub, CancellationToken cancellationToken = default); - Task LogInAsync(string username, string password, CancellationToken cancellationToken); + Task LogInAsync(string username, string password, IServiceHub serviceHub, CancellationToken cancellationToken = default); - Task LogInAsync(string authType, IDictionary data, CancellationToken cancellationToken); + Task LogInAsync(string authType, IDictionary data, IServiceHub serviceHub, CancellationToken cancellationToken = default); - Task GetUserAsync(string sessionToken, CancellationToken cancellationToken); + Task GetUserAsync(string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default); - Task RequestPasswordResetAsync(string email, CancellationToken cancellationToken); + Task RequestPasswordResetAsync(string email, CancellationToken cancellationToken = default); bool RevocableSessionEnabled { get; set; } diff --git a/Parse/Abstractions/Management/Tracking/IParseFieldOperation.cs b/Parse/Abstractions/Management/Tracking/IParseFieldOperation.cs index 1dd018c2..2fa63034 100644 --- a/Parse/Abstractions/Management/Tracking/IParseFieldOperation.cs +++ b/Parse/Abstractions/Management/Tracking/IParseFieldOperation.cs @@ -1,5 +1,7 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. +using Parse.Abstractions.Library; + namespace Parse.Core.Internal { /// @@ -15,7 +17,7 @@ public interface IParseFieldOperation /// Parse as part of a save operation. /// /// An object to be JSONified. - object Encode(); + object Encode(IServiceHub serviceHub); /// /// Returns a field operation that is composed of a previous operation followed by diff --git a/Parse/Abstractions/Platform/Analytics/IParseAnalyticsController.cs b/Parse/Abstractions/Platform/Analytics/IParseAnalyticsController.cs index 334abad3..a8d73489 100644 --- a/Parse/Abstractions/Platform/Analytics/IParseAnalyticsController.cs +++ b/Parse/Abstractions/Platform/Analytics/IParseAnalyticsController.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - +using Parse.Abstractions.Library; namespace Parse.Analytics.Internal { @@ -20,10 +20,7 @@ public interface IParseAnalyticsController /// The session token for the event. /// The asynchonous cancellation token. /// A that will complete successfully once the event has been set to be tracked. - Task TrackEventAsync(string name, - IDictionary dimensions, - string sessionToken, - CancellationToken cancellationToken); + Task TrackEventAsync(string name, IDictionary dimensions, string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default); /// /// Tracks an app open for the specified event. @@ -32,8 +29,6 @@ Task TrackEventAsync(string name, /// The token of the current session. /// The asynchronous cancellation token. /// A the will complete successfully once app openings for the target push notification have been set to be tracked. - Task TrackAppOpenedAsync(string pushHash, - string sessionToken, - CancellationToken cancellationToken); + Task TrackAppOpenedAsync(string pushHash, string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default); } } diff --git a/Parse/Abstractions/Platform/Code/IParseCloudCodeController.cs b/Parse/Abstractions/Platform/Code/IParseCloudCodeController.cs index 834a8984..da1199b1 100644 --- a/Parse/Abstractions/Platform/Code/IParseCloudCodeController.cs +++ b/Parse/Abstractions/Platform/Code/IParseCloudCodeController.cs @@ -3,14 +3,12 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; namespace Parse.Core.Internal { public interface IParseCloudCodeController { - Task CallFunctionAsync(string name, - IDictionary parameters, - string sessionToken, - CancellationToken cancellationToken); + Task CallFunctionAsync(string name, IDictionary parameters, string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default); } } diff --git a/Parse/Abstractions/Platform/Configuration/IParseConfigurationController.cs b/Parse/Abstractions/Platform/Configuration/IParseConfigurationController.cs index b5967390..583a98dd 100644 --- a/Parse/Abstractions/Platform/Configuration/IParseConfigurationController.cs +++ b/Parse/Abstractions/Platform/Configuration/IParseConfigurationController.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; namespace Parse.Core.Internal { @@ -15,6 +16,6 @@ public interface IParseConfigurationController /// The config async. /// Session token. /// Cancellation token. - Task FetchConfigAsync(string sessionToken, CancellationToken cancellationToken); + Task FetchConfigAsync(string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default); } } diff --git a/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigurationController.cs b/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigurationController.cs index 4ef36361..de1f510b 100644 --- a/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigurationController.cs +++ b/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigurationController.cs @@ -1,6 +1,7 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System.Threading.Tasks; +using Parse.Abstractions.Library; namespace Parse.Core.Internal { @@ -10,7 +11,7 @@ public interface IParseCurrentConfigurationController /// Gets the current config async. /// /// The current config async. - Task GetCurrentConfigAsync(); + Task GetCurrentConfigAsync(IServiceHub serviceHub); /// /// Sets the current config async. diff --git a/Parse/Abstractions/Platform/Installation/IParseInstallationCoder.cs b/Parse/Abstractions/Platform/Installation/IParseInstallationCoder.cs index 0182b610..2394cd58 100644 --- a/Parse/Abstractions/Platform/Installation/IParseInstallationCoder.cs +++ b/Parse/Abstractions/Platform/Installation/IParseInstallationCoder.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Parse.Abstractions.Library; namespace Parse.Push.Internal { @@ -7,6 +8,6 @@ public interface IParseInstallationCoder { IDictionary Encode(ParseInstallation installation); - ParseInstallation Decode(IDictionary data); + ParseInstallation Decode(IDictionary data, IServiceHub serviceHub); } } \ No newline at end of file diff --git a/Parse/Abstractions/Platform/Notifications/IParsePushChannelsController.cs b/Parse/Abstractions/Platform/Notifications/IParsePushChannelsController.cs index befa37a5..7c426b5e 100644 --- a/Parse/Abstractions/Platform/Notifications/IParsePushChannelsController.cs +++ b/Parse/Abstractions/Platform/Notifications/IParsePushChannelsController.cs @@ -3,12 +3,14 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; namespace Parse.Push.Internal { public interface IParsePushChannelsController { - Task SubscribeAsync(IEnumerable channels, CancellationToken cancellationToken); - Task UnsubscribeAsync(IEnumerable channels, CancellationToken cancellationToken); + Task SubscribeAsync(IEnumerable channels, IServiceHub serviceHub, CancellationToken cancellationToken); + + Task UnsubscribeAsync(IEnumerable channels, IServiceHub serviceHub, CancellationToken cancellationToken); } } diff --git a/Parse/Abstractions/Platform/Notifications/IParsePushController.cs b/Parse/Abstractions/Platform/Notifications/IParsePushController.cs index f567df76..7e7208c6 100644 --- a/Parse/Abstractions/Platform/Notifications/IParsePushController.cs +++ b/Parse/Abstractions/Platform/Notifications/IParsePushController.cs @@ -2,11 +2,12 @@ using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; namespace Parse.Push.Internal { public interface IParsePushController { - Task SendPushNotificationAsync(IPushState state, CancellationToken cancellationToken); + Task SendPushNotificationAsync(IPushState state, IServiceHub serviceHub, CancellationToken cancellationToken = default); } } diff --git a/Parse/Abstractions/Platform/Session/IParseSessionController.cs b/Parse/Abstractions/Platform/Session/IParseSessionController.cs index 304b2c23..2b8f7af4 100644 --- a/Parse/Abstractions/Platform/Session/IParseSessionController.cs +++ b/Parse/Abstractions/Platform/Session/IParseSessionController.cs @@ -2,16 +2,17 @@ using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; namespace Parse.Core.Internal { public interface IParseSessionController { - Task GetSessionAsync(string sessionToken, CancellationToken cancellationToken); + Task GetSessionAsync(string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default); - Task RevokeAsync(string sessionToken, CancellationToken cancellationToken); + Task RevokeAsync(string sessionToken, CancellationToken cancellationToken = default); - Task UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken); + Task UpgradeToRevocableSessionAsync(string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default); bool IsRevocableSessionToken(string sessionToken); } diff --git a/Parse/Abstractions/Query/IParseQueryController.cs b/Parse/Abstractions/Query/IParseQueryController.cs index bc473785..db7a5fe9 100644 --- a/Parse/Abstractions/Query/IParseQueryController.cs +++ b/Parse/Abstractions/Query/IParseQueryController.cs @@ -8,10 +8,10 @@ namespace Parse.Core.Internal { public interface IParseQueryController { - Task> FindAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject; + Task> FindAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken = default) where T : ParseObject; - Task CountAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject; + Task CountAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken = default) where T : ParseObject; - Task FirstAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject; + Task FirstAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken = default) where T : ParseObject; } } diff --git a/Parse/AnalyticsServiceExtensions.cs b/Parse/AnalyticsServiceExtensions.cs index 72c02640..0418b50e 100644 --- a/Parse/AnalyticsServiceExtensions.cs +++ b/Parse/AnalyticsServiceExtensions.cs @@ -84,7 +84,7 @@ public static Task TrackAnalyticsEventAsync(this IServiceHub serviceHub, string throw new ArgumentException("A name for the custom event must be provided."); } - return serviceHub.CurrentUserController.GetCurrentSessionTokenAsync(CancellationToken.None).OnSuccess(task => serviceHub.AnalyticsController.TrackEventAsync(name, dimensions, task.Result, CancellationToken.None)).Unwrap(); + return serviceHub.CurrentUserController.GetCurrentSessionTokenAsync(serviceHub).OnSuccess(task => serviceHub.AnalyticsController.TrackEventAsync(name, dimensions, task.Result, serviceHub)).Unwrap(); } /// @@ -94,6 +94,6 @@ public static Task TrackAnalyticsEventAsync(this IServiceHub serviceHub, string /// An identifying hash for a given push notification, /// passed down from the server. /// An Async Task that can be waited on or ignored. - static Task TrackLaunchWithPushHashAsync(this IServiceHub serviceHub, string pushHash = null) => serviceHub.CurrentUserController.GetCurrentSessionTokenAsync(CancellationToken.None).OnSuccess(task => serviceHub.AnalyticsController.TrackAppOpenedAsync(pushHash, task.Result, CancellationToken.None)).Unwrap(); + static Task TrackLaunchWithPushHashAsync(this IServiceHub serviceHub, string pushHash = null) => serviceHub.CurrentUserController.GetCurrentSessionTokenAsync(serviceHub).OnSuccess(task => serviceHub.AnalyticsController.TrackAppOpenedAsync(pushHash, task.Result, serviceHub)).Unwrap(); } } diff --git a/Parse/CloudCodeServiceExtensions.cs b/Parse/CloudCodeServiceExtensions.cs index dd14bf18..841e5efe 100644 --- a/Parse/CloudCodeServiceExtensions.cs +++ b/Parse/CloudCodeServiceExtensions.cs @@ -49,6 +49,6 @@ public static class CloudCodeServiceExtensions /// ParseObjects themselves. /// The cancellation token. /// The result of the cloud call. - public static Task CallCloudCodeFunctionAsync(this IServiceHub serviceHub, string name, IDictionary parameters, CancellationToken cancellationToken) => serviceHub.CloudCodeController.CallFunctionAsync(name, parameters, serviceHub.GetCurrentSessionToken(), cancellationToken); + public static Task CallCloudCodeFunctionAsync(this IServiceHub serviceHub, string name, IDictionary parameters, CancellationToken cancellationToken) => serviceHub.CloudCodeController.CallFunctionAsync(name, parameters, serviceHub.GetCurrentSessionToken(), serviceHub, cancellationToken); } } diff --git a/Parse/ConfigurationServiceExtensions.cs b/Parse/ConfigurationServiceExtensions.cs index 20df985a..d3d61ead 100644 --- a/Parse/ConfigurationServiceExtensions.cs +++ b/Parse/ConfigurationServiceExtensions.cs @@ -10,9 +10,9 @@ namespace Parse { public static class ConfigurationServiceExtensions { - public static ParseConfiguration BuildConfiguration(this IServiceHub serviceHub, IDictionary configurationData) => ParseConfiguration.Create(configurationData, serviceHub.Decoder); + public static ParseConfiguration BuildConfiguration(this IServiceHub serviceHub, IDictionary configurationData) => ParseConfiguration.Create(configurationData, serviceHub.Decoder, serviceHub); - public static ParseConfiguration BuildConfiguration(this IParseDataDecoder dataDecoder, IDictionary configurationData) => ParseConfiguration.Create(configurationData, dataDecoder); + public static ParseConfiguration BuildConfiguration(this IParseDataDecoder dataDecoder, IDictionary configurationData, IServiceHub serviceHub) => ParseConfiguration.Create(configurationData, dataDecoder, serviceHub); #warning Investigate if these methods which simply block a thread waiting for an asynchronous process to complete should be eliminated. @@ -20,9 +20,9 @@ public static class ConfigurationServiceExtensions /// Gets the latest fetched ParseConfig. /// /// ParseConfig object - public static ParseConfiguration GetCurrentConfig(this IServiceHub serviceHub) + public static ParseConfiguration GetCurrentConfiguration(this IServiceHub serviceHub) { - Task task = serviceHub.ConfigurationController.CurrentConfigurationController.GetCurrentConfigAsync(); + Task task = serviceHub.ConfigurationController.CurrentConfigurationController.GetCurrentConfigAsync(serviceHub); task.Wait(); return task.Result; @@ -37,6 +37,6 @@ public static ParseConfiguration GetCurrentConfig(this IServiceHub serviceHub) /// /// The cancellation token. /// ParseConfig object that was fetched - public static Task GetAsync(this IServiceHub serviceHub, CancellationToken cancellationToken = default) => serviceHub.ConfigurationController.FetchConfigAsync(serviceHub.GetCurrentSessionToken(), cancellationToken); + public static Task GetConfigurationAsync(this IServiceHub serviceHub, CancellationToken cancellationToken = default) => serviceHub.ConfigurationController.FetchConfigAsync(serviceHub.GetCurrentSessionToken(), serviceHub, cancellationToken); } } diff --git a/Parse/InstallationServiceExtensions.cs b/Parse/InstallationServiceExtensions.cs index 73b766bc..9c9c549a 100644 --- a/Parse/InstallationServiceExtensions.cs +++ b/Parse/InstallationServiceExtensions.cs @@ -32,7 +32,7 @@ public static class InstallationServiceExtensions /// public static ParseInstallation GetCurrentInstallation(this IServiceHub serviceHub) { - Task task = serviceHub.CurrentInstallationController.GetAsync(CancellationToken.None); + Task task = serviceHub.CurrentInstallationController.GetAsync(serviceHub); // TODO (hallucinogen): this will absolutely break on Unity, but how should we resolve this? task.Wait(); diff --git a/Parse/Library/ConcurrentUserServiceHubCloner.cs b/Parse/Library/ConcurrentUserServiceHubCloner.cs index b1bd729d..2a6e9609 100644 --- a/Parse/Library/ConcurrentUserServiceHubCloner.cs +++ b/Parse/Library/ConcurrentUserServiceHubCloner.cs @@ -9,7 +9,10 @@ public class ConcurrentUserServiceHubCloner : IServiceHubCloner { public IServiceHub BuildHub(in IServiceHub reference, IServiceHubComposer composer) { - throw new NotImplementedException { }; + return new MutableServiceHub + { + + }; } } } diff --git a/Parse/Library/MutableServiceHub.cs b/Parse/Library/MutableServiceHub.cs index f8431cc9..ad2148e1 100644 --- a/Parse/Library/MutableServiceHub.cs +++ b/Parse/Library/MutableServiceHub.cs @@ -45,7 +45,7 @@ public class MutableServiceHub : IMutableServiceHub public IParseCurrentInstallationController CurrentInstallationController { get; set; } public IParseInstallationDataFinalizer InstallationDataFinalizer { get; set; } - public void SetDefaults(IServerConnectionData connectionData) + public MutableServiceHub SetDefaults(IServerConnectionData connectionData = default) { ServerConnectionData ??= connectionData; MetadataController ??= new MetadataController @@ -58,7 +58,7 @@ public void SetDefaults(IServerConnectionData connectionData) WebClient ??= new UniversalWebClient { }; StorageController ??= new StorageController { }; - ClassController ??= new ObjectSubclassingController { }; + ClassController ??= new ParseObjectClassController { }; Decoder ??= new ParseDataDecoder(ClassController); @@ -82,6 +82,8 @@ public void SetDefaults(IServerConnectionData connectionData) CurrentInstallationController ??= new ParseCurrentInstallationController(InstallationController, StorageController, InstallationCoder, ClassController); PushChannelsController ??= new ParsePushChannelsController(CurrentInstallationController); InstallationDataFinalizer ??= new ParseInstallationDataFinalizer { }; + + return this; } } } diff --git a/Parse/Library/ServiceHub.cs b/Parse/Library/ServiceHub.cs index b8c14711..e3429d25 100644 --- a/Parse/Library/ServiceHub.cs +++ b/Parse/Library/ServiceHub.cs @@ -25,7 +25,7 @@ public class ServiceHub : IServiceHub public IWebClient WebClient => LateInitializer.GetValue(() => new UniversalWebClient { }); public IStorageController StorageController => LateInitializer.GetValue(() => new StorageController { }); - public IParseObjectClassController ClassController => LateInitializer.GetValue(() => new ObjectSubclassingController { }); + public IParseObjectClassController ClassController => LateInitializer.GetValue(() => new ParseObjectClassController { }); public IParseDataDecoder Decoder => LateInitializer.GetValue(() => new ParseDataDecoder(ClassController)); @@ -49,5 +49,7 @@ public class ServiceHub : IServiceHub public IParsePushController PushController => LateInitializer.GetValue(() => new ParsePushController(CommandRunner, CurrentUserController)); public IParseCurrentInstallationController CurrentInstallationController => LateInitializer.GetValue(() => new ParseCurrentInstallationController(InstallationController, StorageController, InstallationCoder, ClassController)); public IParseInstallationDataFinalizer InstallationDataFinalizer => LateInitializer.GetValue(() => new ParseInstallationDataFinalizer { }); + + public bool Reset() => LateInitializer.Used && LateInitializer.Reset(); } } diff --git a/Parse/Library/Utilities/LateInitializer.cs b/Parse/Library/Utilities/LateInitializer.cs index 5bc0655f..c50d22ff 100644 --- a/Parse/Library/Utilities/LateInitializer.cs +++ b/Parse/Library/Utilities/LateInitializer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Parse.Library.Utilities { @@ -8,7 +9,7 @@ namespace Parse.Library.Utilities /// public class LateInitializer { - Lazy, object>> Storage { get; } = new Lazy, object>> { }; + Lazy, object>> Storage { get; set; } = new Lazy, object>> { }; public TData GetValue(Func generator) { @@ -27,5 +28,38 @@ public TData GetValue(Func generator) } } } + + public bool ClearValue() + { + lock (Storage) + { + if (Storage.IsValueCreated && Storage.Value.Keys.OfType>().FirstOrDefault() is { } key) + { + lock (key) + { + Storage.Value.Remove(key as Func); + return true; + } + } + } + + return false; + } + + public bool Reset() + { + lock (Storage) + { + if (Storage.IsValueCreated) + { + Storage.Value.Clear(); + return true; + } + } + + return false; + } + + public bool Used => Storage.IsValueCreated; } } diff --git a/Parse/Management/ObjectSubclassInfo.cs b/Parse/Management/ObjectSubclassInfo.cs deleted file mode 100644 index 815df622..00000000 --- a/Parse/Management/ObjectSubclassInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Parse.Common.Internal; - -namespace Parse.Core.Internal -{ - internal class ObjectSubclassInfo - { - public ObjectSubclassInfo(Type type, ConstructorInfo constructor) => (TypeInfo, DeclaredName, Constructor, PropertyMappings) = (type.GetTypeInfo(), TypeInfo.GetParseClassName(), Constructor = constructor, PropertyMappings = type.GetProperties().Select(property => (Property: property, FieldNameAttribute: property.GetCustomAttribute(true))).Where(set => set.FieldNameAttribute is { }).ToDictionary(set => set.Property.Name, set => set.FieldNameAttribute.FieldName)); - - public TypeInfo TypeInfo { get; } - - public string DeclaredName { get; } - - public IDictionary PropertyMappings { get; } - - public ParseObject Instantiate() => Constructor.Invoke(default) as ParseObject; - - ConstructorInfo Constructor { get; } - - } -} diff --git a/Parse/Management/ParseCurrentUserController.cs b/Parse/Management/ParseCurrentUserController.cs index 1b6ba7e7..b601db90 100644 --- a/Parse/Management/ParseCurrentUserController.cs +++ b/Parse/Management/ParseCurrentUserController.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; namespace Parse.Core.Internal @@ -75,7 +76,7 @@ public Task SetAsync(ParseUser user, CancellationToken cancellationToken) => Tas return saveTask; }).Unwrap(), cancellationToken); - public Task GetAsync(CancellationToken cancellationToken) + public Task GetAsync(IServiceHub serviceHub, CancellationToken cancellationToken = default) { ParseUser cachedCurrent; @@ -91,7 +92,7 @@ public Task GetAsync(CancellationToken cancellationToken) if (data is string { } serialization) { - user = ClassController.GenerateObjectFromState(ParseObjectCoder.Instance.Decode(Json.Parse(serialization) as IDictionary, Decoder), "_User"); + user = ClassController.GenerateObjectFromState(ParseObjectCoder.Instance.Decode(Json.Parse(serialization) as IDictionary, Decoder, serviceHub), "_User", serviceHub); } return CurrentUser = user; @@ -120,8 +121,8 @@ public void ClearFromDisk() } } - public Task GetCurrentSessionTokenAsync(CancellationToken cancellationToken) => GetAsync(cancellationToken).OnSuccess(task => task.Result?.SessionToken); + public Task GetCurrentSessionTokenAsync(IServiceHub serviceHub, CancellationToken cancellationToken = default) => GetAsync(serviceHub, cancellationToken).OnSuccess(task => task.Result?.SessionToken); - public Task LogOutAsync(CancellationToken cancellationToken) => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => GetAsync(cancellationToken)).Unwrap().OnSuccess(t => ClearFromDisk()), cancellationToken); + public Task LogOutAsync(IServiceHub serviceHub, CancellationToken cancellationToken = default) => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => GetAsync(serviceHub, cancellationToken)).Unwrap().OnSuccess(t => ClearFromDisk()), cancellationToken); } } diff --git a/Parse/Management/ParseObjectClass.cs b/Parse/Management/ParseObjectClass.cs new file mode 100644 index 00000000..60fa8059 --- /dev/null +++ b/Parse/Management/ParseObjectClass.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Parse.Common.Internal; + +namespace Parse.Core.Internal +{ + internal class ParseObjectClass + { + public ParseObjectClass(Type type, ConstructorInfo constructor) => (TypeInfo, DeclaredName, Constructor, PropertyMappings) = (type.GetTypeInfo(), TypeInfo.GetParseClassName(), Constructor = constructor, PropertyMappings = type.GetProperties().Select(property => (Property: property, FieldNameAttribute: property.GetCustomAttribute(true))).Where(set => set.FieldNameAttribute is { }).ToDictionary(set => set.Property.Name, set => set.FieldNameAttribute.FieldName)); + + public TypeInfo TypeInfo { get; } + + public string DeclaredName { get; } + + public IDictionary PropertyMappings { get; } + + public ParseObject Instantiate() => Constructor.Invoke(default) as ParseObject; + + ConstructorInfo Constructor { get; } + } +} diff --git a/Parse/Management/ObjectSubclassingController.cs b/Parse/Management/ParseObjectClassController.cs similarity index 69% rename from Parse/Management/ObjectSubclassingController.cs rename to Parse/Management/ParseObjectClassController.cs index fef91707..4b1567c8 100644 --- a/Parse/Management/ObjectSubclassingController.cs +++ b/Parse/Management/ParseObjectClassController.cs @@ -2,29 +2,30 @@ using System.Collections.Generic; using System.Reflection; using System.Threading; +using Parse.Abstractions.Library; using Parse.Common.Internal; namespace Parse.Core.Internal { - internal class ObjectSubclassingController : IParseObjectClassController + internal class ParseObjectClassController : IParseObjectClassController { // Class names starting with _ are documented to be reserved. Use this one here to allow us to "inherit" certain properties. static string ReservedParseObjectClassName { get; } = "_ParseObject"; ReaderWriterLockSlim Mutex { get; } = new ReaderWriterLockSlim { }; - IDictionary RegisteredSubclasses { get; } = new Dictionary { }; + IDictionary Classes { get; } = new Dictionary { }; Dictionary RegisterActions { get; set; } = new Dictionary { }; - public ObjectSubclassingController() => AddValid(typeof(ParseObject)); + public ParseObjectClassController() => AddValid(typeof(ParseObject)); public string GetClassName(Type type) => type == typeof(ParseObject) ? ReservedParseObjectClassName : type.GetParseClassName(); public Type GetType(string className) { Mutex.EnterReadLock(); - RegisteredSubclasses.TryGetValue(className, out ObjectSubclassInfo info); + Classes.TryGetValue(className, out ParseObjectClass info); Mutex.ExitReadLock(); return info?.TypeInfo.AsType(); @@ -33,10 +34,10 @@ public Type GetType(string className) public bool GetClassMatch(string className, Type type) { Mutex.EnterReadLock(); - RegisteredSubclasses.TryGetValue(className, out ObjectSubclassInfo subclassInfo); + Classes.TryGetValue(className, out ParseObjectClass subclassInfo); Mutex.ExitReadLock(); - return subclassInfo == null ? type == typeof(ParseObject) : subclassInfo.TypeInfo == type.GetTypeInfo(); + return subclassInfo is { } ? subclassInfo.TypeInfo == type.GetTypeInfo() : type == typeof(ParseObject); } public void AddValid(Type type) @@ -58,17 +59,17 @@ public void AddValid(Type type) Mutex.EnterWriteLock(); - if (RegisteredSubclasses.TryGetValue(className, out ObjectSubclassInfo previousInfo)) + if (Classes.TryGetValue(className, out ParseObjectClass 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. + // Previous subclass is parent of new child, fallthrough and actually register this class. /* Do nothing */ } else @@ -79,12 +80,12 @@ public void AddValid(Type type) ConstructorInfo constructor = type.FindConstructor(); - if (constructor == null) + if (constructor is null) { throw new ArgumentException("Cannot register a type that does not implement the default constructor!"); } - RegisteredSubclasses[className] = new ObjectSubclassInfo(type, constructor); + Classes[className] = new ParseObjectClass(type, constructor); } finally { @@ -101,48 +102,53 @@ public void AddValid(Type type) public void RemoveClass(Type type) { Mutex.EnterWriteLock(); - RegisteredSubclasses.Remove(GetClassName(type)); + Classes.Remove(GetClassName(type)); Mutex.ExitWriteLock(); } - public void AddRegisterHook(Type t, Action action) + public void AddRegisterHook(Type type, Action action) { Mutex.EnterWriteLock(); - RegisterActions.Add(GetClassName(t), action); + RegisterActions.Add(GetClassName(type), action); Mutex.ExitWriteLock(); } - public ParseObject Instantiate(string className) + public ParseObject Instantiate(string className, IServiceHub serviceHub) { Mutex.EnterReadLock(); - RegisteredSubclasses.TryGetValue(className, out ObjectSubclassInfo info); + Classes.TryGetValue(className, out ParseObjectClass info); Mutex.ExitReadLock(); - return info is { } ? info.Instantiate() : new ParseObject(className); + return info is { } ? info.Instantiate().Bind(serviceHub) : new ParseObject(className, serviceHub); } public IDictionary GetPropertyMappings(string className) { Mutex.EnterReadLock(); - RegisteredSubclasses.TryGetValue(className, out ObjectSubclassInfo info); + Classes.TryGetValue(className, out ParseObjectClass info); if (info is null) { - RegisteredSubclasses.TryGetValue(ReservedParseObjectClassName, out info); + Classes.TryGetValue(ReservedParseObjectClassName, out info); } Mutex.ExitReadLock(); return info.PropertyMappings; } + bool SDKClassesAdded { get; set; } + // ALTERNATE NAME: AddObject, AddType, AcknowledgeType, CatalogType public void AddIntrinsic() { - AddValid(typeof(ParseUser)); - AddValid(typeof(ParseRole)); - AddValid(typeof(ParseSession)); - AddValid(typeof(ParseInstallation)); + if (!(SDKClassesAdded, SDKClassesAdded = true).SDKClassesAdded) + { + AddValid(typeof(ParseUser)); + AddValid(typeof(ParseRole)); + AddValid(typeof(ParseSession)); + AddValid(typeof(ParseInstallation)); + } } } } diff --git a/Parse/Management/ParseObjectController.cs b/Parse/Management/ParseObjectController.cs index 0017df36..914d668f 100644 --- a/Parse/Management/ParseObjectController.cs +++ b/Parse/Management/ParseObjectController.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; using Parse.Utilities; @@ -18,42 +19,29 @@ public class ParseObjectController : IParseObjectController public ParseObjectController(IParseCommandRunner commandRunner, IParseDataDecoder decoder) => (CommandRunner, Decoder) = (commandRunner, decoder); - public Task FetchAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken) + public Task FetchAsync(IObjectState state, string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default) { ParseCommand command = new ParseCommand($"classes/{Uri.EscapeDataString(state.ClassName)}/{Uri.EscapeDataString(state.ObjectId)}", method: "GET", sessionToken: sessionToken, data: default); - return CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder)); + return CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder, serviceHub)); } - public Task SaveAsync(IObjectState state, IDictionary operations, string sessionToken, CancellationToken cancellationToken) + public Task SaveAsync(IObjectState state, IDictionary operations, string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default) { - ParseCommand command = new ParseCommand(state.ObjectId == null ? $"classes/{Uri.EscapeDataString(state.ClassName)}" : $"classes/{Uri.EscapeDataString(state.ClassName)}/{state.ObjectId}", method: state.ObjectId == null ? "POST" : "PUT", sessionToken: sessionToken, data: operations.GenerateJSONObjectForSaving()); - return CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder).MutatedClone(mutableClone => mutableClone.IsNew = task.Result.Item1 == System.Net.HttpStatusCode.Created)); + ParseCommand command = new ParseCommand(state.ObjectId == null ? $"classes/{Uri.EscapeDataString(state.ClassName)}" : $"classes/{Uri.EscapeDataString(state.ClassName)}/{state.ObjectId}", method: state.ObjectId is null ? "POST" : "PUT", sessionToken: sessionToken, data: serviceHub.GenerateJSONObjectForSaving(operations)); + return CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder, serviceHub).MutatedClone(mutableClone => mutableClone.IsNew = task.Result.Item1 == System.Net.HttpStatusCode.Created)); } - public IList> SaveAllAsync(IList states, IList> operationsList, string sessionToken, CancellationToken cancellationToken) - { - List requests = states.Zip(operationsList, (item, ops) => new ParseCommand(item.ObjectId is null ? $"classes/{Uri.EscapeDataString(item.ClassName)}" : $"classes/{Uri.EscapeDataString(item.ClassName)}/{Uri.EscapeDataString(item.ObjectId)}", method: item.ObjectId == null ? "POST" : "PUT", data: ops.GenerateJSONObjectForSaving())).ToList(); - - IList>> batchTasks = ExecuteBatchRequests(requests, sessionToken, cancellationToken); - List> stateTasks = new List> { }; - - foreach (Task> task in batchTasks) - { - stateTasks.Add(task.OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result, Decoder))); - } - - return stateTasks; - } + public IList> SaveAllAsync(IList states, IList> operationsList, string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default) => ExecuteBatchRequests(states.Zip(operationsList, (item, operations) => new ParseCommand(item.ObjectId is null ? $"classes/{Uri.EscapeDataString(item.ClassName)}" : $"classes/{Uri.EscapeDataString(item.ClassName)}/{Uri.EscapeDataString(item.ObjectId)}", method: item.ObjectId is null ? "POST" : "PUT", data: serviceHub.GenerateJSONObjectForSaving(operations))).ToList(), sessionToken, cancellationToken).Select(task => task.OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result, Decoder, serviceHub))).ToList(); - public Task DeleteAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand($"classes/{state.ClassName}/{state.ObjectId}", method: "DELETE", sessionToken: sessionToken, data: null), cancellationToken: cancellationToken); + public Task DeleteAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand($"classes/{state.ClassName}/{state.ObjectId}", method: "DELETE", sessionToken: sessionToken, data: null), cancellationToken: cancellationToken); - public IList DeleteAllAsync(IList states, string sessionToken, CancellationToken cancellationToken) => ExecuteBatchRequests(states.Where(item => item.ObjectId != null).Select(item => new ParseCommand(String.Format("classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), Uri.EscapeDataString(item.ObjectId)), method: "DELETE", data: null)).ToList(), sessionToken, cancellationToken).Cast().ToList(); + public IList DeleteAllAsync(IList states, string sessionToken, CancellationToken cancellationToken = default) => ExecuteBatchRequests(states.Where(item => item.ObjectId is { }).Select(item => new ParseCommand($"classes/{Uri.EscapeDataString(item.ClassName)}/{Uri.EscapeDataString(item.ObjectId)}", method: "DELETE", data: default)).ToList(), sessionToken, cancellationToken).Cast().ToList(); int MaximumBatchSize { get; } = 50; // TODO (hallucinogen): move this out to a class to be used by Analytics - internal IList>> ExecuteBatchRequests(IList requests, string sessionToken, CancellationToken cancellationToken) + internal IList>> ExecuteBatchRequests(IList requests, string sessionToken, CancellationToken cancellationToken = default) { List>> tasks = new List>>(); int batchSize = requests.Count; @@ -73,7 +61,7 @@ internal IList>> ExecuteBatchRequests(IList>> ExecuteBatchRequest(IList requests, string sessionToken, CancellationToken cancellationToken) + IList>> ExecuteBatchRequest(IList requests, string sessionToken, CancellationToken cancellationToken = default) { int batchSize = requests.Count; diff --git a/Parse/Management/ParseUserController.cs b/Parse/Management/ParseUserController.cs index 8d2b6d9d..8d306bcd 100644 --- a/Parse/Management/ParseUserController.cs +++ b/Parse/Management/ParseUserController.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; namespace Parse.Core.Internal @@ -20,22 +21,22 @@ public class ParseUserController : IParseUserController public ParseUserController(IParseCommandRunner commandRunner, IParseDataDecoder decoder) => (CommandRunner, Decoder) = (commandRunner, decoder); - public Task SignUpAsync(IObjectState state, IDictionary operations, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand("classes/_User", method: "POST", data: operations.GenerateJSONObjectForSaving()), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder).MutatedClone(mutableClone => mutableClone.IsNew = true)); + public Task SignUpAsync(IObjectState state, IDictionary operations, IServiceHub serviceHub, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand("classes/_User", method: "POST", data: serviceHub.GenerateJSONObjectForSaving(operations)), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder, serviceHub).MutatedClone(mutableClone => mutableClone.IsNew = true)); - public Task LogInAsync(string username, string password, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand($"login?{ParseClient.BuildQueryString(new Dictionary { [nameof(username)] = username, [nameof(password)] = password })}", method: "GET", data: null), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder).MutatedClone(mutableClone => mutableClone.IsNew = task.Result.Item1 == System.Net.HttpStatusCode.Created)); + public Task LogInAsync(string username, string password, IServiceHub serviceHub, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand($"login?{ParseClient.BuildQueryString(new Dictionary { [nameof(username)] = username, [nameof(password)] = password })}", method: "GET", data: null), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder, serviceHub).MutatedClone(mutableClone => mutableClone.IsNew = task.Result.Item1 == System.Net.HttpStatusCode.Created)); - public Task LogInAsync(string authType, IDictionary data, CancellationToken cancellationToken) + public Task LogInAsync(string authType, IDictionary data, IServiceHub serviceHub, CancellationToken cancellationToken = default) { Dictionary authData = new Dictionary { [authType] = data }; - return CommandRunner.RunCommandAsync(new ParseCommand("users", method: "POST", data: new Dictionary { [nameof(authData)] = authData }), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder).MutatedClone(mutableClone => mutableClone.IsNew = task.Result.Item1 == System.Net.HttpStatusCode.Created)); + return CommandRunner.RunCommandAsync(new ParseCommand("users", method: "POST", data: new Dictionary { [nameof(authData)] = authData }), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder, serviceHub).MutatedClone(mutableClone => mutableClone.IsNew = task.Result.Item1 == System.Net.HttpStatusCode.Created)); } - public Task GetUserAsync(string sessionToken, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand("users/me", method: "GET", sessionToken: sessionToken, data: default), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder)); + public Task GetUserAsync(string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand("users/me", method: "GET", sessionToken: sessionToken, data: default), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder, serviceHub)); - public Task RequestPasswordResetAsync(string email, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand("requestPasswordReset", method: "POST", data: new Dictionary { [nameof(email)] = email }), cancellationToken: cancellationToken); + public Task RequestPasswordResetAsync(string email, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand("requestPasswordReset", method: "POST", data: new Dictionary { [nameof(email)] = email }), cancellationToken: cancellationToken); } } diff --git a/Parse/Management/Tracking/ParseAddOperation.cs b/Parse/Management/Tracking/ParseAddOperation.cs index c31defbd..a4626233 100644 --- a/Parse/Management/Tracking/ParseAddOperation.cs +++ b/Parse/Management/Tracking/ParseAddOperation.cs @@ -4,53 +4,34 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using Parse.Abstractions.Library; using Parse.Utilities; namespace Parse.Core.Internal { public class ParseAddOperation : IParseFieldOperation { - private ReadOnlyCollection objects; - public ParseAddOperation(IEnumerable objects) => this.objects = new ReadOnlyCollection(objects.ToList()); + ReadOnlyCollection Data { get; } - public object Encode() => new Dictionary { - {"__op", "Add"}, - {nameof(objects), PointerOrLocalIdEncoder.Instance.Encode(objects)} - }; + public ParseAddOperation(IEnumerable objects) => Data = new ReadOnlyCollection(objects.ToList()); - public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) + public object Encode(IServiceHub serviceHub) => new Dictionary { - if (previous == null) - { - return this; - } - if (previous is ParseDeleteOperation) - { - return new ParseSetOperation(objects.ToList()); - } - if (previous is ParseSetOperation) - { - ParseSetOperation setOp = (ParseSetOperation) previous; - IList oldList = Conversion.To>(setOp.Value); - return new ParseSetOperation(oldList.Concat(objects).ToList()); - } - if (previous is ParseAddOperation) - { - return new ParseAddOperation(((ParseAddOperation) previous).Objects.Concat(objects)); - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } + ["__op"] = "Add", + ["objects"] = PointerOrLocalIdEncoder.Instance.Encode(Data, serviceHub) + }; - public object Apply(object oldValue, string key) + public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) => previous switch { - if (oldValue == null) - { - return objects.ToList(); - } - IList oldList = Conversion.To>(oldValue); - return oldList.Concat(objects).ToList(); - } + null => this, + ParseDeleteOperation { } => new ParseSetOperation(Data.ToList()), + ParseSetOperation { } setOp => new ParseSetOperation(Conversion.To>(setOp.Value).Concat(Data).ToList()), + ParseAddOperation { } addition => new ParseAddOperation(addition.Objects.Concat(Data)), + _ => throw new InvalidOperationException("Operation is invalid after previous operation.") + }; - public IEnumerable Objects => objects; + public object Apply(object oldValue, string key) => oldValue is { } ? Conversion.To>(oldValue).Concat(Data).ToList() : Data.ToList(); + + public IEnumerable Objects => Data; } } diff --git a/Parse/Management/Tracking/ParseAddUniqueOperation.cs b/Parse/Management/Tracking/ParseAddUniqueOperation.cs index 108caba4..275ef98e 100644 --- a/Parse/Management/Tracking/ParseAddUniqueOperation.cs +++ b/Parse/Management/Tracking/ParseAddUniqueOperation.cs @@ -4,76 +4,64 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using Parse.Abstractions.Library; using Parse.Utilities; namespace Parse.Core.Internal { public class ParseAddUniqueOperation : IParseFieldOperation { - private ReadOnlyCollection objects; - public ParseAddUniqueOperation(IEnumerable objects) => this.objects = new ReadOnlyCollection(objects.Distinct().ToList()); + ReadOnlyCollection Data { get; } - public object Encode() => new Dictionary { - {"__op", "AddUnique"}, - {nameof(objects), PointerOrLocalIdEncoder.Instance.Encode(objects)} - }; + public ParseAddUniqueOperation(IEnumerable objects) => Data = new ReadOnlyCollection(objects.Distinct().ToList()); - public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) + public object Encode(IServiceHub serviceHub) => new Dictionary { - if (previous == null) - { - return this; - } - if (previous is ParseDeleteOperation) - { - return new ParseSetOperation(objects.ToList()); - } - if (previous is ParseSetOperation) - { - ParseSetOperation setOp = (ParseSetOperation) previous; - IList oldList = Conversion.To>(setOp.Value); - object result = Apply(oldList, null); - return new ParseSetOperation(result); - } - if (previous is ParseAddUniqueOperation) - { - IEnumerable oldList = ((ParseAddUniqueOperation) previous).Objects; - return new ParseAddUniqueOperation((IList) Apply(oldList, null)); - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } + ["__op"] = "AddUnique", + ["objects"] = PointerOrLocalIdEncoder.Instance.Encode(Data, serviceHub) + }; + + public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) => previous switch + { + null => this, + ParseDeleteOperation _ => new ParseSetOperation(Data.ToList()), + ParseSetOperation setOp => new ParseSetOperation(Apply(Conversion.To>(setOp.Value), default)), + ParseAddUniqueOperation addition => new ParseAddUniqueOperation(Apply(addition.Objects, default) as IList), + _ => throw new InvalidOperationException("Operation is invalid after previous operation.") + }; public object Apply(object oldValue, string key) { if (oldValue == null) { - return objects.ToList(); + return Data.ToList(); } - List newList = Conversion.To>(oldValue).ToList(); + + List result = Conversion.To>(oldValue).ToList(); IEqualityComparer comparer = ParseFieldOperations.ParseObjectComparer; - foreach (object objToAdd in objects) + + foreach (object target in Data) { - if (objToAdd is ParseObject) + if (target is ParseObject) { - object matchedObj = newList.FirstOrDefault(listObj => comparer.Equals(objToAdd, listObj)); - if (matchedObj == null) + if (!(result.FirstOrDefault(reference => comparer.Equals(target, reference)) is { } matched)) { - newList.Add(objToAdd); + result.Add(target); } else { - int index = newList.IndexOf(matchedObj); - newList[index] = objToAdd; + result[result.IndexOf(matched)] = target; } } - else if (!newList.Contains(objToAdd, comparer)) + else if (!result.Contains(target, comparer)) { - newList.Add(objToAdd); + result.Add(target); } } - return newList; + + return result; } - public IEnumerable Objects => objects; + public IEnumerable Objects => Data; } } diff --git a/Parse/Management/Tracking/ParseDeleteOperation.cs b/Parse/Management/Tracking/ParseDeleteOperation.cs index cbf14fb0..03ca31a6 100644 --- a/Parse/Management/Tracking/ParseDeleteOperation.cs +++ b/Parse/Management/Tracking/ParseDeleteOperation.cs @@ -1,6 +1,7 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System.Collections.Generic; +using Parse.Abstractions.Library; namespace Parse.Core.Internal { @@ -15,7 +16,7 @@ public class ParseDeleteOperation : IParseFieldOperation private ParseDeleteOperation() { } - public object Encode() => new Dictionary { ["__op"] = "Delete" }; + public object Encode(IServiceHub serviceHub) => new Dictionary { ["__op"] = "Delete" }; public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) => this; diff --git a/Parse/Management/Tracking/ParseIncrementOperation.cs b/Parse/Management/Tracking/ParseIncrementOperation.cs index aa5fc650..0ba0eea0 100644 --- a/Parse/Management/Tracking/ParseIncrementOperation.cs +++ b/Parse/Management/Tracking/ParseIncrementOperation.cs @@ -3,152 +3,123 @@ using System; using System.Collections.Generic; using System.Linq; +using Parse.Abstractions.Library; namespace Parse.Core.Internal { public class ParseIncrementOperation : IParseFieldOperation { - private static readonly IDictionary, Func> adders; + // Defines adders for all of the implicit conversions: http://msdn.microsoft.com/en-US/library/y5b434w4(v=vs.80).aspx. + + static IDictionary, Func> Adders { get; } = 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 + }; static ParseIncrementOperation() { - // 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 (Tuple pair in adders.Keys.ToList()) + + foreach (Tuple pair in Adders.Keys.ToList()) { if (pair.Item1.Equals(pair.Item2)) { continue; } + Tuple reversePair = new Tuple(pair.Item2, pair.Item1); - Func func = adders[pair]; - adders[reversePair] = (left, right) => func(right, left); + Func func = Adders[pair]; + Adders[reversePair] = (left, right) => func(right, left); } } - private object amount; + public ParseIncrementOperation(object amount) => Amount = amount; - public ParseIncrementOperation(object amount) => this.amount = amount; + public object Encode(IServiceHub serviceHub) => new Dictionary + { + ["__op"] = "Increment", + ["amount"] = Amount + }; - public object Encode() => new Dictionary { - {"__op", "Increment"}, - {nameof(amount), amount} - }; + static object Add(object first, object second) => Adders.TryGetValue(new Tuple(first.GetType(), second.GetType()), out Func adder) ? adder(first, second) : throw new InvalidCastException($"Could not add objects of type {first.GetType()} and {second.GetType()} to each other."); - private static object Add(object obj1, object obj2) + public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) => previous switch { - if (adders.TryGetValue(new Tuple(obj1.GetType(), obj2.GetType()), out Func adder)) - { - return adder(obj1, obj2); - } - throw new InvalidCastException("Cannot add " + obj1.GetType() + " to " + obj2.GetType()); - } + null => this, + ParseDeleteOperation _ => new ParseSetOperation(Amount), - public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) - { - if (previous == null) - { - return this; - } - if (previous is ParseDeleteOperation) - { - return new ParseSetOperation(amount); - } - if (previous is ParseSetOperation) - { - object otherAmount = ((ParseSetOperation) previous).Value; - if (otherAmount is string) - { - throw new InvalidOperationException("Cannot increment a non-number type."); - } - object myAmount = amount; - return new ParseSetOperation(Add(otherAmount, myAmount)); - } - if (previous is ParseIncrementOperation) - { - object otherAmount = ((ParseIncrementOperation) previous).Amount; - object myAmount = amount; - return new ParseIncrementOperation(Add(otherAmount, myAmount)); - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } + // This may be a bug, but it was in the original logic. - 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); - } + ParseSetOperation { Value: string { } } => throw new InvalidOperationException("Cannot increment a non-number type."), + ParseSetOperation { Value: var value } => new ParseSetOperation(Add(value, Amount)), + ParseIncrementOperation { Amount: var amount } => new ParseIncrementOperation(Add(amount, Amount)), + _ => throw new InvalidOperationException("Operation is invalid after previous operation.") + }; + + public object Apply(object oldValue, string key) => oldValue is string ? throw new InvalidOperationException("Cannot increment a non-number type.") : Add(oldValue ?? 0, Amount); - public object Amount => amount; + public object Amount { get; } } } diff --git a/Parse/Management/Tracking/ParseRelationOperation.cs b/Parse/Management/Tracking/ParseRelationOperation.cs index b2fe09a4..a463b6cc 100644 --- a/Parse/Management/Tracking/ParseRelationOperation.cs +++ b/Parse/Management/Tracking/ParseRelationOperation.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using Parse.Abstractions.Library; namespace Parse.Core.Internal { @@ -34,9 +35,9 @@ public ParseRelationOperation(IParseObjectClassController classController, IEnum Removals = new ReadOnlyCollection(GetIdsFromObjects(removes).ToList()); } - public object Encode() + public object Encode(IServiceHub serviceHub) { - List additions = Additions.Select(id => PointerOrLocalIdEncoder.Instance.Encode(ClassController.CreateObjectWithoutData(TargetClassName, id))).ToList(), removals = Removals.Select(id => PointerOrLocalIdEncoder.Instance.Encode(ClassController.CreateObjectWithoutData(TargetClassName, id))).ToList(); + List additions = Additions.Select(id => PointerOrLocalIdEncoder.Instance.Encode(ClassController.CreateObjectWithoutData(TargetClassName, id, serviceHub), serviceHub)).ToList(), removals = Removals.Select(id => PointerOrLocalIdEncoder.Instance.Encode(ClassController.CreateObjectWithoutData(TargetClassName, id, serviceHub), serviceHub)).ToList(); Dictionary addition = additions.Count == 0 ? default : new Dictionary { diff --git a/Parse/Management/Tracking/ParseRemoveOperation.cs b/Parse/Management/Tracking/ParseRemoveOperation.cs index a1d54e9e..df62e9b7 100644 --- a/Parse/Management/Tracking/ParseRemoveOperation.cs +++ b/Parse/Management/Tracking/ParseRemoveOperation.cs @@ -4,55 +4,34 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; - +using Parse.Abstractions.Library; using Parse.Utilities; namespace Parse.Core.Internal { public class ParseRemoveOperation : IParseFieldOperation { - private ReadOnlyCollection objects; - public ParseRemoveOperation(IEnumerable objects) => this.objects = new ReadOnlyCollection(objects.Distinct().ToList()); + ReadOnlyCollection Data { get; } - public object Encode() => new Dictionary { - {"__op", "Remove"}, - {nameof(objects), PointerOrLocalIdEncoder.Instance.Encode(objects)} - }; + public ParseRemoveOperation(IEnumerable objects) => Data = new ReadOnlyCollection(objects.Distinct().ToList()); - public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) + public object Encode(IServiceHub serviceHub) => new Dictionary { - if (previous == null) - { - return this; - } - if (previous is ParseDeleteOperation) - { - return previous; - } - if (previous is ParseSetOperation) - { - ParseSetOperation setOp = (ParseSetOperation) previous; - IList oldList = Conversion.As>(setOp.Value); - return new ParseSetOperation(Apply(oldList, null)); - } - if (previous is ParseRemoveOperation) - { - ParseRemoveOperation oldOp = (ParseRemoveOperation) previous; - return new ParseRemoveOperation(oldOp.Objects.Concat(objects)); - } - throw new InvalidOperationException("Operation is invalid after previous operation."); - } + ["__op"] = "Remove", + ["objects"] = PointerOrLocalIdEncoder.Instance.Encode(Data, serviceHub) + }; - public object Apply(object oldValue, string key) + public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) => previous switch { - if (oldValue == null) - { - return new List(); - } - IList oldList = Conversion.As>(oldValue); - return oldList.Except(objects, ParseFieldOperations.ParseObjectComparer).ToList(); - } + null => this, + ParseDeleteOperation _ => previous, + ParseSetOperation setOp => new ParseSetOperation(Apply(Conversion.As>(setOp.Value), default)), + ParseRemoveOperation oldOp => new ParseRemoveOperation(oldOp.Objects.Concat(Data)), + _ => throw new InvalidOperationException("Operation is invalid after previous operation.") + }; + + public object Apply(object oldValue, string key) => oldValue is { } ? Conversion.As>(oldValue).Except(Data, ParseFieldOperations.ParseObjectComparer).ToList() : new List { }; - public IEnumerable Objects => objects; + public IEnumerable Objects => Data; } } diff --git a/Parse/Management/Tracking/ParseSetOperation.cs b/Parse/Management/Tracking/ParseSetOperation.cs index eaceac44..bf41eafc 100644 --- a/Parse/Management/Tracking/ParseSetOperation.cs +++ b/Parse/Management/Tracking/ParseSetOperation.cs @@ -1,12 +1,14 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. +using Parse.Abstractions.Library; + namespace Parse.Core.Internal { public class ParseSetOperation : IParseFieldOperation { public ParseSetOperation(object value) => Value = value; - public object Encode() => PointerOrLocalIdEncoder.Instance.Encode(Value); + public object Encode(IServiceHub serviceHub) => PointerOrLocalIdEncoder.Instance.Encode(Value, serviceHub); public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) => this; diff --git a/Parse/ObjectServiceExtensions.cs b/Parse/ObjectServiceExtensions.cs index 771adaf9..9a91c423 100644 --- a/Parse/ObjectServiceExtensions.cs +++ b/Parse/ObjectServiceExtensions.cs @@ -64,19 +64,19 @@ public static void RemoveClass(this IParseObjectClassController subclassingContr /// /// The class of object to create. /// A new ParseObject for the given class name. - public static ParseObject CreateObject(this IServiceHub serviceHub, string className) => serviceHub.ClassController.Instantiate(className); + public static ParseObject CreateObject(this IServiceHub serviceHub, string className) => serviceHub.ClassController.Instantiate(className, serviceHub); /// /// Creates a new ParseObject based upon a given subclass type. /// /// A new ParseObject for the given class name. - public static T CreateObject(this IServiceHub serviceHub) where T : ParseObject => (T) serviceHub.ClassController.CreateObject(); + public static T CreateObject(this IServiceHub serviceHub) where T : ParseObject => (T) serviceHub.ClassController.CreateObject(serviceHub); /// /// Creates a new ParseObject based upon a given subclass type. /// /// A new ParseObject for the given class name. - public static T CreateObject(this IParseObjectClassController classController) where T : ParseObject => (T) classController.Instantiate(classController.GetClassName(typeof(T))); + public static T CreateObject(this IParseObjectClassController classController, IServiceHub serviceHub) where T : ParseObject => (T) classController.Instantiate(classController.GetClassName(typeof(T)), serviceHub); /// /// Creates a reference to an existing ParseObject for use in creating associations between @@ -87,7 +87,7 @@ public static void RemoveClass(this IParseObjectClassController subclassingContr /// The object's class. /// The object id for the referenced object. /// A ParseObject without data. - public static ParseObject CreateObjectWithoutData(this IServiceHub serviceHub, string className, string objectId) => serviceHub.ClassController.CreateObjectWithoutData(className, objectId); + public static ParseObject CreateObjectWithoutData(this IServiceHub serviceHub, string className, string objectId) => serviceHub.ClassController.CreateObjectWithoutData(className, objectId, serviceHub); /// /// Creates a reference to an existing ParseObject for use in creating associations between @@ -98,14 +98,17 @@ public static void RemoveClass(this IParseObjectClassController subclassingContr /// The object's class. /// The object id for the referenced object. /// A ParseObject without data. - public static ParseObject CreateObjectWithoutData(this IParseObjectClassController classController, string className, string objectId) + public static ParseObject CreateObjectWithoutData(this IParseObjectClassController classController, string className, string objectId, IServiceHub serviceHub) { ParseObject.CreatingPointer.Value = true; try { - ParseObject result = classController.Instantiate(className); + ParseObject result = classController.Instantiate(className, serviceHub); result.ObjectId = objectId; - result.IsDirty = false; // Left in because the property setter might be doing something funky. + + // Left in because the property setter might be doing something funky. + + result.IsDirty = false; return result.IsDirty ? throw new InvalidOperationException("A ParseObject subclass default constructor must not make changes to the object that cause it to be dirty.") : result; } finally { ParseObject.CreatingPointer.Value = false; } @@ -119,7 +122,7 @@ public static ParseObject CreateObjectWithoutData(this IParseObjectClassControll /// /// The object id for the referenced object. /// A ParseObject without data. - public static T CreateObjectWithoutData(this IServiceHub serviceHub, string objectId) where T : ParseObject => (T) CreateObjectWithoutData(serviceHub, serviceHub.ClassController.GetClassName(typeof(T)), objectId); + public static T CreateObjectWithoutData(this IServiceHub serviceHub, string objectId) where T : ParseObject => (T) serviceHub.CreateObjectWithoutData(serviceHub.ClassController.GetClassName(typeof(T)), objectId); /// /// Deletes each object in the provided list. @@ -228,23 +231,23 @@ internal static IEnumerable TraverseObjectDeep(this IServiceHub serviceH } // TODO (hallucinogen): add unit test - internal static T GenerateObjectFromState(this IServiceHub serviceHub, IObjectState state, string defaultClassName) where T : ParseObject => serviceHub.ClassController.GenerateObjectFromState(state, defaultClassName); + internal static T GenerateObjectFromState(this IServiceHub serviceHub, IObjectState state, string defaultClassName) where T : ParseObject => serviceHub.ClassController.GenerateObjectFromState(state, defaultClassName, serviceHub); - internal static T GenerateObjectFromState(this IParseObjectClassController classController, IObjectState state, string defaultClassName) where T : ParseObject + internal static T GenerateObjectFromState(this IParseObjectClassController classController, IObjectState state, string defaultClassName, IServiceHub serviceHub) where T : ParseObject { - T obj = (T) classController.CreateObjectWithoutData(state.ClassName ?? defaultClassName, state.ObjectId); + T obj = (T) classController.CreateObjectWithoutData(state.ClassName ?? defaultClassName, state.ObjectId, serviceHub); obj.HandleFetchResult(state); return obj; } - internal static IDictionary GenerateJSONObjectForSaving(this IDictionary operations) + internal static IDictionary GenerateJSONObjectForSaving(this IServiceHub serviceHub, IDictionary operations) { Dictionary result = new Dictionary(); foreach (KeyValuePair pair in operations) { - result[pair.Key] = PointerOrLocalIdEncoder.Instance.Encode(pair.Value); + result[pair.Key] = PointerOrLocalIdEncoder.Instance.Encode(pair.Value, serviceHub); } return result; @@ -343,7 +346,7 @@ internal static Task DeepSaveAsync(this IServiceHub serviceHub, object target, s List states = (from item in current select item.State).ToList(); List> operationsList = (from item in current select item.StartSave()).ToList(); - IList> saveTasks = serviceHub.ObjectController.SaveAllAsync(states, operationsList, sessionToken, cancellationToken); + IList> saveTasks = serviceHub.ObjectController.SaveAllAsync(states, operationsList, sessionToken, serviceHub, cancellationToken); return Task.WhenAll(saveTasks).ContinueWith(task => { @@ -356,8 +359,7 @@ internal static Task DeepSaveAsync(this IServiceHub serviceHub, object target, s } else { - IObjectState[] serverStates = task.Result; - foreach ((ParseObject item, IObjectState state) pair in current.Zip(serverStates, (item, state) => (item, state))) + foreach ((ParseObject item, IObjectState state) pair in current.Zip(task.Result, (item, state) => (item, state))) { pair.item.HandleSave(pair.state); } @@ -427,7 +429,7 @@ static Task EnqueueForAll(IEnumerable objects, Func o.taskQueue.Mutex)); + LockSet lockSet = new LockSet(objects.Select(o => o.TaskQueue.Mutex)); lockSet.Enter(); try @@ -442,7 +444,7 @@ static Task EnqueueForAll(IEnumerable objects, Func childTasks = new List(); foreach (ParseObject obj in objects) { - obj.taskQueue.Enqueue((Task task) => + obj.TaskQueue.Enqueue((Task task) => { childTasks.Add(task); return fullTask; diff --git a/Parse/ParseClient.cs b/Parse/ParseClient.cs index d6192bc8..74c9cf75 100644 --- a/Parse/ParseClient.cs +++ b/Parse/ParseClient.cs @@ -53,7 +53,7 @@ public class ParseClient : CustomServiceHub, IServiceHubComposer /// /// Services that provide essential functionality. /// - public override IServiceHub Services { get; protected set; } + public override IServiceHub Services { get; internal set; } // TODO: Implement IServiceHubMutator in all IServiceHub-implementing classes in Parse.Library and possibly require all implementations to do so as an efficiency improvement over instantiating an OrchestrationServiceHub, only for another one to be possibly instantiated when configurators are specified. @@ -63,12 +63,12 @@ public class ParseClient : CustomServiceHub, IServiceHubComposer /// way is to put a call to ParseClient.Initialize in your /// Application startup. /// - /// The Application ID provided in the Parse dashboard. + /// The Application ID provided in the Parse dashboard. /// /// The server URI provided in the Parse dashboard. /// /// A service hub to override internal services and thereby make the Parse SDK operate in a custom manner. - public ParseClient(string identifier, string serverURI, IServiceHub serviceHub = default, params IServiceHubMutator[] configurators) : this(new ServerConnectionData { ApplicationID = identifier, ServerURI = serverURI }, serviceHub, configurators) { } + public ParseClient(string application, string serverURI, IServiceHub serviceHub = default, params IServiceHubMutator[] configurators) : this(new ServerConnectionData { ApplicationID = application, ServerURI = serverURI }, serviceHub, configurators) { } /// /// Authenticates this client as belonging to your application. This must be @@ -96,6 +96,8 @@ public ParseClient(IServerConnectionData configuration, IServiceHub serviceHub = { Services = BuildHub(default, Services, configurators); } + + Services.ClassController.AddIntrinsic(); } /// diff --git a/Parse/ParseConfiguration.cs b/Parse/ParseConfiguration.cs index 11b05e98..71955812 100644 --- a/Parse/ParseConfiguration.cs +++ b/Parse/ParseConfiguration.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; +using Parse.Abstractions.Library; using Parse.Common.Internal; using Parse.Core.Internal; using Parse.Management; @@ -9,7 +10,6 @@ namespace Parse { - /// /// The ParseConfig 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". @@ -18,11 +18,13 @@ public class ParseConfiguration : IJsonConvertible { IDictionary Properties { get; } = new Dictionary { }; - internal ParseConfiguration() { } + IServiceHub Services { get; } + + internal ParseConfiguration(IServiceHub serviceHub) => Services = serviceHub; - ParseConfiguration(IDictionary properties) => Properties = properties; + ParseConfiguration(IDictionary properties, IServiceHub serviceHub) : this(serviceHub) => Properties = properties; - internal static ParseConfiguration Create(IDictionary configurationData, IParseDataDecoder decoder) => new ParseConfiguration(decoder.Decode(configurationData["params"]) as IDictionary); + internal static ParseConfiguration Create(IDictionary configurationData, IParseDataDecoder decoder, IServiceHub serviceHub) => new ParseConfiguration(decoder.Decode(configurationData["params"], serviceHub) as IDictionary, serviceHub); /// /// Gets a value for the key of a particular type. @@ -76,7 +78,7 @@ public bool TryGetValue(string key, out T result) IDictionary IJsonConvertible.ConvertToJSON() => new Dictionary { - ["params"] = NoObjectsEncoder.Instance.Encode(Properties) + ["params"] = NoObjectsEncoder.Instance.Encode(Properties, Services) }; } } diff --git a/Parse/ParseInstallation.cs b/Parse/ParseInstallation.cs index 65f5b31c..9ce6bbbc 100644 --- a/Parse/ParseInstallation.cs +++ b/Parse/ParseInstallation.cs @@ -198,22 +198,22 @@ protected override Task SaveAsync(Task toAwait, CancellationToken cancellationTo { Task platformHookTask = null; - if (Client.CurrentInstallationController.IsCurrent(this)) + if (Services.CurrentInstallationController.IsCurrent(this)) { - SetIfDifferent("deviceType", Client.MetadataController.EnvironmentData.Platform); - SetIfDifferent("timeZone", Client.MetadataController.EnvironmentData.TimeZone); + SetIfDifferent("deviceType", Services.MetadataController.EnvironmentData.Platform); + SetIfDifferent("timeZone", Services.MetadataController.EnvironmentData.TimeZone); SetIfDifferent("localeIdentifier", GetLocaleIdentifier()); SetIfDifferent("parseVersion", ParseClient.Version); - SetIfDifferent("appVersion", Client.MetadataController.HostManifestData.Version); - SetIfDifferent("appIdentifier", Client.MetadataController.HostManifestData.Identifier); - SetIfDifferent("appName", Client.MetadataController.HostManifestData.Name); + SetIfDifferent("appVersion", Services.MetadataController.HostManifestData.Version); + SetIfDifferent("appIdentifier", Services.MetadataController.HostManifestData.Identifier); + SetIfDifferent("appName", Services.MetadataController.HostManifestData.Name); #warning InstallationDataFinalizer needs to be injected here somehow or removed. //platformHookTask = Client.InstallationDataFinalizer.FinalizeAsync(this); } - return platformHookTask.Safe().OnSuccess(_ => base.SaveAsync(toAwait, cancellationToken)).Unwrap().OnSuccess(_ => Client.CurrentInstallationController.IsCurrent(this) ? Task.CompletedTask : Client.CurrentInstallationController.SetAsync(this, cancellationToken)).Unwrap(); + return platformHookTask.Safe().OnSuccess(_ => base.SaveAsync(toAwait, cancellationToken)).Unwrap().OnSuccess(_ => Services.CurrentInstallationController.IsCurrent(this) ? Task.CompletedTask : Services.CurrentInstallationController.SetAsync(this, cancellationToken)).Unwrap(); } /// diff --git a/Parse/ParseObject.cs b/Parse/ParseObject.cs index c35ba5f1..aaaf86ed 100644 --- a/Parse/ParseObject.cs +++ b/Parse/ParseObject.cs @@ -7,6 +7,7 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; using Parse.Core.Internal; using Parse.Management; @@ -34,12 +35,12 @@ public class ParseObject : IEnumerable>, INotifyPro internal static ThreadLocal CreatingPointer { get; } = new ThreadLocal(() => false); - internal TaskQueue taskQueue = new TaskQueue { }; + internal TaskQueue TaskQueue { get; } = new TaskQueue { }; /// /// The instance being targeted. /// - internal ParseClient Client { get; } + internal IServiceHub Services { get; private protected set; } /// /// Constructs a new ParseObject with no data in it. A ParseObject constructed in this way will @@ -51,8 +52,8 @@ public class ParseObject : IEnumerable>, INotifyPro /// to name classes in PascalCase. /// /// The className for this ParseObject. - /// The instance to target for any resources. - public ParseObject(string className, ParseClient client = default) + /// The implementation instance to target for any resources. This paramater can be effectively set after construction via . + public ParseObject(string className, IServiceHub serviceHub = default) { // 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 @@ -62,16 +63,16 @@ public ParseObject(string className, ParseClient client = default) bool isPointer = CreatingPointer.Value; CreatingPointer.Value = false; - Client = client ?? ParseClient.Instance ?? throw new InvalidOperationException("A ParseClient needs to be initialized as the configured default instance before any ParseObjects can be instantiated."); + Services = serviceHub ?? ParseClient.Instance /* Technically, it is not possible to throw an exception here for when serviceHub is null because ParseObjectClass.Constructor for derived classes will call this constructor with a null serviceHub, then call Bind, so that would fail. */ /* ?? throw new InvalidOperationException("A ParseClient needs to be initialized as the configured default instance before any ParseObjects can be instantiated.") */; if (AutoClassName.Equals(className ?? throw new ArgumentException("You must specify a Parse class name when creating a new ParseObject."))) { className = GetType().GetParseClassName(); } - // If this is supposed to be created by a factory but wasn't, throw an exception + // If this is supposed to be created by a factory but wasn't, throw an exception. - if (!Client.Services.ClassController.GetClassMatch(className, GetType())) + if (!Services.ClassController.GetClassMatch(className, GetType())) { throw new ArgumentException("You must create this type of ParseObject using ParseObject.Create() or the proper subclass."); } @@ -100,7 +101,14 @@ public ParseObject(string className, ParseClient client = default) /// /// Constructor for use in ParseObject subclasses. Subclasses must specify a ParseClassName attribute. /// - protected ParseObject(ParseClient client = default) : this(AutoClassName, client) { } + protected ParseObject(IServiceHub serviceHub = default) : this(AutoClassName, serviceHub) { } + + /// + /// Attaches the given implementation instance to this or -derived class instance. + /// + /// The serviceHub to use for all operations. + /// The instance which was mutated. + public ParseObject Bind(IServiceHub serviceHub) => (Instance: this, Services = serviceHub).Instance; /// /// Occurs when a property value changes. @@ -123,7 +131,7 @@ public event PropertyChangedEventHandler PropertyChanged [ParseFieldName("ACL")] public ParseACL ACL { - get => GetProperty(null, nameof(ACL)); + get => GetProperty(default, nameof(ACL)); set => SetProperty(value, nameof(ACL)); } @@ -258,7 +266,7 @@ internal bool CanBeSerialized lock (Mutex) { - return Client.CanBeSerializedAsValue(EstimatedData); + return Services.CanBeSerializedAsValue(EstimatedData); } } } @@ -414,16 +422,11 @@ public bool ContainsKey(string key) } } - /// - /// Deletes this object on the server. - /// - public Task DeleteAsync() => DeleteAsync(CancellationToken.None); - /// /// Deletes this object on the server. /// /// The cancellation token. - public Task DeleteAsync(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => DeleteAsync(toAwait, cancellationToken), cancellationToken); + public Task DeleteAsync(CancellationToken cancellationToken = default) => TaskQueue.Enqueue(toAwait => DeleteAsync(toAwait, cancellationToken), cancellationToken); /// /// Gets a value for the key of a particular type. @@ -547,8 +550,7 @@ public void Revert() { lock (Mutex) { - bool wasDirty = CurrentOperations.Count > 0; - if (wasDirty) + if (CurrentOperations.Count > 0) { CurrentOperations.Clear(); RebuildEstimatedData(); @@ -557,16 +559,11 @@ public void Revert() } } - /// - /// Saves this object to the server. - /// - public Task SaveAsync() => SaveAsync(CancellationToken.None); - /// /// Saves this object to the server. /// /// The cancellation token. - public Task SaveAsync(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => SaveAsync(toAwait, cancellationToken), cancellationToken); + public Task SaveAsync(CancellationToken cancellationToken = default) => TaskQueue.Enqueue(toAwait => SaveAsync(toAwait, cancellationToken), cancellationToken); /// /// Populates result with the value for the key, if possible. @@ -612,12 +609,12 @@ internal Task DeleteAsync(Task toAwait, CancellationToken cancellationToken) return Task.FromResult(0); } - string sessionToken = Client.GetCurrentSessionToken(); + string sessionToken = Services.GetCurrentSessionToken(); - return toAwait.OnSuccess(_ => Client.ObjectController.DeleteAsync(State, sessionToken, cancellationToken)).Unwrap().OnSuccess(_ => IsDirty = true); + return toAwait.OnSuccess(_ => Services.ObjectController.DeleteAsync(State, sessionToken, cancellationToken)).Unwrap().OnSuccess(_ => IsDirty = true); } - internal virtual Task FetchAsyncInternal(Task toAwait, CancellationToken cancellationToken) => toAwait.OnSuccess(_ => ObjectId == null ? throw new InvalidOperationException("Cannot refresh an object that hasn't been saved to the server.") : Client.ObjectController.FetchAsync(State, Client.GetCurrentSessionToken(), cancellationToken)).Unwrap().OnSuccess(task => + internal virtual Task FetchAsyncInternal(Task toAwait, CancellationToken cancellationToken) => toAwait.OnSuccess(_ => ObjectId == null ? throw new InvalidOperationException("Cannot refresh an object that hasn't been saved to the server.") : Services.ObjectController.FetchAsync(State, Services.GetCurrentSessionToken(), Services, cancellationToken)).Unwrap().OnSuccess(task => { HandleFetchResult(task.Result); return this; @@ -631,7 +628,7 @@ internal Task DeleteAsync(Task toAwait, CancellationToken cancellationToken) /// Fetches this object with the data from the server. /// /// The cancellation token. - internal Task FetchAsyncInternal(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => FetchAsyncInternal(toAwait, cancellationToken), cancellationToken); + internal Task FetchAsyncInternal(CancellationToken cancellationToken) => TaskQueue.Enqueue(toAwait => FetchAsyncInternal(toAwait, cancellationToken), cancellationToken); internal Task FetchIfNeededAsyncInternal(Task toAwait, CancellationToken cancellationToken) => !IsDataAvailable ? FetchAsyncInternal(toAwait, cancellationToken) : Task.FromResult(this); @@ -640,7 +637,7 @@ internal Task DeleteAsync(Task toAwait, CancellationToken cancellationToken) /// false), fetches this object with the data from the server. /// /// The cancellation token. - internal Task FetchIfNeededAsyncInternal(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => FetchIfNeededAsyncInternal(toAwait, cancellationToken), cancellationToken); + internal Task FetchIfNeededAsyncInternal(CancellationToken cancellationToken) => TaskQueue.Enqueue(toAwait => FetchIfNeededAsyncInternal(toAwait, cancellationToken), cancellationToken); internal void HandleFailedSave(IDictionary operationsBeforeSave) { @@ -863,7 +860,7 @@ internal void RebuildEstimatedData() } } - public IDictionary ServerDataToJSONObjectForSerialization() => PointerOrLocalIdEncoder.Instance.Encode(State.ToDictionary(pair => pair.Key, t => t.Value)) as IDictionary; + public IDictionary ServerDataToJSONObjectForSerialization() => PointerOrLocalIdEncoder.Instance.Encode(State.ToDictionary(pair => pair.Key, pair => pair.Value), Services) as IDictionary; /// /// Perform Set internally which is not gated by mutability check. @@ -944,7 +941,7 @@ internal IDictionary StartSave() /// The value to return if the property is not present on the ParseObject. /// The name of the property. /// The return type of the property. - protected T GetProperty(T defaultValue, [CallerMemberName] string propertyName = null) => TryGetValue(Client.GetFieldForPropertyName(ClassName, propertyName), out T result) ? result : defaultValue; + protected T GetProperty(T defaultValue, [CallerMemberName] string propertyName = null) => TryGetValue(Services.GetFieldForPropertyName(ClassName, propertyName), out T result) ? result : defaultValue; /// /// Gets a relation for a property based upon its associated ParseFieldName attribute. @@ -952,7 +949,7 @@ internal IDictionary StartSave() /// The ParseRelation for the property. /// The name of the property. /// The ParseObject subclass type of the ParseRelation. - protected ParseRelation GetRelationProperty([CallerMemberName] string propertyName = null) where T : ParseObject => GetRelation(Client.GetFieldForPropertyName(ClassName, propertyName)); + protected ParseRelation GetRelationProperty([CallerMemberName] string propertyName = null) where T : ParseObject => GetRelation(Services.GetFieldForPropertyName(ClassName, propertyName)); protected virtual bool CheckKeyMutable(string key) => true; @@ -963,7 +960,7 @@ internal IDictionary StartSave() /// protected void OnFieldsChanged(IEnumerable fields) { - IDictionary mappings = Client.ClassController.GetPropertyMappings(ClassName); + IDictionary mappings = Services.ClassController.GetPropertyMappings(ClassName); foreach (string property in mappings is { } ? fields is { } ? from mapping in mappings join field in fields on mapping.Value equals field select mapping.Key : mappings.Keys : Enumerable.Empty()) { @@ -998,11 +995,11 @@ protected virtual Task SaveAsync(Task toAwait, CancellationToken cancellationTok // Get the JSON representation of the object. currentOperations = StartSave(); - sessionToken = Client.GetCurrentSessionToken(); - deepSaveTask = Client.DeepSaveAsync(EstimatedData, sessionToken, cancellationToken); + sessionToken = Services.GetCurrentSessionToken(); + deepSaveTask = Services.DeepSaveAsync(EstimatedData, sessionToken, cancellationToken); } - return deepSaveTask.OnSuccess(_ => toAwait).Unwrap().OnSuccess(_ => Client.ObjectController.SaveAsync(State, currentOperations, sessionToken, cancellationToken)).Unwrap().ContinueWith(task => + return deepSaveTask.OnSuccess(_ => toAwait).Unwrap().OnSuccess(_ => Services.ObjectController.SaveAsync(State, currentOperations, sessionToken, Services, cancellationToken)).Unwrap().ContinueWith(task => { if (task.IsFaulted || task.IsCanceled) { @@ -1023,7 +1020,7 @@ protected virtual Task SaveAsync(Task toAwait, CancellationToken cancellationTok /// The new value. /// The name of the property. /// The type for the property. - protected void SetProperty(T value, [CallerMemberName] string propertyName = null) => this[Client.GetFieldForPropertyName(ClassName, propertyName)] = value; + protected void SetProperty(T value, [CallerMemberName] string propertyName = null) => this[Services.GetFieldForPropertyName(ClassName, propertyName)] = value; void ApplyOperations(IDictionary operations, IDictionary map) { @@ -1087,9 +1084,9 @@ void CheckKeyIsMutable(string key) /// refreshing or saving. /// /// Map of objectId to ParseObject which have been fetched. - IDictionary CollectFetchedObjects() => Client.TraverseObjectDeep(EstimatedData).OfType().Where(o => o.ObjectId != null && o.IsDataAvailable).GroupBy(o => o.ObjectId).ToDictionary(group => group.Key, group => group.Last()); + IDictionary CollectFetchedObjects() => Services.TraverseObjectDeep(EstimatedData).OfType().Where(o => o.ObjectId != null && o.IsDataAvailable).GroupBy(o => o.ObjectId).ToDictionary(group => group.Key, group => group.Last()); - IEnumerable FindUnsavedChildren() => Client.TraverseObjectDeep(EstimatedData).OfType().Where(o => o.IsDirty); + IEnumerable FindUnsavedChildren() => Services.TraverseObjectDeep(EstimatedData).OfType().Where(o => o.IsDirty); IEnumerator> IEnumerable>.GetEnumerator() { diff --git a/Parse/ParsePush.cs b/Parse/ParsePush.cs index 93363b47..67582374 100644 --- a/Parse/ParsePush.cs +++ b/Parse/ParsePush.cs @@ -208,7 +208,7 @@ void MutateState(Action func) /// console on http://parse.com /// /// CancellationToken to cancel the current operation. - public Task SendAsync(CancellationToken cancellationToken) => Services.PushController.SendPushNotificationAsync(State, cancellationToken); + public Task SendAsync(CancellationToken cancellationToken) => Services.PushController.SendPushNotificationAsync(State, Services, cancellationToken); #endregion } diff --git a/Parse/ParseQuery.cs b/Parse/ParseQuery.cs index 8e620935..e2be3933 100644 --- a/Parse/ParseQuery.cs +++ b/Parse/ParseQuery.cs @@ -618,7 +618,7 @@ internal IDictionary BuildParameters(bool includeClassName = fal { Dictionary result = new Dictionary(); if (Filters != null) - result[nameof(Filters)] = PointerOrLocalIdEncoder.Instance.Encode(Filters); + result[nameof(Filters)] = PointerOrLocalIdEncoder.Instance.Encode(Filters, Services); if (Orderings != null) result["order"] = String.Join(",", Orderings.ToArray()); if (SkipAmount != null) diff --git a/Parse/ParseRelation.cs b/Parse/ParseRelation.cs index 5e53891c..9755e37c 100644 --- a/Parse/ParseRelation.cs +++ b/Parse/ParseRelation.cs @@ -38,7 +38,7 @@ internal void EnsureParentAndKey(ParseObject parent, string key) internal void Add(ParseObject entity) { - ParseRelationOperation change = new ParseRelationOperation(Parent.Client.ClassController, new[] { entity }, default); + ParseRelationOperation change = new ParseRelationOperation(Parent.Services.ClassController, new[] { entity }, default); Parent.PerformOperation(Key, change); TargetClassName = change.TargetClassName; @@ -46,7 +46,7 @@ internal void Add(ParseObject entity) internal void Remove(ParseObject entity) { - ParseRelationOperation change = new ParseRelationOperation(Parent.Client.ClassController, default, new[] { entity }); + ParseRelationOperation change = new ParseRelationOperation(Parent.Services.ClassController, default, new[] { entity }); Parent.PerformOperation(Key, change); TargetClassName = change.TargetClassName; @@ -58,7 +58,7 @@ internal void Remove(ParseObject entity) ["className"] = TargetClassName }; - internal ParseQuery GetQuery() where T : ParseObject => TargetClassName is { } ? new ParseQuery(Parent.Client, TargetClassName).WhereRelatedTo(Parent, Key) : new ParseQuery(Parent.Client, Parent.ClassName).RedirectClassName(Key).WhereRelatedTo(Parent, Key); + internal ParseQuery GetQuery() where T : ParseObject => TargetClassName is { } ? new ParseQuery(Parent.Services, TargetClassName).WhereRelatedTo(Parent, Key) : new ParseQuery(Parent.Services, Parent.ClassName).RedirectClassName(Key).WhereRelatedTo(Parent, Key); internal string TargetClassName { get; set; } @@ -68,7 +68,7 @@ internal void Remove(ParseObject entity) internal static ParseRelationBase CreateRelation(ParseObject parent, string key, string targetClassName) { Expression>> createRelationExpr = () => CreateRelation(parent, key, targetClassName); - return (createRelationExpr.Body as MethodCallExpression).Method.GetGenericMethodDefinition().MakeGenericMethod(parent.Client.ClassController.GetType(targetClassName) ?? typeof(ParseObject)).Invoke(default, new object[] { parent, key, targetClassName }) as ParseRelationBase; + return (createRelationExpr.Body as MethodCallExpression).Method.GetGenericMethodDefinition().MakeGenericMethod(parent.Services.ClassController.GetType(targetClassName) ?? typeof(ParseObject)).Invoke(default, new object[] { parent, key, targetClassName }) as ParseRelationBase; } static ParseRelation CreateRelation(ParseObject parent, string key, string targetClassName) where T : ParseObject => new ParseRelation(parent, key, targetClassName); diff --git a/Parse/ParseUser.cs b/Parse/ParseUser.cs index ea172663..b4fbd5ee 100644 --- a/Parse/ParseUser.cs +++ b/Parse/ParseUser.cs @@ -26,7 +26,7 @@ public bool IsAuthenticated { lock (Mutex) { - return SessionToken is { } && Client.GetCurrentUser() is { } user && user.ObjectId == ObjectId; + return SessionToken is { } && Services.GetCurrentUser() is { } user && user.ObjectId == ObjectId; } } } @@ -65,7 +65,7 @@ internal override void HandleSave(IObjectState serverState) internal Task SetSessionTokenAsync(string newSessionToken, CancellationToken cancellationToken) { MutateState(mutableClone => mutableClone.ServerData["sessionToken"] = newSessionToken); - return Client.SaveCurrentUserAsync(this); + return Services.SaveCurrentUserAsync(this); } /// @@ -125,7 +125,7 @@ internal Task SignUpAsync(Task toAwait, CancellationToken cancellationToken) IDictionary currentOperations = StartSave(); - return toAwait.OnSuccess(_ => Client.UserController.SignUpAsync(State, currentOperations, cancellationToken)).Unwrap().ContinueWith(t => + return toAwait.OnSuccess(_ => Services.UserController.SignUpAsync(State, currentOperations, Services, cancellationToken)).Unwrap().ContinueWith(t => { if (t.IsFaulted || t.IsCanceled) { @@ -136,7 +136,7 @@ internal Task SignUpAsync(Task toAwait, CancellationToken cancellationToken) HandleSave(t.Result); } return t; - }).Unwrap().OnSuccess(_ => Client.SaveCurrentUserAsync(this)).Unwrap(); + }).Unwrap().OnSuccess(_ => Services.SaveCurrentUserAsync(this)).Unwrap(); } /// @@ -152,7 +152,7 @@ internal Task SignUpAsync(Task toAwait, CancellationToken cancellationToken) /// password must be set before calling SignUpAsync. /// /// The cancellation token. - public Task SignUpAsync(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => SignUpAsync(toAwait, cancellationToken), cancellationToken); + public Task SignUpAsync(CancellationToken cancellationToken) => TaskQueue.Enqueue(toAwait => SignUpAsync(toAwait, cancellationToken), cancellationToken); protected override Task SaveAsync(Task toAwait, CancellationToken cancellationToken) { @@ -163,12 +163,12 @@ protected override Task SaveAsync(Task toAwait, CancellationToken cancellationTo throw new InvalidOperationException("You must call SignUpAsync before calling SaveAsync."); } - return base.SaveAsync(toAwait, cancellationToken).OnSuccess(_ => Client.CurrentUserController.IsCurrent(this) ? Client.SaveCurrentUserAsync(this) : Task.CompletedTask).Unwrap(); + return base.SaveAsync(toAwait, cancellationToken).OnSuccess(_ => Services.CurrentUserController.IsCurrent(this) ? Services.SaveCurrentUserAsync(this) : Task.CompletedTask).Unwrap(); } } // If this is already the current user, refresh its state on disk. - internal override Task FetchAsyncInternal(Task toAwait, CancellationToken cancellationToken) => base.FetchAsyncInternal(toAwait, cancellationToken).OnSuccess(t => !Client.CurrentUserController.IsCurrent(this) ? Task.FromResult(t.Result) : Client.SaveCurrentUserAsync(this).OnSuccess(_ => t.Result)).Unwrap(); + internal override Task FetchAsyncInternal(Task toAwait, CancellationToken cancellationToken) => base.FetchAsyncInternal(toAwait, cancellationToken).OnSuccess(t => !Services.CurrentUserController.IsCurrent(this) ? Task.FromResult(t.Result) : Services.SaveCurrentUserAsync(this).OnSuccess(_ => t.Result)).Unwrap(); internal Task LogOutAsync(Task toAwait, CancellationToken cancellationToken) { @@ -181,19 +181,19 @@ internal Task LogOutAsync(Task toAwait, CancellationToken cancellationToken) // Cleanup in-memory session. MutateState(mutableClone => mutableClone.ServerData.Remove("sessionToken")); - Task revokeSessionTask = Client.RevokeSessionAsync(oldSessionToken, cancellationToken); - return Task.WhenAll(revokeSessionTask, Client.CurrentUserController.LogOutAsync(cancellationToken)); + Task revokeSessionTask = Services.RevokeSessionAsync(oldSessionToken, cancellationToken); + return Task.WhenAll(revokeSessionTask, Services.CurrentUserController.LogOutAsync(Services, cancellationToken)); } internal Task UpgradeToRevocableSessionAsync() => UpgradeToRevocableSessionAsync(CancellationToken.None); - internal Task UpgradeToRevocableSessionAsync(CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => UpgradeToRevocableSessionAsync(toAwait, cancellationToken), cancellationToken); + internal Task UpgradeToRevocableSessionAsync(CancellationToken cancellationToken) => TaskQueue.Enqueue(toAwait => UpgradeToRevocableSessionAsync(toAwait, cancellationToken), cancellationToken); internal Task UpgradeToRevocableSessionAsync(Task toAwait, CancellationToken cancellationToken) { string sessionToken = SessionToken; - return toAwait.OnSuccess(_ => Client.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken)).Unwrap().OnSuccess(task => SetSessionTokenAsync(task.Result)).Unwrap(); + return toAwait.OnSuccess(_ => Services.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken)).Unwrap().OnSuccess(task => SetSessionTokenAsync(task.Result)).Unwrap(); } /// @@ -212,7 +212,7 @@ void CleanupAuthData() { lock (Mutex) { - if (!Client.CurrentUserController.IsCurrent(this)) + if (!Services.CurrentUserController.IsCurrent(this)) { return; } @@ -288,7 +288,7 @@ internal void SynchronizeAuthData(IParseAuthenticationProvider provider) } } - internal Task LinkWithAsync(string authType, IDictionary data, CancellationToken cancellationToken) => taskQueue.Enqueue(toAwait => + internal Task LinkWithAsync(string authType, IDictionary data, CancellationToken cancellationToken) => TaskQueue.Enqueue(toAwait => { IDictionary> authData = AuthData; diff --git a/Parse/Platform/Analytics/ParseAnalyticsController.cs b/Parse/Platform/Analytics/ParseAnalyticsController.cs index 8ffd73d9..3e7bab54 100644 --- a/Parse/Platform/Analytics/ParseAnalyticsController.cs +++ b/Parse/Platform/Analytics/ParseAnalyticsController.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Core.Internal; namespace Parse.Analytics.Internal @@ -13,7 +14,7 @@ namespace Parse.Analytics.Internal /// public class ParseAnalyticsController : IParseAnalyticsController { - private IParseCommandRunner Runner { get; } + IParseCommandRunner Runner { get; } /// /// Creates an instance of the Parse Analytics API controller. @@ -29,7 +30,7 @@ public class ParseAnalyticsController : IParseAnalyticsController /// The session token for the event. /// The asynchonous cancellation token. /// A that will complete successfully once the event has been set to be tracked. - public Task TrackEventAsync(string name, IDictionary dimensions, string sessionToken, CancellationToken cancellationToken) + public Task TrackEventAsync(string name, IDictionary dimensions, string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default) { IDictionary data = new Dictionary { @@ -42,7 +43,7 @@ public Task TrackEventAsync(string name, IDictionary dimensions, data[nameof(dimensions)] = dimensions; } - return Runner.RunCommandAsync(new ParseCommand("events/" + name, "POST", sessionToken, data: PointerOrLocalIdEncoder.Instance.Encode(data) as IDictionary), cancellationToken: cancellationToken); + return Runner.RunCommandAsync(new ParseCommand($"events/{name}", "POST", sessionToken, data: PointerOrLocalIdEncoder.Instance.Encode(data, serviceHub) as IDictionary), cancellationToken: cancellationToken); } /// @@ -52,7 +53,7 @@ public Task TrackEventAsync(string name, IDictionary dimensions, /// The token of the current session. /// The asynchronous cancellation token. /// A the will complete successfully once app openings for the target push notification have been set to be tracked. - public Task TrackAppOpenedAsync(string pushHash, string sessionToken, CancellationToken cancellationToken) + public Task TrackAppOpenedAsync(string pushHash, string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default) { IDictionary data = new Dictionary { ["at"] = DateTime.Now }; @@ -61,7 +62,7 @@ public Task TrackAppOpenedAsync(string pushHash, string sessionToken, Cancellati data["push_hash"] = pushHash; } - return Runner.RunCommandAsync(new ParseCommand("events/AppOpened", "POST", sessionToken, data: PointerOrLocalIdEncoder.Instance.Encode(data) as IDictionary), cancellationToken: cancellationToken); + return Runner.RunCommandAsync(new ParseCommand("events/AppOpened", "POST", sessionToken, data: PointerOrLocalIdEncoder.Instance.Encode(data, serviceHub) as IDictionary), cancellationToken: cancellationToken); } } } diff --git a/Parse/Platform/Code/ParseCloudCodeController.cs b/Parse/Platform/Code/ParseCloudCodeController.cs index 9937d7b8..0194c9c2 100644 --- a/Parse/Platform/Code/ParseCloudCodeController.cs +++ b/Parse/Platform/Code/ParseCloudCodeController.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; using Parse.Utilities; @@ -17,15 +18,10 @@ public class ParseCloudCodeController : IParseCloudCodeController public ParseCloudCodeController(IParseCommandRunner commandRunner, IParseDataDecoder decoder) => (CommandRunner, Decoder) = (commandRunner, decoder); - public Task CallFunctionAsync(string name, IDictionary parameters, string sessionToken, CancellationToken cancellationToken) + public Task CallFunctionAsync(string name, IDictionary parameters, string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand($"functions/{Uri.EscapeUriString(name)}", method: "POST", sessionToken: sessionToken, data: NoObjectsEncoder.Instance.Encode(parameters, serviceHub) as IDictionary), cancellationToken: cancellationToken).OnSuccess(task => { - ParseCommand command = new ParseCommand($"functions/{Uri.EscapeUriString(name)}", method: "POST", sessionToken: sessionToken, data: NoObjectsEncoder.Instance.Encode(parameters) as IDictionary); - - return CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(task => - { - IDictionary decoded = Decoder.Decode(task.Result.Item2) as IDictionary; - return !decoded.ContainsKey("result") ? default : Conversion.To(decoded["result"]); - }); - } + IDictionary decoded = Decoder.Decode(task.Result.Item2, serviceHub) as IDictionary; + return !decoded.ContainsKey("result") ? default : Conversion.To(decoded["result"]); + }); } } diff --git a/Parse/Platform/Configuration/ParseConfigurationController.cs b/Parse/Platform/Configuration/ParseConfigurationController.cs index f3aead09..8107cac1 100644 --- a/Parse/Platform/Configuration/ParseConfigurationController.cs +++ b/Parse/Platform/Configuration/ParseConfigurationController.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; namespace Parse.Core.Internal @@ -27,10 +28,10 @@ public ParseConfigurationController(IParseCommandRunner commandRunner, IStorageC Decoder = decoder; } - public Task FetchConfigAsync(string sessionToken, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand("config", method: "GET", sessionToken: sessionToken, data: default), cancellationToken: cancellationToken).OnSuccess(task => + public Task FetchConfigAsync(string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand("config", method: "GET", sessionToken: sessionToken, data: default), cancellationToken: cancellationToken).OnSuccess(task => { cancellationToken.ThrowIfCancellationRequested(); - return Decoder.BuildConfiguration(task.Result.Item2); + return Decoder.BuildConfiguration(task.Result.Item2, serviceHub); }).OnSuccess(task => { cancellationToken.ThrowIfCancellationRequested(); diff --git a/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs b/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs index 1f0bfe8e..92f4ebb2 100644 --- a/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs +++ b/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; namespace Parse.Core.Internal @@ -32,10 +33,10 @@ public ParseCurrentConfigurationController(IStorageController storageController, TaskQueue = new TaskQueue { }; } - public Task GetCurrentConfigAsync() => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => CurrentConfiguration is { } ? Task.FromResult(CurrentConfiguration) : StorageController.LoadAsync().OnSuccess(task => + public Task GetCurrentConfigAsync(IServiceHub serviceHub) => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => CurrentConfiguration is { } ? Task.FromResult(CurrentConfiguration) : StorageController.LoadAsync().OnSuccess(task => { task.Result.TryGetValue(CurrentConfigurationKey, out object data); - return CurrentConfiguration = data is string { } configuration ? Decoder.BuildConfiguration(ParseClient.DeserializeJsonString(configuration)) : new ParseConfiguration { }; + return CurrentConfiguration = data is string { } configuration ? Decoder.BuildConfiguration(ParseClient.DeserializeJsonString(configuration), serviceHub) : new ParseConfiguration(serviceHub); })), CancellationToken.None).Unwrap(); public Task SetCurrentConfigAsync(ParseConfiguration target) => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => diff --git a/Parse/Platform/Installation/ParseCurrentInstallationController.cs b/Parse/Platform/Installation/ParseCurrentInstallationController.cs index df270f2b..672b1b17 100644 --- a/Parse/Platform/Installation/ParseCurrentInstallationController.cs +++ b/Parse/Platform/Installation/ParseCurrentInstallationController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; using Parse.Core.Internal; using Parse.Library; @@ -61,7 +62,7 @@ public Task SetAsync(ParseInstallation installation, CancellationToken cancellat return saveTask; }).Unwrap(), cancellationToken); - public Task GetAsync(CancellationToken cancellationToken) + public Task GetAsync(IServiceHub serviceHub, CancellationToken cancellationToken = default) { ParseInstallation cachedCurrent; cachedCurrent = CurrentInstallation; @@ -80,13 +81,13 @@ public Task GetAsync(CancellationToken cancellationToken) if (temp is string installationDataString) { IDictionary installationData = Json.Parse(installationDataString) as IDictionary; - installation = InstallationCoder.Decode(installationData); + installation = InstallationCoder.Decode(installationData, serviceHub); fetchTask = Task.FromResult(null); } else { - installation = ClassController.CreateObject(); + installation = ClassController.CreateObject(serviceHub); fetchTask = InstallationController.GetAsync().ContinueWith(t => installation.SetIfDifferent("installationId", t.Result.ToString())); } diff --git a/Parse/Platform/Installation/ParseInstallationCoder.cs b/Parse/Platform/Installation/ParseInstallationCoder.cs index 95d8d433..fa25d8f2 100644 --- a/Parse/Platform/Installation/ParseInstallationCoder.cs +++ b/Parse/Platform/Installation/ParseInstallationCoder.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Parse.Abstractions.Library; using Parse.Core.Internal; namespace Parse.Push.Internal @@ -10,12 +11,12 @@ public class ParseInstallationCoder : IParseInstallationCoder IParseObjectClassController ClassController { get; } - public ParseInstallationCoder(IParseDataDecoder decoder, IParseObjectClassController classController) => Decoder = decoder; + public ParseInstallationCoder(IParseDataDecoder decoder, IParseObjectClassController classController) => (Decoder, ClassController) = (decoder, classController); public IDictionary Encode(ParseInstallation installation) { IObjectState state = installation.State; - IDictionary data = PointerOrLocalIdEncoder.Instance.Encode(state.ToDictionary(x => x.Key, x => x.Value)) as IDictionary; + IDictionary data = PointerOrLocalIdEncoder.Instance.Encode(state.ToDictionary(pair => pair.Key, pair => pair.Value), installation.Services) as IDictionary; data["objectId"] = state.ObjectId; @@ -34,6 +35,6 @@ public IDictionary Encode(ParseInstallation installation) return data; } - public ParseInstallation Decode(IDictionary data) => ClassController.GenerateObjectFromState(ParseObjectCoder.Instance.Decode(data, Decoder), "_Installation"); + public ParseInstallation Decode(IDictionary data, IServiceHub serviceHub) => ClassController.GenerateObjectFromState(ParseObjectCoder.Instance.Decode(data, Decoder, serviceHub), "_Installation", serviceHub); } } \ No newline at end of file diff --git a/Parse/Platform/Notifications/ParsePushChannelsController.cs b/Parse/Platform/Notifications/ParsePushChannelsController.cs index 70e9f7d0..1a9664ae 100644 --- a/Parse/Platform/Notifications/ParsePushChannelsController.cs +++ b/Parse/Platform/Notifications/ParsePushChannelsController.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; +using Parse.Common.Internal; using Parse.Library; namespace Parse.Push.Internal @@ -13,16 +15,16 @@ internal class ParsePushChannelsController : IParsePushChannelsController public ParsePushChannelsController(IParseCurrentInstallationController currentInstallationController) => CurrentInstallationController = currentInstallationController; - public Task SubscribeAsync(IEnumerable channels, CancellationToken cancellationToken) => CurrentInstallationController.GetAsync(cancellationToken).ContinueWith(task => + public Task SubscribeAsync(IEnumerable channels, IServiceHub serviceHub, CancellationToken cancellationToken = default) => CurrentInstallationController.GetAsync(serviceHub, cancellationToken).OnSuccess(task => { task.Result.AddRangeUniqueToList(nameof(channels), channels); return task.Result.SaveAsync(cancellationToken); - }); + }).Unwrap(); - public Task UnsubscribeAsync(IEnumerable channels, CancellationToken cancellationToken) => CurrentInstallationController.GetAsync().ContinueWith(task => + public Task UnsubscribeAsync(IEnumerable channels, IServiceHub serviceHub, CancellationToken cancellationToken = default) => CurrentInstallationController.GetAsync(serviceHub, cancellationToken).OnSuccess(task => { task.Result.RemoveAllFromList(nameof(channels), channels); return task.Result.SaveAsync(cancellationToken); - }); + }).Unwrap(); } } diff --git a/Parse/Platform/Notifications/ParsePushController.cs b/Parse/Platform/Notifications/ParsePushController.cs index d64d59b2..88f1beb9 100644 --- a/Parse/Platform/Notifications/ParsePushController.cs +++ b/Parse/Platform/Notifications/ParsePushController.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; using Parse.Core.Internal; @@ -9,23 +10,16 @@ namespace Parse.Push.Internal { internal class ParsePushController : IParsePushController { - private readonly IParseCommandRunner commandRunner; - private readonly IParseCurrentUserController currentUserController; + IParseCommandRunner CommandRunner { get; } + + IParseCurrentUserController CurrentUserController { get; } public ParsePushController(IParseCommandRunner commandRunner, IParseCurrentUserController currentUserController) { - this.commandRunner = commandRunner; - this.currentUserController = currentUserController; + CommandRunner = commandRunner; + CurrentUserController = currentUserController; } - public Task SendPushNotificationAsync(IPushState state, CancellationToken cancellationToken) => currentUserController.GetCurrentSessionTokenAsync(cancellationToken).OnSuccess(sessionTokenTask => - { - ParseCommand command = new ParseCommand("push", - method: "POST", - sessionToken: sessionTokenTask.Result, - data: ParsePushEncoder.Instance.Encode(state)); - - return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken); - }).Unwrap(); + public Task SendPushNotificationAsync(IPushState state, IServiceHub serviceHub, CancellationToken cancellationToken = default) => CurrentUserController.GetCurrentSessionTokenAsync(serviceHub, cancellationToken).OnSuccess(sessionTokenTask => CommandRunner.RunCommandAsync(new ParseCommand("push", method: "POST", sessionToken: sessionTokenTask.Result, data: ParsePushEncoder.Instance.Encode(state)), cancellationToken: cancellationToken)).Unwrap(); } } diff --git a/Parse/Platform/Session/ParseSessionController.cs b/Parse/Platform/Session/ParseSessionController.cs index 3076d2fe..6f608e66 100644 --- a/Parse/Platform/Session/ParseSessionController.cs +++ b/Parse/Platform/Session/ParseSessionController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; namespace Parse.Core.Internal @@ -15,11 +16,11 @@ public class ParseSessionController : IParseSessionController public ParseSessionController(IParseCommandRunner commandRunner, IParseDataDecoder decoder) => (CommandRunner, Decoder) = (commandRunner, decoder); - public Task GetSessionAsync(string sessionToken, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand("sessions/me", method: "GET", sessionToken: sessionToken, data: null), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder)); + public Task GetSessionAsync(string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand("sessions/me", method: "GET", sessionToken: sessionToken, data: null), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder, serviceHub)); - public Task RevokeAsync(string sessionToken, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand("logout", method: "POST", sessionToken: sessionToken, data: new Dictionary { }), cancellationToken: cancellationToken); + public Task RevokeAsync(string sessionToken, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand("logout", method: "POST", sessionToken: sessionToken, data: new Dictionary { }), cancellationToken: cancellationToken); - public Task UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand("upgradeToRevocableSession", method: "POST", sessionToken: sessionToken, data: new Dictionary()), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder)); + public Task UpgradeToRevocableSessionAsync(string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand("upgradeToRevocableSession", method: "POST", sessionToken: sessionToken, data: new Dictionary()), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder, serviceHub)); public bool IsRevocableSessionToken(string sessionToken) => sessionToken.Contains("r:"); } diff --git a/Parse/PushServiceExtensions.cs b/Parse/PushServiceExtensions.cs index 4bbf7197..c2a0491c 100644 --- a/Parse/PushServiceExtensions.cs +++ b/Parse/PushServiceExtensions.cs @@ -168,7 +168,7 @@ public static event EventHandler ParsePushNotificati /// /// The channels to which this installation should subscribe. /// CancellationToken to cancel the current operation. - public static Task SubscribeToPushChannelsAsync(this IServiceHub serviceHub, IEnumerable channels, CancellationToken cancellationToken = default) => serviceHub.PushChannelsController.SubscribeAsync(channels, cancellationToken); + public static Task SubscribeToPushChannelsAsync(this IServiceHub serviceHub, IEnumerable channels, CancellationToken cancellationToken = default) => serviceHub.PushChannelsController.SubscribeAsync(channels, serviceHub, cancellationToken); /// /// Unsubscribe the current installation from this channel. This is shorthand for: @@ -194,7 +194,7 @@ public static event EventHandler ParsePushNotificati /// /// The channels from which this installation should unsubscribe. /// CancellationToken to cancel the current operation. - public static Task UnsubscribeToPushChannelsAsync(this IServiceHub serviceHub, IEnumerable channels, CancellationToken cancellationToken = default) => serviceHub.PushChannelsController.UnsubscribeAsync(channels, cancellationToken); + public static Task UnsubscribeToPushChannelsAsync(this IServiceHub serviceHub, IEnumerable channels, CancellationToken cancellationToken = default) => serviceHub.PushChannelsController.UnsubscribeAsync(channels, serviceHub, cancellationToken); #endregion } diff --git a/Parse/Query/ParseQueryController.cs b/Parse/Query/ParseQueryController.cs index 2cbeb16c..b0239c05 100644 --- a/Parse/Query/ParseQueryController.cs +++ b/Parse/Query/ParseQueryController.cs @@ -5,10 +5,14 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Library; using Parse.Common.Internal; namespace Parse.Core.Internal { + /// + /// A straightforward implementation of that uses to decode raw server data when needed. + /// internal class ParseQueryController : IParseQueryController { IParseCommandRunner CommandRunner { get; } @@ -17,9 +21,9 @@ internal class ParseQueryController : IParseQueryController public ParseQueryController(IParseCommandRunner commandRunner, IParseDataDecoder decoder) => (CommandRunner, Decoder) = (commandRunner, decoder); - public Task> FindAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject => FindAsync(query.ClassName, query.BuildParameters(), user?.SessionToken, cancellationToken).OnSuccess(t => (from item in t.Result["results"] as IList select ParseObjectCoder.Instance.Decode(item as IDictionary, Decoder))); + public Task> FindAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken = default) where T : ParseObject => FindAsync(query.ClassName, query.BuildParameters(), user?.SessionToken, cancellationToken).OnSuccess(t => (from item in t.Result["results"] as IList select ParseObjectCoder.Instance.Decode(item as IDictionary, Decoder, user.Services))); - public Task CountAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject + public Task CountAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken = default) where T : ParseObject { IDictionary parameters = query.BuildParameters(); parameters["limit"] = 0; @@ -28,14 +32,14 @@ public Task CountAsync(ParseQuery query, ParseUser user, Cancellation return FindAsync(query.ClassName, parameters, user?.SessionToken, cancellationToken).OnSuccess(task => Convert.ToInt32(task.Result["count"])); } - public Task FirstAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject + public Task FirstAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken = default) where T : ParseObject { IDictionary parameters = query.BuildParameters(); parameters["limit"] = 1; - return FindAsync(query.ClassName, parameters, user?.SessionToken, cancellationToken).OnSuccess(task => (task.Result["results"] as IList).FirstOrDefault() as IDictionary is Dictionary item && item != null ? ParseObjectCoder.Instance.Decode(item, Decoder) : null); + return FindAsync(query.ClassName, parameters, user?.SessionToken, cancellationToken).OnSuccess(task => (task.Result["results"] as IList).FirstOrDefault() as IDictionary is Dictionary item && item != null ? ParseObjectCoder.Instance.Decode(item, Decoder, user.Services) : null); } - Task> FindAsync(string className, IDictionary parameters, string sessionToken, CancellationToken cancellationToken) => CommandRunner.RunCommandAsync(new ParseCommand($"classes/{Uri.EscapeDataString(className)}?{ParseClient.BuildQueryString(parameters)}", method: "GET", sessionToken: sessionToken, data: null), cancellationToken: cancellationToken).OnSuccess(t => t.Result.Item2); + Task> FindAsync(string className, IDictionary parameters, string sessionToken, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand($"classes/{Uri.EscapeDataString(className)}?{ParseClient.BuildQueryString(parameters)}", method: "GET", sessionToken: sessionToken, data: null), cancellationToken: cancellationToken).OnSuccess(t => t.Result.Item2); } } diff --git a/Parse/SessionsServiceExtensions.cs b/Parse/SessionsServiceExtensions.cs index 8783c755..62bb8301 100644 --- a/Parse/SessionsServiceExtensions.cs +++ b/Parse/SessionsServiceExtensions.cs @@ -28,11 +28,11 @@ public static class SessionsServiceExtensions { null => Task.FromResult(default), { SessionToken: null } => Task.FromResult(default), - { SessionToken: { } sessionToken } => serviceHub.SessionController.GetSessionAsync(sessionToken, cancellationToken).OnSuccess(successTask => serviceHub.GenerateObjectFromState(successTask.Result, "_Session")) + { SessionToken: { } sessionToken } => serviceHub.SessionController.GetSessionAsync(sessionToken, serviceHub, cancellationToken).OnSuccess(successTask => serviceHub.GenerateObjectFromState(successTask.Result, "_Session")) }).Unwrap(); public static Task RevokeSessionAsync(this IServiceHub serviceHub, string sessionToken, CancellationToken cancellationToken) => sessionToken is null || !serviceHub.SessionController.IsRevocableSessionToken(sessionToken) ? Task.CompletedTask : serviceHub.SessionController.RevokeAsync(sessionToken, cancellationToken); - public static Task UpgradeToRevocableSessionAsync(this IServiceHub serviceHub, string sessionToken, CancellationToken cancellationToken) => sessionToken is null || serviceHub.SessionController.IsRevocableSessionToken(sessionToken) ? Task.FromResult(sessionToken) : serviceHub.SessionController.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken).OnSuccess(task => serviceHub.GenerateObjectFromState(task.Result, "_Session").SessionToken); + public static Task UpgradeToRevocableSessionAsync(this IServiceHub serviceHub, string sessionToken, CancellationToken cancellationToken) => sessionToken is null || serviceHub.SessionController.IsRevocableSessionToken(sessionToken) ? Task.FromResult(sessionToken) : serviceHub.SessionController.UpgradeToRevocableSessionAsync(sessionToken, serviceHub, cancellationToken).OnSuccess(task => serviceHub.GenerateObjectFromState(task.Result, "_Session").SessionToken); } } diff --git a/Parse/UserServiceExtensions.cs b/Parse/UserServiceExtensions.cs index 91010748..e8305af2 100644 --- a/Parse/UserServiceExtensions.cs +++ b/Parse/UserServiceExtensions.cs @@ -18,16 +18,7 @@ internal static string GetCurrentSessionToken(this IServiceHub serviceHub) return sessionTokenTask.Result; } - internal static Task GetCurrentSessionTokenAsync(this IServiceHub serviceHub, CancellationToken cancellationToken = default) => serviceHub.CurrentUserController.GetCurrentSessionTokenAsync(cancellationToken); - - /// - /// 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(this IServiceHub serviceHub, string username, string password) => LogInAsync(serviceHub, username, password, CancellationToken.None); + internal static Task GetCurrentSessionTokenAsync(this IServiceHub serviceHub, CancellationToken cancellationToken = default) => serviceHub.CurrentUserController.GetCurrentSessionTokenAsync(serviceHub, cancellationToken); /// /// Logs in a user with a username and password. On success, this saves the session to disk so you @@ -37,20 +28,12 @@ internal static string GetCurrentSessionToken(this IServiceHub serviceHub) /// The password to log in with. /// The cancellation token. /// The newly logged-in user. - public static Task LogInAsync(this IServiceHub serviceHub, string username, string password, CancellationToken cancellationToken) => serviceHub.UserController.LogInAsync(username, password, cancellationToken).OnSuccess(task => + public static Task LogInAsync(this IServiceHub serviceHub, string username, string password, CancellationToken cancellationToken = default) => serviceHub.UserController.LogInAsync(username, password, serviceHub, cancellationToken).OnSuccess(task => { ParseUser user = serviceHub.GenerateObjectFromState(task.Result, "_User"); return SaveCurrentUserAsync(serviceHub, 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(this IServiceHub serviceHub, string sessionToken) => BecomeAsync(serviceHub, 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 . @@ -58,7 +41,7 @@ public static Task LogInAsync(this IServiceHub serviceHub, string use /// The session token to authorize with /// The cancellation token. /// The user if authorization was successful - public static Task BecomeAsync(this IServiceHub serviceHub, string sessionToken, CancellationToken cancellationToken) => serviceHub.UserController.GetUserAsync(sessionToken, cancellationToken).OnSuccess(t => + public static Task BecomeAsync(this IServiceHub serviceHub, string sessionToken, CancellationToken cancellationToken = default) => serviceHub.UserController.GetUserAsync(sessionToken, serviceHub, cancellationToken).OnSuccess(t => { ParseUser user = serviceHub.GenerateObjectFromState(t.Result, "_User"); return SaveCurrentUserAsync(serviceHub, user).OnSuccess(_ => user); @@ -93,7 +76,7 @@ public static Task BecomeAsync(this IServiceHub serviceHub, string se public static Task LogOutAsync(this IServiceHub serviceHub, CancellationToken cancellationToken) => GetCurrentUserAsync(serviceHub).OnSuccess(task => { LogOutWithProviders(); - return task.Result is { } user ? user.taskQueue.Enqueue(toAwait => user.LogOutAsync(toAwait, cancellationToken), cancellationToken) : Task.CompletedTask; + return task.Result is { } user ? user.TaskQueue.Enqueue(toAwait => user.LogOutAsync(toAwait, cancellationToken), cancellationToken) : Task.CompletedTask; }).Unwrap(); static void LogOutWithProviders() @@ -122,7 +105,7 @@ public static ParseUser GetCurrentUser(this IServiceHub serviceHub) /// Gets the currently logged in ParseUser with a valid session, either from memory or disk /// if necessary, asynchronously. /// - internal static Task GetCurrentUserAsync(this IServiceHub serviceHub, CancellationToken cancellationToken = default) => serviceHub.CurrentUserController.GetAsync(cancellationToken); + internal static Task GetCurrentUserAsync(this IServiceHub serviceHub, CancellationToken cancellationToken = default) => serviceHub.CurrentUserController.GetAsync(serviceHub, cancellationToken); internal static Task SaveCurrentUserAsync(this IServiceHub serviceHub, ParseUser user, CancellationToken cancellationToken = default) => serviceHub.CurrentUserController.SetAsync(user, cancellationToken); @@ -188,7 +171,7 @@ public static Task LogInWithAsync(this IServiceHub serviceHub, string { ParseUser user = null; - return serviceHub.UserController.LogInAsync(authType, data, cancellationToken).OnSuccess(task => + return serviceHub.UserController.LogInAsync(authType, data, serviceHub, cancellationToken).OnSuccess(task => { user = serviceHub.GenerateObjectFromState(task.Result, "_User"); diff --git a/Parse/Utilities/Encoding/IParseDataDecoder.cs b/Parse/Utilities/Encoding/IParseDataDecoder.cs index bddd7f2a..ae9865b1 100644 --- a/Parse/Utilities/Encoding/IParseDataDecoder.cs +++ b/Parse/Utilities/Encoding/IParseDataDecoder.cs @@ -1,9 +1,20 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. +// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. + +using Parse.Abstractions.Library; namespace Parse.Core.Internal { + /// + /// A generalized input data decoding interface for the Parse SDK. + /// public interface IParseDataDecoder { - object Decode(object data); + /// + /// Decodes input data into Parse-SDK-related entities, such as instances, which is why an implementation instance is sometimes required. + /// + /// The target input data to decode. + /// A implementation instance to use when instantiating s. + /// A Parse SDK entity such as a . + object Decode(object data, IServiceHub serviceHub); } } \ No newline at end of file diff --git a/Parse/Utilities/Encoding/ParseDataDecoder.cs b/Parse/Utilities/Encoding/ParseDataDecoder.cs index 1c8f5bfb..5383335e 100644 --- a/Parse/Utilities/Encoding/ParseDataDecoder.cs +++ b/Parse/Utilities/Encoding/ParseDataDecoder.cs @@ -19,7 +19,7 @@ public class ParseDataDecoder : IParseDataDecoder static string[] Types { get; } = { "Date", "Bytes", "Pointer", "File", "GeoPoint", "Object", "Relation" }; - public object Decode(object data) => data switch + public object Decode(object data, IServiceHub serviceHub) => data switch { null => default, IDictionary { } dictionary when dictionary.ContainsKey("__op") => ParseFieldOperations.Decode(dictionary), @@ -27,18 +27,18 @@ public class ParseDataDecoder : IParseDataDecoder { "Date" => ParseDate(dictionary["iso"] as string), "Bytes" => Convert.FromBase64String(dictionary["base64"] as string), - "Pointer" => DecodePointer(dictionary["className"] as string, dictionary["objectId"] as string), + "Pointer" => DecodePointer(dictionary["className"] as string, dictionary["objectId"] as string, serviceHub), "File" => new ParseFile(dictionary["name"] as string, new Uri(dictionary["url"] as string)), "GeoPoint" => new ParseGeoPoint(Conversion.To(dictionary["latitude"]), Conversion.To(dictionary["longitude"])), - "Object" => ClassController.GenerateObjectFromState(ParseObjectCoder.Instance.Decode(dictionary, this), dictionary["className"] as string), + "Object" => ClassController.GenerateObjectFromState(ParseObjectCoder.Instance.Decode(dictionary, this, serviceHub), dictionary["className"] as string, serviceHub), "Relation" => ParseRelationBase.CreateRelation(null, null, dictionary["className"] as string) }, - IDictionary { } dictionary => dictionary.ToDictionary(pair => pair.Key, pair => Decode(pair.Value)), - IList { } list => list.Select(Decode), + IDictionary { } dictionary => dictionary.ToDictionary(pair => pair.Key, pair => Decode(pair.Value, serviceHub)), + IList { } list => list.Select(item => Decode(item, serviceHub)), _ => data }; - protected virtual object DecodePointer(string className, string objectId) => ClassController.CreateObjectWithoutData(className, objectId); + protected virtual object DecodePointer(string className, string objectId, IServiceHub serviceHub) => ClassController.CreateObjectWithoutData(className, objectId, serviceHub); // TODO(hallucinogen): Figure out if we should be more flexible with the date formats we accept. diff --git a/Parse/Utilities/Encoding/ParseDataEncoder.cs b/Parse/Utilities/Encoding/ParseDataEncoder.cs index 201da820..96f1a0be 100644 --- a/Parse/Utilities/Encoding/ParseDataEncoder.cs +++ b/Parse/Utilities/Encoding/ParseDataEncoder.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using Parse.Abstractions.Library; using Parse.Common.Internal; using Parse.Utilities; @@ -20,7 +21,7 @@ public abstract class ParseDataEncoder // If this object has a special encoding, encode it and return the encoded object. Otherwise, just return the original object. - public object Encode(object value) => value switch + public object Encode(object value, IServiceHub serviceHub) => value switch { DateTime { } date => new Dictionary { @@ -34,18 +35,18 @@ public abstract class ParseDataEncoder }, ParseObject { } entity => EncodeObject(entity), IJsonConvertible { } jsonConvertible => jsonConvertible.ConvertToJSON(), - { } when Conversion.As>(value) is { } dictionary => dictionary.ToDictionary(pair => pair.Key, pair => Encode(pair.Value)), - { } when Conversion.As>(value) is { } list => EncodeList(list), + { } when Conversion.As>(value) is { } dictionary => dictionary.ToDictionary(pair => pair.Key, pair => Encode(pair.Value, serviceHub)), + { } when Conversion.As>(value) is { } list => EncodeList(list, serviceHub), // TODO (hallucinogen): convert IParseFieldOperation to IJsonConvertible - IParseFieldOperation { } fieldOperation => fieldOperation.Encode(), + IParseFieldOperation { } fieldOperation => fieldOperation.Encode(serviceHub), _ => value }; protected abstract IDictionary EncodeObject(ParseObject value); - object EncodeList(IList list) + object EncodeList(IList list, IServiceHub serviceHub) { List encoded = new List { }; @@ -63,7 +64,7 @@ object EncodeList(IList list) throw new ArgumentException("Invalid type for value in an array"); } - encoded.Add(Encode(item)); + encoded.Add(Encode(item, serviceHub)); } return encoded; diff --git a/Parse/Utilities/Encoding/ParseObjectCoder.cs b/Parse/Utilities/Encoding/ParseObjectCoder.cs index 7cb76726..f4ff162a 100644 --- a/Parse/Utilities/Encoding/ParseObjectCoder.cs +++ b/Parse/Utilities/Encoding/ParseObjectCoder.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using Parse.Abstractions.Library; namespace Parse.Core.Internal { @@ -15,7 +16,7 @@ public class ParseObjectCoder ParseObjectCoder() { } - public IDictionary Encode(T state, IDictionary operations, ParseDataEncoder encoder) where T : IObjectState + public IDictionary Encode(T state, IDictionary operations, ParseDataEncoder encoder, IServiceHub serviceHub) where T : IObjectState { Dictionary result = new Dictionary(); foreach (KeyValuePair pair in operations) @@ -23,13 +24,13 @@ public IDictionary Encode(T state, IDictionary data, IParseDataDecoder decoder) + public IObjectState Decode(IDictionary data, IParseDataDecoder decoder, IServiceHub serviceHub) { IDictionary serverData = new Dictionary { }, mutableData = new Dictionary(data); @@ -55,7 +56,7 @@ public IObjectState Decode(IDictionary data, IParseDataDecoder d continue; } - serverData[pair.Key] = decoder.Decode(pair.Value); + serverData[pair.Key] = decoder.Decode(pair.Value, serviceHub); } return new MutableObjectState diff --git a/Parse/Utilities/InternalExtensions.cs b/Parse/Utilities/InternalExtensions.cs index eab6e299..27e57f15 100644 --- a/Parse/Utilities/InternalExtensions.cs +++ b/Parse/Utilities/InternalExtensions.cs @@ -45,8 +45,7 @@ public static bool CollectionsEqual(this IEnumerable a, IEnumerable b) a != null && b != null && a.SequenceEqual(b); - public static Task OnSuccess(this Task task, - Func, TResult> continuation) => ((Task) task).OnSuccess(t => continuation((Task) t)); + public static Task OnSuccess(this Task task, Func, TResult> continuation) => ((Task) task).OnSuccess(t => continuation((Task) t)); public static Task OnSuccess(this Task task, Action> continuation) => task.OnSuccess((Func, object>) (t => { @@ -54,8 +53,7 @@ public static Task OnSuccess(this Task task, Action> continu return null; })); - public static Task OnSuccess(this Task task, - Func continuation) => task.ContinueWith(t => + public static Task OnSuccess(this Task task, Func continuation) => task.ContinueWith(t => { if (t.IsFaulted) { From 645b19fc7ada56a69acf371605504adaf4e1d96d Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Thu, 9 Apr 2020 06:51:46 -0700 Subject: [PATCH 08/24] Fix all bugs according to tests. --- Parse.Test/AnalyticsControllerTests.cs | 14 +-- Parse.Test/CloudControllerTests.cs | 2 +- Parse.Test/CommandTests.cs | 32 ++--- Parse.Test/JsonTests.cs | 5 +- Parse.Test/ObjectControllerTests.cs | 40 +++---- Parse.Test/ObjectTests.cs | 13 +- Parse.Test/PushEncoderTests.cs | 41 ++++--- Parse.Test/PushTests.cs | 15 ++- Parse.Test/SessionControllerTests.cs | 6 +- Parse.Test/UserControllerTests.cs | 12 +- Parse.Test/UserTests.cs | 4 +- .../Abstractions/Library/CustomServiceHub.cs | 6 +- Parse/Library/MutableServiceHub.cs | 2 +- Parse/Library/ServiceHub.cs | 2 +- Parse/Management/ParseCommandRunner.cs | 2 +- Parse/Management/ParseObjectClass.cs | 8 +- .../Management/ParseObjectClassController.cs | 4 +- Parse/Management/ParseObjectController.cs | 6 +- .../Tracking/ParseRelationOperation.cs | 2 +- Parse/ParseClient.cs | 11 +- Parse/ParseObject.cs | 16 ++- Parse/ParseQuery.cs | 113 +++++++++++++++--- Parse/ParseRelation.cs | 27 +++-- Parse/Utilities/Encoding/ParseDataDecoder.cs | 4 +- 24 files changed, 255 insertions(+), 132 deletions(-) diff --git a/Parse.Test/AnalyticsControllerTests.cs b/Parse.Test/AnalyticsControllerTests.cs index 0c31a4c4..d6c99bcf 100644 --- a/Parse.Test/AnalyticsControllerTests.cs +++ b/Parse.Test/AnalyticsControllerTests.cs @@ -31,7 +31,7 @@ public Task TestTrackEventWithEmptyDimensions() Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/events/SomeEvent"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "events/SomeEvent"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -46,7 +46,7 @@ public Task TestTrackEventWithNonEmptyDimensions() Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath.Contains("/1/events/SomeEvent")), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path.Contains("events/SomeEvent")), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -54,14 +54,14 @@ public Task TestTrackEventWithNonEmptyDimensions() [AsyncStateMachine(typeof(AnalyticsControllerTests))] public Task TestTrackAppOpenedWithEmptyPushHash() { - Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary())); + Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary { })); - return new ParseAnalyticsController(mockRunner.Object).TrackAppOpenedAsync(default, sessionToken: default, serviceHub: Client, cancellationToken: CancellationToken.None).ContinueWith(t => + return new ParseAnalyticsController(mockRunner.Object).TrackAppOpenedAsync(default, sessionToken: default, serviceHub: Client, cancellationToken: CancellationToken.None).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/events/AppOpened"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "events/AppOpened"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } diff --git a/Parse.Test/CloudControllerTests.cs b/Parse.Test/CloudControllerTests.cs index ef80294e..7da79be3 100644 --- a/Parse.Test/CloudControllerTests.cs +++ b/Parse.Test/CloudControllerTests.cs @@ -47,7 +47,7 @@ public Task TestCallFunction() [TestMethod] [AsyncStateMachine(typeof(CloudControllerTests))] - public Task TestCallFunctionWithComplexType() => new ParseCloudCodeController(CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary() { { "result", new Dictionary { { "fosco", "ben" }, { "list", new List { 1, 2, 3 } } } } })).Object, Client.Decoder).CallFunctionAsync>("someFunction", default, default, Client, CancellationToken.None).ContinueWith(task => + public Task TestCallFunctionWithComplexType() => new ParseCloudCodeController(CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary { { "result", new Dictionary { { "fosco", "ben" }, { "list", new List { 1, 2, 3 } } } } })).Object, Client.Decoder).CallFunctionAsync>("someFunction", default, default, Client, CancellationToken.None).ContinueWith(task => { Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); diff --git a/Parse.Test/CommandTests.cs b/Parse.Test/CommandTests.cs index bfe15773..ccdc9dc9 100644 --- a/Parse.Test/CommandTests.cs +++ b/Parse.Test/CommandTests.cs @@ -34,7 +34,7 @@ public void TestMakeCommand() { ParseCommand command = new ParseCommand("endpoint", method: "GET", sessionToken: "abcd", headers: default, data: default); - Assert.AreEqual("/1/endpoint", command.Target.AbsolutePath); + Assert.AreEqual("endpoint", command.Path); Assert.AreEqual("GET", command.Method); Assert.IsTrue(command.Headers.Any(pair => pair.Key == "X-Parse-Session-Token" && pair.Value == "abcd")); } @@ -44,13 +44,13 @@ public void TestMakeCommand() public Task TestRunCommand() { Mock mockHttpClient = new Mock(); - Mock mockInstallationIdController = new Mock(); + Mock mockInstallationController = new Mock(); Task> fakeResponse = Task.FromResult(new Tuple(HttpStatusCode.OK, "{}")); mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(fakeResponse); - mockInstallationIdController.Setup(controller => controller.GetAsync()).Returns(Task.FromResult(default)); + mockInstallationController.Setup(installation => installation.GetAsync()).Returns(Task.FromResult(default)); - return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, Client.MetadataController, Client.ServerConnectionData, new Lazy(() => Client.UserController)).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: default)).ContinueWith(task => + return new ParseCommandRunner(mockHttpClient.Object, mockInstallationController.Object, Client.MetadataController, Client.ServerConnectionData, new Lazy(() => Client.UserController)).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: default)).ContinueWith(task => { Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); @@ -64,19 +64,19 @@ public Task TestRunCommand() public Task TestRunCommandWithArrayResult() { Mock mockHttpClient = new Mock(); - Mock mockInstallationIdController = new Mock(); + Mock mockInstallationController = new Mock(); mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "[]"))); - mockInstallationIdController.Setup(controller => controller.GetAsync()).Returns(Task.FromResult(default)); + mockInstallationController.Setup(installation => installation.GetAsync()).Returns(Task.FromResult(default)); - return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, Client.MetadataController, Client.ServerConnectionData, new Lazy(() => Client.UserController)).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: default)).ContinueWith(t => + return new ParseCommandRunner(mockHttpClient.Object, mockInstallationController.Object, Client.MetadataController, Client.ServerConnectionData, new Lazy(() => Client.UserController)).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: default)).ContinueWith(task => { - Assert.IsFalse(t.IsFaulted); - Assert.IsFalse(t.IsCanceled); - Assert.IsInstanceOfType(t.Result.Item2, typeof(IDictionary)); - Assert.AreEqual(1, t.Result.Item2.Count); - Assert.IsTrue(t.Result.Item2.ContainsKey("results")); - Assert.IsInstanceOfType(t.Result.Item2["results"], typeof(IList)); + Assert.IsFalse(task.IsFaulted); + Assert.IsFalse(task.IsCanceled); + Assert.IsInstanceOfType(task.Result.Item2, typeof(IDictionary)); + Assert.AreEqual(1, task.Result.Item2.Count); + Assert.IsTrue(task.Result.Item2.ContainsKey("results")); + Assert.IsInstanceOfType(task.Result.Item2["results"], typeof(IList)); }); } @@ -85,12 +85,12 @@ public Task TestRunCommandWithArrayResult() public Task TestRunCommandWithInvalidString() { Mock mockHttpClient = new Mock(); - Mock mockInstallationIdController = new Mock(); + Mock mockInstallationController = new Mock(); mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "invalid"))); - mockInstallationIdController.Setup(controller => controller.GetAsync()).Returns(Task.FromResult(default)); + mockInstallationController.Setup(controller => controller.GetAsync()).Returns(Task.FromResult(default)); - return new ParseCommandRunner(mockHttpClient.Object, mockInstallationIdController.Object, Client.MetadataController, Client.ServerConnectionData, new Lazy(() => Client.UserController)).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: default)).ContinueWith(task => + return new ParseCommandRunner(mockHttpClient.Object, mockInstallationController.Object, Client.MetadataController, Client.ServerConnectionData, new Lazy(() => Client.UserController)).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: default)).ContinueWith(task => { Assert.IsTrue(task.IsFaulted); Assert.IsFalse(task.IsCanceled); diff --git a/Parse.Test/JsonTests.cs b/Parse.Test/JsonTests.cs index 82ac2b6b..08b9bbe3 100644 --- a/Parse.Test/JsonTests.cs +++ b/Parse.Test/JsonTests.cs @@ -221,7 +221,10 @@ public void TestEncodeJson() list.Add(12.34); list.Add(1.23456789123456789); encoded = Json.Encode(list); - Assert.AreEqual("[{},\"1234 a\\t\\r\\n\",1234,12.34,1.23456789123457]", encoded); + + // This string should be [{},\"1234 a\\t\\r\\n\",1234,12.34,1.23456789123457] for .NET Framework (https://github.com/dotnet/runtime/issues/31483). + + Assert.AreEqual("[{},\"1234 a\\t\\r\\n\",1234,12.34,1.234567891234568]", encoded); dict["arr"] = new List(); encoded = Json.Encode(dict); diff --git a/Parse.Test/ObjectControllerTests.cs b/Parse.Test/ObjectControllerTests.cs index 1a79a472..3d22a97c 100644 --- a/Parse.Test/ObjectControllerTests.cs +++ b/Parse.Test/ObjectControllerTests.cs @@ -28,12 +28,12 @@ public Task TestFetch() { Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary { ["__type"] = "Object", ["className"] = "Corgi", ["objectId"] = "st4nl3yW", ["doge"] = "isShibaInu", ["createdAt"] = "2015-09-18T18:11:28.943Z" })); - return new ParseObjectController(mockRunner.Object, Client.Decoder).FetchAsync(new MutableObjectState { ClassName = "Corgi", ObjectId = "st4nl3yW", ServerData = new Dictionary { ["corgi"] = "isNotDoge" } }, default, Client, CancellationToken.None).ContinueWith(task => + return new ParseObjectController(mockRunner.Object, Client.Decoder, Client.ServerConnectionData).FetchAsync(new MutableObjectState { ClassName = "Corgi", ObjectId = "st4nl3yW", ServerData = new Dictionary { ["corgi"] = "isNotDoge" } }, default, Client, CancellationToken.None).ContinueWith(task => { Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = task.Result; Assert.AreEqual("isShibaInu", newState["doge"]); @@ -49,12 +49,12 @@ public Task TestSave() { Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.Accepted, new Dictionary { ["__type"] = "Object", ["className"] = "Corgi", ["objectId"] = "st4nl3yW", ["doge"] = "isShibaInu", ["createdAt"] = "2015-09-18T18:11:28.943Z" })); - return new ParseObjectController(mockRunner.Object, Client.Decoder).SaveAsync(new MutableObjectState { ClassName = "Corgi", ObjectId = "st4nl3yW", ServerData = new Dictionary { ["corgi"] = "isNotDoge" } }, new Dictionary { ["gogo"] = new Mock { }.Object }, default, Client, CancellationToken.None).ContinueWith(task => + return new ParseObjectController(mockRunner.Object, Client.Decoder, Client.ServerConnectionData).SaveAsync(new MutableObjectState { ClassName = "Corgi", ObjectId = "st4nl3yW", ServerData = new Dictionary { ["corgi"] = "isNotDoge" } }, new Dictionary { ["gogo"] = new Mock { }.Object }, default, Client, CancellationToken.None).ContinueWith(task => { Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = task.Result; Assert.AreEqual("isShibaInu", newState["doge"]); @@ -87,13 +87,13 @@ public Task TestSaveNewObject() Tuple> response = new Tuple>(HttpStatusCode.Created, responseDict); Mock mockRunner = CreateMockRunner(response); - ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder); + ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder, Client.ServerConnectionData); return controller.SaveAsync(state, operations, default, Client, CancellationToken.None).ContinueWith(task => { Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "classes/Corgi"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = task.Result; Assert.AreEqual("isShibaInu", newState["doge"]); @@ -151,7 +151,7 @@ public Task TestSaveAll() Tuple> response = new Tuple>(HttpStatusCode.OK, responseDict); Mock mockRunner = CreateMockRunner(response); - ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder); + ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder, Client.ServerConnectionData); IList> tasks = controller.SaveAllAsync(states, operationsList, default, Client, CancellationToken.None); return Task.WhenAll(tasks).ContinueWith(_ => @@ -169,7 +169,7 @@ public Task TestSaveAll() Assert.IsNotNull(serverState.UpdatedAt); } - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -235,7 +235,7 @@ public Task TestSaveAllManyObjects() Mock mockRunner = new Mock { }; mockRunner.SetupSequence(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)).Returns(Task.FromResult(response)).Returns(Task.FromResult(response2)); - ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder); + ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder, Client.ServerConnectionData); IList> tasks = controller.SaveAllAsync(states, operationsList, default, Client, CancellationToken.None); return Task.WhenAll(tasks).ContinueWith(_ => @@ -253,7 +253,7 @@ public Task TestSaveAllManyObjects() Assert.IsNotNull(serverState.UpdatedAt); } - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(3)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(3)); }); } @@ -270,12 +270,12 @@ public Task TestDelete() Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.OK, new Dictionary { })); - return new ParseObjectController(mockRunner.Object, Client.Decoder).DeleteAsync(state, default, CancellationToken.None).ContinueWith(task => + return new ParseObjectController(mockRunner.Object, Client.Decoder, Client.ServerConnectionData).DeleteAsync(state, default, CancellationToken.None).ContinueWith(task => { Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "classes/Corgi/st4nl3yW"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -306,14 +306,14 @@ public Task TestDeleteAll() Tuple> response = new Tuple>(HttpStatusCode.OK, responseDict); Mock mockRunner = CreateMockRunner(response); - ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder); + ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder, Client.ServerConnectionData); IList tasks = controller.DeleteAllAsync(states, default, CancellationToken.None); return Task.WhenAll(tasks).ContinueWith(_ => { Assert.IsTrue(tasks.All(task => task.IsCompleted && !task.IsCanceled && !task.IsFaulted)); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -357,14 +357,14 @@ public Task TestDeleteAllManyObjects() Mock mockRunner = new Mock(); mockRunner.SetupSequence(obj => obj.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(response)).Returns(Task.FromResult(response)).Returns(Task.FromResult(response2)); - ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder); + ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder, Client.ServerConnectionData); IList tasks = controller.DeleteAllAsync(states, null, CancellationToken.None); return Task.WhenAll(tasks).ContinueWith(_ => { Assert.IsTrue(tasks.All(task => task.IsCompleted && !task.IsCanceled && !task.IsFaulted)); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(3)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(3)); }); } @@ -408,7 +408,7 @@ public Task TestDeleteAllFailSome() Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.OK, new Dictionary { [nameof(results)] = results })); - ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder); + ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder, Client.ServerConnectionData); IList tasks = controller.DeleteAllAsync(states, null, CancellationToken.None); return Task.WhenAll(tasks).ContinueWith(_ => @@ -429,7 +429,7 @@ public Task TestDeleteAllFailSome() } } - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -461,7 +461,7 @@ public Task TestDeleteAllInconsistent() Mock mockRunner = CreateMockRunner(new Tuple>(HttpStatusCode.OK, new Dictionary { [nameof(results)] = results })); - ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder); + ParseObjectController controller = new ParseObjectController(mockRunner.Object, Client.Decoder, Client.ServerConnectionData); IList tasks = controller.DeleteAllAsync(states, null, CancellationToken.None); return Task.WhenAll(tasks).ContinueWith(_ => @@ -469,7 +469,7 @@ public Task TestDeleteAllInconsistent() Assert.IsTrue(tasks.All(task => task.IsFaulted)); Assert.IsInstanceOfType(tasks[0].Exception.InnerException, typeof(InvalidOperationException)); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "batch"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } diff --git a/Parse.Test/ObjectTests.cs b/Parse.Test/ObjectTests.cs index 14f14afc..30cd88c3 100644 --- a/Parse.Test/ObjectTests.cs +++ b/Parse.Test/ObjectTests.cs @@ -259,7 +259,8 @@ public void TestPropertiesGetterSetter() [TestMethod] public void TestAddToList() { - ParseObject obj = new ParseObject("Corgi"); + ParseObject obj = new ParseObject("Corgi").Bind(Client); + obj.AddToList("emptyList", "gogo"); obj["existingList"] = new List() { "rich" }; @@ -280,7 +281,8 @@ public void TestAddToList() [TestMethod] public void TestAddUniqueToList() { - ParseObject obj = new ParseObject("Corgi"); + ParseObject obj = new ParseObject("Corgi").Bind(Client); + obj.AddUniqueToList("emptyList", "gogo"); obj["existingList"] = new List() { "gogo" }; @@ -301,7 +303,7 @@ public void TestAddUniqueToList() [TestMethod] public void TestRemoveAllFromList() { - ParseObject obj = new ParseObject("Corgi") { ["existingList"] = new List() { "gogo", "Queen of Pain" } }; + ParseObject obj = new ParseObject("Corgi", Client) { ["existingList"] = new List { "gogo", "Queen of Pain" } }; obj.RemoveAllFromList("existingList", new List() { "gogo", "missingItem" }); Assert.AreEqual(1, obj.Get>("existingList").Count); @@ -327,7 +329,9 @@ public void TestTryGetValue() ["sessionToken"] = "se551onT0k3n" } }; + ParseObject obj = Client.GenerateObjectFromState(state, "Omitted"); + obj.TryGetValue("username", out string res); Assert.AreEqual("kevin", res); @@ -352,12 +356,15 @@ public void TestGet() ["sessionToken"] = "se551onT0k3n" } }; + ParseObject obj = Client.GenerateObjectFromState(state, "Omitted"); Assert.AreEqual("kevin", obj.Get("username")); Assert.ThrowsException(() => obj.Get("username")); Assert.ThrowsException(() => obj.Get("missingItem")); } +#warning Some tests are not implemented. + [TestMethod] public void TestIsDataAvailable() { diff --git a/Parse.Test/PushEncoderTests.cs b/Parse.Test/PushEncoderTests.cs index 3ac10a0f..826330f7 100644 --- a/Parse.Test/PushEncoderTests.cs +++ b/Parse.Test/PushEncoderTests.cs @@ -18,7 +18,7 @@ public void TestEncodeEmpty() state.Alert = "alert"; Assert.ThrowsException(() => ParsePushEncoder.Instance.Encode(state)); - state.Channels = new List { { "channel" } }; + state.Channels = new List { "channel" }; ParsePushEncoder.Instance.Encode(state); } @@ -28,28 +28,27 @@ public void TestEncode() { MutablePushState state = new MutablePushState { - Data = new Dictionary { - { "alert", "Some Alert" } - }, - Channels = new List { - { "channel" } - } + Data = new Dictionary + { + ["alert"] = "Some Alert" + }, + Channels = new List { "channel" } }; - IDictionary expected = new Dictionary { - { - "data", new Dictionary {{ - "alert", "Some Alert" - }} - }, - { - "where", new Dictionary {{ - "channels", new Dictionary {{ - "$in", new List {{ "channel" }} - }} - }} - } - }; + IDictionary expected = new Dictionary + { + ["data"] = new Dictionary + { + ["alert"] = "Some Alert" + }, + ["where"] = new Dictionary + { + ["channels"] = new Dictionary + { + ["$in"] = new List { "channel" } + } + } + }; Assert.AreEqual(JsonConvert.SerializeObject(expected), JsonConvert.SerializeObject(ParsePushEncoder.Instance.Encode(state))); } diff --git a/Parse.Test/PushTests.cs b/Parse.Test/PushTests.cs index 0d673810..a3dec4c9 100644 --- a/Parse.Test/PushTests.cs +++ b/Parse.Test/PushTests.cs @@ -95,18 +95,20 @@ public Task TestSubscribe() hub.PushChannelsController = GetMockedPushChannelsController(channels); channels.Add("test"); - return Client.SubscribeToPushChannelAsync("test").ContinueWith(task => + + return client.SubscribeToPushChannelAsync("test").ContinueWith(task => { Assert.IsTrue(task.IsCompleted); Assert.IsFalse(task.IsFaulted); - return Client.SubscribeToPushChannelsAsync(new List { { "test" } }); + return client.SubscribeToPushChannelsAsync(new List { "test" }); }).Unwrap().ContinueWith(task => { Assert.IsTrue(task.IsCompleted); Assert.IsFalse(task.IsFaulted); - return Client.SubscribeToPushChannelsAsync(new List { { "test" } }, new CancellationTokenSource { }.Token); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource { }; + return client.SubscribeToPushChannelsAsync(new List { "test" }, cancellationTokenSource.Token); }).Unwrap().ContinueWith(task => { Assert.IsTrue(task.IsCompleted); @@ -127,18 +129,19 @@ public Task TestUnsubscribe() channels.Add("test"); - return Client.UnsubscribeToPushChannelAsync("test").ContinueWith(task => + return client.UnsubscribeToPushChannelAsync("test").ContinueWith(task => { Assert.IsTrue(task.IsCompleted); Assert.IsFalse(task.IsFaulted); - return Client.UnsubscribeToPushChannelsAsync(new List { { "test" } }); + return client.UnsubscribeToPushChannelsAsync(new List { { "test" } }); }).ContinueWith(task => { Assert.IsTrue(task.IsCompleted); Assert.IsFalse(task.IsFaulted); - return Client.UnsubscribeToPushChannelsAsync(new List { { "test" } }, new CancellationTokenSource { }.Token); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource { }; + return client.UnsubscribeToPushChannelsAsync(new List { { "test" } }, cancellationTokenSource.Token); }).ContinueWith(task => { Assert.IsTrue(task.IsCompleted); diff --git a/Parse.Test/SessionControllerTests.cs b/Parse.Test/SessionControllerTests.cs index d7ed9798..6193b62f 100644 --- a/Parse.Test/SessionControllerTests.cs +++ b/Parse.Test/SessionControllerTests.cs @@ -49,7 +49,7 @@ public Task TestGetSession() Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/sessions/me"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "sessions/me"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState session = task.Result; Assert.AreEqual(2, session.Count()); @@ -69,7 +69,7 @@ public Task TestRevoke() Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/logout"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "logout"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } @@ -92,7 +92,7 @@ public Task TestUpgradeToRevocableSession() { Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/upgradeToRevocableSession"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "upgradeToRevocableSession"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState session = task.Result; Assert.AreEqual(2, session.Count()); diff --git a/Parse.Test/UserControllerTests.cs b/Parse.Test/UserControllerTests.cs index e2067dbc..0e674896 100644 --- a/Parse.Test/UserControllerTests.cs +++ b/Parse.Test/UserControllerTests.cs @@ -18,7 +18,7 @@ public class UserControllerTests ParseClient Client { get; set; } [TestInitialize] - public void SetUp() => new ParseClient(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); + public void SetUp() => Client = new ParseClient(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestMethod] [AsyncStateMachine(typeof(UserControllerTests))] @@ -55,7 +55,7 @@ public Task TestSignUp() Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/classes/_User"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "classes/_User"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = task.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); @@ -85,7 +85,7 @@ public Task TestLogInWithUsernamePassword() Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/login"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "login?username=grantland&password=123grantland123"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = task.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); @@ -115,7 +115,7 @@ public Task TestLogInWithAuthData() Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/users"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "users"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = task.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); @@ -145,7 +145,7 @@ public Task TestGetUserFromSessionToken() Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/users/me"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "users/me"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); IObjectState newState = task.Result; Assert.AreEqual("s3ss10nt0k3n", newState["sessionToken"]); @@ -166,7 +166,7 @@ public Task TestRequestPasswordReset() Assert.IsFalse(t.IsFaulted); Assert.IsFalse(t.IsCanceled); - mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Target.AbsolutePath == "/1/requestPasswordReset"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + mockRunner.Verify(obj => obj.RunCommandAsync(It.Is(command => command.Path == "requestPasswordReset"), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); }); } diff --git a/Parse.Test/UserTests.cs b/Parse.Test/UserTests.cs index e911a99d..2bba99ae 100644 --- a/Parse.Test/UserTests.cs +++ b/Parse.Test/UserTests.cs @@ -35,7 +35,7 @@ public void TestRemoveFields() }; ParseUser user = Client.GenerateObjectFromState(state, "_User"); - Assert.ThrowsException(() => user.Remove("username")); + Assert.ThrowsException(() => user.Remove("username")); try { @@ -762,7 +762,7 @@ public Task TestLogInWith() [TestMethod] public void TestImmutableKeys() { - ParseUser user = new ParseUser(); + ParseUser user = new ParseUser { }.Bind(Client) as ParseUser; string[] immutableKeys = new string[] { "sessionToken", "isNew" }; foreach (string key in immutableKeys) diff --git a/Parse/Abstractions/Library/CustomServiceHub.cs b/Parse/Abstractions/Library/CustomServiceHub.cs index a45fbc06..de23d43c 100644 --- a/Parse/Abstractions/Library/CustomServiceHub.cs +++ b/Parse/Abstractions/Library/CustomServiceHub.cs @@ -49,10 +49,10 @@ public abstract class CustomServiceHub : ICustomServiceHub public virtual IParseCurrentInstallationController CurrentInstallationController => Services.CurrentInstallationController; - public virtual IServerConnectionData ServerConnectionData { set; get; } + public virtual IServerConnectionData ServerConnectionData => Services.ServerConnectionData; - public virtual IParseDataDecoder Decoder { set; get; } + public virtual IParseDataDecoder Decoder => Services.Decoder; - public virtual IParseInstallationDataFinalizer InstallationDataFinalizer { set; get; } + public virtual IParseInstallationDataFinalizer InstallationDataFinalizer => Services.InstallationDataFinalizer; } } diff --git a/Parse/Library/MutableServiceHub.cs b/Parse/Library/MutableServiceHub.cs index ad2148e1..51651ef1 100644 --- a/Parse/Library/MutableServiceHub.cs +++ b/Parse/Library/MutableServiceHub.cs @@ -68,7 +68,7 @@ public MutableServiceHub SetDefaults(IServerConnectionData connectionData = defa CloudCodeController ??= new ParseCloudCodeController(CommandRunner, Decoder); ConfigurationController ??= new ParseConfigurationController(CommandRunner, StorageController, Decoder); FileController ??= new ParseFileController(CommandRunner); - ObjectController ??= new ParseObjectController(CommandRunner, Decoder); + ObjectController ??= new ParseObjectController(CommandRunner, Decoder, ServerConnectionData); QueryController ??= new ParseQueryController(CommandRunner, Decoder); SessionController ??= new ParseSessionController(CommandRunner, Decoder); UserController ??= new ParseUserController(CommandRunner, Decoder); diff --git a/Parse/Library/ServiceHub.cs b/Parse/Library/ServiceHub.cs index e3429d25..828c78fe 100644 --- a/Parse/Library/ServiceHub.cs +++ b/Parse/Library/ServiceHub.cs @@ -35,7 +35,7 @@ public class ServiceHub : IServiceHub public IParseCloudCodeController CloudCodeController => LateInitializer.GetValue(() => new ParseCloudCodeController(CommandRunner, Decoder)); public IParseConfigurationController ConfigurationController => LateInitializer.GetValue(() => new ParseConfigurationController(CommandRunner, StorageController, Decoder)); public IParseFileController FileController => LateInitializer.GetValue(() => new ParseFileController(CommandRunner)); - public IParseObjectController ObjectController => LateInitializer.GetValue(() => new ParseObjectController(CommandRunner, Decoder)); + public IParseObjectController ObjectController => LateInitializer.GetValue(() => new ParseObjectController(CommandRunner, Decoder, ServerConnectionData)); public IParseQueryController QueryController => LateInitializer.GetValue(() => new ParseQueryController(CommandRunner, Decoder)); public IParseSessionController SessionController => LateInitializer.GetValue(() => new ParseSessionController(CommandRunner, Decoder)); public IParseUserController UserController => LateInitializer.GetValue(() => new ParseUserController(CommandRunner, Decoder)); diff --git a/Parse/Management/ParseCommandRunner.cs b/Parse/Management/ParseCommandRunner.cs index f241ff8a..a9d9f408 100644 --- a/Parse/Management/ParseCommandRunner.cs +++ b/Parse/Management/ParseCommandRunner.cs @@ -76,7 +76,7 @@ public Task>> RunCommandAsync( if (responseCode < 200 || responseCode > 299) { - throw new ParseFailureException(contentJson.ContainsKey("code") ? (ParseFailureException.ErrorCode) contentJson["code"] : ParseFailureException.ErrorCode.OtherCause, contentJson.ContainsKey("error") ? contentJson["error"] as string : content); + throw new ParseFailureException(contentJson.ContainsKey("code") ? (ParseFailureException.ErrorCode) (long) contentJson["code"] : ParseFailureException.ErrorCode.OtherCause, contentJson.ContainsKey("error") ? contentJson["error"] as string : content); } return new Tuple>(response.Item1, contentJson); diff --git a/Parse/Management/ParseObjectClass.cs b/Parse/Management/ParseObjectClass.cs index 60fa8059..5a09827e 100644 --- a/Parse/Management/ParseObjectClass.cs +++ b/Parse/Management/ParseObjectClass.cs @@ -8,7 +8,13 @@ namespace Parse.Core.Internal { internal class ParseObjectClass { - public ParseObjectClass(Type type, ConstructorInfo constructor) => (TypeInfo, DeclaredName, Constructor, PropertyMappings) = (type.GetTypeInfo(), TypeInfo.GetParseClassName(), Constructor = constructor, PropertyMappings = type.GetProperties().Select(property => (Property: property, FieldNameAttribute: property.GetCustomAttribute(true))).Where(set => set.FieldNameAttribute is { }).ToDictionary(set => set.Property.Name, set => set.FieldNameAttribute.FieldName)); + public ParseObjectClass(Type type, ConstructorInfo constructor) + { + TypeInfo = type.GetTypeInfo(); + DeclaredName = TypeInfo.GetParseClassName(); + Constructor = Constructor = constructor; + PropertyMappings = type.GetProperties().Select(property => (Property: property, FieldNameAttribute: property.GetCustomAttribute(true))).Where(set => set.FieldNameAttribute is { }).ToDictionary(set => set.Property.Name, set => set.FieldNameAttribute.FieldName); + } public TypeInfo TypeInfo { get; } diff --git a/Parse/Management/ParseObjectClassController.cs b/Parse/Management/ParseObjectClassController.cs index 4b1567c8..39ab042b 100644 --- a/Parse/Management/ParseObjectClassController.cs +++ b/Parse/Management/ParseObjectClassController.cs @@ -78,7 +78,9 @@ public void AddValid(Type type) } } - ConstructorInfo constructor = type.FindConstructor(); +#warning Constructor detection may erroneously find a constructor which should not be used. + + ConstructorInfo constructor = type.FindConstructor() ?? type.FindConstructor(typeof(string), typeof(IServiceHub)); if (constructor is null) { diff --git a/Parse/Management/ParseObjectController.cs b/Parse/Management/ParseObjectController.cs index 914d668f..e98c7d48 100644 --- a/Parse/Management/ParseObjectController.cs +++ b/Parse/Management/ParseObjectController.cs @@ -17,7 +17,9 @@ public class ParseObjectController : IParseObjectController IParseDataDecoder Decoder { get; } - public ParseObjectController(IParseCommandRunner commandRunner, IParseDataDecoder decoder) => (CommandRunner, Decoder) = (commandRunner, decoder); + IServerConnectionData ServerConnectionData { get; } + + public ParseObjectController(IParseCommandRunner commandRunner, IParseDataDecoder decoder, IServerConnectionData serverConnectionData) => (CommandRunner, Decoder, ServerConnectionData) = (commandRunner, decoder, serverConnectionData); public Task FetchAsync(IObjectState state, string sessionToken, IServiceHub serviceHub, CancellationToken cancellationToken = default) { @@ -81,7 +83,7 @@ IList>> ExecuteBatchRequest(IList Dictionary results = new Dictionary { ["method"] = request.Method, - ["path"] = request.Target.AbsolutePath, + ["path"] = request is { Path: { }, Resource: { } } ? request.Target.AbsolutePath : new Uri(new Uri(ServerConnectionData.ServerURI), request.Path).AbsolutePath, }; if (request.DataObject != null) diff --git a/Parse/Management/Tracking/ParseRelationOperation.cs b/Parse/Management/Tracking/ParseRelationOperation.cs index a463b6cc..43af3492 100644 --- a/Parse/Management/Tracking/ParseRelationOperation.cs +++ b/Parse/Management/Tracking/ParseRelationOperation.cs @@ -74,7 +74,7 @@ public object Encode(IServiceHub serviceHub) public object Apply(object oldValue, string key) => oldValue switch { _ when Additions.Count == 0 && Removals.Count == 0 => default, - null => ParseRelationBase.CreateRelation(null, key, TargetClassName), + null => ClassController.CreateRelation(null, key, TargetClassName), ParseRelationBase { TargetClassName: { } oldClassname } when oldClassname != TargetClassName => throw new InvalidOperationException($"Related object must be a {oldClassname}, but a {TargetClassName} was passed in."), ParseRelationBase { } oldRelation => (Relation: oldRelation, oldRelation.TargetClassName = TargetClassName).Relation, _ => throw new InvalidOperationException("Operation is invalid after previous operation.") diff --git a/Parse/ParseClient.cs b/Parse/ParseClient.cs index 74c9cf75..8e17f1ca 100644 --- a/Parse/ParseClient.cs +++ b/Parse/ParseClient.cs @@ -86,7 +86,16 @@ public ParseClient(IServerConnectionData configuration, IServiceHub serviceHub = IServerConnectionData GenerateServerConnectionData() => configuration switch { null => throw new ArgumentNullException(nameof(configuration)), - ServerConnectionData { Test: true } data => data, + ServerConnectionData { Test: true, ServerURI: { } } data => data, + ServerConnectionData { Test: true } data => new ServerConnectionData + { + ApplicationID = data.ApplicationID, + Headers = data.Headers, + MasterKey = data.MasterKey, + Test = data.Test, + Key = data.Key, + ServerURI = "https://api.parse.com/1/" + }, { ServerURI: "https://api.parse.com/1/" } => throw new InvalidOperationException("Since the official parse server has shut down, you must specify a URI that points to a hosted instance."), { ApplicationID: { }, ServerURI: { }, Key: { } } data => data, _ => throw new InvalidOperationException("The IClientConfiguration implementation instance provided to the ParseClient constructor must be populated with configuration information.") diff --git a/Parse/ParseObject.cs b/Parse/ParseObject.cs index aaaf86ed..44edf471 100644 --- a/Parse/ParseObject.cs +++ b/Parse/ParseObject.cs @@ -63,18 +63,22 @@ public ParseObject(string className, IServiceHub serviceHub = default) bool isPointer = CreatingPointer.Value; CreatingPointer.Value = false; - Services = serviceHub ?? ParseClient.Instance /* Technically, it is not possible to throw an exception here for when serviceHub is null because ParseObjectClass.Constructor for derived classes will call this constructor with a null serviceHub, then call Bind, so that would fail. */ /* ?? throw new InvalidOperationException("A ParseClient needs to be initialized as the configured default instance before any ParseObjects can be instantiated.") */; - if (AutoClassName.Equals(className ?? throw new ArgumentException("You must specify a Parse class name when creating a new ParseObject."))) { className = GetType().GetParseClassName(); } - // If this is supposed to be created by a factory but wasn't, throw an exception. + // Technically, an exception should be thrown here for when both serviceHub and ParseClient.Instance is null, but it is not possible because ParseObjectClass.Constructor for derived classes is a reference to this constructor, and it will be called with a null serviceHub, then Bind will be called on the constructed object, so this needs to fail softly, unfortunately. + // Services = ... ?? throw new InvalidOperationException("A ParseClient needs to be initialized as the configured default instance before any ParseObjects can be instantiated.") - if (!Services.ClassController.GetClassMatch(className, GetType())) + if ((Services = serviceHub ?? ParseClient.Instance) is { }) { - throw new ArgumentException("You must create this type of ParseObject using ParseObject.Create() or the proper subclass."); + // If this is supposed to be created by a factory but wasn't, throw an exception. + + if (!Services.ClassController.GetClassMatch(className, GetType())) + { + throw new ArgumentException("You must create this type of ParseObject using ParseObject.Create() or the proper subclass."); + } } State = new MutableObjectState { ClassName = className }; @@ -99,7 +103,7 @@ public ParseObject(string className, IServiceHub serviceHub = default) #region ParseObject Creation /// - /// Constructor for use in ParseObject subclasses. Subclasses must specify a ParseClassName attribute. + /// Constructor for use in ParseObject subclasses. Subclasses must specify a ParseClassName attribute. Subclasses that do not implement a constructor accepting will need to be bond to an implementation instance via after construction. /// protected ParseObject(IServiceHub serviceHub = default) : this(AutoClassName, serviceHub) { } diff --git a/Parse/ParseQuery.cs b/Parse/ParseQuery.cs index e2be3933..a3d1d6c5 100644 --- a/Parse/ParseQuery.cs +++ b/Parse/ParseQuery.cs @@ -442,7 +442,17 @@ public ParseQuery(IServiceHub serviceHub) : this(serviceHub, serviceHub.ClassCon /// The key in the objects from the subquery to look in. /// The subquery to run /// A new query with the additional constraint. - public ParseQuery WhereMatchesKeyInQuery(string key, string keyInQuery, ParseQuery query) where TOther : ParseObject => new ParseQuery(this, where: new Dictionary { { key, new Dictionary { { "$select", new Dictionary { { nameof(query), query.BuildParameters(true) }, { nameof(key), keyInQuery } } } } } }); + public ParseQuery WhereMatchesKeyInQuery(string key, string keyInQuery, ParseQuery query) where TOther : ParseObject => new ParseQuery(this, where: new Dictionary + { + [key] = new Dictionary + { + ["$select"] = new Dictionary + { + [nameof(query)] = query.BuildParameters(true), + [nameof(key)] = keyInQuery + } + } + }); /// /// Adds a constraint to the query that requires a particular key's value @@ -452,7 +462,17 @@ public ParseQuery(IServiceHub serviceHub) : this(serviceHub, serviceHub.ClassCon /// The key in the objects from the subquery to look in. /// The subquery to run /// A new query with the additional constraint. - public ParseQuery WhereDoesNotMatchesKeyInQuery(string key, string keyInQuery, ParseQuery query) where TOther : ParseObject => new ParseQuery(this, where: new Dictionary { { key, new Dictionary { { "$dontSelect", new Dictionary { { nameof(query), query.BuildParameters(true) }, { nameof(key), keyInQuery } } } } } }); + public ParseQuery WhereDoesNotMatchesKeyInQuery(string key, string keyInQuery, ParseQuery query) where TOther : ParseObject => new ParseQuery(this, where: new Dictionary + { + [key] = new Dictionary + { + ["$dontSelect"] = new Dictionary + { + [nameof(query)] = query.BuildParameters(true), + [nameof(key)] = keyInQuery + } + } + }); /// /// Adds a constraint to the query that requires that a particular key's value @@ -462,7 +482,13 @@ public ParseQuery(IServiceHub serviceHub) : this(serviceHub, serviceHub.ClassCon /// The key to check. /// The query that the value should match. /// A new query with the additional constraint. - public ParseQuery WhereMatchesQuery(string key, ParseQuery query) where TOther : ParseObject => new ParseQuery(this, where: new Dictionary { { key, new Dictionary { { "$inQuery", query.BuildParameters(true) } } } }); + public ParseQuery WhereMatchesQuery(string key, ParseQuery query) where TOther : ParseObject => new ParseQuery(this, where: new Dictionary + { + [key] = new Dictionary + { + ["$inQuery"] = query.BuildParameters(true) + } + }); /// /// Adds a proximity-based constraint for finding objects with keys whose GeoPoint @@ -471,7 +497,13 @@ public ParseQuery(IServiceHub serviceHub) : this(serviceHub, serviceHub.ClassCon /// The key that the ParseGeoPoint is stored in. /// The reference ParseGeoPoint. /// A new query with the additional constraint. - public ParseQuery WhereNear(string key, ParseGeoPoint point) => new ParseQuery(this, where: new Dictionary { { key, new Dictionary { { "$nearSphere", point } } } }); + public ParseQuery WhereNear(string key, ParseGeoPoint point) => new ParseQuery(this, where: new Dictionary + { + [key] = new Dictionary + { + ["$nearSphere"] = point + } + }); /// /// Adds a constraint to the query that requires a particular key's value to be @@ -480,7 +512,13 @@ public ParseQuery(IServiceHub serviceHub) : this(serviceHub, serviceHub.ClassCon /// The key to check. /// The values that will match. /// A new query with the additional constraint. - public ParseQuery WhereNotContainedIn(string key, IEnumerable values) => new ParseQuery(this, where: new Dictionary { { key, new Dictionary { { "$nin", values.ToList() } } } }); + public ParseQuery WhereNotContainedIn(string key, IEnumerable values) => new ParseQuery(this, where: new Dictionary + { + [key] = new Dictionary + { + ["$nin"] = values.ToList() + } + }); /// /// Adds a constraint to the query that requires a particular key's value not @@ -489,7 +527,13 @@ public ParseQuery(IServiceHub serviceHub) : this(serviceHub, serviceHub.ClassCon /// The key to check. /// The value that that must not be equalled. /// A new query with the additional constraint. - public ParseQuery WhereNotEqualTo(string key, object value) => new ParseQuery(this, where: new Dictionary { { key, new Dictionary { { "$ne", value } } } }); + public ParseQuery WhereNotEqualTo(string key, object value) => new ParseQuery(this, where: new Dictionary + { + [key] = new Dictionary + { + ["$ne"] = value + } + }); /// /// Adds a constraint for finding string values that start with the provided string. @@ -498,7 +542,13 @@ public ParseQuery(IServiceHub serviceHub) : this(serviceHub, serviceHub.ClassCon /// 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 ParseQuery WhereStartsWith(string key, string suffix) => new ParseQuery(this, where: new Dictionary { { key, new Dictionary { { "$regex", "^" + RegexQuote(suffix) } } } }); + public ParseQuery WhereStartsWith(string key, string suffix) => new ParseQuery(this, where: new Dictionary + { + [key] = new Dictionary + { + ["$regex"] = $"^{RegexQuote(suffix)}" + } + }); /// /// Add a constraint to the query that requires a particular key's coordinates to be @@ -508,7 +558,20 @@ public ParseQuery(IServiceHub serviceHub) : this(serviceHub, serviceHub.ClassCon /// The lower-left inclusive corner of the box. /// The upper-right inclusive corner of the box. /// A new query with the additional constraint. - public ParseQuery WhereWithinGeoBox(string key, ParseGeoPoint southwest, ParseGeoPoint northeast) => new ParseQuery(this, where: new Dictionary { { key, new Dictionary { { "$within", new Dictionary { { "$box", new[] { southwest, northeast } } } } } } }); + public ParseQuery WhereWithinGeoBox(string key, ParseGeoPoint southwest, ParseGeoPoint northeast) => new ParseQuery(this, 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 @@ -518,9 +581,22 @@ public ParseQuery(IServiceHub serviceHub) : this(serviceHub, serviceHub.ClassCon /// The reference ParseGeoPoint. /// The maximum distance (in radians) of results to return. /// A new query with the additional constraint. - public ParseQuery WhereWithinDistance(string key, ParseGeoPoint point, ParseGeoDistance maxDistance) => new ParseQuery(WhereNear(key, point), where: new Dictionary { { key, new Dictionary { { "$maxDistance", maxDistance.Radians } } } }); + public ParseQuery WhereWithinDistance(string key, ParseGeoPoint point, ParseGeoDistance maxDistance) => new ParseQuery(WhereNear(key, point), where: new Dictionary + { + [key] = new Dictionary + { + ["$maxDistance"] = maxDistance.Radians + } + }); - internal ParseQuery WhereRelatedTo(ParseObject parent, string key) => new ParseQuery(this, where: new Dictionary { { "$relatedTo", new Dictionary { { "object", parent }, { nameof(key), key } } } }); + internal ParseQuery WhereRelatedTo(ParseObject parent, string key) => new ParseQuery(this, where: new Dictionary + { + ["$relatedTo"] = new Dictionary + { + ["object"] = parent, + [nameof(key)] = key + } + }); #endregion @@ -618,21 +694,21 @@ internal IDictionary BuildParameters(bool includeClassName = fal { Dictionary result = new Dictionary(); if (Filters != null) - result[nameof(Filters)] = PointerOrLocalIdEncoder.Instance.Encode(Filters, Services); + result["where"] = PointerOrLocalIdEncoder.Instance.Encode(Filters, Services); if (Orderings != null) result["order"] = String.Join(",", Orderings.ToArray()); if (SkipAmount != null) - result[nameof(SkipAmount)] = SkipAmount.Value; + result["skip"] = SkipAmount.Value; if (LimitAmount != null) - result[nameof(LimitAmount)] = LimitAmount.Value; + result["limit"] = LimitAmount.Value; if (Includes != null) result["include"] = String.Join(",", Includes.ToArray()); if (KeySelections != null) result["keys"] = String.Join(",", KeySelections.ToArray()); if (includeClassName) - result[nameof(ClassName)] = ClassName; + result["className"] = ClassName; if (RedirectClassNameForKey != null) - result[nameof(RedirectClassNameForKey)] = RedirectClassNameForKey; + result["redirectClassNameForKey"] = RedirectClassNameForKey; return result; } @@ -652,16 +728,23 @@ IDictionary EncodeRegex(Regex regex, string modifiers) { string options = GetRegexOptions(regex, modifiers); Dictionary dict = new Dictionary { ["$regex"] = regex.ToString() }; + if (!String.IsNullOrEmpty(options)) + { dict["$options"] = options; + } + return dict; } void EnsureNotInstallationQuery() { // The ParseInstallation class is not accessible from this project; using string literal. + if (ClassName.Equals("_Installation")) + { throw new InvalidOperationException("Cannot directly query the Installation class."); + } } /// diff --git a/Parse/ParseRelation.cs b/Parse/ParseRelation.cs index 9755e37c..348861e2 100644 --- a/Parse/ParseRelation.cs +++ b/Parse/ParseRelation.cs @@ -13,6 +13,22 @@ namespace Parse { + public static class RelationServiceExtensions + { + /// + /// Produces the proper ParseRelation<T> instance for the given classname. + /// + internal static ParseRelationBase CreateRelation(this IServiceHub serviceHub, ParseObject parent, string key, string targetClassName) => serviceHub.ClassController.CreateRelation(parent, key, targetClassName); + + internal static ParseRelationBase CreateRelation(this IParseObjectClassController classController, ParseObject parent, string key, string targetClassName) + { + Expression>> createRelationExpr = () => CreateRelation(parent, key, targetClassName); + return (createRelationExpr.Body as MethodCallExpression).Method.GetGenericMethodDefinition().MakeGenericMethod(classController.GetType(targetClassName) ?? typeof(ParseObject)).Invoke(default, new object[] { parent, key, targetClassName }) as ParseRelationBase; + } + + static ParseRelation CreateRelation(ParseObject parent, string key, string targetClassName) where T : ParseObject => new ParseRelation(parent, key, targetClassName); + } + /// /// A common base class for ParseRelations. /// @@ -61,17 +77,6 @@ internal void Remove(ParseObject entity) internal ParseQuery GetQuery() where T : ParseObject => TargetClassName is { } ? new ParseQuery(Parent.Services, TargetClassName).WhereRelatedTo(Parent, Key) : new ParseQuery(Parent.Services, Parent.ClassName).RedirectClassName(Key).WhereRelatedTo(Parent, Key); internal string TargetClassName { get; set; } - - /// - /// Produces the proper ParseRelation<T> instance for the given classname. - /// - internal static ParseRelationBase CreateRelation(ParseObject parent, string key, string targetClassName) - { - Expression>> createRelationExpr = () => CreateRelation(parent, key, targetClassName); - return (createRelationExpr.Body as MethodCallExpression).Method.GetGenericMethodDefinition().MakeGenericMethod(parent.Services.ClassController.GetType(targetClassName) ?? typeof(ParseObject)).Invoke(default, new object[] { parent, key, targetClassName }) as ParseRelationBase; - } - - static ParseRelation CreateRelation(ParseObject parent, string key, string targetClassName) where T : ParseObject => new ParseRelation(parent, key, targetClassName); } /// diff --git a/Parse/Utilities/Encoding/ParseDataDecoder.cs b/Parse/Utilities/Encoding/ParseDataDecoder.cs index 5383335e..cb8f1786 100644 --- a/Parse/Utilities/Encoding/ParseDataDecoder.cs +++ b/Parse/Utilities/Encoding/ParseDataDecoder.cs @@ -31,10 +31,10 @@ public class ParseDataDecoder : IParseDataDecoder "File" => new ParseFile(dictionary["name"] as string, new Uri(dictionary["url"] as string)), "GeoPoint" => new ParseGeoPoint(Conversion.To(dictionary["latitude"]), Conversion.To(dictionary["longitude"])), "Object" => ClassController.GenerateObjectFromState(ParseObjectCoder.Instance.Decode(dictionary, this, serviceHub), dictionary["className"] as string, serviceHub), - "Relation" => ParseRelationBase.CreateRelation(null, null, dictionary["className"] as string) + "Relation" => serviceHub.CreateRelation(null, null, dictionary["className"] as string) }, IDictionary { } dictionary => dictionary.ToDictionary(pair => pair.Key, pair => Decode(pair.Value, serviceHub)), - IList { } list => list.Select(item => Decode(item, serviceHub)), + IList { } list => list.Select(item => Decode(item, serviceHub)).ToList(), _ => data }; From b4ede59b2fb8ad13512c7ea791f9e80d4e90aba1 Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Wed, 29 Apr 2020 06:16:13 -0700 Subject: [PATCH 09/24] Added theoretical support for concurrent users. --- .../Abstractions/Library/IServiceHubCloner.cs | 2 +- .../Storage/IStorageController.cs | 2 +- .../Library/ConcurrentUserServiceHubCloner.cs | 17 ++++-- .../Management/ParseCurrentUserController.cs | 2 +- Parse/ParseClient.cs | 2 +- Parse/Properties/Resources.Designer.cs | 9 +++ Parse/Properties/Resources.resx | 3 + Parse/Storage/StorageController.cs | 57 ++++++++++++++++--- Parse/Utilities/Encoding/ParseObjectCoder.cs | 2 +- 9 files changed, 77 insertions(+), 19 deletions(-) diff --git a/Parse/Abstractions/Library/IServiceHubCloner.cs b/Parse/Abstractions/Library/IServiceHubCloner.cs index 35316d40..68c2273d 100644 --- a/Parse/Abstractions/Library/IServiceHubCloner.cs +++ b/Parse/Abstractions/Library/IServiceHubCloner.cs @@ -5,6 +5,6 @@ namespace Parse.Abstractions.Library { public interface IServiceHubCloner { - public IServiceHub BuildHub(in IServiceHub reference, IServiceHubComposer composer); + public IServiceHub BuildHub(in IServiceHub reference, IServiceHubComposer composer, params IServiceHubMutator[] requestedMutators); } } diff --git a/Parse/Abstractions/Storage/IStorageController.cs b/Parse/Abstractions/Storage/IStorageController.cs index edd27a7c..187e5f10 100644 --- a/Parse/Abstractions/Storage/IStorageController.cs +++ b/Parse/Abstractions/Storage/IStorageController.cs @@ -13,7 +13,7 @@ public interface IStorageController /// /// Cleans up any temporary files and/or directories created during SDK operation. /// - public void Clean(); + public void Clear(); /// /// Gets the file wrapper for the specified . diff --git a/Parse/Library/ConcurrentUserServiceHubCloner.cs b/Parse/Library/ConcurrentUserServiceHubCloner.cs index 2a6e9609..0c86553d 100644 --- a/Parse/Library/ConcurrentUserServiceHubCloner.cs +++ b/Parse/Library/ConcurrentUserServiceHubCloner.cs @@ -1,18 +1,23 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using Parse.Abstractions.Library; +using Parse.Common.Internal; +using Parse.Core.Internal; namespace Parse.Library { - public class ConcurrentUserServiceHubCloner : IServiceHubCloner + public class ConcurrentUserServiceHubCloner : IServiceHubCloner, IServiceHubMutator { - public IServiceHub BuildHub(in IServiceHub reference, IServiceHubComposer composer) - { - return new MutableServiceHub - { + public bool Valid { get; } = true; + + public IServiceHub BuildHub(in IServiceHub reference, IServiceHubComposer composer, params IServiceHubMutator[] requestedMutators) => composer.BuildHub(default, reference, requestedMutators.Concat(new[] { this }).ToArray()); - }; + public void Mutate(ref IMutableServiceHub target, in IServiceHub composedHub) + { + target.Cloner = this; + target.CurrentUserController = new ParseCurrentUserController(new ConcurrentUserStorageController { }, composedHub.ClassController, composedHub.Decoder); } } } diff --git a/Parse/Management/ParseCurrentUserController.cs b/Parse/Management/ParseCurrentUserController.cs index b601db90..71b50fe5 100644 --- a/Parse/Management/ParseCurrentUserController.cs +++ b/Parse/Management/ParseCurrentUserController.cs @@ -123,6 +123,6 @@ public void ClearFromDisk() public Task GetCurrentSessionTokenAsync(IServiceHub serviceHub, CancellationToken cancellationToken = default) => GetAsync(serviceHub, cancellationToken).OnSuccess(task => task.Result?.SessionToken); - public Task LogOutAsync(IServiceHub serviceHub, CancellationToken cancellationToken = default) => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => GetAsync(serviceHub, cancellationToken)).Unwrap().OnSuccess(t => ClearFromDisk()), cancellationToken); + public Task LogOutAsync(IServiceHub serviceHub, CancellationToken cancellationToken = default) => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => GetAsync(serviceHub, cancellationToken)).Unwrap().OnSuccess(task => ClearFromDisk()), cancellationToken); } } diff --git a/Parse/ParseClient.cs b/Parse/ParseClient.cs index 8e17f1ca..507635a5 100644 --- a/Parse/ParseClient.cs +++ b/Parse/ParseClient.cs @@ -98,7 +98,7 @@ public ParseClient(IServerConnectionData configuration, IServiceHub serviceHub = }, { ServerURI: "https://api.parse.com/1/" } => throw new InvalidOperationException("Since the official parse server has shut down, you must specify a URI that points to a hosted instance."), { ApplicationID: { }, ServerURI: { }, Key: { } } data => data, - _ => throw new InvalidOperationException("The IClientConfiguration implementation instance provided to the ParseClient constructor must be populated with configuration information.") + _ => throw new InvalidOperationException("The IServerConnectionData implementation instance provided to the ParseClient constructor must be populated with the information needed to connect to a Parse server instance.") }; if (configurators is { Length: int length } && length > 0) diff --git a/Parse/Properties/Resources.Designer.cs b/Parse/Properties/Resources.Designer.cs index bba82286..f66708a0 100644 --- a/Parse/Properties/Resources.Designer.cs +++ b/Parse/Properties/Resources.Designer.cs @@ -60,6 +60,15 @@ internal Resources() { } } + /// + /// Looks up a localized string similar to This storage controller is not configured to use physical files to store information. Data is hosted in system memory.. + /// + internal static string ConcurrentUserStorageControllerFileOperationNotSupportedMessage { + get { + return ResourceManager.GetString("ConcurrentUserStorageControllerFileOperationNotSupportedMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Mutating a storage dictionary is an asynchronous operation as the storing file needs to be modified.. /// diff --git a/Parse/Properties/Resources.resx b/Parse/Properties/Resources.resx index 452cdfd1..eb8d7799 100644 --- a/Parse/Properties/Resources.resx +++ b/Parse/Properties/Resources.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + This storage controller is not configured to use physical files to store information. Data is hosted in system memory. + Mutating a storage dictionary is an asynchronous operation as the storing file needs to be modified. diff --git a/Parse/Storage/StorageController.cs b/Parse/Storage/StorageController.cs index e719b82a..f6a052d5 100644 --- a/Parse/Storage/StorageController.cs +++ b/Parse/Storage/StorageController.cs @@ -11,15 +11,54 @@ using Parse.Library; using Parse.Management; using Parse.Storage; +using static Parse.Properties.Resources; namespace Parse.Common.Internal { + public class ConcurrentUserStorageController : IStorageController + { + class VirtualStorageDictionary : Dictionary, IStorageDictionary + { + public Task AddAsync(string key, object value) + { + Add(key, value); + return Task.CompletedTask; + } + + public Task RemoveAsync(string key) + { + Remove(key); + return Task.CompletedTask; + } + } + + VirtualStorageDictionary Storage { get; } = new VirtualStorageDictionary { }; + + public void Clear() => Storage.Clear(); + + public FileInfo GetWrapperForRelativePersistentStorageFilePath(string path) => throw new NotSupportedException(ConcurrentUserStorageControllerFileOperationNotSupportedMessage); + + public Task> LoadAsync() => Task.FromResult>(Storage); + + public Task> SaveAsync(IDictionary contents) + { + foreach (KeyValuePair pair in contents) + { + ((IDictionary)Storage).Add(pair); + } + + return Task.FromResult>(Storage); + } + + public Task TransferAsync(string originFilePath, string targetFilePath) => Task.FromException(new NotSupportedException(ConcurrentUserStorageControllerFileOperationNotSupportedMessage)); + } + /// /// Implements `IStorageController` for PCL targets, based off of PCLStorage. /// public class StorageController : IStorageController { - private class StorageDictionary : IStorageDictionary + class StorageDictionary : IStorageDictionary { public StorageDictionary(FileInfo file) => File = file; @@ -40,6 +79,8 @@ internal Task LoadAsync() => File.ReadAllTextAsync().ContinueWith(task => } }); + // TODO: Check if the call to ToDictionary is necessary here considering contents is IDictionary. + internal void Update(IDictionary contents) => Lock(() => Storage = contents.ToDictionary(element => element.Key, element => element.Value)); public Task AddAsync(string key, object value) @@ -60,13 +101,13 @@ public Task RemoveAsync(string key) } } - public void Add(string key, object value) => throw new NotSupportedException(Properties.Resources.StorageDictionarySynchronousMutationNotSupportedMessage); + public void Add(string key, object value) => throw new NotSupportedException(StorageDictionarySynchronousMutationNotSupportedMessage); - public bool Remove(string key) => throw new NotSupportedException(Properties.Resources.StorageDictionarySynchronousMutationNotSupportedMessage); + public bool Remove(string key) => throw new NotSupportedException(StorageDictionarySynchronousMutationNotSupportedMessage); - public void Add(KeyValuePair item) => throw new NotSupportedException(Properties.Resources.StorageDictionarySynchronousMutationNotSupportedMessage); + public void Add(KeyValuePair item) => throw new NotSupportedException(StorageDictionarySynchronousMutationNotSupportedMessage); - public bool Remove(KeyValuePair item) => throw new NotSupportedException(Properties.Resources.StorageDictionarySynchronousMutationNotSupportedMessage); + public bool Remove(KeyValuePair item) => throw new NotSupportedException(StorageDictionarySynchronousMutationNotSupportedMessage); public bool ContainsKey(string key) => Lock(() => Storage.ContainsKey(key)); @@ -125,7 +166,7 @@ void Lock(Action operation) public object this[string key] { get => Storage[key]; - set => throw new NotSupportedException(Properties.Resources.StorageDictionarySynchronousMutationNotSupportedMessage); + set => throw new NotSupportedException(StorageDictionarySynchronousMutationNotSupportedMessage); } } @@ -168,9 +209,9 @@ public Task> SaveAsync(IDictionary Storage as IStorageDictionary); }).Unwrap()); - // TODO: Attach the following method to AppDomain.CurrentDomain.ProcessExit. + // TODO: Attach the following method to AppDomain.CurrentDomain.ProcessExit if that actually ever made sense for anything except randomly generated file names, otherwise attach the delegate when it is known the file name is a randomly generated string. - public void Clean() + public void Clear() { if (new FileInfo(FallbackPersistentStorageFilePath) is { Exists: true } file) { diff --git a/Parse/Utilities/Encoding/ParseObjectCoder.cs b/Parse/Utilities/Encoding/ParseObjectCoder.cs index f4ff162a..835dc760 100644 --- a/Parse/Utilities/Encoding/ParseObjectCoder.cs +++ b/Parse/Utilities/Encoding/ParseObjectCoder.cs @@ -18,7 +18,7 @@ public class ParseObjectCoder public IDictionary Encode(T state, IDictionary operations, ParseDataEncoder encoder, IServiceHub serviceHub) where T : IObjectState { - Dictionary result = new Dictionary(); + Dictionary result = new Dictionary { }; foreach (KeyValuePair pair in operations) { // Serialize the data From fea1dee0c886e8248d02c9e7ab23c2de7f85a126 Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Wed, 29 Apr 2020 13:59:49 -0700 Subject: [PATCH 10/24] Change main project folder and namespace structure again, and clean up residual and/or unneeded files and systems. --- .appveyor.yml | 4 +- .../Device/IDeviceInfoController.cs | 24 -- .../IObjectSubclassingController.cs | 21 - .../Management/IParseCommandRunner.cs | 26 -- .../Management/IParseCorePlugins.cs | 26 -- .../Management/IParseCurrentUserController.cs | 14 - .../Management/IParseFileController.cs | 18 - .../Management/IParseObjectController.cs | 21 - .../IParseObjectCurrentController.cs | 54 --- .../Management/IParseUserController.cs | 27 -- .../Management/Tracking/IObjectState.cs | 21 - .../Tracking/IParseFieldOperation.cs | 46 -- .../Analytics/IParseAnalyticsController.cs | 39 -- .../Analytics/IParseAnalyticsPlugins.cs | 15 - .../IParseAuthenticationProvider.cs | 40 -- .../Code/IParseCloudCodeController.cs | 16 - .../Configuration/IParseConfigController.cs | 24 -- .../IParseCurrentConfigController.cs | 34 -- .../Platform/Files/IParseFileController.cs | 18 - .../Installation/IInstallationIdController.cs | 27 -- .../Installation/IParseInstallationCoder.cs | 12 - .../IParsePushChannelsController.cs | 14 - .../Notifications/IParsePushController.cs | 12 - .../Notifications/IParsePushPlugins.cs | 15 - .../Session/IParseSessionController.cs | 18 - .../Query/IParseQueryController.cs | 17 - .../Storage/IStorageController.cs | 56 --- .../ParseDownloadProgressEventArgs.cs | 19 - .../ParsePushNotificationEventArgs.cs | 44 -- .../Framework/ParseUploadProgressEventArgs.cs | 19 - Parse.Core/Parse.Core.csproj | 8 - {Parse.Test => Parse.Tests}/ACLTests.cs | 7 +- .../AnalyticsControllerTests.cs | 10 +- {Parse.Test => Parse.Tests}/AnalyticsTests.cs | 11 +- .../CloudControllerTests.cs | 9 +- {Parse.Test => Parse.Tests}/CloudTests.cs | 10 +- {Parse.Test => Parse.Tests}/CommandTests.cs | 25 +- {Parse.Test => Parse.Tests}/ConfigTests.cs | 12 +- .../ConversionTests.cs | 6 +- .../CurrentUserControllerTests.cs | 11 +- {Parse.Test => Parse.Tests}/DecoderTests.cs | 7 +- {Parse.Test => Parse.Tests}/EncoderTests.cs | 24 +- .../FileControllerTests.cs | 8 +- {Parse.Test => Parse.Tests}/FileStateTests.cs | 4 +- {Parse.Test => Parse.Tests}/FileTests.cs | 13 +- {Parse.Test => Parse.Tests}/GeoPointTests.cs | 24 +- .../InstallationIdControllerTests.cs | 9 +- .../InstallationTests.cs | 11 +- {Parse.Test => Parse.Tests}/JsonTests.cs | 154 +++---- {Parse.Test => Parse.Tests}/MoqExtensions.cs | 2 +- .../ObjectCoderTests.cs | 7 +- .../ObjectControllerTests.cs | 13 +- .../ObjectStateTests.cs | 7 +- {Parse.Test => Parse.Tests}/ObjectTests.cs | 9 +- .../Parse.Tests.csproj | 0 {Parse.Test => Parse.Tests}/ProgressTests.cs | 4 +- .../PushEncoderTests.cs | 4 +- {Parse.Test => Parse.Tests}/PushStateTests.cs | 5 +- {Parse.Test => Parse.Tests}/PushTests.cs | 12 +- {Parse.Test => Parse.Tests}/RelationTests.cs | 6 +- .../SessionControllerTests.cs | 14 +- {Parse.Test => Parse.Tests}/SessionTests.cs | 15 +- .../UserControllerTests.cs | 15 +- {Parse.Test => Parse.Tests}/UserTests.cs | 16 +- Parse.sln | 16 +- .../Control}/IParseFieldOperation.cs | 4 +- .../CustomServiceHub.cs | 20 +- .../Infrastructure/Data}/IParseDataDecoder.cs | 4 +- .../Execution}/IParseCommandRunner.cs | 3 +- .../Infrastructure/Execution}/IWebClient.cs | 7 +- .../ICustomServiceHub.cs | 6 +- .../Infrastructure/IDataTransferLevel.cs | 7 +- .../IEnvironmentData.cs | 2 +- .../IHostManifestData.cs | 2 +- .../Infrastructure}/IJsonConvertible.cs | 2 +- .../IMetadataController.cs | 5 +- .../IMutableServiceHub.cs | 23 +- .../IServerConnectionData.cs | 2 +- .../IServiceHub.cs | 23 +- .../IServiceHubCloner.cs | 5 +- .../IServiceHubComposer.cs | 2 +- .../IServiceHubMutator.cs | 6 +- .../IStorageConfiguration.cs | 3 +- .../IStorageController.cs | 3 +- .../Management/IParseCorePlugins.cs | 42 -- .../Analytics/IParseAnalyticsController.cs | 4 +- .../Analytics/IParseAnalyticsPlugins.cs | 15 - .../IParseAuthenticationProvider.cs | 2 +- .../IParseCloudCodeController.cs | 4 +- .../IParseConfigurationController.cs | 5 +- .../IParseCurrentConfigurationController.cs | 5 +- .../Platform/Files/IParseFileController.cs | 4 +- .../IParseCurrentInstallationController.cs | 4 +- .../IParseInstallationCoder.cs | 5 +- .../IParseInstallationController.cs | 2 +- .../IParseInstallationDataFinalizer.cs | 2 +- .../Notifications/IParsePushPlugins.cs | 15 - .../Platform/Notifications/IPushState.cs | 20 - .../Objects}/IObjectState.cs | 3 +- .../Objects}/IParseObjectClassController.cs | 4 +- .../Objects}/IParseObjectController.cs | 5 +- .../Objects}/IParseObjectCurrentController.cs | 4 +- .../IParsePushChannelsController.cs | 4 +- .../IParsePushController.cs | 4 +- .../Abstractions/Platform/Push}/IPushState.cs | 3 +- .../Queries}/IParseQueryController.cs | 3 +- .../IParseSessionController.cs | 5 +- .../Users}/IParseCurrentUserController.cs | 5 +- .../Users}/IParseUserController.cs | 6 +- .../CacheLocationMutator.cs | 8 +- .../ConcurrentUserServiceHubCloner.cs | 10 +- .../Control}/ParseAddOperation.cs | 8 +- .../Control}/ParseAddUniqueOperation.cs | 8 +- .../Control}/ParseDeleteOperation.cs | 5 +- .../Control}/ParseFieldOperations.cs | 3 +- .../Control}/ParseIncrementOperation.cs | 5 +- .../Control}/ParseRelationOperation.cs | 7 +- .../Control}/ParseRemoveOperation.cs | 8 +- .../Control}/ParseSetOperation.cs | 6 +- .../Data}/NoObjectsEncoder.cs | 2 +- .../Data}/ParseDataDecoder.cs | 9 +- .../Data}/ParseDataEncoder.cs | 8 +- .../Data}/ParseObjectCoder.cs | 8 +- .../Data}/PointerOrLocalIdEncoder.cs | 2 +- .../DataTransferLevel.cs} | 8 +- .../EnvironmentData.cs | 6 +- .../Execution}/ParseCommand.cs | 6 +- .../Execution}/ParseCommandRunner.cs | 21 +- .../Execution}/UniversalWebClient.cs | 5 +- .../Execution}/WebRequest.cs | 2 +- .../HostManifestData.cs | 5 +- ...entifierBasedCacheLocationConfiguration.cs | 5 +- ...MetadataBasedCacheLocationConfiguration.cs | 7 +- .../MetadataController.cs | 7 +- .../MetadataMutator.cs | 8 +- .../MutableServiceHub.cs | 34 +- .../OrchestrationServiceHub.cs | 25 +- .../ParseClassNameAttribute.cs | 0 .../ParseFailureException.cs | 2 +- .../ParseFieldNameAttribute.cs | 0 .../ServerConnectionData.cs | 6 +- .../{Library => Infrastructure}/ServiceHub.cs | 38 +- .../StorageController.cs | 31 +- .../Utilities/AssemblyLister.cs | 6 +- .../Utilities/Conversion.cs | 21 +- .../Utilities/FileUtilities.cs} | 5 +- .../Utilities/FlexibleDictionaryWrapper.cs | 5 +- .../Utilities/FlexibleListWrapper.cs | 5 +- .../Utilities/IdentityEqualityComparer.cs | 2 +- .../Utilities/InternalExtensions.cs | 12 +- .../Utilities/JsonUtilities.cs} | 56 +-- .../Utilities/LateInitializer.cs | 12 +- .../{ => Infrastructure}/Utilities/LockSet.cs | 2 +- .../Utilities/ReflectionUtilities.cs} | 4 +- .../Utilities/SynchronizedEventHandler.cs | 56 +-- .../Utilities/TaskQueue.cs | 20 +- .../Utilities/ThreadingUtilities.cs | 2 +- .../Utilities/XamarinAttributes.cs | 407 ++++++++++++++++++ Parse/Management/LightParseCorePlugins.cs | 54 --- Parse/Management/ParseCorePlugins.cs | 283 ------------ Parse/Modules/IParseModule.cs | 8 - Parse/Modules/ParseModuleAttribute.cs | 16 - Parse/Modules/ParseModuleController.cs | 86 ---- Parse/Parse.csproj | 9 +- .../Analytics/ParseAnalyticsController.cs | 11 +- .../Analytics/ParseAnalyticsPlugins.cs | 83 ---- .../ParseCloudCodeController.cs | 12 +- .../Configuration}/ParseConfiguration.cs | 14 +- .../ParseConfigurationController.cs | 11 +- .../ParseCurrentConfigurationController.cs | 9 +- Parse/Platform/Files/FileState.cs | 2 +- Parse/{ => Platform/Files}/ParseFile.cs | 7 +- Parse/Platform/Files/ParseFileController.cs | 14 +- .../ParseCurrentInstallationController.cs | 22 +- .../Installations}/ParseInstallation.cs | 7 +- .../ParseInstallationCoder.cs | 16 +- .../ParseInstallationController.cs | 12 +- .../ParseInstallationDataFinalizer.cs | 4 +- .../Location}/ParseGeoDistance.cs | 0 .../{ => Platform/Location}/ParseGeoPoint.cs | 2 +- .../Notifications/ParsePushPlugins.cs | 143 ------ .../Objects}/MutableObjectState.cs | 26 +- Parse/{ => Platform/Objects}/ParseObject.cs | 13 +- .../Objects}/ParseObjectClass.cs | 5 +- .../Objects}/ParseObjectClassController.cs | 19 +- .../Objects}/ParseObjectController.cs | 33 +- Parse/{ => Platform}/ParseClient.cs | 17 +- .../MutablePushState.cs | 7 +- Parse/{ => Platform/Push}/ParsePush.cs | 5 +- .../ParsePushChannelsController.cs | 10 +- .../ParsePushController.cs | 11 +- .../ParsePushEncoder.cs | 18 +- .../ParsePushModule.cs | 3 - .../Push}/ParsePushNotificationEvent.cs | 8 +- Parse/{ => Platform/Queries}/ParseQuery.cs | 8 +- .../Queries}/ParseQueryController.cs | 13 +- .../{ => Platform/Relations}/ParseRelation.cs | 8 +- Parse/{ => Platform/Roles}/ParseRole.cs | 0 Parse/Platform/Security/ParseACL.cs | 4 +- Parse/{ => Platform/Sessions}/ParseSession.cs | 2 - .../ParseSessionController.cs | 14 +- .../Users}/ParseCurrentUserController.cs | 33 +- Parse/{ => Platform/Users}/ParseUser.cs | 7 +- .../Users}/ParseUserController.cs | 16 +- Parse/{Properties => }/Resources.Designer.cs | 58 +-- Parse/{Properties => }/Resources.resx | 0 .../AnalyticsServiceExtensions.cs | 7 +- .../CloudCodeServiceExtensions.cs | 4 +- .../ConfigurationServiceExtensions.cs | 5 +- .../InstallationServiceExtensions.cs | 3 +- .../ObjectServiceExtensions.cs | 10 +- Parse/Utilities/ParseExtensions.cs | 6 +- Parse/Utilities/ParseFileExtensions.cs | 2 +- Parse/Utilities/ParseQueryExtensions.cs | 4 +- Parse/Utilities/ParseRelationExtensions.cs | 2 +- Parse/Utilities/ParseUserExtensions.cs | 3 +- .../{ => Utilities}/PushServiceExtensions.cs | 5 +- .../{ => Utilities}/QueryServiceExtensions.cs | 2 +- .../{ => Utilities}/RoleServiceExtensions.cs | 2 +- .../SessionsServiceExtensions.cs | 5 +- .../{ => Utilities}/UserServiceExtensions.cs | 7 +- Parse/Utilities/XamarinAttributes.cs | 406 ----------------- 222 files changed, 1266 insertions(+), 2851 deletions(-) delete mode 100644 Parse.Core/Abstractions/Device/IDeviceInfoController.cs delete mode 100644 Parse.Core/Abstractions/Management/IObjectSubclassingController.cs delete mode 100644 Parse.Core/Abstractions/Management/IParseCommandRunner.cs delete mode 100644 Parse.Core/Abstractions/Management/IParseCorePlugins.cs delete mode 100644 Parse.Core/Abstractions/Management/IParseCurrentUserController.cs delete mode 100644 Parse.Core/Abstractions/Management/IParseFileController.cs delete mode 100644 Parse.Core/Abstractions/Management/IParseObjectController.cs delete mode 100644 Parse.Core/Abstractions/Management/IParseObjectCurrentController.cs delete mode 100644 Parse.Core/Abstractions/Management/IParseUserController.cs delete mode 100644 Parse.Core/Abstractions/Management/Tracking/IObjectState.cs delete mode 100644 Parse.Core/Abstractions/Management/Tracking/IParseFieldOperation.cs delete mode 100644 Parse.Core/Abstractions/Platform/Analytics/IParseAnalyticsController.cs delete mode 100644 Parse.Core/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs delete mode 100644 Parse.Core/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs delete mode 100644 Parse.Core/Abstractions/Platform/Code/IParseCloudCodeController.cs delete mode 100644 Parse.Core/Abstractions/Platform/Configuration/IParseConfigController.cs delete mode 100644 Parse.Core/Abstractions/Platform/Configuration/IParseCurrentConfigController.cs delete mode 100644 Parse.Core/Abstractions/Platform/Files/IParseFileController.cs delete mode 100644 Parse.Core/Abstractions/Platform/Installation/IInstallationIdController.cs delete mode 100644 Parse.Core/Abstractions/Platform/Installation/IParseInstallationCoder.cs delete mode 100644 Parse.Core/Abstractions/Platform/Notifications/IParsePushChannelsController.cs delete mode 100644 Parse.Core/Abstractions/Platform/Notifications/IParsePushController.cs delete mode 100644 Parse.Core/Abstractions/Platform/Notifications/IParsePushPlugins.cs delete mode 100644 Parse.Core/Abstractions/Platform/Session/IParseSessionController.cs delete mode 100644 Parse.Core/Abstractions/Query/IParseQueryController.cs delete mode 100644 Parse.Core/Abstractions/Storage/IStorageController.cs delete mode 100644 Parse.Core/Framework/ParseDownloadProgressEventArgs.cs delete mode 100644 Parse.Core/Framework/ParsePushNotificationEventArgs.cs delete mode 100644 Parse.Core/Framework/ParseUploadProgressEventArgs.cs delete mode 100644 Parse.Core/Parse.Core.csproj rename {Parse.Test => Parse.Tests}/ACLTests.cs (95%) rename {Parse.Test => Parse.Tests}/AnalyticsControllerTests.cs (95%) rename {Parse.Test => Parse.Tests}/AnalyticsTests.cs (96%) rename {Parse.Test => Parse.Tests}/CloudControllerTests.cs (94%) rename {Parse.Test => Parse.Tests}/CloudTests.cs (91%) rename {Parse.Test => Parse.Tests}/CommandTests.cs (83%) rename {Parse.Test => Parse.Tests}/ConfigTests.cs (94%) rename {Parse.Test => Parse.Tests}/ConversionTests.cs (87%) rename {Parse.Test => Parse.Tests}/CurrentUserControllerTests.cs (97%) rename {Parse.Test => Parse.Tests}/DecoderTests.cs (98%) rename {Parse.Test => Parse.Tests}/EncoderTests.cs (96%) rename {Parse.Test => Parse.Tests}/FileControllerTests.cs (96%) rename {Parse.Test => Parse.Tests}/FileStateTests.cs (95%) rename {Parse.Test => Parse.Tests}/FileTests.cs (91%) rename {Parse.Test => Parse.Tests}/GeoPointTests.cs (87%) rename {Parse.Test => Parse.Tests}/InstallationIdControllerTests.cs (97%) rename {Parse.Test => Parse.Tests}/InstallationTests.cs (97%) rename {Parse.Test => Parse.Tests}/JsonTests.cs (66%) rename {Parse.Test => Parse.Tests}/MoqExtensions.cs (98%) rename {Parse.Test => Parse.Tests}/ObjectCoderTests.cs (92%) rename {Parse.Test => Parse.Tests}/ObjectControllerTests.cs (98%) rename {Parse.Test => Parse.Tests}/ObjectStateTests.cs (96%) rename {Parse.Test => Parse.Tests}/ObjectTests.cs (99%) rename Parse.Test/Parse.Test.csproj => Parse.Tests/Parse.Tests.csproj (100%) rename {Parse.Test => Parse.Tests}/ProgressTests.cs (96%) rename {Parse.Test => Parse.Tests}/PushEncoderTests.cs (97%) rename {Parse.Test => Parse.Tests}/PushStateTests.cs (90%) rename {Parse.Test => Parse.Tests}/PushTests.cs (96%) rename {Parse.Test => Parse.Tests}/RelationTests.cs (91%) rename {Parse.Test => Parse.Tests}/SessionControllerTests.cs (94%) rename {Parse.Test => Parse.Tests}/SessionTests.cs (96%) rename {Parse.Test => Parse.Tests}/UserControllerTests.cs (96%) rename {Parse.Test => Parse.Tests}/UserTests.cs (98%) rename Parse/Abstractions/{Management/Tracking => Infrastructure/Control}/IParseFieldOperation.cs (97%) rename Parse/Abstractions/{Library => Infrastructure}/CustomServiceHub.cs (80%) rename Parse/{Utilities/Encoding => Abstractions/Infrastructure/Data}/IParseDataDecoder.cs (94%) rename Parse/Abstractions/{Management => Infrastructure/Execution}/IParseCommandRunner.cs (92%) rename Parse/{Utilities => Abstractions/Infrastructure/Execution}/IWebClient.cs (73%) rename Parse/Abstractions/{Library => Infrastructure}/ICustomServiceHub.cs (50%) rename Parse.Core/Abstractions/Platform/Installation/IParseCurrentInstallationController.cs => Parse/Abstractions/Infrastructure/IDataTransferLevel.cs (64%) rename Parse/Abstractions/{Library => Infrastructure}/IEnvironmentData.cs (94%) rename Parse/Abstractions/{Library => Infrastructure}/IHostManifestData.cs (93%) rename Parse/{Utilities => Abstractions/Infrastructure}/IJsonConvertible.cs (94%) rename Parse/Abstractions/{Library => Infrastructure}/IMetadataController.cs (91%) rename Parse/Abstractions/{Library => Infrastructure}/IMutableServiceHub.cs (72%) rename Parse/Abstractions/{Library => Infrastructure}/IServerConnectionData.cs (95%) rename Parse/Abstractions/{Library => Infrastructure}/IServiceHub.cs (77%) rename Parse/Abstractions/{Library => Infrastructure}/IServiceHubCloner.cs (73%) rename Parse/Abstractions/{Library => Infrastructure}/IServiceHubComposer.cs (88%) rename Parse/Abstractions/{Library => Infrastructure}/IServiceHubMutator.cs (78%) rename Parse/Abstractions/{Storage => Infrastructure}/IStorageConfiguration.cs (85%) rename Parse/Abstractions/{Storage => Infrastructure}/IStorageController.cs (98%) delete mode 100644 Parse/Abstractions/Management/IParseCorePlugins.cs delete mode 100644 Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs rename Parse/Abstractions/Platform/{Code => Cloud}/IParseCloudCodeController.cs (87%) rename Parse/Abstractions/Platform/{Installation => Installations}/IParseCurrentInstallationController.cs (81%) rename Parse/Abstractions/Platform/{Installation => Installations}/IParseInstallationCoder.cs (78%) rename Parse/Abstractions/Platform/{Installation => Installations}/IParseInstallationController.cs (95%) rename Parse/Abstractions/Platform/{Installation => Installations}/IParseInstallationDataFinalizer.cs (92%) delete mode 100644 Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs delete mode 100644 Parse/Abstractions/Platform/Notifications/IPushState.cs rename Parse/Abstractions/{Management/Tracking => Platform/Objects}/IObjectState.cs (90%) rename Parse/Abstractions/{Management => Platform/Objects}/IParseObjectClassController.cs (86%) rename Parse/Abstractions/{Management => Platform/Objects}/IParseObjectController.cs (90%) rename Parse/Abstractions/{Management => Platform/Objects}/IParseObjectCurrentController.cs (96%) rename Parse/Abstractions/Platform/{Notifications => Push}/IParsePushChannelsController.cs (89%) rename Parse/Abstractions/Platform/{Notifications => Push}/IParsePushController.cs (86%) rename {Parse.Core/Abstractions/Platform/Notifications => Parse/Abstractions/Platform/Push}/IPushState.cs (91%) rename Parse/Abstractions/{Query => Platform/Queries}/IParseQueryController.cs (90%) rename Parse/Abstractions/Platform/{Session => Sessions}/IParseSessionController.cs (86%) rename Parse/Abstractions/{Management => Platform/Users}/IParseCurrentUserController.cs (83%) rename Parse/Abstractions/{Management => Platform/Users}/IParseUserController.cs (87%) rename Parse/{Library => Infrastructure}/CacheLocationMutator.cs (85%) rename Parse/{Library => Infrastructure}/ConcurrentUserServiceHubCloner.cs (79%) rename Parse/{Management/Tracking => Infrastructure/Control}/ParseAddOperation.cs (89%) rename Parse/{Management/Tracking => Infrastructure/Control}/ParseAddUniqueOperation.cs (92%) rename Parse/{Management/Tracking => Infrastructure/Control}/ParseDeleteOperation.cs (88%) rename Parse/{Management/Tracking => Infrastructure/Control}/ParseFieldOperations.cs (94%) rename Parse/{Management/Tracking => Infrastructure/Control}/ParseIncrementOperation.cs (98%) rename Parse/{Management/Tracking => Infrastructure/Control}/ParseRelationOperation.cs (96%) rename Parse/{Management/Tracking => Infrastructure/Control}/ParseRemoveOperation.cs (89%) rename Parse/{Management/Tracking => Infrastructure/Control}/ParseSetOperation.cs (82%) rename Parse/{Utilities/Encoding => Infrastructure/Data}/NoObjectsEncoder.cs (96%) rename Parse/{Utilities/Encoding => Infrastructure/Data}/ParseDataDecoder.cs (92%) rename Parse/{Utilities/Encoding => Infrastructure/Data}/ParseDataEncoder.cs (95%) rename Parse/{Utilities/Encoding => Infrastructure/Data}/ParseObjectCoder.cs (92%) rename Parse/{Utilities/Encoding => Infrastructure/Data}/PointerOrLocalIdEncoder.cs (97%) rename Parse/{Abstractions/Library/IDataTransferLevel.cs => Infrastructure/DataTransferLevel.cs} (85%) rename Parse/{Library => Infrastructure}/EnvironmentData.cs (91%) rename Parse/{Management => Infrastructure/Execution}/ParseCommand.cs (93%) rename Parse/{Management => Infrastructure/Execution}/ParseCommandRunner.cs (92%) rename Parse/{Utilities => Infrastructure/Execution}/UniversalWebClient.cs (97%) rename Parse/{Utilities => Infrastructure/Execution}/WebRequest.cs (96%) rename Parse/{Library => Infrastructure}/HostManifestData.cs (97%) rename Parse/{Storage => Infrastructure}/IdentifierBasedCacheLocationConfiguration.cs (95%) rename Parse/{Storage => Infrastructure}/MetadataBasedCacheLocationConfiguration.cs (93%) rename Parse/{Library => Infrastructure}/MetadataController.cs (76%) rename Parse/{Library => Infrastructure}/MetadataMutator.cs (86%) rename Parse/{Library => Infrastructure}/MutableServiceHub.cs (81%) rename Parse/{Library => Infrastructure}/OrchestrationServiceHub.cs (82%) rename Parse/{Utilities => Infrastructure}/ParseClassNameAttribute.cs (100%) rename Parse/{Library => Infrastructure}/ParseFailureException.cs (99%) rename Parse/{Utilities => Infrastructure}/ParseFieldNameAttribute.cs (100%) rename Parse/{Library => Infrastructure}/ServerConnectionData.cs (93%) rename Parse/{Library => Infrastructure}/ServiceHub.cs (79%) rename Parse/{Storage => Infrastructure}/StorageController.cs (95%) rename Parse/{ => Infrastructure}/Utilities/AssemblyLister.cs (95%) rename Parse/{ => Infrastructure}/Utilities/Conversion.cs (91%) rename Parse/{Utilities/StorageManager.cs => Infrastructure/Utilities/FileUtilities.cs} (95%) rename Parse/{ => Infrastructure}/Utilities/FlexibleDictionaryWrapper.cs (97%) rename Parse/{ => Infrastructure}/Utilities/FlexibleListWrapper.cs (96%) rename Parse/{ => Infrastructure}/Utilities/IdentityEqualityComparer.cs (95%) rename Parse/{ => Infrastructure}/Utilities/InternalExtensions.cs (91%) rename Parse/{Utilities/Json.cs => Infrastructure/Utilities/JsonUtilities.cs} (94%) rename Parse/{Library => Infrastructure}/Utilities/LateInitializer.cs (91%) rename Parse/{ => Infrastructure}/Utilities/LockSet.cs (96%) rename Parse/{Utilities/ReflectionHelpers.cs => Infrastructure/Utilities/ReflectionUtilities.cs} (96%) rename Parse/{ => Infrastructure}/Utilities/SynchronizedEventHandler.cs (51%) rename Parse/{ => Infrastructure}/Utilities/TaskQueue.cs (87%) rename Parse/{ => Infrastructure}/Utilities/ThreadingUtilities.cs (92%) create mode 100644 Parse/Infrastructure/Utilities/XamarinAttributes.cs delete mode 100644 Parse/Management/LightParseCorePlugins.cs delete mode 100644 Parse/Management/ParseCorePlugins.cs delete mode 100644 Parse/Modules/IParseModule.cs delete mode 100644 Parse/Modules/ParseModuleAttribute.cs delete mode 100644 Parse/Modules/ParseModuleController.cs delete mode 100644 Parse/Platform/Analytics/ParseAnalyticsPlugins.cs rename Parse/Platform/{Code => Cloud}/ParseCloudCodeController.cs (82%) rename Parse/{ => Platform/Configuration}/ParseConfiguration.cs (94%) rename Parse/{ => Platform/Files}/ParseFile.cs (98%) rename Parse/Platform/{Installation => Installations}/ParseCurrentInstallationController.cs (90%) rename Parse/{ => Platform/Installations}/ParseInstallation.cs (99%) rename Parse/Platform/{Installation => Installations}/ParseInstallationCoder.cs (84%) rename Parse/Platform/{Installation => Installations}/ParseInstallationController.cs (92%) rename Parse/Platform/{Installation => Installations}/ParseInstallationDataFinalizer.cs (79%) rename Parse/{ => Platform/Location}/ParseGeoDistance.cs (100%) rename Parse/{ => Platform/Location}/ParseGeoPoint.cs (98%) delete mode 100644 Parse/Platform/Notifications/ParsePushPlugins.cs rename Parse/{Management/Tracking => Platform/Objects}/MutableObjectState.cs (83%) rename Parse/{ => Platform/Objects}/ParseObject.cs (99%) rename Parse/{Management => Platform/Objects}/ParseObjectClass.cs (90%) rename Parse/{Management => Platform/Objects}/ParseObjectClassController.cs (94%) rename Parse/{Management => Platform/Objects}/ParseObjectController.cs (95%) rename Parse/{ => Platform}/ParseClient.cs (95%) rename Parse/Platform/{Notifications => Push}/MutablePushState.cs (95%) rename Parse/{ => Platform/Push}/ParsePush.cs (98%) rename Parse/Platform/{Notifications => Push}/ParsePushChannelsController.cs (87%) rename Parse/Platform/{Notifications => Push}/ParsePushController.cs (81%) rename Parse/Platform/{Notifications => Push}/ParsePushEncoder.cs (89%) rename Parse/Platform/{Notifications => Push}/ParsePushModule.cs (92%) rename Parse/{Library => Platform/Push}/ParsePushNotificationEvent.cs (86%) rename Parse/{ => Platform/Queries}/ParseQuery.cs (99%) rename Parse/{Query => Platform/Queries}/ParseQueryController.cs (89%) rename Parse/{ => Platform/Relations}/ParseRelation.cs (97%) rename Parse/{ => Platform/Roles}/ParseRole.cs (100%) rename Parse/{ => Platform/Sessions}/ParseSession.cs (95%) rename Parse/Platform/{Session => Sessions}/ParseSessionController.cs (84%) rename Parse/{Management => Platform/Users}/ParseCurrentUserController.cs (88%) rename Parse/{ => Platform/Users}/ParseUser.cs (98%) rename Parse/{Management => Platform/Users}/ParseUserController.cs (89%) rename Parse/{Properties => }/Resources.Designer.cs (85%) rename Parse/{Properties => }/Resources.resx (100%) rename Parse/{ => Utilities}/AnalyticsServiceExtensions.cs (96%) rename Parse/{ => Utilities}/CloudCodeServiceExtensions.cs (97%) rename Parse/{ => Utilities}/ConfigurationServiceExtensions.cs (95%) rename Parse/{ => Utilities}/InstallationServiceExtensions.cs (97%) rename Parse/{ => Utilities}/ObjectServiceExtensions.cs (99%) rename Parse/{ => Utilities}/PushServiceExtensions.cs (98%) rename Parse/{ => Utilities}/QueryServiceExtensions.cs (98%) rename Parse/{ => Utilities}/RoleServiceExtensions.cs (93%) rename Parse/{ => Utilities}/SessionsServiceExtensions.cs (96%) rename Parse/{ => Utilities}/UserServiceExtensions.cs (98%) delete mode 100644 Parse/Utilities/XamarinAttributes.cs diff --git a/.appveyor.yml b/.appveyor.yml index 0a93da90..49ee4709 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,5 +1,5 @@ version: '2.0.0-develop-{build}' -image: Visual Studio 2017 +image: Visual Studio 2019 before_build: - nuget restore Parse.sln build_script: @@ -9,7 +9,7 @@ before_test: - choco install opencover.portable - choco install codecov test_script: - - OpenCover.Console.exe -target:dotnet.exe -targetargs:"test --test-adapter-path:. --logger:Appveyor /p:DebugType=full .\Parse.Test\Parse.Test.csproj" -filter:"+[Parse*]* -[Parse.Test*]*" -oldstyle -output:parse_sdk_dotnet_coverage.xml -register:user + - OpenCover.Console.exe -target:dotnet.exe -targetargs:"test --test-adapter-path:. --logger:Appveyor /p:DebugType=full .\Parse.Tests\Parse.Tests.csproj" -filter:"+[Parse*]* -[Parse.Tests*]*" -oldstyle -output:parse_sdk_dotnet_coverage.xml -register:user after_test: - codecov -f "parse_sdk_dotnet_coverage.xml" artifacts: diff --git a/Parse.Core/Abstractions/Device/IDeviceInfoController.cs b/Parse.Core/Abstractions/Device/IDeviceInfoController.cs deleted file mode 100644 index 9d4d36ca..00000000 --- a/Parse.Core/Abstractions/Device/IDeviceInfoController.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Threading.Tasks; - -namespace Parse.Push.Internal -{ - public interface IDeviceInfoController - { - string DeviceType { get; } - string DeviceTimeZone { get; } - string AppBuildVersion { get; } - string AppIdentifier { get; } - string AppName { get; } - - - /// - /// Executes platform specific hook that mutate the installation based on - /// the device platforms. - /// - /// Installation to be mutated. - /// - Task ExecuteParseInstallationSaveHookAsync(ParseInstallation installation); - - void Initialize(); - } -} diff --git a/Parse.Core/Abstractions/Management/IObjectSubclassingController.cs b/Parse.Core/Abstractions/Management/IObjectSubclassingController.cs deleted file mode 100644 index d899f917..00000000 --- a/Parse.Core/Abstractions/Management/IObjectSubclassingController.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Parse.Core.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); - - ParseObject Instantiate(string className); - IDictionary GetPropertyMappings(string className); - } -} diff --git a/Parse.Core/Abstractions/Management/IParseCommandRunner.cs b/Parse.Core/Abstractions/Management/IParseCommandRunner.cs deleted file mode 100644 index 0739a45d..00000000 --- a/Parse.Core/Abstractions/Management/IParseCommandRunner.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Core.Internal -{ - public interface IParseCommandRunner - { - /// - /// 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(ParseCommand command, - IProgress uploadProgress = null, - IProgress downloadProgress = null, - CancellationToken cancellationToken = default(CancellationToken)); - } -} diff --git a/Parse.Core/Abstractions/Management/IParseCorePlugins.cs b/Parse.Core/Abstractions/Management/IParseCorePlugins.cs deleted file mode 100644 index 76cc4bac..00000000 --- a/Parse.Core/Abstractions/Management/IParseCorePlugins.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using Parse.Common.Internal; - -namespace Parse.Core.Internal -{ - public interface IParseCorePlugins - { - void Reset(); - - IHttpClient HttpClient { get; } - IParseCommandRunner CommandRunner { get; } - IStorageController StorageController { get; } - - IParseCloudCodeController CloudCodeController { get; } - IParseConfigController ConfigController { get; } - IParseFileController FileController { get; } - IParseObjectController ObjectController { get; } - IParseQueryController QueryController { get; } - IParseSessionController SessionController { get; } - IParseUserController UserController { get; } - IObjectSubclassingController SubclassingController { get; } - IParseCurrentUserController CurrentUserController { get; } - IInstallationIdController InstallationIdController { get; } - } -} \ No newline at end of file diff --git a/Parse.Core/Abstractions/Management/IParseCurrentUserController.cs b/Parse.Core/Abstractions/Management/IParseCurrentUserController.cs deleted file mode 100644 index c61b74ce..00000000 --- a/Parse.Core/Abstractions/Management/IParseCurrentUserController.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Core.Internal -{ - public interface IParseCurrentUserController : IParseObjectCurrentController - { - Task GetCurrentSessionTokenAsync(CancellationToken cancellationToken); - - Task LogOutAsync(CancellationToken cancellationToken); - } -} diff --git a/Parse.Core/Abstractions/Management/IParseFileController.cs b/Parse.Core/Abstractions/Management/IParseFileController.cs deleted file mode 100644 index f2e1e6a2..00000000 --- a/Parse.Core/Abstractions/Management/IParseFileController.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Core.Internal -{ - public interface IParseFileController - { - Task SaveAsync(FileState state, - Stream dataStream, - string sessionToken, - IProgress progress, - CancellationToken cancellationToken); - } -} diff --git a/Parse.Core/Abstractions/Management/IParseObjectController.cs b/Parse.Core/Abstractions/Management/IParseObjectController.cs deleted file mode 100644 index e0f95415..00000000 --- a/Parse.Core/Abstractions/Management/IParseObjectController.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Core.Internal -{ - public interface IParseObjectController - { - Task FetchAsync(IObjectState state, 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/Parse.Core/Abstractions/Management/IParseObjectCurrentController.cs b/Parse.Core/Abstractions/Management/IParseObjectCurrentController.cs deleted file mode 100644 index 016e54ba..00000000 --- a/Parse.Core/Abstractions/Management/IParseObjectCurrentController.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Core.Internal -{ - /// - /// IParseObjectCurrentController controls the single-instance - /// persistence used throughout the code-base. Sample usages are and - /// . - /// - /// Type of object being persisted. - public interface IParseObjectCurrentController where T : ParseObject - { - /// - /// 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/Parse.Core/Abstractions/Management/IParseUserController.cs b/Parse.Core/Abstractions/Management/IParseUserController.cs deleted file mode 100644 index c3ccaff9..00000000 --- a/Parse.Core/Abstractions/Management/IParseUserController.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Core.Internal -{ - public interface IParseUserController - { - Task SignUpAsync(IObjectState state, - IDictionary operations, - CancellationToken cancellationToken); - - Task LogInAsync(string username, - string password, - CancellationToken cancellationToken); - - Task LogInAsync(string authType, - IDictionary data, - CancellationToken cancellationToken); - - Task GetUserAsync(string sessionToken, CancellationToken cancellationToken); - - Task RequestPasswordResetAsync(string email, CancellationToken cancellationToken); - } -} diff --git a/Parse.Core/Abstractions/Management/Tracking/IObjectState.cs b/Parse.Core/Abstractions/Management/Tracking/IObjectState.cs deleted file mode 100644 index 473f4428..00000000 --- a/Parse.Core/Abstractions/Management/Tracking/IObjectState.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.Collections.Generic; - -namespace Parse.Core.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/Parse.Core/Abstractions/Management/Tracking/IParseFieldOperation.cs b/Parse.Core/Abstractions/Management/Tracking/IParseFieldOperation.cs deleted file mode 100644 index 1dd018c2..00000000 --- a/Parse.Core/Abstractions/Management/Tracking/IParseFieldOperation.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -namespace Parse.Core.Internal -{ - /// - /// A ParseFieldOperation represents a modification to a value in a ParseObject. - /// For example, setting, deleting, or incrementing a value are all different kinds of - /// ParseFieldOperations. ParseFieldOperations themselves can be considered to be - /// immutable. - /// - public interface IParseFieldOperation - { - /// - /// Converts the ParseFieldOperation to a data structure that can be converted to JSON and sent to - /// Parse 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 ParseFieldOperation or this. - IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous); - - /// - /// Returns a new estimated value based on a previous value and this operation. This - /// value is not intended to be sent to Parse, 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 ParseRelation 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/Parse.Core/Abstractions/Platform/Analytics/IParseAnalyticsController.cs b/Parse.Core/Abstractions/Platform/Analytics/IParseAnalyticsController.cs deleted file mode 100644 index 334abad3..00000000 --- a/Parse.Core/Abstractions/Platform/Analytics/IParseAnalyticsController.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - - -namespace Parse.Analytics.Internal -{ - /// - /// The interface for the Parse Analytics API controller. - /// - public interface IParseAnalyticsController - { - /// - /// Tracks an event matching the specified details. - /// - /// The name of the event. - /// The parameters of the event. - /// The session token for the event. - /// The asynchonous cancellation token. - /// A that will complete successfully once the event has been set to be tracked. - Task TrackEventAsync(string name, - IDictionary dimensions, - string sessionToken, - CancellationToken cancellationToken); - - /// - /// Tracks an app open for the specified event. - /// - /// The hash for the target push notification. - /// The token of the current session. - /// The asynchronous cancellation token. - /// A the will complete successfully once app openings for the target push notification have been set to be tracked. - Task TrackAppOpenedAsync(string pushHash, - string sessionToken, - CancellationToken cancellationToken); - } -} diff --git a/Parse.Core/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs b/Parse.Core/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs deleted file mode 100644 index 709b8e7c..00000000 --- a/Parse.Core/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using Parse.Analytics.Internal; -using Parse.Core.Internal; - -namespace Parse.Analytics -{ - public interface IParseAnalyticsPlugins - { - void Reset(); - - IParseCorePlugins CorePlugins { get; } - IParseAnalyticsController AnalyticsController { get; } - } -} \ No newline at end of file diff --git a/Parse.Core/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs b/Parse.Core/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs deleted file mode 100644 index 96e4145f..00000000 --- a/Parse.Core/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Core.Internal -{ - public interface IParseAuthenticationProvider - { - /// - /// 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/Parse.Core/Abstractions/Platform/Code/IParseCloudCodeController.cs b/Parse.Core/Abstractions/Platform/Code/IParseCloudCodeController.cs deleted file mode 100644 index 834a8984..00000000 --- a/Parse.Core/Abstractions/Platform/Code/IParseCloudCodeController.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Core.Internal -{ - public interface IParseCloudCodeController - { - Task CallFunctionAsync(string name, - IDictionary parameters, - string sessionToken, - CancellationToken cancellationToken); - } -} diff --git a/Parse.Core/Abstractions/Platform/Configuration/IParseConfigController.cs b/Parse.Core/Abstractions/Platform/Configuration/IParseConfigController.cs deleted file mode 100644 index f3b8c07f..00000000 --- a/Parse.Core/Abstractions/Platform/Configuration/IParseConfigController.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Core.Internal -{ - public interface IParseConfigController - { - /// - /// Gets the current config controller. - /// - /// The current config controller. - IParseCurrentConfigController 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/Parse.Core/Abstractions/Platform/Configuration/IParseCurrentConfigController.cs b/Parse.Core/Abstractions/Platform/Configuration/IParseCurrentConfigController.cs deleted file mode 100644 index 35571e52..00000000 --- a/Parse.Core/Abstractions/Platform/Configuration/IParseCurrentConfigController.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Threading.Tasks; - -namespace Parse.Core.Internal -{ - public interface IParseCurrentConfigController - { - /// - /// Gets the current config async. - /// - /// The current config async. - Task GetCurrentConfigAsync(); - - /// - /// Sets the current config async. - /// - /// The current config async. - /// Config. - Task SetCurrentConfigAsync(ParseConfig 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/Parse.Core/Abstractions/Platform/Files/IParseFileController.cs b/Parse.Core/Abstractions/Platform/Files/IParseFileController.cs deleted file mode 100644 index f2e1e6a2..00000000 --- a/Parse.Core/Abstractions/Platform/Files/IParseFileController.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Core.Internal -{ - public interface IParseFileController - { - Task SaveAsync(FileState state, - Stream dataStream, - string sessionToken, - IProgress progress, - CancellationToken cancellationToken); - } -} diff --git a/Parse.Core/Abstractions/Platform/Installation/IInstallationIdController.cs b/Parse.Core/Abstractions/Platform/Installation/IInstallationIdController.cs deleted file mode 100644 index a95cfb1f..00000000 --- a/Parse.Core/Abstractions/Platform/Installation/IInstallationIdController.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.Threading.Tasks; - -namespace Parse.Core.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/Parse.Core/Abstractions/Platform/Installation/IParseInstallationCoder.cs b/Parse.Core/Abstractions/Platform/Installation/IParseInstallationCoder.cs deleted file mode 100644 index 0182b610..00000000 --- a/Parse.Core/Abstractions/Platform/Installation/IParseInstallationCoder.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; - -namespace Parse.Push.Internal -{ - // TODO: (richardross) once coder is refactored, make this extend IParseObjectCoder. - public interface IParseInstallationCoder - { - IDictionary Encode(ParseInstallation installation); - - ParseInstallation Decode(IDictionary data); - } -} \ No newline at end of file diff --git a/Parse.Core/Abstractions/Platform/Notifications/IParsePushChannelsController.cs b/Parse.Core/Abstractions/Platform/Notifications/IParsePushChannelsController.cs deleted file mode 100644 index befa37a5..00000000 --- a/Parse.Core/Abstractions/Platform/Notifications/IParsePushChannelsController.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Push.Internal -{ - public interface IParsePushChannelsController - { - Task SubscribeAsync(IEnumerable channels, CancellationToken cancellationToken); - Task UnsubscribeAsync(IEnumerable channels, CancellationToken cancellationToken); - } -} diff --git a/Parse.Core/Abstractions/Platform/Notifications/IParsePushController.cs b/Parse.Core/Abstractions/Platform/Notifications/IParsePushController.cs deleted file mode 100644 index f567df76..00000000 --- a/Parse.Core/Abstractions/Platform/Notifications/IParsePushController.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Push.Internal -{ - public interface IParsePushController - { - Task SendPushNotificationAsync(IPushState state, CancellationToken cancellationToken); - } -} diff --git a/Parse.Core/Abstractions/Platform/Notifications/IParsePushPlugins.cs b/Parse.Core/Abstractions/Platform/Notifications/IParsePushPlugins.cs deleted file mode 100644 index 695bc288..00000000 --- a/Parse.Core/Abstractions/Platform/Notifications/IParsePushPlugins.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Parse.Core.Internal; - -namespace Parse.Push.Internal -{ - public interface IParsePushPlugins - { - void Reset(); - - IParseCorePlugins CorePlugins { get; } - IParsePushChannelsController PushChannelsController { get; } - IParsePushController PushController { get; } - IParseCurrentInstallationController CurrentInstallationController { get; } - IDeviceInfoController DeviceInfoController { get; } - } -} \ No newline at end of file diff --git a/Parse.Core/Abstractions/Platform/Session/IParseSessionController.cs b/Parse.Core/Abstractions/Platform/Session/IParseSessionController.cs deleted file mode 100644 index 304b2c23..00000000 --- a/Parse.Core/Abstractions/Platform/Session/IParseSessionController.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Core.Internal -{ - public interface IParseSessionController - { - 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/Parse.Core/Abstractions/Query/IParseQueryController.cs b/Parse.Core/Abstractions/Query/IParseQueryController.cs deleted file mode 100644 index bc473785..00000000 --- a/Parse.Core/Abstractions/Query/IParseQueryController.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Core.Internal -{ - public interface IParseQueryController - { - Task> FindAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject; - - Task CountAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject; - - Task FirstAsync(ParseQuery query, ParseUser user, CancellationToken cancellationToken) where T : ParseObject; - } -} diff --git a/Parse.Core/Abstractions/Storage/IStorageController.cs b/Parse.Core/Abstractions/Storage/IStorageController.cs deleted file mode 100644 index b0925a84..00000000 --- a/Parse.Core/Abstractions/Storage/IStorageController.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Parse.Common.Internal -{ - /// - /// An abstraction for accessing persistent storage in the Parse 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/Parse.Core/Framework/ParseDownloadProgressEventArgs.cs b/Parse.Core/Framework/ParseDownloadProgressEventArgs.cs deleted file mode 100644 index b9da12ec..00000000 --- a/Parse.Core/Framework/ParseDownloadProgressEventArgs.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; - -namespace Parse -{ - /// - /// Represents download progress. - /// - public class ParseDownloadProgressEventArgs : EventArgs - { - public ParseDownloadProgressEventArgs() { } - - /// - /// Gets the progress (a number between 0.0 and 1.0) of a download. - /// - public double Progress { get; set; } - } -} diff --git a/Parse.Core/Framework/ParsePushNotificationEventArgs.cs b/Parse.Core/Framework/ParsePushNotificationEventArgs.cs deleted file mode 100644 index 0df3b323..00000000 --- a/Parse.Core/Framework/ParsePushNotificationEventArgs.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.Collections.Generic; - -namespace Parse -{ - /// - /// A wrapper around Parse push notification payload. - /// - public class ParsePushNotificationEventArgs : EventArgs - { - internal ParsePushNotificationEventArgs(IDictionary payload) - { - Payload = payload; - -#if !IOS - StringPayload = Json.Encode(payload); -#endif - } - - // TODO: (richardross) investigate this. - // Obj-C type -> .NET type is impossible to do flawlessly (especially - // on NSNumber). We can't transform NSDictionary into string because of this reason. -#if !IOS - internal ParsePushNotificationEventArgs(string stringPayload) - { - StringPayload = stringPayload; - - Payload = Json.Parse(stringPayload) as IDictionary; - } -#endif - - /// - /// The payload of the push notification as IDictionary. - /// - public IDictionary Payload { get; internal set; } - - /// - /// The payload of the push notification as string. - /// - public string StringPayload { get; internal set; } - } -} diff --git a/Parse.Core/Framework/ParseUploadProgressEventArgs.cs b/Parse.Core/Framework/ParseUploadProgressEventArgs.cs deleted file mode 100644 index 35df390c..00000000 --- a/Parse.Core/Framework/ParseUploadProgressEventArgs.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; - -namespace Parse -{ - /// - /// Represents upload progress. - /// - public class ParseUploadProgressEventArgs : EventArgs - { - public ParseUploadProgressEventArgs() { } - - /// - /// Gets the progress (a number between 0.0 and 1.0) of an upload. - /// - public double Progress { get; set; } - } -} diff --git a/Parse.Core/Parse.Core.csproj b/Parse.Core/Parse.Core.csproj deleted file mode 100644 index fddef9d2..00000000 --- a/Parse.Core/Parse.Core.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - netstandard2.0 - latest - - - diff --git a/Parse.Test/ACLTests.cs b/Parse.Tests/ACLTests.cs similarity index 95% rename from Parse.Test/ACLTests.cs rename to Parse.Tests/ACLTests.cs index 919fa678..b6e01fec 100644 --- a/Parse.Test/ACLTests.cs +++ b/Parse.Tests/ACLTests.cs @@ -1,10 +1,9 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Parse.Core.Internal; -using Parse.Library; -using Parse.Management; +using Parse.Infrastructure; +using Parse.Platform.Objects; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class ACLTests diff --git a/Parse.Test/AnalyticsControllerTests.cs b/Parse.Tests/AnalyticsControllerTests.cs similarity index 95% rename from Parse.Test/AnalyticsControllerTests.cs rename to Parse.Tests/AnalyticsControllerTests.cs index d6c99bcf..07a63b77 100644 --- a/Parse.Test/AnalyticsControllerTests.cs +++ b/Parse.Tests/AnalyticsControllerTests.cs @@ -6,11 +6,13 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Analytics.Internal; -using Parse.Core.Internal; -using Parse.Library; +using Parse.Infrastructure; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Infrastructure; +using Parse.Platform.Analytics; +using Parse.Infrastructure.Execution; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class AnalyticsControllerTests diff --git a/Parse.Test/AnalyticsTests.cs b/Parse.Tests/AnalyticsTests.cs similarity index 96% rename from Parse.Test/AnalyticsTests.cs rename to Parse.Tests/AnalyticsTests.cs index 0a5471fa..c5efee08 100644 --- a/Parse.Test/AnalyticsTests.cs +++ b/Parse.Tests/AnalyticsTests.cs @@ -4,13 +4,12 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Abstractions.Library; -using Parse.Abstractions.Management; -using Parse.Analytics.Internal; -using Parse.Core.Internal; -using Parse.Library; +using Parse.Abstractions.Infrastructure; +using Parse.Infrastructure; +using Parse.Abstractions.Platform.Analytics; +using Parse.Abstractions.Platform.Users; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class AnalyticsTests diff --git a/Parse.Test/CloudControllerTests.cs b/Parse.Tests/CloudControllerTests.cs similarity index 94% rename from Parse.Test/CloudControllerTests.cs rename to Parse.Tests/CloudControllerTests.cs index 7da79be3..81de1986 100644 --- a/Parse.Test/CloudControllerTests.cs +++ b/Parse.Tests/CloudControllerTests.cs @@ -6,10 +6,13 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Core.Internal; -using Parse.Library; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Infrastructure; +using Parse.Infrastructure.Execution; +using Parse.Platform.Cloud; -namespace Parse.Test +namespace Parse.Tests { #warning Class refactoring requires completion. diff --git a/Parse.Test/CloudTests.cs b/Parse.Tests/CloudTests.cs similarity index 91% rename from Parse.Test/CloudTests.cs rename to Parse.Tests/CloudTests.cs index 46b44f69..dd1a6878 100644 --- a/Parse.Test/CloudTests.cs +++ b/Parse.Tests/CloudTests.cs @@ -4,12 +4,12 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Abstractions.Library; -using Parse.Core.Internal; -using Parse.Library; -using Parse.Management; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Cloud; +using Parse.Abstractions.Platform.Users; +using Parse.Infrastructure; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class CloudTests diff --git a/Parse.Test/CommandTests.cs b/Parse.Tests/CommandTests.cs similarity index 83% rename from Parse.Test/CommandTests.cs rename to Parse.Tests/CommandTests.cs index ccdc9dc9..8091b75c 100644 --- a/Parse.Test/CommandTests.cs +++ b/Parse.Tests/CommandTests.cs @@ -7,13 +7,14 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Library; -using Parse.Management; - -namespace Parse.Test +using Parse.Infrastructure; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Installations; +using Parse.Abstractions.Platform.Users; +using Parse.Infrastructure.Execution; +using Parse.Abstractions.Infrastructure.Execution; + +namespace Parse.Tests { #warning Initialization and cleaning steps may be redundant for each test method. It may be possible to simply reset the required services before each run. #warning Class refactoring requires completion. @@ -46,7 +47,7 @@ public Task TestRunCommand() Mock mockHttpClient = new Mock(); Mock mockInstallationController = new Mock(); Task> fakeResponse = Task.FromResult(new Tuple(HttpStatusCode.OK, "{}")); - mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(fakeResponse); + mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(fakeResponse); mockInstallationController.Setup(installation => installation.GetAsync()).Returns(Task.FromResult(default)); @@ -65,7 +66,7 @@ public Task TestRunCommandWithArrayResult() { Mock mockHttpClient = new Mock(); Mock mockInstallationController = new Mock(); - mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "[]"))); + mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "[]"))); mockInstallationController.Setup(installation => installation.GetAsync()).Returns(Task.FromResult(default)); @@ -86,7 +87,7 @@ public Task TestRunCommandWithInvalidString() { Mock mockHttpClient = new Mock(); Mock mockInstallationController = new Mock(); - mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "invalid"))); + mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.OK, "invalid"))); mockInstallationController.Setup(controller => controller.GetAsync()).Returns(Task.FromResult(default)); @@ -105,7 +106,7 @@ public Task TestRunCommandWithErrorCode() { Mock mockHttpClient = new Mock(); Mock mockInstallationController = new Mock(); - mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.NotFound, "{ \"code\": 101, \"error\": \"Object not found.\" }"))); + mockHttpClient.Setup(obj => obj.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.NotFound, "{ \"code\": 101, \"error\": \"Object not found.\" }"))); mockInstallationController.Setup(controller => controller.GetAsync()).Returns(Task.FromResult(default)); @@ -127,7 +128,7 @@ public Task TestRunCommandWithInternalServerError() Mock mockHttpClient = new Mock(); Mock mockInstallationController = new Mock(); - mockHttpClient.Setup(client => client.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.InternalServerError, default))); + mockHttpClient.Setup(client => client.ExecuteAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())).Returns(Task.FromResult(new Tuple(HttpStatusCode.InternalServerError, default))); mockInstallationController.Setup(installationController => installationController.GetAsync()).Returns(Task.FromResult(default)); return new ParseCommandRunner(mockHttpClient.Object, mockInstallationController.Object, Client.MetadataController, Client.ServerConnectionData, new Lazy(() => Client.UserController)).RunCommandAsync(new ParseCommand("endpoint", method: "GET", data: default)).ContinueWith(task => diff --git a/Parse.Test/ConfigTests.cs b/Parse.Tests/ConfigTests.cs similarity index 94% rename from Parse.Test/ConfigTests.cs rename to Parse.Tests/ConfigTests.cs index 3974b4c6..a6c45f3c 100644 --- a/Parse.Test/ConfigTests.cs +++ b/Parse.Tests/ConfigTests.cs @@ -5,13 +5,13 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Newtonsoft.Json; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Library; -using Parse.Management; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Configuration; +using Parse.Abstractions.Platform.Users; +using Parse.Infrastructure; +using Parse.Platform.Configuration; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class ConfigTests diff --git a/Parse.Test/ConversionTests.cs b/Parse.Tests/ConversionTests.cs similarity index 87% rename from Parse.Test/ConversionTests.cs rename to Parse.Tests/ConversionTests.cs index e65e3388..2bd27fdc 100644 --- a/Parse.Test/ConversionTests.cs +++ b/Parse.Tests/ConversionTests.cs @@ -1,8 +1,8 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Parse.Utilities; +using Parse.Infrastructure.Utilities; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class ConversionTests @@ -25,7 +25,7 @@ public void TestConvertToFloatUsingNonInvariantNumberFormat() try { float inputValue = 1234.56f; - string jsonEncoded = Common.Internal.Json.Encode(inputValue); + string jsonEncoded = JsonUtilities.Encode(inputValue); float convertedValue = (float) Conversion.ConvertTo(jsonEncoded); Assert.IsTrue(inputValue == convertedValue); } diff --git a/Parse.Test/CurrentUserControllerTests.cs b/Parse.Tests/CurrentUserControllerTests.cs similarity index 97% rename from Parse.Test/CurrentUserControllerTests.cs rename to Parse.Tests/CurrentUserControllerTests.cs index 3172bb46..7785a161 100644 --- a/Parse.Test/CurrentUserControllerTests.cs +++ b/Parse.Tests/CurrentUserControllerTests.cs @@ -5,12 +5,13 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Library; -using Parse.Management; +using Parse.Infrastructure; +using Parse.Abstractions.Infrastructure; +using Parse.Infrastructure.Utilities; +using Parse.Platform.Objects; +using Parse.Platform.Users; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class CurrentUserControllerTests diff --git a/Parse.Test/DecoderTests.cs b/Parse.Tests/DecoderTests.cs similarity index 98% rename from Parse.Test/DecoderTests.cs rename to Parse.Tests/DecoderTests.cs index 9f5c11f3..c074cb83 100644 --- a/Parse.Test/DecoderTests.cs +++ b/Parse.Tests/DecoderTests.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Parse.Core.Internal; -using Parse.Library; +using Parse.Abstractions.Internal; +using Parse.Infrastructure; +using Parse.Infrastructure.Data; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class DecoderTests diff --git a/Parse.Test/EncoderTests.cs b/Parse.Tests/EncoderTests.cs similarity index 96% rename from Parse.Test/EncoderTests.cs rename to Parse.Tests/EncoderTests.cs index f86fb406..505938de 100644 --- a/Parse.Test/EncoderTests.cs +++ b/Parse.Tests/EncoderTests.cs @@ -3,11 +3,13 @@ using System.IO; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Parse.Core.Internal; -using Parse.Library; +using Parse.Abstractions.Internal; +using Parse.Infrastructure; +using Parse.Infrastructure.Control; +using Parse.Infrastructure.Data; // TODO (hallucinogen): mock ParseACL, ParseObject, ParseUser once we have their Interfaces -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class EncoderTests @@ -36,19 +38,19 @@ public void TestIsValidType() Assert.IsTrue(ParseDataEncoder.Validate(new byte[] { 1, 2, 3, 4 })); Assert.IsTrue(ParseDataEncoder.Validate(nameof(corgi))); Assert.IsTrue(ParseDataEncoder.Validate(corgi)); - Assert.IsTrue(ParseDataEncoder.Validate(new ParseACL())); + Assert.IsTrue(ParseDataEncoder.Validate(new ParseACL { })); Assert.IsTrue(ParseDataEncoder.Validate(new ParseFile("Corgi", new byte[0]))); Assert.IsTrue(ParseDataEncoder.Validate(new ParseGeoPoint(1, 2))); Assert.IsTrue(ParseDataEncoder.Validate(corgiRelation)); - Assert.IsTrue(ParseDataEncoder.Validate(new DateTime())); - Assert.IsTrue(ParseDataEncoder.Validate(new List())); - Assert.IsTrue(ParseDataEncoder.Validate(new Dictionary())); - Assert.IsTrue(ParseDataEncoder.Validate(new Dictionary())); + Assert.IsTrue(ParseDataEncoder.Validate(new DateTime { })); + Assert.IsTrue(ParseDataEncoder.Validate(new List { })); + Assert.IsTrue(ParseDataEncoder.Validate(new Dictionary { })); + Assert.IsTrue(ParseDataEncoder.Validate(new Dictionary { })); - Assert.IsFalse(ParseDataEncoder.Validate(new ParseAddOperation(new List()))); + Assert.IsFalse(ParseDataEncoder.Validate(new ParseAddOperation(new List { }))); Assert.IsFalse(ParseDataEncoder.Validate(Task.FromResult(new ParseObject("Corgi")))); - Assert.ThrowsException(() => ParseDataEncoder.Validate(new Dictionary())); - Assert.ThrowsException(() => ParseDataEncoder.Validate(new Dictionary())); + Assert.ThrowsException(() => ParseDataEncoder.Validate(new Dictionary { })); + Assert.ThrowsException(() => ParseDataEncoder.Validate(new Dictionary { })); } [TestMethod] diff --git a/Parse.Test/FileControllerTests.cs b/Parse.Tests/FileControllerTests.cs similarity index 96% rename from Parse.Test/FileControllerTests.cs rename to Parse.Tests/FileControllerTests.cs index 2b8480ce..8d8ddbd3 100644 --- a/Parse.Test/FileControllerTests.cs +++ b/Parse.Tests/FileControllerTests.cs @@ -7,10 +7,12 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Core.Internal; -using Parse.Library; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Infrastructure.Execution; +using Parse.Platform.Files; -namespace Parse.Test +namespace Parse.Tests { #warning Refactor this class. #warning Skipped initialization step may be needed. diff --git a/Parse.Test/FileStateTests.cs b/Parse.Tests/FileStateTests.cs similarity index 95% rename from Parse.Test/FileStateTests.cs rename to Parse.Tests/FileStateTests.cs index c5596e6c..e624a7da 100644 --- a/Parse.Test/FileStateTests.cs +++ b/Parse.Tests/FileStateTests.cs @@ -1,8 +1,8 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Parse.Core.Internal; +using Parse.Platform.Files; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class FileStateTests diff --git a/Parse.Test/FileTests.cs b/Parse.Tests/FileTests.cs similarity index 91% rename from Parse.Test/FileTests.cs rename to Parse.Tests/FileTests.cs index a49af804..86b1745c 100644 --- a/Parse.Test/FileTests.cs +++ b/Parse.Tests/FileTests.cs @@ -5,11 +5,14 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Core.Internal; -using Parse.Library; -using Parse.Management; - -namespace Parse.Test +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Internal; +using Parse.Abstractions.Platform.Files; +using Parse.Abstractions.Platform.Users; +using Parse.Infrastructure; +using Parse.Platform.Files; + +namespace Parse.Tests { [TestClass] public class FileTests diff --git a/Parse.Test/GeoPointTests.cs b/Parse.Tests/GeoPointTests.cs similarity index 87% rename from Parse.Test/GeoPointTests.cs rename to Parse.Tests/GeoPointTests.cs index e43eaac0..a9611042 100644 --- a/Parse.Test/GeoPointTests.cs +++ b/Parse.Tests/GeoPointTests.cs @@ -3,11 +3,11 @@ using System.Globalization; using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Library; +using Parse.Infrastructure; +using Parse.Infrastructure.Data; +using Parse.Infrastructure.Utilities; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class GeoPointTests @@ -17,17 +17,21 @@ public class GeoPointTests [TestMethod] public void TestGeoPointCultureInvariantParsing() { - CultureInfo originalCulture = Thread.CurrentThread.CurrentCulture; - foreach (CultureInfo c in CultureInfo.GetCultures(CultureTypes.AllCultures)) + CultureInfo initialCulture = Thread.CurrentThread.CurrentCulture; + + foreach (CultureInfo culture in CultureInfo.GetCultures(CultureTypes.AllCultures)) { - Thread.CurrentThread.CurrentCulture = c; + Thread.CurrentThread.CurrentCulture = culture; + ParseGeoPoint point = new ParseGeoPoint(1.234, 1.234); - string serialized = Json.Encode(new Dictionary { { nameof(point), NoObjectsEncoder.Instance.Encode(point, Client) } }); - IDictionary deserialized = Client.Decoder.Decode(Json.Parse(serialized), Client) as IDictionary; + IDictionary deserialized = Client.Decoder.Decode(JsonUtilities.Parse(JsonUtilities.Encode(new Dictionary { [nameof(point)] = NoObjectsEncoder.Instance.Encode(point, Client) })), Client) as IDictionary; ParseGeoPoint pointAgain = (ParseGeoPoint) deserialized[nameof(point)]; + Assert.AreEqual(1.234, pointAgain.Latitude); Assert.AreEqual(1.234, pointAgain.Longitude); } + + Thread.CurrentThread.CurrentCulture = initialCulture; } [TestMethod] @@ -38,11 +42,13 @@ public void TestGeoPointConstructor() Assert.AreEqual(0.0, point.Longitude); point = new ParseGeoPoint(42, 36); + Assert.AreEqual(42.0, point.Latitude); Assert.AreEqual(36.0, point.Longitude); point.Latitude = 12; point.Longitude = 24; + Assert.AreEqual(12.0, point.Latitude); Assert.AreEqual(24.0, point.Longitude); } diff --git a/Parse.Test/InstallationIdControllerTests.cs b/Parse.Tests/InstallationIdControllerTests.cs similarity index 97% rename from Parse.Test/InstallationIdControllerTests.cs rename to Parse.Tests/InstallationIdControllerTests.cs index ede7d2c3..f4326e76 100644 --- a/Parse.Test/InstallationIdControllerTests.cs +++ b/Parse.Tests/InstallationIdControllerTests.cs @@ -3,12 +3,11 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Library; -using Parse.Management; +using Parse.Infrastructure; +using Parse.Abstractions.Infrastructure; +using Parse.Platform.Installations; -namespace Parse.Test +namespace Parse.Tests { #warning Class refactoring may be required. diff --git a/Parse.Test/InstallationTests.cs b/Parse.Tests/InstallationTests.cs similarity index 97% rename from Parse.Test/InstallationTests.cs rename to Parse.Tests/InstallationTests.cs index b99d8399..88516ec5 100644 --- a/Parse.Test/InstallationTests.cs +++ b/Parse.Tests/InstallationTests.cs @@ -4,13 +4,12 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Abstractions.Library; -using Parse.Core.Internal; -using Parse.Library; -using Parse.Management; -using Parse.Push.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Installations; +using Parse.Infrastructure; +using Parse.Platform.Objects; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class InstallationTests diff --git a/Parse.Test/JsonTests.cs b/Parse.Tests/JsonTests.cs similarity index 66% rename from Parse.Test/JsonTests.cs rename to Parse.Tests/JsonTests.cs index 08b9bbe3..ef67f6e8 100644 --- a/Parse.Test/JsonTests.cs +++ b/Parse.Tests/JsonTests.cs @@ -2,87 +2,87 @@ using System.Collections; using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Parse.Common.Internal; +using Parse.Infrastructure.Utilities; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class JsonTests { [TestMethod] - public void TestEmptyJsonStringFail() => Assert.ThrowsException(() => Json.Parse("")); + public void TestEmptyJsonStringFail() => Assert.ThrowsException(() => JsonUtilities.Parse("")); [TestMethod] public void TestInvalidJsonStringAsRootFail() { - Assert.ThrowsException(() => Json.Parse("\n")); - Assert.ThrowsException(() => Json.Parse("a")); - Assert.ThrowsException(() => Json.Parse("abc")); - Assert.ThrowsException(() => Json.Parse("\u1234")); - Assert.ThrowsException(() => Json.Parse("\t")); - Assert.ThrowsException(() => Json.Parse("\t\n\r")); - Assert.ThrowsException(() => Json.Parse(" ")); - Assert.ThrowsException(() => Json.Parse("1234")); - Assert.ThrowsException(() => Json.Parse("1,3")); - Assert.ThrowsException(() => Json.Parse("{1")); - Assert.ThrowsException(() => Json.Parse("3}")); - Assert.ThrowsException(() => Json.Parse("}")); + Assert.ThrowsException(() => JsonUtilities.Parse("\n")); + Assert.ThrowsException(() => JsonUtilities.Parse("a")); + Assert.ThrowsException(() => JsonUtilities.Parse("abc")); + Assert.ThrowsException(() => JsonUtilities.Parse("\u1234")); + Assert.ThrowsException(() => JsonUtilities.Parse("\t")); + Assert.ThrowsException(() => JsonUtilities.Parse("\t\n\r")); + Assert.ThrowsException(() => JsonUtilities.Parse(" ")); + Assert.ThrowsException(() => JsonUtilities.Parse("1234")); + Assert.ThrowsException(() => JsonUtilities.Parse("1,3")); + Assert.ThrowsException(() => JsonUtilities.Parse("{1")); + Assert.ThrowsException(() => JsonUtilities.Parse("3}")); + Assert.ThrowsException(() => JsonUtilities.Parse("}")); } [TestMethod] - public void TestEmptyJsonObject() => Assert.IsTrue(Json.Parse("{}") is IDictionary); + public void TestEmptyJsonObject() => Assert.IsTrue(JsonUtilities.Parse("{}") is IDictionary); [TestMethod] - public void TestEmptyJsonArray() => Assert.IsTrue(Json.Parse("[]") is IList); + public void TestEmptyJsonArray() => Assert.IsTrue(JsonUtilities.Parse("[]") is IList); [TestMethod] public void TestOneJsonObject() { - Assert.ThrowsException(() => Json.Parse("{ 1 }")); - Assert.ThrowsException(() => Json.Parse("{ 1 : 1 }")); - Assert.ThrowsException(() => Json.Parse("{ 1 : \"abc\" }")); + Assert.ThrowsException(() => JsonUtilities.Parse("{ 1 }")); + Assert.ThrowsException(() => JsonUtilities.Parse("{ 1 : 1 }")); + Assert.ThrowsException(() => JsonUtilities.Parse("{ 1 : \"abc\" }")); - object parsed = Json.Parse("{\"abc\" : \"def\"}"); + object parsed = JsonUtilities.Parse("{\"abc\" : \"def\"}"); Assert.IsTrue(parsed is IDictionary); IDictionary parsedDict = parsed as IDictionary; Assert.AreEqual("def", parsedDict["abc"]); - parsed = Json.Parse("{\"abc\" : {} }"); + parsed = JsonUtilities.Parse("{\"abc\" : {} }"); Assert.IsTrue(parsed is IDictionary); parsedDict = parsed as IDictionary; Assert.IsTrue(parsedDict["abc"] is IDictionary); - parsed = Json.Parse("{\"abc\" : \"6060\"}"); + parsed = JsonUtilities.Parse("{\"abc\" : \"6060\"}"); Assert.IsTrue(parsed is IDictionary); parsedDict = parsed as IDictionary; Assert.AreEqual("6060", parsedDict["abc"]); - parsed = Json.Parse("{\"\" : \"\"}"); + parsed = JsonUtilities.Parse("{\"\" : \"\"}"); Assert.IsTrue(parsed is IDictionary); parsedDict = parsed as IDictionary; Assert.AreEqual("", parsedDict[""]); - parsed = Json.Parse("{\" abc\" : \"def \"}"); + parsed = JsonUtilities.Parse("{\" abc\" : \"def \"}"); Assert.IsTrue(parsed is IDictionary); parsedDict = parsed as IDictionary; Assert.AreEqual("def ", parsedDict[" abc"]); - parsed = Json.Parse("{\"1\" : 6060}"); + parsed = JsonUtilities.Parse("{\"1\" : 6060}"); Assert.IsTrue(parsed is IDictionary); parsedDict = parsed as IDictionary; Assert.AreEqual((long) 6060, parsedDict["1"]); - parsed = Json.Parse("{\"1\" : null}"); + parsed = JsonUtilities.Parse("{\"1\" : null}"); Assert.IsTrue(parsed is IDictionary); parsedDict = parsed as IDictionary; Assert.IsNull(parsedDict["1"]); - parsed = Json.Parse("{\"1\" : true}"); + parsed = JsonUtilities.Parse("{\"1\" : true}"); Assert.IsTrue(parsed is IDictionary); parsedDict = parsed as IDictionary; Assert.IsTrue((bool) parsedDict["1"]); - parsed = Json.Parse("{\"1\" : false}"); + parsed = JsonUtilities.Parse("{\"1\" : false}"); Assert.IsTrue(parsed is IDictionary); parsedDict = parsed as IDictionary; Assert.IsFalse((bool) parsedDict["1"]); @@ -91,37 +91,37 @@ public void TestOneJsonObject() [TestMethod] public void TestMultipleJsonObjectAsRootFail() { - Assert.ThrowsException(() => Json.Parse("{},")); - Assert.ThrowsException(() => Json.Parse("{\"abc\" : \"def\"},")); - Assert.ThrowsException(() => Json.Parse("{\"abc\" : \"def\" \"def\"}")); - Assert.ThrowsException(() => Json.Parse("{}, {}")); - Assert.ThrowsException(() => Json.Parse("{},\n{}")); + Assert.ThrowsException(() => JsonUtilities.Parse("{},")); + Assert.ThrowsException(() => JsonUtilities.Parse("{\"abc\" : \"def\"},")); + Assert.ThrowsException(() => JsonUtilities.Parse("{\"abc\" : \"def\" \"def\"}")); + Assert.ThrowsException(() => JsonUtilities.Parse("{}, {}")); + Assert.ThrowsException(() => JsonUtilities.Parse("{},\n{}")); } [TestMethod] public void TestOneJsonArray() { - Assert.ThrowsException(() => Json.Parse("[ 1 : 1 ]")); - Assert.ThrowsException(() => Json.Parse("[ 1 1 ]")); - Assert.ThrowsException(() => Json.Parse("[ 1 : \"1\" ]")); - Assert.ThrowsException(() => Json.Parse("[ \"1\" : \"1\" ]")); + Assert.ThrowsException(() => JsonUtilities.Parse("[ 1 : 1 ]")); + Assert.ThrowsException(() => JsonUtilities.Parse("[ 1 1 ]")); + Assert.ThrowsException(() => JsonUtilities.Parse("[ 1 : \"1\" ]")); + Assert.ThrowsException(() => JsonUtilities.Parse("[ \"1\" : \"1\" ]")); - object parsed = Json.Parse("[ 1 ]"); + object parsed = JsonUtilities.Parse("[ 1 ]"); Assert.IsTrue(parsed is IList); IList parsedList = parsed as IList; Assert.AreEqual((long) 1, parsedList[0]); - parsed = Json.Parse("[ \n ]"); + parsed = JsonUtilities.Parse("[ \n ]"); Assert.IsTrue(parsed is IList); parsedList = parsed as IList; Assert.AreEqual(0, parsedList.Count); - parsed = Json.Parse("[ \"asdf\" ]"); + parsed = JsonUtilities.Parse("[ \"asdf\" ]"); Assert.IsTrue(parsed is IList); parsedList = parsed as IList; Assert.AreEqual("asdf", parsedList[0]); - parsed = Json.Parse("[ \"\u849c\" ]"); + parsed = JsonUtilities.Parse("[ \"\u849c\" ]"); Assert.IsTrue(parsed is IList); parsedList = parsed as IList; Assert.AreEqual("\u849c", parsedList[0]); @@ -130,25 +130,25 @@ public void TestOneJsonArray() [TestMethod] public void TestMultipleJsonArrayAsRootFail() { - Assert.ThrowsException(() => Json.Parse("[],")); - Assert.ThrowsException(() => Json.Parse("[\"abc\" : \"def\"],")); - Assert.ThrowsException(() => Json.Parse("[], []")); - Assert.ThrowsException(() => Json.Parse("[],\n[]")); + Assert.ThrowsException(() => JsonUtilities.Parse("[],")); + Assert.ThrowsException(() => JsonUtilities.Parse("[\"abc\" : \"def\"],")); + Assert.ThrowsException(() => JsonUtilities.Parse("[], []")); + Assert.ThrowsException(() => JsonUtilities.Parse("[],\n[]")); } [TestMethod] public void TestJsonArrayInsideJsonObject() { - Assert.ThrowsException(() => Json.Parse("{ [] }")); - Assert.ThrowsException(() => Json.Parse("{ [], [] }")); - Assert.ThrowsException(() => Json.Parse("{ \"abc\": [], [] }")); + Assert.ThrowsException(() => JsonUtilities.Parse("{ [] }")); + Assert.ThrowsException(() => JsonUtilities.Parse("{ [], [] }")); + Assert.ThrowsException(() => JsonUtilities.Parse("{ \"abc\": [], [] }")); - object parsed = Json.Parse("{ \"abc\": [] }"); + object parsed = JsonUtilities.Parse("{ \"abc\": [] }"); Assert.IsTrue(parsed is IDictionary); IDictionary parsedDict = parsed as IDictionary; Assert.IsTrue(parsedDict["abc"] is IList); - parsed = Json.Parse("{ \"6060\" :\n[ 6060 ]\t}"); + parsed = JsonUtilities.Parse("{ \"6060\" :\n[ 6060 ]\t}"); Assert.IsTrue(parsed is IDictionary); parsedDict = parsed as IDictionary; Assert.IsTrue(parsedDict["6060"] is IList); @@ -159,15 +159,15 @@ public void TestJsonArrayInsideJsonObject() [TestMethod] public void TestJsonObjectInsideJsonArray() { - Assert.ThrowsException(() => Json.Parse("[ {} : {} ]")); + Assert.ThrowsException(() => JsonUtilities.Parse("[ {} : {} ]")); // whitespace test - object parsed = Json.Parse("[\t\n{}\r\t]"); + object parsed = JsonUtilities.Parse("[\t\n{}\r\t]"); Assert.IsTrue(parsed is IList); IList parsedList = parsed as IList; Assert.IsTrue(parsedList[0] is IDictionary); - parsed = Json.Parse("[ {}, { \"final\" : \"fantasy\"} ]"); + parsed = JsonUtilities.Parse("[ {}, { \"final\" : \"fantasy\"} ]"); Assert.IsTrue(parsed is IList); parsedList = parsed as IList; Assert.IsTrue(parsedList[0] is IDictionary); @@ -181,11 +181,11 @@ public void TestJsonObjectWithElements() { // Just make sure they don't throw exception as we already check their content correctness // in other unit tests. - Json.Parse("{ \"mura\": \"masa\" }"); - Json.Parse("{ \"mura\": 1234 }"); - Json.Parse("{ \"mura\": { \"masa\": 1234 } }"); - Json.Parse("{ \"mura\": { \"masa\": [ 1234 ] } }"); - Json.Parse("{ \"mura\": { \"masa\": [ 1234 ] }, \"arr\": [] }"); + JsonUtilities.Parse("{ \"mura\": \"masa\" }"); + JsonUtilities.Parse("{ \"mura\": 1234 }"); + JsonUtilities.Parse("{ \"mura\": { \"masa\": 1234 } }"); + JsonUtilities.Parse("{ \"mura\": { \"masa\": [ 1234 ] } }"); + JsonUtilities.Parse("{ \"mura\": { \"masa\": [ 1234 ] }, \"arr\": [] }"); } [TestMethod] @@ -193,62 +193,62 @@ public void TestJsonArrayWithElements() { // Just make sure they don't throw exception as we already check their content correctness // in other unit tests. - Json.Parse("[ \"mura\" ]"); - Json.Parse("[ \"\u1234\" ]"); - Json.Parse("[ \"\u1234ff\", \"\u1234\" ]"); - Json.Parse("[ [], [], [], [] ]"); - Json.Parse("[ [], [ {}, {} ], [ {} ], [] ]"); + JsonUtilities.Parse("[ \"mura\" ]"); + JsonUtilities.Parse("[ \"\u1234\" ]"); + JsonUtilities.Parse("[ \"\u1234ff\", \"\u1234\" ]"); + JsonUtilities.Parse("[ [], [], [], [] ]"); + JsonUtilities.Parse("[ [], [ {}, {} ], [ {} ], [] ]"); } [TestMethod] public void TestEncodeJson() { Dictionary dict = new Dictionary(); - string encoded = Json.Encode(dict); + string encoded = JsonUtilities.Encode(dict); Assert.AreEqual("{}", encoded); List list = new List(); - encoded = Json.Encode(list); + encoded = JsonUtilities.Encode(list); Assert.AreEqual("[]", encoded); Dictionary dictChild = new Dictionary(); list.Add(dictChild); - encoded = Json.Encode(list); + encoded = JsonUtilities.Encode(list); Assert.AreEqual("[{}]", encoded); list.Add("1234 a\t\r\n"); list.Add(1234); list.Add(12.34); list.Add(1.23456789123456789); - encoded = Json.Encode(list); + encoded = JsonUtilities.Encode(list); // This string should be [{},\"1234 a\\t\\r\\n\",1234,12.34,1.23456789123457] for .NET Framework (https://github.com/dotnet/runtime/issues/31483). Assert.AreEqual("[{},\"1234 a\\t\\r\\n\",1234,12.34,1.234567891234568]", encoded); dict["arr"] = new List(); - encoded = Json.Encode(dict); + encoded = JsonUtilities.Encode(dict); Assert.AreEqual("{\"arr\":[]}", encoded); dict["\u1234"] = "\u1234"; - encoded = Json.Encode(dict); + encoded = JsonUtilities.Encode(dict); Assert.AreEqual("{\"arr\":[],\"\u1234\":\"\u1234\"}", encoded); - encoded = Json.Encode(new List { true, false, null }); + encoded = JsonUtilities.Encode(new List { true, false, null }); Assert.AreEqual("[true,false,null]", encoded); } [TestMethod] public void TestSpecialJsonNumbersAndModifiers() { - Assert.ThrowsException(() => Json.Parse("+123456789")); + Assert.ThrowsException(() => JsonUtilities.Parse("+123456789")); - Json.Parse("{ \"mura\": -123456789123456789 }"); - Json.Parse("{ \"mura\": 1.1234567891234567E308 }"); - Json.Parse("{ \"PI\": 3.141e-10 }"); - Json.Parse("{ \"PI\": 3.141E-10 }"); + JsonUtilities.Parse("{ \"mura\": -123456789123456789 }"); + JsonUtilities.Parse("{ \"mura\": 1.1234567891234567E308 }"); + JsonUtilities.Parse("{ \"PI\": 3.141e-10 }"); + JsonUtilities.Parse("{ \"PI\": 3.141E-10 }"); - Assert.AreEqual(123456789123456789, (Json.Parse("{ \"mura\": 123456789123456789 }") as IDictionary)["mura"]); + Assert.AreEqual(123456789123456789, (JsonUtilities.Parse("{ \"mura\": 123456789123456789 }") as IDictionary)["mura"]); } } } diff --git a/Parse.Test/MoqExtensions.cs b/Parse.Tests/MoqExtensions.cs similarity index 98% rename from Parse.Test/MoqExtensions.cs rename to Parse.Tests/MoqExtensions.cs index f88e44cf..abd9a606 100644 --- a/Parse.Test/MoqExtensions.cs +++ b/Parse.Tests/MoqExtensions.cs @@ -2,7 +2,7 @@ using Moq.Language; using Moq.Language.Flow; -namespace Parse.Test +namespace Parse.Tests { // MIT licensed, w/ attribution: // http://stackoverflow.com/a/19598345/427309 diff --git a/Parse.Test/ObjectCoderTests.cs b/Parse.Tests/ObjectCoderTests.cs similarity index 92% rename from Parse.Test/ObjectCoderTests.cs rename to Parse.Tests/ObjectCoderTests.cs index a0849618..fb2d0e45 100644 --- a/Parse.Test/ObjectCoderTests.cs +++ b/Parse.Tests/ObjectCoderTests.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Parse.Core.Internal; -using Parse.Library; +using Parse.Infrastructure; +using Parse.Infrastructure.Data; +using Parse.Platform.Objects; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class ObjectCoderTests diff --git a/Parse.Test/ObjectControllerTests.cs b/Parse.Tests/ObjectControllerTests.cs similarity index 98% rename from Parse.Test/ObjectControllerTests.cs rename to Parse.Tests/ObjectControllerTests.cs index 3d22a97c..d56da4da 100644 --- a/Parse.Test/ObjectControllerTests.cs +++ b/Parse.Tests/ObjectControllerTests.cs @@ -7,10 +7,15 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Core.Internal; -using Parse.Library; - -namespace Parse.Test +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure; +using Parse.Infrastructure.Execution; +using Parse.Platform.Objects; + +namespace Parse.Tests { #warning Finish refactoring. diff --git a/Parse.Test/ObjectStateTests.cs b/Parse.Tests/ObjectStateTests.cs similarity index 96% rename from Parse.Test/ObjectStateTests.cs rename to Parse.Tests/ObjectStateTests.cs index 053560d7..250ce4ba 100644 --- a/Parse.Test/ObjectStateTests.cs +++ b/Parse.Tests/ObjectStateTests.cs @@ -2,9 +2,12 @@ using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Parse.Core.Internal; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure.Control; +using Parse.Platform.Objects; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class ObjectStateTests diff --git a/Parse.Test/ObjectTests.cs b/Parse.Tests/ObjectTests.cs similarity index 99% rename from Parse.Test/ObjectTests.cs rename to Parse.Tests/ObjectTests.cs index 30cd88c3..e3aef397 100644 --- a/Parse.Test/ObjectTests.cs +++ b/Parse.Tests/ObjectTests.cs @@ -4,11 +4,12 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Parse.Core.Internal; -using Parse.Library; -using Parse.Management; +using Parse.Abstractions.Internal; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure; +using Parse.Platform.Objects; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class ObjectTests diff --git a/Parse.Test/Parse.Test.csproj b/Parse.Tests/Parse.Tests.csproj similarity index 100% rename from Parse.Test/Parse.Test.csproj rename to Parse.Tests/Parse.Tests.csproj diff --git a/Parse.Test/ProgressTests.cs b/Parse.Tests/ProgressTests.cs similarity index 96% rename from Parse.Test/ProgressTests.cs rename to Parse.Tests/ProgressTests.cs index 0eecff4d..5268f646 100644 --- a/Parse.Test/ProgressTests.cs +++ b/Parse.Tests/ProgressTests.cs @@ -1,8 +1,10 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Parse.Abstractions.Infrastructure; +using Parse.Infrastructure; -namespace Parse.Test +namespace Parse.Tests { #warning Refactor if possible. diff --git a/Parse.Test/PushEncoderTests.cs b/Parse.Tests/PushEncoderTests.cs similarity index 97% rename from Parse.Test/PushEncoderTests.cs rename to Parse.Tests/PushEncoderTests.cs index 826330f7..0a2d583f 100644 --- a/Parse.Test/PushEncoderTests.cs +++ b/Parse.Tests/PushEncoderTests.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; -using Parse.Push.Internal; +using Parse.Platform.Push; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class PushEncoderTests diff --git a/Parse.Test/PushStateTests.cs b/Parse.Tests/PushStateTests.cs similarity index 90% rename from Parse.Test/PushStateTests.cs rename to Parse.Tests/PushStateTests.cs index 0c99d471..bc08fb8a 100644 --- a/Parse.Test/PushStateTests.cs +++ b/Parse.Tests/PushStateTests.cs @@ -1,7 +1,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Parse.Push.Internal; +using Parse.Abstractions.Platform.Push; +using Parse.Platform.Push; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class PushStateTests diff --git a/Parse.Test/PushTests.cs b/Parse.Tests/PushTests.cs similarity index 96% rename from Parse.Test/PushTests.cs rename to Parse.Tests/PushTests.cs index a3dec4c9..6b5f609b 100644 --- a/Parse.Test/PushTests.cs +++ b/Parse.Tests/PushTests.cs @@ -4,13 +4,13 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Library; -using Parse.Management; -using Parse.Push.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Push; +using Parse.Infrastructure.Utilities; +using Parse.Infrastructure; +using Parse.Platform.Push; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class PushTests diff --git a/Parse.Test/RelationTests.cs b/Parse.Tests/RelationTests.cs similarity index 91% rename from Parse.Test/RelationTests.cs rename to Parse.Tests/RelationTests.cs index 584b73c9..d0929b21 100644 --- a/Parse.Test/RelationTests.cs +++ b/Parse.Tests/RelationTests.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Parse.Core.Internal; -using Parse.Library; +using Parse.Abstractions.Internal; +using Parse.Infrastructure; -namespace Parse.Test +namespace Parse.Tests { [TestClass] public class RelationTests diff --git a/Parse.Test/SessionControllerTests.cs b/Parse.Tests/SessionControllerTests.cs similarity index 94% rename from Parse.Test/SessionControllerTests.cs rename to Parse.Tests/SessionControllerTests.cs index 6193b62f..18d16156 100644 --- a/Parse.Test/SessionControllerTests.cs +++ b/Parse.Tests/SessionControllerTests.cs @@ -7,10 +7,16 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Core.Internal; -using Parse.Library; - -namespace Parse.Test +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Internal; +using Parse.Abstractions.Platform.Objects; +using Parse.Abstractions.Platform.Sessions; +using Parse.Infrastructure; +using Parse.Infrastructure.Execution; +using Parse.Platform.Sessions; + +namespace Parse.Tests { [TestClass] public class SessionControllerTests diff --git a/Parse.Test/SessionTests.cs b/Parse.Tests/SessionTests.cs similarity index 96% rename from Parse.Test/SessionTests.cs rename to Parse.Tests/SessionTests.cs index f2eaa5e9..deb7fe24 100644 --- a/Parse.Test/SessionTests.cs +++ b/Parse.Tests/SessionTests.cs @@ -4,12 +4,15 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Abstractions.Library; -using Parse.Core.Internal; -using Parse.Library; -using Parse.Management; - -namespace Parse.Test +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Internal; +using Parse.Abstractions.Platform.Objects; +using Parse.Abstractions.Platform.Sessions; +using Parse.Abstractions.Platform.Users; +using Parse.Infrastructure; +using Parse.Platform.Objects; + +namespace Parse.Tests { [TestClass] public class SessionTests diff --git a/Parse.Test/UserControllerTests.cs b/Parse.Tests/UserControllerTests.cs similarity index 96% rename from Parse.Test/UserControllerTests.cs rename to Parse.Tests/UserControllerTests.cs index 0e674896..edb2ac12 100644 --- a/Parse.Test/UserControllerTests.cs +++ b/Parse.Tests/UserControllerTests.cs @@ -6,11 +6,16 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Abstractions.Library; -using Parse.Core.Internal; -using Parse.Library; - -namespace Parse.Test +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure; +using Parse.Infrastructure.Execution; +using Parse.Platform.Objects; +using Parse.Platform.Users; + +namespace Parse.Tests { [TestClass] public class UserControllerTests diff --git a/Parse.Test/UserTests.cs b/Parse.Tests/UserTests.cs similarity index 98% rename from Parse.Test/UserTests.cs rename to Parse.Tests/UserTests.cs index 2bba99ae..9e799457 100644 --- a/Parse.Test/UserTests.cs +++ b/Parse.Tests/UserTests.cs @@ -5,12 +5,16 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Parse.Abstractions.Library; -using Parse.Core.Internal; -using Parse.Library; -using Parse.Management; - -namespace Parse.Test +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Internal; +using Parse.Infrastructure; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Platform.Objects; +using Parse.Abstractions.Platform.Sessions; +using Parse.Abstractions.Platform.Users; +using Parse.Platform.Objects; + +namespace Parse.Tests { #warning Class refactoring requires completion. diff --git a/Parse.sln b/Parse.sln index c4b0fb52..cb26bb64 100644 --- a/Parse.sln +++ b/Parse.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 16.0.29905.134 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Parse", "Parse\Parse.csproj", "{297FE1CA-AEDF-47BB-964D-E82780EA0A1C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Parse.Test", "Parse.Test\Parse.Test.csproj", "{F54F1CF4-89AB-48E9-99C5-6CCD88B3EDBB}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{35AC7BF1-C7AA-4B48-A41D-858F226E660E}" ProjectSection(SolutionItems) = preProject .appveyor.yml = .appveyor.yml @@ -17,7 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Parse.Core", "Parse.Core\Parse.Core.csproj", "{F1AC4286-3E67-4C0B-AB46-B03208A996E7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Parse.Tests", "Parse.Tests\Parse.Tests.csproj", "{E5529694-B75B-4F07-8436-A749B5E801C3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -29,14 +27,10 @@ Global {297FE1CA-AEDF-47BB-964D-E82780EA0A1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {297FE1CA-AEDF-47BB-964D-E82780EA0A1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {297FE1CA-AEDF-47BB-964D-E82780EA0A1C}.Release|Any CPU.Build.0 = Release|Any CPU - {F54F1CF4-89AB-48E9-99C5-6CCD88B3EDBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F54F1CF4-89AB-48E9-99C5-6CCD88B3EDBB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F54F1CF4-89AB-48E9-99C5-6CCD88B3EDBB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F54F1CF4-89AB-48E9-99C5-6CCD88B3EDBB}.Release|Any CPU.Build.0 = Release|Any CPU - {F1AC4286-3E67-4C0B-AB46-B03208A996E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F1AC4286-3E67-4C0B-AB46-B03208A996E7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F1AC4286-3E67-4C0B-AB46-B03208A996E7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F1AC4286-3E67-4C0B-AB46-B03208A996E7}.Release|Any CPU.Build.0 = Release|Any CPU + {E5529694-B75B-4F07-8436-A749B5E801C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5529694-B75B-4F07-8436-A749B5E801C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5529694-B75B-4F07-8436-A749B5E801C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5529694-B75B-4F07-8436-A749B5E801C3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Parse/Abstractions/Management/Tracking/IParseFieldOperation.cs b/Parse/Abstractions/Infrastructure/Control/IParseFieldOperation.cs similarity index 97% rename from Parse/Abstractions/Management/Tracking/IParseFieldOperation.cs rename to Parse/Abstractions/Infrastructure/Control/IParseFieldOperation.cs index 2fa63034..102b0b98 100644 --- a/Parse/Abstractions/Management/Tracking/IParseFieldOperation.cs +++ b/Parse/Abstractions/Infrastructure/Control/IParseFieldOperation.cs @@ -1,8 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Abstractions.Library; - -namespace Parse.Core.Internal +namespace Parse.Abstractions.Infrastructure.Control { /// /// A ParseFieldOperation represents a modification to a value in a ParseObject. diff --git a/Parse/Abstractions/Library/CustomServiceHub.cs b/Parse/Abstractions/Infrastructure/CustomServiceHub.cs similarity index 80% rename from Parse/Abstractions/Library/CustomServiceHub.cs rename to Parse/Abstractions/Infrastructure/CustomServiceHub.cs index de23d43c..493e240f 100644 --- a/Parse/Abstractions/Library/CustomServiceHub.cs +++ b/Parse/Abstractions/Infrastructure/CustomServiceHub.cs @@ -1,9 +1,17 @@ -using Parse.Analytics.Internal; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Push.Internal; - -namespace Parse.Abstractions.Library +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Analytics; +using Parse.Abstractions.Platform.Cloud; +using Parse.Abstractions.Platform.Configuration; +using Parse.Abstractions.Platform.Files; +using Parse.Abstractions.Platform.Installations; +using Parse.Abstractions.Platform.Objects; +using Parse.Abstractions.Platform.Push; +using Parse.Abstractions.Platform.Queries; +using Parse.Abstractions.Platform.Sessions; +using Parse.Abstractions.Platform.Users; + +namespace Parse.Abstractions.Infrastructure { public abstract class CustomServiceHub : ICustomServiceHub { diff --git a/Parse/Utilities/Encoding/IParseDataDecoder.cs b/Parse/Abstractions/Infrastructure/Data/IParseDataDecoder.cs similarity index 94% rename from Parse/Utilities/Encoding/IParseDataDecoder.cs rename to Parse/Abstractions/Infrastructure/Data/IParseDataDecoder.cs index ae9865b1..590cf169 100644 --- a/Parse/Utilities/Encoding/IParseDataDecoder.cs +++ b/Parse/Abstractions/Infrastructure/Data/IParseDataDecoder.cs @@ -1,8 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Abstractions.Library; - -namespace Parse.Core.Internal +namespace Parse.Abstractions.Infrastructure.Data { /// /// A generalized input data decoding interface for the Parse SDK. diff --git a/Parse/Abstractions/Management/IParseCommandRunner.cs b/Parse/Abstractions/Infrastructure/Execution/IParseCommandRunner.cs similarity index 92% rename from Parse/Abstractions/Management/IParseCommandRunner.cs rename to Parse/Abstractions/Infrastructure/Execution/IParseCommandRunner.cs index d4d7cb3b..6718f003 100644 --- a/Parse/Abstractions/Management/IParseCommandRunner.cs +++ b/Parse/Abstractions/Infrastructure/Execution/IParseCommandRunner.cs @@ -5,8 +5,9 @@ using System.Net; using System.Threading; using System.Threading.Tasks; +using Parse.Infrastructure.Execution; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Infrastructure.Execution { public interface IParseCommandRunner { diff --git a/Parse/Utilities/IWebClient.cs b/Parse/Abstractions/Infrastructure/Execution/IWebClient.cs similarity index 73% rename from Parse/Utilities/IWebClient.cs rename to Parse/Abstractions/Infrastructure/Execution/IWebClient.cs index 0fc12941..db4f50ba 100644 --- a/Parse/Utilities/IWebClient.cs +++ b/Parse/Abstractions/Infrastructure/Execution/IWebClient.cs @@ -1,11 +1,12 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; -using System.Net; using System.Threading; using System.Threading.Tasks; +using Parse.Infrastructure.Execution; +using Status = System.Net.HttpStatusCode; -namespace Parse.Common.Internal +namespace Parse.Abstractions.Infrastructure.Execution { public interface IWebClient { @@ -18,6 +19,6 @@ public interface IWebClient /// Download progress callback. /// The cancellation token. /// A task that resolves to Htt - Task> ExecuteAsync(WebRequest httpRequest, IProgress uploadProgress, IProgress downloadProgress, CancellationToken cancellationToken); + Task> ExecuteAsync(WebRequest httpRequest, IProgress uploadProgress, IProgress downloadProgress, CancellationToken cancellationToken = default); } } diff --git a/Parse/Abstractions/Library/ICustomServiceHub.cs b/Parse/Abstractions/Infrastructure/ICustomServiceHub.cs similarity index 50% rename from Parse/Abstractions/Library/ICustomServiceHub.cs rename to Parse/Abstractions/Infrastructure/ICustomServiceHub.cs index fdee9ac2..967f77bc 100644 --- a/Parse/Abstractions/Library/ICustomServiceHub.cs +++ b/Parse/Abstractions/Infrastructure/ICustomServiceHub.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Parse.Abstractions.Library +namespace Parse.Abstractions.Infrastructure { public interface ICustomServiceHub : IServiceHub { diff --git a/Parse.Core/Abstractions/Platform/Installation/IParseCurrentInstallationController.cs b/Parse/Abstractions/Infrastructure/IDataTransferLevel.cs similarity index 64% rename from Parse.Core/Abstractions/Platform/Installation/IParseCurrentInstallationController.cs rename to Parse/Abstractions/Infrastructure/IDataTransferLevel.cs index d8fd32fa..4257d1b4 100644 --- a/Parse.Core/Abstractions/Platform/Installation/IParseCurrentInstallationController.cs +++ b/Parse/Abstractions/Infrastructure/IDataTransferLevel.cs @@ -1,10 +1,9 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Core.Internal; - -namespace Parse.Push.Internal +namespace Parse.Abstractions.Infrastructure { - public interface IParseCurrentInstallationController : IParseObjectCurrentController + public interface IDataTransferLevel { + double Amount { get; set; } } } diff --git a/Parse/Abstractions/Library/IEnvironmentData.cs b/Parse/Abstractions/Infrastructure/IEnvironmentData.cs similarity index 94% rename from Parse/Abstractions/Library/IEnvironmentData.cs rename to Parse/Abstractions/Infrastructure/IEnvironmentData.cs index 1bb3a965..14b454b5 100644 --- a/Parse/Abstractions/Library/IEnvironmentData.cs +++ b/Parse/Abstractions/Infrastructure/IEnvironmentData.cs @@ -1,4 +1,4 @@ -namespace Parse.Abstractions.Library +namespace Parse.Abstractions.Infrastructure { /// /// Information about the environment in which the library will be operating. diff --git a/Parse/Abstractions/Library/IHostManifestData.cs b/Parse/Abstractions/Infrastructure/IHostManifestData.cs similarity index 93% rename from Parse/Abstractions/Library/IHostManifestData.cs rename to Parse/Abstractions/Infrastructure/IHostManifestData.cs index 88b01cc6..b8f2c5f7 100644 --- a/Parse/Abstractions/Library/IHostManifestData.cs +++ b/Parse/Abstractions/Infrastructure/IHostManifestData.cs @@ -1,4 +1,4 @@ -namespace Parse.Abstractions.Library +namespace Parse.Abstractions.Infrastructure { /// /// Information about the application using the Parse SDK. diff --git a/Parse/Utilities/IJsonConvertible.cs b/Parse/Abstractions/Infrastructure/IJsonConvertible.cs similarity index 94% rename from Parse/Utilities/IJsonConvertible.cs rename to Parse/Abstractions/Infrastructure/IJsonConvertible.cs index 8bcc731c..6071d8bb 100644 --- a/Parse/Utilities/IJsonConvertible.cs +++ b/Parse/Abstractions/Infrastructure/IJsonConvertible.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; -namespace Parse.Common.Internal +namespace Parse.Abstractions.Infrastructure { /// /// Represents an object that can be converted into JSON. diff --git a/Parse/Abstractions/Library/IMetadataController.cs b/Parse/Abstractions/Infrastructure/IMetadataController.cs similarity index 91% rename from Parse/Abstractions/Library/IMetadataController.cs rename to Parse/Abstractions/Infrastructure/IMetadataController.cs index a7239284..90332138 100644 --- a/Parse/Abstractions/Library/IMetadataController.cs +++ b/Parse/Abstractions/Infrastructure/IMetadataController.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Text; - -namespace Parse.Abstractions.Library +namespace Parse.Abstractions.Infrastructure { /// /// A controller for metadata. This is provided in a dependency injection container because if a beta feature is activated for a client managing a specific aspect of application operation, then this might need to be reflected in the application versioning information as it is used to determine the data cache location. diff --git a/Parse/Abstractions/Library/IMutableServiceHub.cs b/Parse/Abstractions/Infrastructure/IMutableServiceHub.cs similarity index 72% rename from Parse/Abstractions/Library/IMutableServiceHub.cs rename to Parse/Abstractions/Infrastructure/IMutableServiceHub.cs index 0afb8ac4..895c8a71 100644 --- a/Parse/Abstractions/Library/IMutableServiceHub.cs +++ b/Parse/Abstractions/Infrastructure/IMutableServiceHub.cs @@ -1,14 +1,19 @@ #pragma warning disable CS0108 // Member hides inherited member; missing new keyword -using System; -using System.Collections.Generic; -using System.Text; -using Parse.Analytics.Internal; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Push.Internal; - -namespace Parse.Abstractions.Library +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Analytics; +using Parse.Abstractions.Platform.Cloud; +using Parse.Abstractions.Platform.Configuration; +using Parse.Abstractions.Platform.Files; +using Parse.Abstractions.Platform.Installations; +using Parse.Abstractions.Platform.Objects; +using Parse.Abstractions.Platform.Push; +using Parse.Abstractions.Platform.Queries; +using Parse.Abstractions.Platform.Sessions; +using Parse.Abstractions.Platform.Users; + +namespace Parse.Abstractions.Infrastructure { public interface IMutableServiceHub : IServiceHub { diff --git a/Parse/Abstractions/Library/IServerConnectionData.cs b/Parse/Abstractions/Infrastructure/IServerConnectionData.cs similarity index 95% rename from Parse/Abstractions/Library/IServerConnectionData.cs rename to Parse/Abstractions/Infrastructure/IServerConnectionData.cs index 5ef8933f..bad1c01d 100644 --- a/Parse/Abstractions/Library/IServerConnectionData.cs +++ b/Parse/Abstractions/Infrastructure/IServerConnectionData.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Parse.Abstractions.Library +namespace Parse.Abstractions.Infrastructure { public interface IServerConnectionData { diff --git a/Parse/Abstractions/Library/IServiceHub.cs b/Parse/Abstractions/Infrastructure/IServiceHub.cs similarity index 77% rename from Parse/Abstractions/Library/IServiceHub.cs rename to Parse/Abstractions/Infrastructure/IServiceHub.cs index d4544ad3..a81891c0 100644 --- a/Parse/Abstractions/Library/IServiceHub.cs +++ b/Parse/Abstractions/Infrastructure/IServiceHub.cs @@ -1,14 +1,19 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -using System; -using System.Collections.Generic; -using System.Text; -using Parse.Analytics.Internal; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Push.Internal; - -namespace Parse.Abstractions.Library +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Analytics; +using Parse.Abstractions.Platform.Cloud; +using Parse.Abstractions.Platform.Configuration; +using Parse.Abstractions.Platform.Files; +using Parse.Abstractions.Platform.Installations; +using Parse.Abstractions.Platform.Objects; +using Parse.Abstractions.Platform.Push; +using Parse.Abstractions.Platform.Queries; +using Parse.Abstractions.Platform.Sessions; +using Parse.Abstractions.Platform.Users; + +namespace Parse.Abstractions.Infrastructure { // TODO: Consider splitting up IServiceHub into IResourceHub and IServiceHub, where the former would provide the current functionality of IServiceHub and the latter would be a public-facing sub-section containing formerly-static memebers from classes such as ParseObject which require the use of some broader resource. diff --git a/Parse/Abstractions/Library/IServiceHubCloner.cs b/Parse/Abstractions/Infrastructure/IServiceHubCloner.cs similarity index 73% rename from Parse/Abstractions/Library/IServiceHubCloner.cs rename to Parse/Abstractions/Infrastructure/IServiceHubCloner.cs index 68c2273d..d88dea9e 100644 --- a/Parse/Abstractions/Library/IServiceHubCloner.cs +++ b/Parse/Abstractions/Infrastructure/IServiceHubCloner.cs @@ -1,7 +1,4 @@ -using System; -using System.Text; - -namespace Parse.Abstractions.Library +namespace Parse.Abstractions.Infrastructure { public interface IServiceHubCloner { diff --git a/Parse/Abstractions/Library/IServiceHubComposer.cs b/Parse/Abstractions/Infrastructure/IServiceHubComposer.cs similarity index 88% rename from Parse/Abstractions/Library/IServiceHubComposer.cs rename to Parse/Abstractions/Infrastructure/IServiceHubComposer.cs index 7f515d58..73dc3597 100644 --- a/Parse/Abstractions/Library/IServiceHubComposer.cs +++ b/Parse/Abstractions/Infrastructure/IServiceHubComposer.cs @@ -1,4 +1,4 @@ -namespace Parse.Abstractions.Library +namespace Parse.Abstractions.Infrastructure { // ALTERNATE NAME: IClient, IDataContainmentHub, IResourceContainmentHub, IDataContainer, IServiceHubComposer diff --git a/Parse/Abstractions/Library/IServiceHubMutator.cs b/Parse/Abstractions/Infrastructure/IServiceHubMutator.cs similarity index 78% rename from Parse/Abstractions/Library/IServiceHubMutator.cs rename to Parse/Abstractions/Infrastructure/IServiceHubMutator.cs index d5eebedc..8a4b4490 100644 --- a/Parse/Abstractions/Library/IServiceHubMutator.cs +++ b/Parse/Abstractions/Infrastructure/IServiceHubMutator.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Parse.Abstractions.Library +namespace Parse.Abstractions.Infrastructure { // IServiceHubComposer, IServiceHubMutator, IServiceHubConfigurator, IClientConfigurator, IServiceConfigurationLayer diff --git a/Parse/Abstractions/Storage/IStorageConfiguration.cs b/Parse/Abstractions/Infrastructure/IStorageConfiguration.cs similarity index 85% rename from Parse/Abstractions/Storage/IStorageConfiguration.cs rename to Parse/Abstractions/Infrastructure/IStorageConfiguration.cs index af721dd3..6f40e5b2 100644 --- a/Parse/Abstractions/Storage/IStorageConfiguration.cs +++ b/Parse/Abstractions/Infrastructure/IStorageConfiguration.cs @@ -1,5 +1,4 @@ -using Parse.Abstractions.Library; -using Parse.Abstractions.Management; +using Parse.Abstractions.Infrastructure; namespace Parse.Abstractions.Storage { diff --git a/Parse/Abstractions/Storage/IStorageController.cs b/Parse/Abstractions/Infrastructure/IStorageController.cs similarity index 98% rename from Parse/Abstractions/Storage/IStorageController.cs rename to Parse/Abstractions/Infrastructure/IStorageController.cs index 187e5f10..e82fb0ba 100644 --- a/Parse/Abstractions/Storage/IStorageController.cs +++ b/Parse/Abstractions/Infrastructure/IStorageController.cs @@ -1,9 +1,8 @@ -using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -namespace Parse.Common.Internal +namespace Parse.Abstractions.Infrastructure { /// /// An abstraction for accessing persistent storage in the Parse SDK. diff --git a/Parse/Abstractions/Management/IParseCorePlugins.cs b/Parse/Abstractions/Management/IParseCorePlugins.cs deleted file mode 100644 index 7829d299..00000000 --- a/Parse/Abstractions/Management/IParseCorePlugins.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Core.Internal; - -namespace Parse.Abstractions.Management -{ - /// - /// The dependency injection container for the .NET Parse SDK. - /// - public interface IParseCorePlugins - { - IMetadataController MetadataController { get; } - - IWebClient WebClient { get; } - IStorageController StorageController { get; } - IParseObjectClassController SubclassingController { get; } - - IParseInstallationController InstallationController { get; } - IParseCommandRunner CommandRunner { get; } - - IParseCloudCodeController CloudCodeController { get; } - IParseConfigurationController ConfigController { get; } - IParseFileController FileController { get; } - IParseObjectController ObjectController { get; } - IParseQueryController QueryController { get; } - IParseSessionController SessionController { get; } - IParseUserController UserController { get; } - IParseCurrentUserController CurrentUserController { get; } - - // IParseCurrentConfigController CurrentConfigController { get; } - - public void Reset(); - - // /// - // /// Sets the default controller instances if not explicitly overridden. This method should effectively perform a -coalescing assign on all of the properties of the implementation instance. - // /// - - // public void SetDefaults(); - } -} \ No newline at end of file diff --git a/Parse/Abstractions/Platform/Analytics/IParseAnalyticsController.cs b/Parse/Abstractions/Platform/Analytics/IParseAnalyticsController.cs index a8d73489..250d9ad9 100644 --- a/Parse/Abstractions/Platform/Analytics/IParseAnalyticsController.cs +++ b/Parse/Abstractions/Platform/Analytics/IParseAnalyticsController.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; -namespace Parse.Analytics.Internal +namespace Parse.Abstractions.Platform.Analytics { /// /// The interface for the Parse Analytics API controller. diff --git a/Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs b/Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs deleted file mode 100644 index 5b07eafa..00000000 --- a/Parse/Abstractions/Platform/Analytics/IParseAnalyticsPlugins.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using Parse.Abstractions.Management; -using Parse.Analytics.Internal; - -namespace Parse.Analytics -{ - public interface IParseAnalyticsPlugins - { - void Reset(); - - IParseCorePlugins CorePlugins { get; } - IParseAnalyticsController AnalyticsController { get; } - } -} \ No newline at end of file diff --git a/Parse/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs b/Parse/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs index 96e4145f..d177ee9d 100644 --- a/Parse/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs +++ b/Parse/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs @@ -4,7 +4,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Platform.Authentication { public interface IParseAuthenticationProvider { diff --git a/Parse/Abstractions/Platform/Code/IParseCloudCodeController.cs b/Parse/Abstractions/Platform/Cloud/IParseCloudCodeController.cs similarity index 87% rename from Parse/Abstractions/Platform/Code/IParseCloudCodeController.cs rename to Parse/Abstractions/Platform/Cloud/IParseCloudCodeController.cs index da1199b1..a816ad2e 100644 --- a/Parse/Abstractions/Platform/Code/IParseCloudCodeController.cs +++ b/Parse/Abstractions/Platform/Cloud/IParseCloudCodeController.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Platform.Cloud { public interface IParseCloudCodeController { diff --git a/Parse/Abstractions/Platform/Configuration/IParseConfigurationController.cs b/Parse/Abstractions/Platform/Configuration/IParseConfigurationController.cs index 583a98dd..58fb0247 100644 --- a/Parse/Abstractions/Platform/Configuration/IParseConfigurationController.cs +++ b/Parse/Abstractions/Platform/Configuration/IParseConfigurationController.cs @@ -2,9 +2,10 @@ using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; +using Parse.Platform.Configuration; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Platform.Configuration { public interface IParseConfigurationController { diff --git a/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigurationController.cs b/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigurationController.cs index de1f510b..427073c8 100644 --- a/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigurationController.cs +++ b/Parse/Abstractions/Platform/Configuration/IParseCurrentConfigurationController.cs @@ -1,9 +1,10 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System.Threading.Tasks; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; +using Parse.Platform.Configuration; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Platform.Configuration { public interface IParseCurrentConfigurationController { diff --git a/Parse/Abstractions/Platform/Files/IParseFileController.cs b/Parse/Abstractions/Platform/Files/IParseFileController.cs index 90d2bf94..bfe1a117 100644 --- a/Parse/Abstractions/Platform/Files/IParseFileController.cs +++ b/Parse/Abstractions/Platform/Files/IParseFileController.cs @@ -4,8 +4,10 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Infrastructure; +using Parse.Platform.Files; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Platform.Files { public interface IParseFileController { diff --git a/Parse/Abstractions/Platform/Installation/IParseCurrentInstallationController.cs b/Parse/Abstractions/Platform/Installations/IParseCurrentInstallationController.cs similarity index 81% rename from Parse/Abstractions/Platform/Installation/IParseCurrentInstallationController.cs rename to Parse/Abstractions/Platform/Installations/IParseCurrentInstallationController.cs index d8fd32fa..77b73077 100644 --- a/Parse/Abstractions/Platform/Installation/IParseCurrentInstallationController.cs +++ b/Parse/Abstractions/Platform/Installations/IParseCurrentInstallationController.cs @@ -1,8 +1,8 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Core.Internal; +using Parse.Abstractions.Platform.Objects; -namespace Parse.Push.Internal +namespace Parse.Abstractions.Platform.Installations { public interface IParseCurrentInstallationController : IParseObjectCurrentController { diff --git a/Parse/Abstractions/Platform/Installation/IParseInstallationCoder.cs b/Parse/Abstractions/Platform/Installations/IParseInstallationCoder.cs similarity index 78% rename from Parse/Abstractions/Platform/Installation/IParseInstallationCoder.cs rename to Parse/Abstractions/Platform/Installations/IParseInstallationCoder.cs index 2394cd58..0db3b683 100644 --- a/Parse/Abstractions/Platform/Installation/IParseInstallationCoder.cs +++ b/Parse/Abstractions/Platform/Installations/IParseInstallationCoder.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; -namespace Parse.Push.Internal +namespace Parse.Abstractions.Platform.Installations { // TODO: (richardross) once coder is refactored, make this extend IParseObjectCoder. + public interface IParseInstallationCoder { IDictionary Encode(ParseInstallation installation); diff --git a/Parse/Abstractions/Platform/Installation/IParseInstallationController.cs b/Parse/Abstractions/Platform/Installations/IParseInstallationController.cs similarity index 95% rename from Parse/Abstractions/Platform/Installation/IParseInstallationController.cs rename to Parse/Abstractions/Platform/Installations/IParseInstallationController.cs index 47b978b9..1fc9f1b4 100644 --- a/Parse/Abstractions/Platform/Installation/IParseInstallationController.cs +++ b/Parse/Abstractions/Platform/Installations/IParseInstallationController.cs @@ -3,7 +3,7 @@ using System; using System.Threading.Tasks; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Platform.Installations { public interface IParseInstallationController { diff --git a/Parse/Abstractions/Platform/Installation/IParseInstallationDataFinalizer.cs b/Parse/Abstractions/Platform/Installations/IParseInstallationDataFinalizer.cs similarity index 92% rename from Parse/Abstractions/Platform/Installation/IParseInstallationDataFinalizer.cs rename to Parse/Abstractions/Platform/Installations/IParseInstallationDataFinalizer.cs index 7ded545b..9296ffef 100644 --- a/Parse/Abstractions/Platform/Installation/IParseInstallationDataFinalizer.cs +++ b/Parse/Abstractions/Platform/Installations/IParseInstallationDataFinalizer.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace Parse.Push.Internal +namespace Parse.Abstractions.Platform.Installations { public interface IParseInstallationDataFinalizer { diff --git a/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs b/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs deleted file mode 100644 index 9d782a0e..00000000 --- a/Parse/Abstractions/Platform/Notifications/IParsePushPlugins.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Parse.Abstractions.Management; - -namespace Parse.Push.Internal -{ - public interface IParsePushPlugins - { - void Reset(); - - IParseCorePlugins CorePlugins { get; } - IParsePushChannelsController PushChannelsController { get; } - IParsePushController PushController { get; } - IParseCurrentInstallationController CurrentInstallationController { get; } - IParseInstallationDataFinalizer DeviceInfoController { get; } - } -} \ No newline at end of file diff --git a/Parse/Abstractions/Platform/Notifications/IPushState.cs b/Parse/Abstractions/Platform/Notifications/IPushState.cs deleted file mode 100644 index 4b8fd9b4..00000000 --- a/Parse/Abstractions/Platform/Notifications/IPushState.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System; -using System.Collections.Generic; - -namespace Parse.Push.Internal -{ - public interface IPushState - { - ParseQuery Query { get; } - IEnumerable Channels { get; } - DateTime? Expiration { get; } - TimeSpan? ExpirationInterval { get; } - DateTime? PushTime { get; } - IDictionary Data { get; } - string Alert { get; } - - IPushState MutatedClone(Action func); - } -} diff --git a/Parse/Abstractions/Management/Tracking/IObjectState.cs b/Parse/Abstractions/Platform/Objects/IObjectState.cs similarity index 90% rename from Parse/Abstractions/Management/Tracking/IObjectState.cs rename to Parse/Abstractions/Platform/Objects/IObjectState.cs index 473f4428..1da67c59 100644 --- a/Parse/Abstractions/Management/Tracking/IObjectState.cs +++ b/Parse/Abstractions/Platform/Objects/IObjectState.cs @@ -2,8 +2,9 @@ using System; using System.Collections.Generic; +using Parse.Platform.Objects; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Platform.Objects { public interface IObjectState : IEnumerable> { diff --git a/Parse/Abstractions/Management/IParseObjectClassController.cs b/Parse/Abstractions/Platform/Objects/IParseObjectClassController.cs similarity index 86% rename from Parse/Abstractions/Management/IParseObjectClassController.cs rename to Parse/Abstractions/Platform/Objects/IParseObjectClassController.cs index d8436434..07ad585c 100644 --- a/Parse/Abstractions/Management/IParseObjectClassController.cs +++ b/Parse/Abstractions/Platform/Objects/IParseObjectClassController.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Platform.Objects { public interface IParseObjectClassController { diff --git a/Parse/Abstractions/Management/IParseObjectController.cs b/Parse/Abstractions/Platform/Objects/IParseObjectController.cs similarity index 90% rename from Parse/Abstractions/Management/IParseObjectController.cs rename to Parse/Abstractions/Platform/Objects/IParseObjectController.cs index 9ee6c31c..a17bfd14 100644 --- a/Parse/Abstractions/Management/IParseObjectController.cs +++ b/Parse/Abstractions/Platform/Objects/IParseObjectController.cs @@ -3,9 +3,10 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Infrastructure; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Platform.Objects { public interface IParseObjectController { diff --git a/Parse/Abstractions/Management/IParseObjectCurrentController.cs b/Parse/Abstractions/Platform/Objects/IParseObjectCurrentController.cs similarity index 96% rename from Parse/Abstractions/Management/IParseObjectCurrentController.cs rename to Parse/Abstractions/Platform/Objects/IParseObjectCurrentController.cs index c6dd3cd0..55418651 100644 --- a/Parse/Abstractions/Management/IParseObjectCurrentController.cs +++ b/Parse/Abstractions/Platform/Objects/IParseObjectCurrentController.cs @@ -2,9 +2,9 @@ using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Platform.Objects { /// /// IParseObjectCurrentController controls the single-instance diff --git a/Parse/Abstractions/Platform/Notifications/IParsePushChannelsController.cs b/Parse/Abstractions/Platform/Push/IParsePushChannelsController.cs similarity index 89% rename from Parse/Abstractions/Platform/Notifications/IParsePushChannelsController.cs rename to Parse/Abstractions/Platform/Push/IParsePushChannelsController.cs index 7c426b5e..e24829b8 100644 --- a/Parse/Abstractions/Platform/Notifications/IParsePushChannelsController.cs +++ b/Parse/Abstractions/Platform/Push/IParsePushChannelsController.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; -namespace Parse.Push.Internal +namespace Parse.Abstractions.Platform.Push { public interface IParsePushChannelsController { diff --git a/Parse/Abstractions/Platform/Notifications/IParsePushController.cs b/Parse/Abstractions/Platform/Push/IParsePushController.cs similarity index 86% rename from Parse/Abstractions/Platform/Notifications/IParsePushController.cs rename to Parse/Abstractions/Platform/Push/IParsePushController.cs index 7e7208c6..c5c5e133 100644 --- a/Parse/Abstractions/Platform/Notifications/IParsePushController.cs +++ b/Parse/Abstractions/Platform/Push/IParsePushController.cs @@ -2,9 +2,9 @@ using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; -namespace Parse.Push.Internal +namespace Parse.Abstractions.Platform.Push { public interface IParsePushController { diff --git a/Parse.Core/Abstractions/Platform/Notifications/IPushState.cs b/Parse/Abstractions/Platform/Push/IPushState.cs similarity index 91% rename from Parse.Core/Abstractions/Platform/Notifications/IPushState.cs rename to Parse/Abstractions/Platform/Push/IPushState.cs index 4b8fd9b4..e62a16b0 100644 --- a/Parse.Core/Abstractions/Platform/Notifications/IPushState.cs +++ b/Parse/Abstractions/Platform/Push/IPushState.cs @@ -2,8 +2,9 @@ using System; using System.Collections.Generic; +using Parse.Platform.Push; -namespace Parse.Push.Internal +namespace Parse.Abstractions.Platform.Push { public interface IPushState { diff --git a/Parse/Abstractions/Query/IParseQueryController.cs b/Parse/Abstractions/Platform/Queries/IParseQueryController.cs similarity index 90% rename from Parse/Abstractions/Query/IParseQueryController.cs rename to Parse/Abstractions/Platform/Queries/IParseQueryController.cs index db7a5fe9..0e174fea 100644 --- a/Parse/Abstractions/Query/IParseQueryController.cs +++ b/Parse/Abstractions/Platform/Queries/IParseQueryController.cs @@ -3,8 +3,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Platform.Objects; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Platform.Queries { public interface IParseQueryController { diff --git a/Parse/Abstractions/Platform/Session/IParseSessionController.cs b/Parse/Abstractions/Platform/Sessions/IParseSessionController.cs similarity index 86% rename from Parse/Abstractions/Platform/Session/IParseSessionController.cs rename to Parse/Abstractions/Platform/Sessions/IParseSessionController.cs index 2b8f7af4..ab6016e0 100644 --- a/Parse/Abstractions/Platform/Session/IParseSessionController.cs +++ b/Parse/Abstractions/Platform/Sessions/IParseSessionController.cs @@ -2,9 +2,10 @@ using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Objects; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Platform.Sessions { public interface IParseSessionController { diff --git a/Parse/Abstractions/Management/IParseCurrentUserController.cs b/Parse/Abstractions/Platform/Users/IParseCurrentUserController.cs similarity index 83% rename from Parse/Abstractions/Management/IParseCurrentUserController.cs rename to Parse/Abstractions/Platform/Users/IParseCurrentUserController.cs index df9246d2..5b6b50a5 100644 --- a/Parse/Abstractions/Management/IParseCurrentUserController.cs +++ b/Parse/Abstractions/Platform/Users/IParseCurrentUserController.cs @@ -2,9 +2,10 @@ using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Objects; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Platform.Users { public interface IParseCurrentUserController : IParseObjectCurrentController { diff --git a/Parse/Abstractions/Management/IParseUserController.cs b/Parse/Abstractions/Platform/Users/IParseUserController.cs similarity index 87% rename from Parse/Abstractions/Management/IParseUserController.cs rename to Parse/Abstractions/Platform/Users/IParseUserController.cs index 0eed3e99..11cd0217 100644 --- a/Parse/Abstractions/Management/IParseUserController.cs +++ b/Parse/Abstractions/Platform/Users/IParseUserController.cs @@ -3,9 +3,11 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Objects; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Platform.Users { public interface IParseUserController { diff --git a/Parse/Library/CacheLocationMutator.cs b/Parse/Infrastructure/CacheLocationMutator.cs similarity index 85% rename from Parse/Library/CacheLocationMutator.cs rename to Parse/Infrastructure/CacheLocationMutator.cs index 8422ef8f..c8552759 100644 --- a/Parse/Library/CacheLocationMutator.cs +++ b/Parse/Infrastructure/CacheLocationMutator.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Storage; -using Parse.Common.Internal; -namespace Parse.Library +namespace Parse.Infrastructure { /// /// An for the relative storagecache location. diff --git a/Parse/Library/ConcurrentUserServiceHubCloner.cs b/Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs similarity index 79% rename from Parse/Library/ConcurrentUserServiceHubCloner.cs rename to Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs index 0c86553d..527a7d81 100644 --- a/Parse/Library/ConcurrentUserServiceHubCloner.cs +++ b/Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs @@ -1,12 +1,8 @@ -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Core.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Platform.Users; -namespace Parse.Library +namespace Parse.Infrastructure { public class ConcurrentUserServiceHubCloner : IServiceHubCloner, IServiceHubMutator { diff --git a/Parse/Management/Tracking/ParseAddOperation.cs b/Parse/Infrastructure/Control/ParseAddOperation.cs similarity index 89% rename from Parse/Management/Tracking/ParseAddOperation.cs rename to Parse/Infrastructure/Control/ParseAddOperation.cs index a4626233..b485bbe1 100644 --- a/Parse/Management/Tracking/ParseAddOperation.cs +++ b/Parse/Infrastructure/Control/ParseAddOperation.cs @@ -4,10 +4,12 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using Parse.Abstractions.Library; -using Parse.Utilities; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Infrastructure.Data; +using Parse.Infrastructure.Utilities; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Control { public class ParseAddOperation : IParseFieldOperation { diff --git a/Parse/Management/Tracking/ParseAddUniqueOperation.cs b/Parse/Infrastructure/Control/ParseAddUniqueOperation.cs similarity index 92% rename from Parse/Management/Tracking/ParseAddUniqueOperation.cs rename to Parse/Infrastructure/Control/ParseAddUniqueOperation.cs index 275ef98e..6d36bb47 100644 --- a/Parse/Management/Tracking/ParseAddUniqueOperation.cs +++ b/Parse/Infrastructure/Control/ParseAddUniqueOperation.cs @@ -4,10 +4,12 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using Parse.Abstractions.Library; -using Parse.Utilities; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Infrastructure.Data; +using Parse.Infrastructure.Utilities; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Control { public class ParseAddUniqueOperation : IParseFieldOperation { diff --git a/Parse/Management/Tracking/ParseDeleteOperation.cs b/Parse/Infrastructure/Control/ParseDeleteOperation.cs similarity index 88% rename from Parse/Management/Tracking/ParseDeleteOperation.cs rename to Parse/Infrastructure/Control/ParseDeleteOperation.cs index 03ca31a6..6a0feb0a 100644 --- a/Parse/Management/Tracking/ParseDeleteOperation.cs +++ b/Parse/Infrastructure/Control/ParseDeleteOperation.cs @@ -1,9 +1,10 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System.Collections.Generic; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Control; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Control { /// /// An operation where a field is deleted from the object. diff --git a/Parse/Management/Tracking/ParseFieldOperations.cs b/Parse/Infrastructure/Control/ParseFieldOperations.cs similarity index 94% rename from Parse/Management/Tracking/ParseFieldOperations.cs rename to Parse/Infrastructure/Control/ParseFieldOperations.cs index f410fb5e..593a02a3 100644 --- a/Parse/Management/Tracking/ParseFieldOperations.cs +++ b/Parse/Infrastructure/Control/ParseFieldOperations.cs @@ -2,8 +2,9 @@ using System; using System.Collections.Generic; +using Parse.Abstractions.Infrastructure.Control; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Control { public class ParseObjectIdComparer : IEqualityComparer { diff --git a/Parse/Management/Tracking/ParseIncrementOperation.cs b/Parse/Infrastructure/Control/ParseIncrementOperation.cs similarity index 98% rename from Parse/Management/Tracking/ParseIncrementOperation.cs rename to Parse/Infrastructure/Control/ParseIncrementOperation.cs index 0ba0eea0..eec00787 100644 --- a/Parse/Management/Tracking/ParseIncrementOperation.cs +++ b/Parse/Infrastructure/Control/ParseIncrementOperation.cs @@ -3,9 +3,10 @@ using System; using System.Collections.Generic; using System.Linq; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Control; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Control { public class ParseIncrementOperation : IParseFieldOperation { diff --git a/Parse/Management/Tracking/ParseRelationOperation.cs b/Parse/Infrastructure/Control/ParseRelationOperation.cs similarity index 96% rename from Parse/Management/Tracking/ParseRelationOperation.cs rename to Parse/Infrastructure/Control/ParseRelationOperation.cs index 43af3492..43915d77 100644 --- a/Parse/Management/Tracking/ParseRelationOperation.cs +++ b/Parse/Infrastructure/Control/ParseRelationOperation.cs @@ -4,9 +4,12 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure.Data; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Control { public class ParseRelationOperation : IParseFieldOperation { diff --git a/Parse/Management/Tracking/ParseRemoveOperation.cs b/Parse/Infrastructure/Control/ParseRemoveOperation.cs similarity index 89% rename from Parse/Management/Tracking/ParseRemoveOperation.cs rename to Parse/Infrastructure/Control/ParseRemoveOperation.cs index df62e9b7..17987e0c 100644 --- a/Parse/Management/Tracking/ParseRemoveOperation.cs +++ b/Parse/Infrastructure/Control/ParseRemoveOperation.cs @@ -4,10 +4,12 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using Parse.Abstractions.Library; -using Parse.Utilities; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Infrastructure.Data; +using Parse.Infrastructure.Utilities; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Control { public class ParseRemoveOperation : IParseFieldOperation { diff --git a/Parse/Management/Tracking/ParseSetOperation.cs b/Parse/Infrastructure/Control/ParseSetOperation.cs similarity index 82% rename from Parse/Management/Tracking/ParseSetOperation.cs rename to Parse/Infrastructure/Control/ParseSetOperation.cs index bf41eafc..30c27a97 100644 --- a/Parse/Management/Tracking/ParseSetOperation.cs +++ b/Parse/Infrastructure/Control/ParseSetOperation.cs @@ -1,8 +1,10 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Infrastructure.Data; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Control { public class ParseSetOperation : IParseFieldOperation { diff --git a/Parse/Utilities/Encoding/NoObjectsEncoder.cs b/Parse/Infrastructure/Data/NoObjectsEncoder.cs similarity index 96% rename from Parse/Utilities/Encoding/NoObjectsEncoder.cs rename to Parse/Infrastructure/Data/NoObjectsEncoder.cs index 55a62c99..30215c3a 100644 --- a/Parse/Utilities/Encoding/NoObjectsEncoder.cs +++ b/Parse/Infrastructure/Data/NoObjectsEncoder.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Data { // This class isn't really a Singleton, but since it has no state, it's more efficient to get the default instance. diff --git a/Parse/Utilities/Encoding/ParseDataDecoder.cs b/Parse/Infrastructure/Data/ParseDataDecoder.cs similarity index 92% rename from Parse/Utilities/Encoding/ParseDataDecoder.cs rename to Parse/Infrastructure/Data/ParseDataDecoder.cs index cb8f1786..7be73093 100644 --- a/Parse/Utilities/Encoding/ParseDataDecoder.cs +++ b/Parse/Infrastructure/Data/ParseDataDecoder.cs @@ -4,10 +4,13 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using Parse.Abstractions.Library; -using Parse.Utilities; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure.Control; +using Parse.Infrastructure.Utilities; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Data { public class ParseDataDecoder : IParseDataDecoder { diff --git a/Parse/Utilities/Encoding/ParseDataEncoder.cs b/Parse/Infrastructure/Data/ParseDataEncoder.cs similarity index 95% rename from Parse/Utilities/Encoding/ParseDataEncoder.cs rename to Parse/Infrastructure/Data/ParseDataEncoder.cs index 96f1a0be..8cee8060 100644 --- a/Parse/Utilities/Encoding/ParseDataEncoder.cs +++ b/Parse/Infrastructure/Data/ParseDataEncoder.cs @@ -4,11 +4,11 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Utilities; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Infrastructure.Utilities; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Data { /// /// A ParseEncoder can be used to transform objects such as into JSON diff --git a/Parse/Utilities/Encoding/ParseObjectCoder.cs b/Parse/Infrastructure/Data/ParseObjectCoder.cs similarity index 92% rename from Parse/Utilities/Encoding/ParseObjectCoder.cs rename to Parse/Infrastructure/Data/ParseObjectCoder.cs index 835dc760..8dedd347 100644 --- a/Parse/Utilities/Encoding/ParseObjectCoder.cs +++ b/Parse/Infrastructure/Data/ParseObjectCoder.cs @@ -2,9 +2,13 @@ using System; using System.Collections.Generic; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Platform.Objects; +using Parse.Platform.Objects; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Data { // TODO: (richardross) refactor entire parse coder interfaces. diff --git a/Parse/Utilities/Encoding/PointerOrLocalIdEncoder.cs b/Parse/Infrastructure/Data/PointerOrLocalIdEncoder.cs similarity index 97% rename from Parse/Utilities/Encoding/PointerOrLocalIdEncoder.cs rename to Parse/Infrastructure/Data/PointerOrLocalIdEncoder.cs index 37cd8208..d6a81d1d 100644 --- a/Parse/Utilities/Encoding/PointerOrLocalIdEncoder.cs +++ b/Parse/Infrastructure/Data/PointerOrLocalIdEncoder.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Data { /// /// A that encodes as pointers. If the object does not have an , uses a local id. diff --git a/Parse/Abstractions/Library/IDataTransferLevel.cs b/Parse/Infrastructure/DataTransferLevel.cs similarity index 85% rename from Parse/Abstractions/Library/IDataTransferLevel.cs rename to Parse/Infrastructure/DataTransferLevel.cs index bb652e16..6dfcf1dd 100644 --- a/Parse/Abstractions/Library/IDataTransferLevel.cs +++ b/Parse/Infrastructure/DataTransferLevel.cs @@ -1,14 +1,10 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; +using Parse.Abstractions.Infrastructure; -namespace Parse +namespace Parse.Infrastructure { - public interface IDataTransferLevel - { - double Amount { get; set; } - } - /// /// Represents upload progress. /// diff --git a/Parse/Library/EnvironmentData.cs b/Parse/Infrastructure/EnvironmentData.cs similarity index 91% rename from Parse/Library/EnvironmentData.cs rename to Parse/Infrastructure/EnvironmentData.cs index 19dcbc64..3ec2061d 100644 --- a/Parse/Library/EnvironmentData.cs +++ b/Parse/Infrastructure/EnvironmentData.cs @@ -1,10 +1,8 @@ using System; -using System.Collections.Generic; using System.Runtime.InteropServices; -using System.Text; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; -namespace Parse.Library +namespace Parse.Infrastructure { /// /// Inferred data about the environment in which parse is operating. diff --git a/Parse/Management/ParseCommand.cs b/Parse/Infrastructure/Execution/ParseCommand.cs similarity index 93% rename from Parse/Management/ParseCommand.cs rename to Parse/Infrastructure/Execution/ParseCommand.cs index 0ab81e84..705b2e1e 100644 --- a/Parse/Management/ParseCommand.cs +++ b/Parse/Infrastructure/Execution/ParseCommand.cs @@ -5,9 +5,9 @@ using System.IO; using System.Linq; using System.Text; -using Parse.Common.Internal; +using Parse.Infrastructure.Utilities; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Execution { /// /// ParseCommand is an with pre-populated @@ -19,7 +19,7 @@ public class ParseCommand : WebRequest public override Stream Data { - get => base.Data ??= DataObject is { } ? new MemoryStream(Encoding.UTF8.GetBytes(Json.Encode(DataObject))) : default; + get => base.Data ??= DataObject is { } ? new MemoryStream(Encoding.UTF8.GetBytes(JsonUtilities.Encode(DataObject))) : default; set => base.Data = value; } diff --git a/Parse/Management/ParseCommandRunner.cs b/Parse/Infrastructure/Execution/ParseCommandRunner.cs similarity index 92% rename from Parse/Management/ParseCommandRunner.cs rename to Parse/Infrastructure/Execution/ParseCommandRunner.cs index a9d9f408..1af00f9a 100644 --- a/Parse/Management/ParseCommandRunner.cs +++ b/Parse/Infrastructure/Execution/ParseCommandRunner.cs @@ -1,16 +1,17 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System; -using System.Linq; using System.Collections.Generic; using System.Net; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Library; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Installations; +using Parse.Abstractions.Platform.Users; +using Parse.Infrastructure.Utilities; -namespace Parse.Core.Internal +namespace Parse.Infrastructure.Execution { /// /// The command runner for all SDK operations that need to interact with the targeted deployment of Parse Server. @@ -18,11 +19,17 @@ namespace Parse.Core.Internal public class ParseCommandRunner : IParseCommandRunner { IWebClient WebClient { get; } + IParseInstallationController InstallationController { get; } + IMetadataController MetadataController { get; } + IServerConnectionData ServerConnectionData { get; } + Lazy UserController { get; } + IWebClient GetWebClient() => WebClient; + /// /// Creates a new Parse SDK command runner. /// @@ -45,7 +52,7 @@ public ParseCommandRunner(IWebClient webClient, IParseInstallationController ins /// An instance to push download progress data to. /// An asynchronous operation cancellation token that dictates if and when the operation should be cancelled. /// - public Task>> RunCommandAsync(ParseCommand command, IProgress uploadProgress = null, IProgress downloadProgress = null, CancellationToken cancellationToken = default) => PrepareCommand(command).ContinueWith(commandTask => WebClient.ExecuteAsync(commandTask.Result, uploadProgress, downloadProgress, cancellationToken).OnSuccess(task => + public Task>> RunCommandAsync(ParseCommand command, IProgress uploadProgress = null, IProgress downloadProgress = null, CancellationToken cancellationToken = default) => PrepareCommand(command).ContinueWith(commandTask => GetWebClient().ExecuteAsync(commandTask.Result, uploadProgress, downloadProgress, cancellationToken).OnSuccess(task => { cancellationToken.ThrowIfCancellationRequested(); @@ -67,7 +74,7 @@ public Task>> RunCommandAsync( { // TODO: Newer versions of Parse Server send the failure results back as HTML. - contentJson = content.StartsWith("[") ? new Dictionary { ["results"] = Json.Parse(content) } : Json.Parse(content) as IDictionary; + contentJson = content.StartsWith("[") ? new Dictionary { ["results"] = JsonUtilities.Parse(content) } : JsonUtilities.Parse(content) as IDictionary; } catch (Exception e) { diff --git a/Parse/Utilities/UniversalWebClient.cs b/Parse/Infrastructure/Execution/UniversalWebClient.cs similarity index 97% rename from Parse/Utilities/UniversalWebClient.cs rename to Parse/Infrastructure/Execution/UniversalWebClient.cs index 2c38aa41..30f07a69 100644 --- a/Parse/Utilities/UniversalWebClient.cs +++ b/Parse/Infrastructure/Execution/UniversalWebClient.cs @@ -8,9 +8,12 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Infrastructure.Utilities; using BCLWebClient = System.Net.Http.HttpClient; -namespace Parse.Common.Internal +namespace Parse.Infrastructure.Execution { /// /// A universal implementation of . diff --git a/Parse/Utilities/WebRequest.cs b/Parse/Infrastructure/Execution/WebRequest.cs similarity index 96% rename from Parse/Utilities/WebRequest.cs rename to Parse/Infrastructure/Execution/WebRequest.cs index 61ce2a70..e4a3b059 100644 --- a/Parse/Utilities/WebRequest.cs +++ b/Parse/Infrastructure/Execution/WebRequest.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.IO; -namespace Parse.Common.Internal +namespace Parse.Infrastructure.Execution { /// /// IHttpRequest is an interface that provides an API to execute HTTP request data. diff --git a/Parse/Library/HostManifestData.cs b/Parse/Infrastructure/HostManifestData.cs similarity index 97% rename from Parse/Library/HostManifestData.cs rename to Parse/Infrastructure/HostManifestData.cs index ce43fda8..2eb27961 100644 --- a/Parse/Library/HostManifestData.cs +++ b/Parse/Infrastructure/HostManifestData.cs @@ -1,9 +1,8 @@ using System; using System.Reflection; -using Parse.Abstractions.Library; -using Parse.Storage; +using Parse.Abstractions.Infrastructure; -namespace Parse.Library +namespace Parse.Infrastructure { /// /// In the event that you would like to use the Parse SDK diff --git a/Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs b/Parse/Infrastructure/IdentifierBasedCacheLocationConfiguration.cs similarity index 95% rename from Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs rename to Parse/Infrastructure/IdentifierBasedCacheLocationConfiguration.cs index f275ac48..9e188e2e 100644 --- a/Parse/Storage/IdentifierBasedCacheLocationConfiguration.cs +++ b/Parse/Infrastructure/IdentifierBasedCacheLocationConfiguration.cs @@ -1,10 +1,9 @@ using System; using System.IO; -using Parse.Abstractions.Library; -using Parse.Abstractions.Management; +using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Storage; -namespace Parse.Storage +namespace Parse.Infrastructure { /// /// A configuration of the Parse SDK persistent storage location based on an identifier. diff --git a/Parse/Storage/MetadataBasedCacheLocationConfiguration.cs b/Parse/Infrastructure/MetadataBasedCacheLocationConfiguration.cs similarity index 93% rename from Parse/Storage/MetadataBasedCacheLocationConfiguration.cs rename to Parse/Infrastructure/MetadataBasedCacheLocationConfiguration.cs index ee3e27ad..2e16b8f6 100644 --- a/Parse/Storage/MetadataBasedCacheLocationConfiguration.cs +++ b/Parse/Infrastructure/MetadataBasedCacheLocationConfiguration.cs @@ -1,12 +1,9 @@ -using System; using System.IO; using System.Reflection; -using Parse.Abstractions.Library; -using Parse.Abstractions.Management; +using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Storage; -using Parse.Library; -namespace Parse.Storage +namespace Parse.Infrastructure { /// /// A configuration of the Parse SDK persistent storage location based on product metadata such as company name and product name. diff --git a/Parse/Library/MetadataController.cs b/Parse/Infrastructure/MetadataController.cs similarity index 76% rename from Parse/Library/MetadataController.cs rename to Parse/Infrastructure/MetadataController.cs index a5fc1a42..6d8b83b3 100644 --- a/Parse/Library/MetadataController.cs +++ b/Parse/Infrastructure/MetadataController.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; -namespace Parse.Library +namespace Parse.Infrastructure { public class MetadataController : IMetadataController { diff --git a/Parse/Library/MetadataMutator.cs b/Parse/Infrastructure/MetadataMutator.cs similarity index 86% rename from Parse/Library/MetadataMutator.cs rename to Parse/Infrastructure/MetadataMutator.cs index 6b7adb6a..351f779b 100644 --- a/Parse/Library/MetadataMutator.cs +++ b/Parse/Infrastructure/MetadataMutator.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Parse.Abstractions.Library; -using Parse.Abstractions.Storage; +using Parse.Abstractions.Infrastructure; -namespace Parse.Library +namespace Parse.Infrastructure { /// /// An for setting metadata information manually. diff --git a/Parse/Library/MutableServiceHub.cs b/Parse/Infrastructure/MutableServiceHub.cs similarity index 81% rename from Parse/Library/MutableServiceHub.cs rename to Parse/Infrastructure/MutableServiceHub.cs index 51651ef1..f01ad617 100644 --- a/Parse/Library/MutableServiceHub.cs +++ b/Parse/Infrastructure/MutableServiceHub.cs @@ -1,11 +1,31 @@ using System; -using Parse.Abstractions.Library; -using Parse.Analytics.Internal; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Push.Internal; - -namespace Parse.Library +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Analytics; +using Parse.Abstractions.Platform.Cloud; +using Parse.Abstractions.Platform.Configuration; +using Parse.Abstractions.Platform.Files; +using Parse.Abstractions.Platform.Installations; +using Parse.Abstractions.Platform.Objects; +using Parse.Abstractions.Platform.Push; +using Parse.Abstractions.Platform.Queries; +using Parse.Abstractions.Platform.Sessions; +using Parse.Abstractions.Platform.Users; +using Parse.Infrastructure.Data; +using Parse.Infrastructure.Execution; +using Parse.Platform.Analytics; +using Parse.Platform.Cloud; +using Parse.Platform.Configuration; +using Parse.Platform.Files; +using Parse.Platform.Installations; +using Parse.Platform.Objects; +using Parse.Platform.Push; +using Parse.Platform.Queries; +using Parse.Platform.Sessions; +using Parse.Platform.Users; + +namespace Parse.Infrastructure { /// /// A service hub that is mutable. diff --git a/Parse/Library/OrchestrationServiceHub.cs b/Parse/Infrastructure/OrchestrationServiceHub.cs similarity index 82% rename from Parse/Library/OrchestrationServiceHub.cs rename to Parse/Infrastructure/OrchestrationServiceHub.cs index 51224d3f..2ac8d29e 100644 --- a/Parse/Library/OrchestrationServiceHub.cs +++ b/Parse/Infrastructure/OrchestrationServiceHub.cs @@ -1,13 +1,18 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Parse.Abstractions.Library; -using Parse.Analytics.Internal; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Push.Internal; - -namespace Parse.Library +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Analytics; +using Parse.Abstractions.Platform.Cloud; +using Parse.Abstractions.Platform.Configuration; +using Parse.Abstractions.Platform.Files; +using Parse.Abstractions.Platform.Installations; +using Parse.Abstractions.Platform.Objects; +using Parse.Abstractions.Platform.Push; +using Parse.Abstractions.Platform.Queries; +using Parse.Abstractions.Platform.Sessions; +using Parse.Abstractions.Platform.Users; + +namespace Parse.Infrastructure { public class OrchestrationServiceHub : IServiceHub { diff --git a/Parse/Utilities/ParseClassNameAttribute.cs b/Parse/Infrastructure/ParseClassNameAttribute.cs similarity index 100% rename from Parse/Utilities/ParseClassNameAttribute.cs rename to Parse/Infrastructure/ParseClassNameAttribute.cs diff --git a/Parse/Library/ParseFailureException.cs b/Parse/Infrastructure/ParseFailureException.cs similarity index 99% rename from Parse/Library/ParseFailureException.cs rename to Parse/Infrastructure/ParseFailureException.cs index 32e3d1ba..57389a0b 100644 --- a/Parse/Library/ParseFailureException.cs +++ b/Parse/Infrastructure/ParseFailureException.cs @@ -2,7 +2,7 @@ using System; -namespace Parse +namespace Parse.Infrastructure { /// /// Exceptions that may occur when sending requests to Parse. diff --git a/Parse/Utilities/ParseFieldNameAttribute.cs b/Parse/Infrastructure/ParseFieldNameAttribute.cs similarity index 100% rename from Parse/Utilities/ParseFieldNameAttribute.cs rename to Parse/Infrastructure/ParseFieldNameAttribute.cs diff --git a/Parse/Library/ServerConnectionData.cs b/Parse/Infrastructure/ServerConnectionData.cs similarity index 93% rename from Parse/Library/ServerConnectionData.cs rename to Parse/Infrastructure/ServerConnectionData.cs index cdcd49cb..6cedaccc 100644 --- a/Parse/Library/ServerConnectionData.cs +++ b/Parse/Infrastructure/ServerConnectionData.cs @@ -1,9 +1,7 @@ -using System; using System.Collections.Generic; -using System.Text; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; -namespace Parse.Library +namespace Parse.Infrastructure { /// /// Represents the configuration of the Parse SDK. diff --git a/Parse/Library/ServiceHub.cs b/Parse/Infrastructure/ServiceHub.cs similarity index 79% rename from Parse/Library/ServiceHub.cs rename to Parse/Infrastructure/ServiceHub.cs index 828c78fe..cec0e064 100644 --- a/Parse/Library/ServiceHub.cs +++ b/Parse/Infrastructure/ServiceHub.cs @@ -1,14 +1,32 @@ using System; -using System.Linq; -using System.Text; -using Parse.Abstractions.Library; -using Parse.Analytics.Internal; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Library.Utilities; -using Parse.Push.Internal; - -namespace Parse.Library +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Analytics; +using Parse.Abstractions.Platform.Cloud; +using Parse.Abstractions.Platform.Configuration; +using Parse.Abstractions.Platform.Files; +using Parse.Abstractions.Platform.Installations; +using Parse.Abstractions.Platform.Objects; +using Parse.Abstractions.Platform.Push; +using Parse.Abstractions.Platform.Queries; +using Parse.Abstractions.Platform.Sessions; +using Parse.Abstractions.Platform.Users; +using Parse.Infrastructure.Data; +using Parse.Infrastructure.Execution; +using Parse.Infrastructure.Utilities; +using Parse.Platform.Analytics; +using Parse.Platform.Cloud; +using Parse.Platform.Configuration; +using Parse.Platform.Files; +using Parse.Platform.Installations; +using Parse.Platform.Objects; +using Parse.Platform.Push; +using Parse.Platform.Queries; +using Parse.Platform.Sessions; +using Parse.Platform.Users; + +namespace Parse.Infrastructure { /// diff --git a/Parse/Storage/StorageController.cs b/Parse/Infrastructure/StorageController.cs similarity index 95% rename from Parse/Storage/StorageController.cs rename to Parse/Infrastructure/StorageController.cs index f6a052d5..9f3d4104 100644 --- a/Parse/Storage/StorageController.cs +++ b/Parse/Infrastructure/StorageController.cs @@ -6,14 +6,11 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Parse.Core.Internal; -using Parse.Internal.Utilities; -using Parse.Library; -using Parse.Management; -using Parse.Storage; -using static Parse.Properties.Resources; - -namespace Parse.Common.Internal +using Parse.Abstractions.Infrastructure; +using Parse.Infrastructure.Utilities; +using static Parse.Resources; + +namespace Parse.Infrastructure { public class ConcurrentUserStorageController : IStorageController { @@ -43,9 +40,7 @@ public Task RemoveAsync(string key) public Task> SaveAsync(IDictionary contents) { foreach (KeyValuePair pair in contents) - { - ((IDictionary)Storage).Add(pair); - } + ((IDictionary) Storage).Add(pair); return Task.FromResult>(Storage); } @@ -62,21 +57,19 @@ class StorageDictionary : IStorageDictionary { public StorageDictionary(FileInfo file) => File = file; - internal Task SaveAsync() => Lock(() => File.WriteContentAsync(Json.Encode(Storage))); + internal Task SaveAsync() => Lock(() => File.WriteContentAsync(JsonUtilities.Encode(Storage))); internal Task LoadAsync() => File.ReadAllTextAsync().ContinueWith(task => { lock (Mutex) - { try { - Storage = Json.Parse(task.Result) as Dictionary; + Storage = JsonUtilities.Parse(task.Result) as Dictionary; } catch { Storage = new Dictionary { }; } - } }); // TODO: Check if the call to ToDictionary is necessary here considering contents is IDictionary. @@ -114,9 +107,7 @@ public Task RemoveAsync(string key) public bool TryGetValue(string key, out object value) { lock (Mutex) - { return (Result: Storage.TryGetValue(key, out object found), value = found).Result; - } } public void Clear() => Lock(() => Storage.Clear()); @@ -138,17 +129,13 @@ public bool TryGetValue(string key, out object value) TResult Lock(Func operation) { lock (Mutex) - { return operation.Invoke(); - } } void Lock(Action operation) { lock (Mutex) - { operation.Invoke(); - } } ICollection> Elements => Storage as ICollection>; @@ -214,9 +201,7 @@ public Task> SaveAsync(IDictionary diff --git a/Parse/Utilities/AssemblyLister.cs b/Parse/Infrastructure/Utilities/AssemblyLister.cs similarity index 95% rename from Parse/Utilities/AssemblyLister.cs rename to Parse/Infrastructure/Utilities/AssemblyLister.cs index 49be336d..c3105903 100644 --- a/Parse/Utilities/AssemblyLister.cs +++ b/Parse/Infrastructure/Utilities/AssemblyLister.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -namespace AssemblyLister +namespace Parse.Infrastructure.Utilities { /// /// A class that lets you list all loaded assemblies in a PCL-compliant way. @@ -28,9 +28,7 @@ private static IEnumerable DeepWalkReferences(this Assembly assembly, seen ??= new HashSet(); if (!seen.Add(assembly.FullName)) - { return Enumerable.Empty(); - } List assemblies = new List { assembly }; diff --git a/Parse/Utilities/Conversion.cs b/Parse/Infrastructure/Utilities/Conversion.cs similarity index 91% rename from Parse/Utilities/Conversion.cs rename to Parse/Infrastructure/Utilities/Conversion.cs index ce2319d3..fc870831 100644 --- a/Parse/Utilities/Conversion.cs +++ b/Parse/Infrastructure/Utilities/Conversion.cs @@ -2,9 +2,8 @@ using System; using System.Collections.Generic; -using Parse.Common.Internal; -namespace Parse.Utilities +namespace Parse.Infrastructure.Utilities { #warning Possibly should be refactored. @@ -50,33 +49,23 @@ public static class Conversion internal static object ConvertTo(object value) { if (value is T || value == null) - { return value; - } if (typeof(T).IsPrimitive) - { return (T) Convert.ChangeType(value, typeof(T), System.Globalization.CultureInfo.InvariantCulture); - } if (typeof(T).IsConstructedGenericType) { // Add lifting for nullables. Only supports conversions between primitives. - if (ReflectionHelpers.CheckWrappedWithNullable(typeof(T)) && typeof(T).GenericTypeArguments[0] is { IsPrimitive: true } innerType) - { + if (typeof(T).CheckWrappedWithNullable() && typeof(T).GenericTypeArguments[0] is { IsPrimitive: true } innerType) return (T) Convert.ChangeType(value, innerType, System.Globalization.CultureInfo.InvariantCulture); - } if (GetInterfaceType(value.GetType(), typeof(IList<>)) is { } listType && typeof(T).GetGenericTypeDefinition() == typeof(IList<>)) - { return Activator.CreateInstance(typeof(FlexibleListWrapper<,>).MakeGenericType(typeof(T).GenericTypeArguments[0], listType.GenericTypeArguments[0]), value); - } if (GetInterfaceType(value.GetType(), typeof(IDictionary<,>)) is { } dictType && typeof(T).GetGenericTypeDefinition() == typeof(IDictionary<,>)) - { return Activator.CreateInstance(typeof(FlexibleDictionaryWrapper<,>).MakeGenericType(typeof(T).GenericTypeArguments[1], dictType.GenericTypeArguments[1]), value); - } } return value; @@ -97,17 +86,11 @@ static Type GetInterfaceType(Type objType, Type genericInterfaceType) Tuple cacheKey = new Tuple(objType, genericInterfaceType); if (InterfaceLookupCache.ContainsKey(cacheKey)) - { return InterfaceLookupCache[cacheKey]; - } foreach (Type type in objType.GetInterfaces()) - { if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == genericInterfaceType) - { return InterfaceLookupCache[cacheKey] = type; - } - } return default; } diff --git a/Parse/Utilities/StorageManager.cs b/Parse/Infrastructure/Utilities/FileUtilities.cs similarity index 95% rename from Parse/Utilities/StorageManager.cs rename to Parse/Infrastructure/Utilities/FileUtilities.cs index d99f217c..43280b2b 100644 --- a/Parse/Utilities/StorageManager.cs +++ b/Parse/Infrastructure/Utilities/FileUtilities.cs @@ -1,14 +1,13 @@ -using System; using System.IO; using System.Text; using System.Threading.Tasks; -namespace Parse.Internal.Utilities +namespace Parse.Infrastructure.Utilities { /// /// A collection of utility methods and properties for writing to the app-specific persistent storage folder. /// - internal static class StorageManager + internal static class FileUtilities { /// /// Asynchronously read all of the little-endian 16-bit character units (UTF-16) contained within the file wrapped by the provided instance. diff --git a/Parse/Utilities/FlexibleDictionaryWrapper.cs b/Parse/Infrastructure/Utilities/FlexibleDictionaryWrapper.cs similarity index 97% rename from Parse/Utilities/FlexibleDictionaryWrapper.cs rename to Parse/Infrastructure/Utilities/FlexibleDictionaryWrapper.cs index 284aabe4..8f644275 100644 --- a/Parse/Utilities/FlexibleDictionaryWrapper.cs +++ b/Parse/Infrastructure/Utilities/FlexibleDictionaryWrapper.cs @@ -2,9 +2,8 @@ using System.Collections.Generic; using System.Linq; -using Parse.Utilities; -namespace Parse.Common.Internal +namespace Parse.Infrastructure.Utilities { /// /// Provides a Dictionary implementation that can delegate to any other @@ -69,10 +68,8 @@ public bool Remove(KeyValuePair item) => toWrap.Remove(new KeyValu public IEnumerator> GetEnumerator() { foreach (KeyValuePair pair in toWrap) - { yield return new KeyValuePair(pair.Key, (TOut) Conversion.ConvertTo(pair.Value)); - } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/Parse/Utilities/FlexibleListWrapper.cs b/Parse/Infrastructure/Utilities/FlexibleListWrapper.cs similarity index 96% rename from Parse/Utilities/FlexibleListWrapper.cs rename to Parse/Infrastructure/Utilities/FlexibleListWrapper.cs index 800fb652..1a93d348 100644 --- a/Parse/Utilities/FlexibleListWrapper.cs +++ b/Parse/Infrastructure/Utilities/FlexibleListWrapper.cs @@ -3,9 +3,8 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using Parse.Utilities; -namespace Parse.Common.Internal +namespace Parse.Infrastructure.Utilities { /// /// Provides a List implementation that can delegate to any other @@ -50,9 +49,7 @@ public void CopyTo(TOut[] array, int arrayIndex) => toWrap.Select(item => (TOut) public IEnumerator GetEnumerator() { foreach (object item in (IEnumerable) toWrap) - { yield return (TOut) Conversion.ConvertTo(item); - } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/Parse/Utilities/IdentityEqualityComparer.cs b/Parse/Infrastructure/Utilities/IdentityEqualityComparer.cs similarity index 95% rename from Parse/Utilities/IdentityEqualityComparer.cs rename to Parse/Infrastructure/Utilities/IdentityEqualityComparer.cs index 58e433b3..6bbbc0a6 100644 --- a/Parse/Utilities/IdentityEqualityComparer.cs +++ b/Parse/Infrastructure/Utilities/IdentityEqualityComparer.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace Parse.Common.Internal +namespace Parse.Infrastructure.Utilities { /// /// An equality comparer that uses the object identity (i.e. ReferenceEquals) diff --git a/Parse/Utilities/InternalExtensions.cs b/Parse/Infrastructure/Utilities/InternalExtensions.cs similarity index 91% rename from Parse/Utilities/InternalExtensions.cs rename to Parse/Infrastructure/Utilities/InternalExtensions.cs index 27e57f15..164f2d13 100644 --- a/Parse/Utilities/InternalExtensions.cs +++ b/Parse/Infrastructure/Utilities/InternalExtensions.cs @@ -6,7 +6,7 @@ using System.Runtime.ExceptionServices; using System.Threading.Tasks; -namespace Parse.Common.Internal +namespace Parse.Infrastructure.Utilities { /// /// Provides helper methods that allow us to use terser code elsewhere. @@ -35,9 +35,7 @@ public static TValue GetOrDefault(this IDictionary s TValue defaultValue) { if (self.TryGetValue(key, out TValue value)) - { return value; - } return defaultValue; } @@ -59,13 +57,9 @@ public static Task OnSuccess(this Task task, Func OnSuccess(this Task task, Func continuation) => task.OnSuccess((Func) (t => @@ -93,9 +85,7 @@ public static Task WhileAsync(Func> predicate, Func body) iterate = () => predicate().OnSuccess(t => { if (!t.Result) - { return Task.FromResult(0); - } return body().OnSuccess(_ => iterate()).Unwrap(); }).Unwrap(); return iterate(); diff --git a/Parse/Utilities/Json.cs b/Parse/Infrastructure/Utilities/JsonUtilities.cs similarity index 94% rename from Parse/Utilities/Json.cs rename to Parse/Infrastructure/Utilities/JsonUtilities.cs index 21ffd1f7..40c8da3c 100644 --- a/Parse/Utilities/Json.cs +++ b/Parse/Infrastructure/Utilities/JsonUtilities.cs @@ -7,13 +7,13 @@ using System.Text; using System.Text.RegularExpressions; -namespace Parse.Common.Internal +namespace Parse.Infrastructure.Utilities { /// /// 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 + public class JsonUtilities { /// /// Place at the start of a regex to force the match to begin wherever the search starts (i.e. @@ -58,29 +58,21 @@ internal bool ParseObject(out object output) output = null; int initialCurrentIndex = CurrentIndex; if (!Accept(startObject)) - { return false; - } Dictionary dict = new Dictionary { }; while (true) { if (!ParseMember(out object pairValue)) - { break; - } Tuple pair = pairValue as Tuple; dict[pair.Item1] = pair.Item2; if (!Accept(valueSeparator)) - { break; - } } if (!Accept(endObject)) - { return false; - } output = dict; return true; } @@ -92,17 +84,11 @@ private bool ParseMember(out object output) { output = null; if (!ParseString(out object key)) - { return false; - } if (!Accept(nameSeparator)) - { return false; - } if (!ParseValue(out object value)) - { return false; - } output = new Tuple((string) key, value); return true; } @@ -114,26 +100,18 @@ internal bool ParseArray(out object output) { output = null; if (!Accept(startArray)) - { return false; - } List list = new List(); while (true) { if (!ParseValue(out object value)) - { break; - } list.Add(value); if (!Accept(valueSeparator)) - { break; - } } if (!Accept(endArray)) - { return false; - } output = list; return true; } @@ -172,9 +150,7 @@ private bool ParseString(out object output) { output = null; if (!Accept(stringValue, out Match m)) - { return false; - } // handle escapes: int offset = 0; Group contentCapture = m.Groups["content"]; @@ -229,9 +205,7 @@ private bool ParseNumber(out object output) { output = null; if (!Accept(numberValue, out Match m)) - { return false; - } if (m.Groups["frac"].Length > 0 || m.Groups["exp"].Length > 0) { // It's a double. @@ -252,9 +226,7 @@ private bool Accept(Regex matcher, out Match match) { match = matcher.Match(Input, CurrentIndex); if (match.Success) - { Skip(match.Length); - } return match.Success; } @@ -324,19 +296,15 @@ private bool Accept(char[] condition) 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; } } @@ -354,9 +322,7 @@ public static object Parse(string input) if ((parser.ParseObject(out object output) || parser.ParseArray(out output)) && parser.CurrentIndex == input.Length) - { return output; - } throw new ArgumentException("Input JSON was invalid."); } @@ -368,13 +334,9 @@ public static object Parse(string input) public static string Encode(IDictionary dict) { if (dict == null) - { throw new ArgumentNullException(); - } if (dict.Count == 0) - { return "{}"; - } StringBuilder builder = new StringBuilder("{"); foreach (KeyValuePair pair in dict) { @@ -395,13 +357,9 @@ public static string Encode(IDictionary dict) public static string Encode(IList list) { if (list == null) - { throw new ArgumentNullException(); - } if (list.Count == 0) - { return "[]"; - } StringBuilder builder = new StringBuilder("["); foreach (object item in list) { @@ -418,13 +376,9 @@ public static string Encode(IList list) public static string Encode(object obj) { if (obj is IDictionary dict) - { return Encode(dict); - } if (obj is IList list) - { return Encode(list); - } if (obj is string str) { str = escapePattern.Replace(str, m => @@ -452,17 +406,11 @@ public static string Encode(object obj) return "\"" + str + "\""; } if (obj is null) - { return "null"; - } if (obj is bool) - { return (bool) obj ? "true" : "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/Parse/Library/Utilities/LateInitializer.cs b/Parse/Infrastructure/Utilities/LateInitializer.cs similarity index 91% rename from Parse/Library/Utilities/LateInitializer.cs rename to Parse/Infrastructure/Utilities/LateInitializer.cs index c50d22ff..68faf9b2 100644 --- a/Parse/Library/Utilities/LateInitializer.cs +++ b/Parse/Infrastructure/Utilities/LateInitializer.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace Parse.Library.Utilities +namespace Parse.Infrastructure.Utilities { /// /// A wrapper over a dictionary from value generator to value. Uses the fact that lambda expressions in a specific location are cached, so the cost of instantiating a generator delegate is only incurred once at the call site of and subsequent calls look up the result of the first generation from the dictionary based on the hash of the generator delegate. This is effectively a lazy initialization mechanism that allows the member type to remain unchanged. @@ -14,11 +14,8 @@ public class LateInitializer public TData GetValue(Func generator) { lock (generator) - { if (Storage.Value.TryGetValue(generator as Func, out object data)) - { return (TData) data; - } else { TData result = generator.Invoke(); @@ -26,22 +23,17 @@ public TData GetValue(Func generator) Storage.Value.Add(generator as Func, result); return result; } - } } public bool ClearValue() { lock (Storage) - { if (Storage.IsValueCreated && Storage.Value.Keys.OfType>().FirstOrDefault() is { } key) - { lock (key) { Storage.Value.Remove(key as Func); return true; } - } - } return false; } @@ -49,13 +41,11 @@ public bool ClearValue() public bool Reset() { lock (Storage) - { if (Storage.IsValueCreated) { Storage.Value.Clear(); return true; } - } return false; } diff --git a/Parse/Utilities/LockSet.cs b/Parse/Infrastructure/Utilities/LockSet.cs similarity index 96% rename from Parse/Utilities/LockSet.cs rename to Parse/Infrastructure/Utilities/LockSet.cs index 03e5d549..659c538d 100644 --- a/Parse/Utilities/LockSet.cs +++ b/Parse/Infrastructure/Utilities/LockSet.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; using System.Threading; -namespace Parse.Common.Internal +namespace Parse.Infrastructure.Utilities { public class LockSet { diff --git a/Parse/Utilities/ReflectionHelpers.cs b/Parse/Infrastructure/Utilities/ReflectionUtilities.cs similarity index 96% rename from Parse/Utilities/ReflectionHelpers.cs rename to Parse/Infrastructure/Utilities/ReflectionUtilities.cs index 47035152..b5cf9d3d 100644 --- a/Parse/Utilities/ReflectionHelpers.cs +++ b/Parse/Infrastructure/Utilities/ReflectionUtilities.cs @@ -5,9 +5,9 @@ using System.Linq; using System.Reflection; -namespace Parse.Common.Internal +namespace Parse.Infrastructure.Utilities { - public static class ReflectionHelpers + public static class ReflectionUtilities { /// /// Gets all of the defined constructors that aren't static on a given instance. diff --git a/Parse/Utilities/SynchronizedEventHandler.cs b/Parse/Infrastructure/Utilities/SynchronizedEventHandler.cs similarity index 51% rename from Parse/Utilities/SynchronizedEventHandler.cs rename to Parse/Infrastructure/Utilities/SynchronizedEventHandler.cs index c3da3e96..468f25ac 100644 --- a/Parse/Utilities/SynchronizedEventHandler.cs +++ b/Parse/Infrastructure/Utilities/SynchronizedEventHandler.cs @@ -6,7 +6,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Parse.Common.Internal +namespace Parse.Infrastructure.Utilities { /// /// Represents an event handler that calls back from the synchronization context @@ -15,48 +15,39 @@ namespace Parse.Common.Internal /// public class SynchronizedEventHandler { - private LinkedList> delegates = - new LinkedList>(); - public void Add(Delegate del) + LinkedList> Callbacks { get; } = new LinkedList> { }; + + public void Add(Delegate target) { - lock (delegates) + lock (Callbacks) { - TaskFactory factory; - if (SynchronizationContext.Current != null) - { - factory = - new TaskFactory(CancellationToken.None, - TaskCreationOptions.None, - TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.FromCurrentSynchronizationContext()); - } - else - { - factory = Task.Factory; - } - foreach (Delegate d in del.GetInvocationList()) + TaskFactory factory = SynchronizationContext.Current is { } ? new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.FromCurrentSynchronizationContext()) : Task.Factory; + + foreach (Delegate invocation in target.GetInvocationList()) { - delegates.AddLast(new Tuple(d, factory)); + Callbacks.AddLast(new Tuple(invocation, factory)); } } } - public void Remove(Delegate del) + public void Remove(Delegate target) { - lock (delegates) + lock (Callbacks) { - if (delegates.Count == 0) + if (Callbacks.Count == 0) { return; } - foreach (Delegate d in del.GetInvocationList()) + + foreach (Delegate invocation in target.GetInvocationList()) { - LinkedListNode> node = delegates.First; + LinkedListNode> node = Callbacks.First; + while (node != null) { - if (node.Value.Item1 == d) + if (node.Value.Item1 == invocation) { - delegates.Remove(node); + Callbacks.Remove(node); break; } node = node.Next; @@ -69,14 +60,13 @@ public Task Invoke(object sender, T args) { IEnumerable> toInvoke; Task[] toContinue = new[] { Task.FromResult(0) }; - lock (delegates) + + lock (Callbacks) { - toInvoke = delegates.ToList(); + toInvoke = Callbacks.ToList(); } - List> invocations = toInvoke - .Select(p => p.Item2.ContinueWhenAll(toContinue, - _ => p.Item1.DynamicInvoke(sender, args))) - .ToList(); + + List> invocations = toInvoke.Select(callback => callback.Item2.ContinueWhenAll(toContinue, _ => callback.Item1.DynamicInvoke(sender, args))).ToList(); return Task.WhenAll(invocations); } } diff --git a/Parse/Utilities/TaskQueue.cs b/Parse/Infrastructure/Utilities/TaskQueue.cs similarity index 87% rename from Parse/Utilities/TaskQueue.cs rename to Parse/Infrastructure/Utilities/TaskQueue.cs index a9e53f53..7710a071 100644 --- a/Parse/Utilities/TaskQueue.cs +++ b/Parse/Infrastructure/Utilities/TaskQueue.cs @@ -4,7 +4,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Parse.Common.Internal +namespace Parse.Infrastructure.Utilities { /// /// A helper class for enqueuing tasks @@ -15,8 +15,7 @@ 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(); + Task Tail { get; set; } /// /// Gets a cancellable task that can be safely awaited and is dependent @@ -30,10 +29,9 @@ public class TaskQueue /// A new task that should be awaited by enqueued tasks. private Task GetTaskToAwait(CancellationToken cancellationToken) { - lock (mutex) + lock (Mutex) { - Task toAwait = tail ?? Task.FromResult(true); - return toAwait.ContinueWith(task => { }, cancellationToken); + return (Tail ?? Task.FromResult(true)).ContinueWith(task => { }, cancellationToken); } } @@ -53,9 +51,11 @@ public T Enqueue(Func taskStart, CancellationToken cancellationToken { Task oldTail; T task; - lock (mutex) + + lock (Mutex) { - oldTail = tail ?? Task.FromResult(true); + oldTail = 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 @@ -64,11 +64,11 @@ public T Enqueue(Func taskStart, CancellationToken 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. - tail = Task.WhenAll(oldTail, task); + Tail = Task.WhenAll(oldTail, task); } return task; } - public object Mutex => mutex; + public object Mutex { get; } = new object { }; } } diff --git a/Parse/Utilities/ThreadingUtilities.cs b/Parse/Infrastructure/Utilities/ThreadingUtilities.cs similarity index 92% rename from Parse/Utilities/ThreadingUtilities.cs rename to Parse/Infrastructure/Utilities/ThreadingUtilities.cs index 37b1e82e..90ecd537 100644 --- a/Parse/Utilities/ThreadingUtilities.cs +++ b/Parse/Infrastructure/Utilities/ThreadingUtilities.cs @@ -1,6 +1,6 @@ using System; -namespace Parse.Internal.Utilities +namespace Parse.Infrastructure.Utilities { internal static class ThreadingUtilities { diff --git a/Parse/Infrastructure/Utilities/XamarinAttributes.cs b/Parse/Infrastructure/Utilities/XamarinAttributes.cs new file mode 100644 index 00000000..108bba23 --- /dev/null +++ b/Parse/Infrastructure/Utilities/XamarinAttributes.cs @@ -0,0 +1,407 @@ +using System; +using System.Collections.Generic; + +namespace Parse.Infrastructure.Utilities +{ + /// + /// 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. + /// + static List AOTPreservations => 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) + }; + } +} diff --git a/Parse/Management/LightParseCorePlugins.cs b/Parse/Management/LightParseCorePlugins.cs deleted file mode 100644 index 34f36a07..00000000 --- a/Parse/Management/LightParseCorePlugins.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Parse.Abstractions.Library; -using Parse.Abstractions.Management; -using Parse.Common.Internal; -using Parse.Core.Internal; - -namespace Parse.Management -{ - /// - /// Parse dependency injection container implemented as a bare-minimum value type so instances cost less; this is to be used when a container is needed to pass only a few dependencies to a dependent entity, and the main container is unaccessible, but the indiviudal requirements are. - /// - /// This class has no implementation for . - public struct LightParseCorePlugins : IParseCorePlugins - { - public IMetadataController MetadataController { get; set; } - - public IWebClient WebClient { get; set; } - - public IStorageController StorageController { get; set; } - - public IParseObjectClassController SubclassingController { get; set; } - - public IParseInstallationController InstallationController { get; set; } - - public IParseCommandRunner CommandRunner { get; set; } - - public IParseCloudCodeController CloudCodeController { get; set; } - - public IParseConfigurationController ConfigController { get; set; } - - public IParseFileController FileController { get; set; } - - public IParseObjectController ObjectController { get; set; } - - public IParseQueryController QueryController { get; set; } - - public IParseSessionController SessionController { get; set; } - - public IParseUserController UserController { get; set; } - - public IParseCurrentUserController CurrentUserController { get; set; } - - public IParseCurrentConfigurationController CurrentConfigController { get; set; } - - public void Reset() => throw new NotImplementedException { }; - - /// - /// Will a . - /// - public void SetDefaults() => throw new NotSupportedException { }; - } -} diff --git a/Parse/Management/ParseCorePlugins.cs b/Parse/Management/ParseCorePlugins.cs deleted file mode 100644 index 8f65f230..00000000 --- a/Parse/Management/ParseCorePlugins.cs +++ /dev/null @@ -1,283 +0,0 @@ -//// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -//using Parse.Abstractions.Library; -//using Parse.Abstractions.Management; -//using Parse.Common.Internal; -//using Parse.Core.Internal; -//using Parse.Library; - -//namespace Parse.Management -//{ -// public class ParseCorePlugins : IParseCorePlugins -// { -// //static object Mutex { get; } = new object { }; -// //static IParseCorePlugins instance; - -// //public static IParseCorePlugins Instance -// //{ -// // get -// // { -// // lock (Mutex) -// // return instance ??= new ParseCorePlugins { }; -// // } -// // set -// // { -// // lock (Mutex) -// // instance = value; -// // } -// //} - -// object InstanceMutex { get; } = new object { }; - -// #region Server Controllers - -// IMetadataController metadataController; - -// IWebClient webClient; -// IParseCommandRunner commandRunner; -// IStorageController storageController; - -// IParseCloudCodeController cloudCodeController; -// IParseConfigController configController; -// IParseFileController fileController; -// IParseObjectController objectController; -// IParseQueryController queryController; -// IParseSessionController sessionController; -// IParseUserController userController; -// IObjectSubclassingController subclassingController; - -// #endregion - -// #region Current Instance Controller - -// IParseCurrentUserController currentUserController; -// IParseInstallationController installationController; - -// #endregion - -// public void Reset() -// { -// lock (InstanceMutex) -// { -// MetadataController = null; - -// WebClient = null; -// CommandRunner = null; -// StorageController = null; - -// CloudCodeController = null; -// FileController = null; -// ObjectController = null; -// SessionController = null; -// UserController = null; -// SubclassingController = null; - -// CurrentUserController = null; -// InstallationController = null; -// } -// } - -// public IMetadataController MetadataController -// { -// get -// { -// lock (InstanceMutex) -// return metadataController ??= new MetadataController { }; -// } -// set -// { -// lock (InstanceMutex) -// metadataController = value; -// } -// } - -// public IWebClient WebClient -// { -// get -// { -// lock (InstanceMutex) -// return webClient ??= new UniversalWebClient { }; -// } -// set -// { -// lock (InstanceMutex) -// webClient = value; -// } -// } - -// public IParseCommandRunner CommandRunner -// { -// get -// { -// lock (InstanceMutex) -// return commandRunner ??= new ParseCommandRunner(WebClient, InstallationController, MetadataController); -// } -// set -// { -// lock (InstanceMutex) -// commandRunner = value; -// } -// } - -// public IStorageController StorageController -// { -// get -// { -// lock (InstanceMutex) -// return storageController ??= new StorageController(); -// } -// set -// { -// lock (InstanceMutex) -// storageController = value; -// } -// } - -// public IParseCloudCodeController CloudCodeController -// { -// get -// { -// lock (InstanceMutex) -// return cloudCodeController ??= new ParseCloudCodeController(CommandRunner); -// } -// set -// { -// lock (InstanceMutex) -// cloudCodeController = value; -// } -// } - -// public IParseFileController FileController -// { -// get -// { -// lock (InstanceMutex) -// return fileController ??= new ParseFileController(CommandRunner); -// } -// set -// { -// lock (InstanceMutex) -// fileController = value; -// } -// } - -// public IParseConfigController ConfigController -// { -// get -// { -// lock (InstanceMutex) -// return configController ??= new ParseConfigController(CommandRunner, StorageController); -// } -// set -// { -// lock (InstanceMutex) -// configController = value; -// } -// } - -// public IParseObjectController ObjectController -// { -// get -// { -// lock (InstanceMutex) -// return objectController ??= new ParseObjectController(CommandRunner); -// } -// set -// { -// lock (InstanceMutex) -// objectController = value; -// } -// } - -// public IParseQueryController QueryController -// { -// get -// { -// lock (InstanceMutex) -// return queryController ??= new ParseQueryController(CommandRunner); -// } -// set -// { -// lock (InstanceMutex) -// queryController = value; -// } -// } - -// public IParseSessionController SessionController -// { -// get -// { -// lock (InstanceMutex) -// return sessionController ??= new ParseSessionController(CommandRunner); -// } -// set -// { -// lock (InstanceMutex) -// sessionController = value; -// } -// } - -// public IParseUserController UserController -// { -// get -// { -// lock (InstanceMutex) -// return userController ??= new ParseUserController(CommandRunner); -// } -// set -// { -// lock (InstanceMutex) -// userController = value; -// } -// } - -// public IParseCurrentUserController CurrentUserController -// { -// get -// { -// lock (InstanceMutex) -// return currentUserController ??= new ParseCurrentUserController(StorageController); -// } -// set -// { -// lock (InstanceMutex) -// currentUserController = value; -// } -// } - -// public IObjectSubclassingController SubclassingController -// { -// get -// { -// lock (InstanceMutex) -// { -// if (subclassingController == null) -// { -// subclassingController = new ObjectSubclassingController(); -// subclassingController.AddRegisterHook(typeof(ParseUser), () => CurrentUserController.ClearFromMemory()); -// } -// return subclassingController; -// } -// } -// set -// { -// lock (InstanceMutex) -// subclassingController = value; -// } -// } - -// public IParseInstallationController InstallationController -// { -// get -// { -// lock (InstanceMutex) -// return installationController ??= new ParseInstallationController(StorageController); -// } -// set -// { -// lock (InstanceMutex) -// installationController = value; -// } -// } -// } -//} diff --git a/Parse/Modules/IParseModule.cs b/Parse/Modules/IParseModule.cs deleted file mode 100644 index 6d59806b..00000000 --- a/Parse/Modules/IParseModule.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Parse.Common.Internal -{ - public interface IParseModule - { - void ExecuteModuleRegistrationHook(); - void ExecuteLibraryInitializationHook(); - } -} \ No newline at end of file diff --git a/Parse/Modules/ParseModuleAttribute.cs b/Parse/Modules/ParseModuleAttribute.cs deleted file mode 100644 index 87d1a1c0..00000000 --- a/Parse/Modules/ParseModuleAttribute.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Parse.Common.Internal -{ - [AttributeUsage(AttributeTargets.Assembly)] - public class ParseModuleAttribute : Attribute - { - /// - /// Instantiates a new ParseModuleAttribute. - /// - /// The type to which this module is applied. - public ParseModuleAttribute(Type ModuleType) => this.ModuleType = ModuleType; - - public Type ModuleType { get; private set; } - } -} \ No newline at end of file diff --git a/Parse/Modules/ParseModuleController.cs b/Parse/Modules/ParseModuleController.cs deleted file mode 100644 index c1ce919f..00000000 --- a/Parse/Modules/ParseModuleController.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using AssemblyLister; - -namespace Parse.Common.Internal -{ - /// - /// The class which controls the loading of other ParseModules - /// - public class ParseModuleController - { - public static ParseModuleController Instance { get; } = new ParseModuleController { }; - - object Mutex { get; } = new object { }; - - List Modules { get; } = new List { }; - - bool Initialized { get; set; } = false; - - public void RegisterModule(IParseModule module) - { - if (module == null) - { - return; - } - - lock (Mutex) - { - Modules.Add(module); - module.ExecuteModuleRegistrationHook(); - - if (Initialized) - { - module.ExecuteLibraryInitializationHook(); - } - } - } - - public void ScanForModules() - { - IEnumerable moduleTypes = Lister.AllAssemblies.SelectMany(asm => asm.GetCustomAttributes()).Select(attr => attr.ModuleType).Where(type => type != null && type.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IParseModule))); - - lock (Mutex) - { - foreach (Type moduleType in moduleTypes) - { - try - { - if (moduleType.FindConstructor() is { } constructor) - { - RegisterModule(constructor.Invoke(new object[] { }) as IParseModule); - } - } - catch (Exception) - { - // Ignore, either constructor threw or was private. - } - } - } - } - - public void Reset() - { - lock (Mutex) - { - Modules.Clear(); - Initialized = false; - } - } - - public void BroadcastParseInitialization() - { - lock (Mutex) - { - foreach (IParseModule module in Modules) - { - module.ExecuteLibraryInitializationHook(); - } - - Initialized = true; - } - } - } -} \ No newline at end of file diff --git a/Parse/Parse.csproj b/Parse/Parse.csproj index f6f4e82a..e2d9d5b9 100644 --- a/Parse/Parse.csproj +++ b/Parse/Parse.csproj @@ -24,7 +24,7 @@ - + True True Resources.resx @@ -32,15 +32,10 @@ - + ResXFileCodeGenerator Resources.Designer.cs - - - - - diff --git a/Parse/Platform/Analytics/ParseAnalyticsController.cs b/Parse/Platform/Analytics/ParseAnalyticsController.cs index 3e7bab54..1a6a36c5 100644 --- a/Parse/Platform/Analytics/ParseAnalyticsController.cs +++ b/Parse/Platform/Analytics/ParseAnalyticsController.cs @@ -4,10 +4,13 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Core.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Analytics; +using Parse.Infrastructure.Data; +using Parse.Infrastructure.Execution; -namespace Parse.Analytics.Internal +namespace Parse.Platform.Analytics { /// /// The controller for the Parse Analytics API. @@ -58,9 +61,7 @@ public Task TrackAppOpenedAsync(string pushHash, string sessionToken, IServiceHu IDictionary data = new Dictionary { ["at"] = DateTime.Now }; if (pushHash != null) - { data["push_hash"] = pushHash; - } return Runner.RunCommandAsync(new ParseCommand("events/AppOpened", "POST", sessionToken, data: PointerOrLocalIdEncoder.Instance.Encode(data, serviceHub) as IDictionary), cancellationToken: cancellationToken); } diff --git a/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs b/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs deleted file mode 100644 index f3fd6be9..00000000 --- a/Parse/Platform/Analytics/ParseAnalyticsPlugins.cs +++ /dev/null @@ -1,83 +0,0 @@ -//// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -//using Parse.Abstractions.Management; -//using Parse.Management; - -//namespace Parse.Analytics.Internal -//{ -// public class ParseAnalyticsPlugins : IParseAnalyticsPlugins -// { -// private static readonly object instanceMutex = new object(); -// private static IParseAnalyticsPlugins instance; -// public static IParseAnalyticsPlugins Instance -// { -// get -// { -// lock (instanceMutex) -// { -// instance ??= new ParseAnalyticsPlugins(); -// return instance; -// } -// } -// set -// { -// lock (instanceMutex) -// { -// instance = value; -// } -// } -// } - -// private readonly object mutex = new object(); - -// private IParseCorePlugins corePlugins; -// private IParseAnalyticsController analyticsController; - -// public void Reset() -// { -// lock (mutex) -// { -// CorePlugins = null; -// AnalyticsController = null; -// } -// } - -// public IParseCorePlugins CorePlugins -// { -// get -// { -// lock (mutex) -// { -// corePlugins ??= ParseCorePlugins.Instance; -// return corePlugins; -// } -// } -// set -// { -// lock (mutex) -// { -// corePlugins = value; -// } -// } -// } - -// public IParseAnalyticsController AnalyticsController -// { -// get -// { -// lock (mutex) -// { -// analyticsController ??= new ParseAnalyticsController(CorePlugins.CommandRunner); -// return analyticsController; -// } -// } -// set -// { -// lock (mutex) -// { -// analyticsController = value; -// } -// } -// } -// } -//} \ No newline at end of file diff --git a/Parse/Platform/Code/ParseCloudCodeController.cs b/Parse/Platform/Cloud/ParseCloudCodeController.cs similarity index 82% rename from Parse/Platform/Code/ParseCloudCodeController.cs rename to Parse/Platform/Cloud/ParseCloudCodeController.cs index 0194c9c2..2eeef0cf 100644 --- a/Parse/Platform/Code/ParseCloudCodeController.cs +++ b/Parse/Platform/Cloud/ParseCloudCodeController.cs @@ -4,11 +4,15 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Utilities; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Cloud; +using Parse.Infrastructure.Utilities; +using Parse.Infrastructure.Data; +using Parse.Infrastructure.Execution; -namespace Parse.Core.Internal +namespace Parse.Platform.Cloud { public class ParseCloudCodeController : IParseCloudCodeController { diff --git a/Parse/ParseConfiguration.cs b/Parse/Platform/Configuration/ParseConfiguration.cs similarity index 94% rename from Parse/ParseConfiguration.cs rename to Parse/Platform/Configuration/ParseConfiguration.cs index 71955812..67ccd10b 100644 --- a/Parse/ParseConfiguration.cs +++ b/Parse/Platform/Configuration/ParseConfiguration.cs @@ -1,14 +1,12 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System.Collections.Generic; -using System.Threading; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Management; -using Parse.Utilities; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Infrastructure.Data; +using Parse.Infrastructure.Utilities; -namespace Parse +namespace Parse.Platform.Configuration { /// /// The ParseConfig is a representation of the remote configuration object, @@ -50,7 +48,6 @@ public class ParseConfiguration : IJsonConvertible public bool TryGetValue(string key, out T result) { if (Properties.ContainsKey(key)) - { try { T temp = Conversion.To(Properties[key]); @@ -61,7 +58,6 @@ public bool TryGetValue(string key, out T result) { // Could not convert, do nothing. } - } result = default; return false; diff --git a/Parse/Platform/Configuration/ParseConfigurationController.cs b/Parse/Platform/Configuration/ParseConfigurationController.cs index 8107cac1..f4f6210c 100644 --- a/Parse/Platform/Configuration/ParseConfigurationController.cs +++ b/Parse/Platform/Configuration/ParseConfigurationController.cs @@ -2,10 +2,15 @@ using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Configuration; +using Parse.Infrastructure.Utilities; +using Parse; +using Parse.Infrastructure.Execution; -namespace Parse.Core.Internal +namespace Parse.Platform.Configuration { /// /// Config controller. diff --git a/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs b/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs index 92f4ebb2..092564f5 100644 --- a/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs +++ b/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs @@ -1,12 +1,13 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Configuration; +using Parse.Infrastructure.Utilities; -namespace Parse.Core.Internal +namespace Parse.Platform.Configuration { /// /// Parse current config controller. diff --git a/Parse/Platform/Files/FileState.cs b/Parse/Platform/Files/FileState.cs index 12e9eff2..9cebbc76 100644 --- a/Parse/Platform/Files/FileState.cs +++ b/Parse/Platform/Files/FileState.cs @@ -2,7 +2,7 @@ using System; -namespace Parse.Core.Internal +namespace Parse.Platform.Files { public class FileState { diff --git a/Parse/ParseFile.cs b/Parse/Platform/Files/ParseFile.cs similarity index 98% rename from Parse/ParseFile.cs rename to Parse/Platform/Files/ParseFile.cs index 40309c81..4d7602bb 100644 --- a/Parse/ParseFile.cs +++ b/Parse/Platform/Files/ParseFile.cs @@ -5,10 +5,9 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Management; +using Parse.Abstractions.Infrastructure; +using Parse.Infrastructure.Utilities; +using Parse.Platform.Files; namespace Parse { diff --git a/Parse/Platform/Files/ParseFileController.cs b/Parse/Platform/Files/ParseFileController.cs index 1958129a..4f4ff0eb 100644 --- a/Parse/Platform/Files/ParseFileController.cs +++ b/Parse/Platform/Files/ParseFileController.cs @@ -6,9 +6,13 @@ using System.Net; using System.Threading; using System.Threading.Tasks; -using Parse.Common.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Files; +using Parse.Infrastructure.Execution; +using Parse.Infrastructure.Utilities; -namespace Parse.Core.Internal +namespace Parse.Platform.Files { public class ParseFileController : IParseFileController { @@ -19,16 +23,12 @@ public class ParseFileController : IParseFileController public Task SaveAsync(FileState state, Stream dataStream, string sessionToken, IProgress progress, CancellationToken cancellationToken = default) { if (state.Location != null) - { // !isDirty return Task.FromResult(state); - } if (cancellationToken.IsCancellationRequested) - { return Task.FromCanceled(cancellationToken); - } long oldPosition = dataStream.Position; @@ -49,9 +49,7 @@ public Task SaveAsync(FileState state, Stream dataStream, string sess // Rewind the stream on failure or cancellation (if possible). if ((task.IsFaulted || task.IsCanceled) && dataStream.CanSeek) - { dataStream.Seek(oldPosition, SeekOrigin.Begin); - } return task; }).Unwrap(); diff --git a/Parse/Platform/Installation/ParseCurrentInstallationController.cs b/Parse/Platform/Installations/ParseCurrentInstallationController.cs similarity index 90% rename from Parse/Platform/Installation/ParseCurrentInstallationController.cs rename to Parse/Platform/Installations/ParseCurrentInstallationController.cs index 672b1b17..b1a5c5a1 100644 --- a/Parse/Platform/Installation/ParseCurrentInstallationController.cs +++ b/Parse/Platform/Installations/ParseCurrentInstallationController.cs @@ -3,12 +3,12 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Library; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Installations; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure.Utilities; -namespace Parse.Push.Internal +namespace Parse.Platform.Installations { internal class ParseCurrentInstallationController : IParseCurrentInstallationController { @@ -41,22 +41,18 @@ internal ParseInstallation CurrentInstallation get { lock (Mutex) - { return CurrentInstallationValue; - } } set { lock (Mutex) - { CurrentInstallationValue = value; - } } } public Task SetAsync(ParseInstallation installation, CancellationToken cancellationToken) => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => { - Task saveTask = StorageController.LoadAsync().OnSuccess(storage => installation is { } ? storage.Result.AddAsync(ParseInstallationKey, Json.Encode(InstallationCoder.Encode(installation))) : storage.Result.RemoveAsync(ParseInstallationKey)).Unwrap(); + Task saveTask = StorageController.LoadAsync().OnSuccess(storage => installation is { } ? storage.Result.AddAsync(ParseInstallationKey, JsonUtilities.Encode(InstallationCoder.Encode(installation))) : storage.Result.RemoveAsync(ParseInstallationKey)).Unwrap(); CurrentInstallation = installation; return saveTask; @@ -68,9 +64,7 @@ public Task GetAsync(IServiceHub serviceHub, CancellationToke cachedCurrent = CurrentInstallation; if (cachedCurrent != null) - { return Task.FromResult(cachedCurrent); - } return TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => StorageController.LoadAsync().OnSuccess(stroage => { @@ -80,7 +74,7 @@ public Task GetAsync(IServiceHub serviceHub, CancellationToke if (temp is string installationDataString) { - IDictionary installationData = Json.Parse(installationDataString) as IDictionary; + IDictionary installationData = JsonUtilities.Parse(installationDataString) as IDictionary; installation = InstallationCoder.Decode(installationData, serviceHub); fetchTask = Task.FromResult(null); @@ -99,9 +93,7 @@ public Task GetAsync(IServiceHub serviceHub, CancellationToke public Task ExistsAsync(CancellationToken cancellationToken) { if (CurrentInstallation != null) - { return Task.FromResult(true); - } return TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => StorageController.LoadAsync().OnSuccess(s => s.Result.ContainsKey(ParseInstallationKey))).Unwrap(), cancellationToken); } diff --git a/Parse/ParseInstallation.cs b/Parse/Platform/Installations/ParseInstallation.cs similarity index 99% rename from Parse/ParseInstallation.cs rename to Parse/Platform/Installations/ParseInstallation.cs index 9ce6bbbc..b42401b2 100644 --- a/Parse/ParseInstallation.cs +++ b/Parse/Platform/Installations/ParseInstallation.cs @@ -3,14 +3,9 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Library; -using Parse.Management; -using Parse.Push.Internal; +using Parse.Infrastructure.Utilities; namespace Parse { diff --git a/Parse/Platform/Installation/ParseInstallationCoder.cs b/Parse/Platform/Installations/ParseInstallationCoder.cs similarity index 84% rename from Parse/Platform/Installation/ParseInstallationCoder.cs rename to Parse/Platform/Installations/ParseInstallationCoder.cs index fa25d8f2..1744f32e 100644 --- a/Parse/Platform/Installation/ParseInstallationCoder.cs +++ b/Parse/Platform/Installations/ParseInstallationCoder.cs @@ -1,9 +1,13 @@ using System.Collections.Generic; using System.Linq; -using Parse.Abstractions.Library; -using Parse.Core.Internal; - -namespace Parse.Push.Internal +using Parse; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Platform.Installations; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure.Data; + +namespace Parse.Platform.Installations { public class ParseInstallationCoder : IParseInstallationCoder { @@ -23,14 +27,10 @@ public IDictionary Encode(ParseInstallation installation) // The following operations use the date and time serialization format defined by ISO standard 8601. if (state.CreatedAt is { }) - { data["createdAt"] = state.CreatedAt.Value.ToString(ParseClient.DateFormatStrings[0]); - } if (state.UpdatedAt is { }) - { data["updatedAt"] = state.UpdatedAt.Value.ToString(ParseClient.DateFormatStrings[0]); - } return data; } diff --git a/Parse/Platform/Installation/ParseInstallationController.cs b/Parse/Platform/Installations/ParseInstallationController.cs similarity index 92% rename from Parse/Platform/Installation/ParseInstallationController.cs rename to Parse/Platform/Installations/ParseInstallationController.cs index 15702d00..31f1a1ef 100644 --- a/Parse/Platform/Installation/ParseInstallationController.cs +++ b/Parse/Platform/Installations/ParseInstallationController.cs @@ -2,9 +2,11 @@ using System; using System.Threading.Tasks; -using Parse.Common.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Installations; +using Parse.Infrastructure.Utilities; -namespace Parse.Core.Internal +namespace Parse.Platform.Installations { public class ParseInstallationController : IParseInstallationController { @@ -34,12 +36,8 @@ public Task SetAsync(Guid? installationId) public Task GetAsync() { lock (Mutex) - { if (InstallationId is { }) - { return Task.FromResult(InstallationId); - } - } return StorageController.LoadAsync().OnSuccess(storageTask => { @@ -48,9 +46,7 @@ public Task SetAsync(Guid? installationId) try { lock (Mutex) - { return Task.FromResult(InstallationId = new Guid(id as string)); - } } catch (Exception) { diff --git a/Parse/Platform/Installation/ParseInstallationDataFinalizer.cs b/Parse/Platform/Installations/ParseInstallationDataFinalizer.cs similarity index 79% rename from Parse/Platform/Installation/ParseInstallationDataFinalizer.cs rename to Parse/Platform/Installations/ParseInstallationDataFinalizer.cs index 704c0b73..4f6d335f 100644 --- a/Parse/Platform/Installation/ParseInstallationDataFinalizer.cs +++ b/Parse/Platform/Installations/ParseInstallationDataFinalizer.cs @@ -1,7 +1,7 @@ -using System; using System.Threading.Tasks; +using Parse.Abstractions.Platform.Installations; -namespace Parse.Push.Internal +namespace Parse.Platform.Installations { /// /// Controls the device information. diff --git a/Parse/ParseGeoDistance.cs b/Parse/Platform/Location/ParseGeoDistance.cs similarity index 100% rename from Parse/ParseGeoDistance.cs rename to Parse/Platform/Location/ParseGeoDistance.cs diff --git a/Parse/ParseGeoPoint.cs b/Parse/Platform/Location/ParseGeoPoint.cs similarity index 98% rename from Parse/ParseGeoPoint.cs rename to Parse/Platform/Location/ParseGeoPoint.cs index 054d8fef..eead203a 100644 --- a/Parse/ParseGeoPoint.cs +++ b/Parse/Platform/Location/ParseGeoPoint.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; -using Parse.Common.Internal; +using Parse.Abstractions.Infrastructure; namespace Parse { diff --git a/Parse/Platform/Notifications/ParsePushPlugins.cs b/Parse/Platform/Notifications/ParsePushPlugins.cs deleted file mode 100644 index bc2bd030..00000000 --- a/Parse/Platform/Notifications/ParsePushPlugins.cs +++ /dev/null @@ -1,143 +0,0 @@ -//using Parse.Abstractions.Management; -//using Parse.Management; - -//namespace Parse.Push.Internal -//{ -// public class ParsePushPlugins : IParsePushPlugins -// { -// private static readonly object instanceMutex = new object(); -// private static IParsePushPlugins instance; -// public static IParsePushPlugins Instance -// { -// get -// { -// instance ??= new ParsePushPlugins(); -// return instance; -// } -// set -// { -// lock (instanceMutex) -// { -// instance = value; -// } -// } -// } - -// private readonly object mutex = new object(); - -// private IParseCorePlugins corePlugins; -// private IParsePushChannelsController pushChannelsController; -// private IParsePushController pushController; -// private IParseCurrentInstallationController currentInstallationController; -// private IInstallationDataFinalizer deviceInfoController; - -// public void Reset() -// { -// lock (mutex) -// { -// CorePlugins = null; -// PushChannelsController = null; -// PushController = null; -// CurrentInstallationController = null; -// DeviceInfoController = null; -// } -// } - -// public IParseCorePlugins CorePlugins -// { -// get -// { -// lock (mutex) -// { -// corePlugins ??= ParseCorePlugins.Instance; -// return corePlugins; -// } -// } -// set -// { -// lock (mutex) -// { -// corePlugins = value; -// } -// } -// } - -// public IParsePushChannelsController PushChannelsController -// { -// get -// { -// lock (mutex) -// { -// pushChannelsController ??= new ParsePushChannelsController(); -// return pushChannelsController; -// } -// } -// set -// { -// lock (mutex) -// { -// pushChannelsController = value; -// } -// } -// } - -// public IParsePushController PushController -// { -// get -// { -// lock (mutex) -// { -// pushController ??= new ParsePushController(CorePlugins.CommandRunner, CorePlugins.CurrentUserController); -// return pushController; -// } -// } -// set -// { -// lock (mutex) -// { -// pushController = value; -// } -// } -// } - -// public IParseCurrentInstallationController CurrentInstallationController -// { -// get -// { -// lock (mutex) -// { -// currentInstallationController ??= new ParseCurrentInstallationController( -// CorePlugins.InstallationController, CorePlugins.StorageController, ParseInstallationCoder.Instance -// ); -// return currentInstallationController; -// } -// } -// set -// { -// lock (mutex) -// { -// currentInstallationController = value; -// } -// } -// } - -// public IInstallationDataFinalizer DeviceInfoController -// { -// get -// { -// lock (mutex) -// { -// deviceInfoController ??= new InstallationDataFinalizer(); -// return deviceInfoController; -// } -// } -// set -// { -// lock (mutex) -// { -// deviceInfoController = value; -// } -// } -// } -// } -//} diff --git a/Parse/Management/Tracking/MutableObjectState.cs b/Parse/Platform/Objects/MutableObjectState.cs similarity index 83% rename from Parse/Management/Tracking/MutableObjectState.cs rename to Parse/Platform/Objects/MutableObjectState.cs index 26498bf7..f8ad5bd9 100644 --- a/Parse/Management/Tracking/MutableObjectState.cs +++ b/Parse/Platform/Objects/MutableObjectState.cs @@ -3,8 +3,11 @@ using System; using System.Collections.Generic; using System.Linq; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure.Control; -namespace Parse.Core.Internal +namespace Parse.Platform.Objects { public class MutableObjectState : IObjectState { @@ -14,14 +17,7 @@ public class MutableObjectState : IObjectState 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 => serverData; - - set => serverData = value; - } + public IDictionary ServerData { get; set; } = new Dictionary { }; public object this[string key] => ServerData[key]; @@ -35,13 +31,9 @@ public void Apply(IDictionary operationSet) ServerData.TryGetValue(pair.Key, out object oldValue); object newValue = pair.Value.Apply(oldValue, pair.Key); if (newValue != ParseDeleteOperation.Token) - { ServerData[pair.Key] = newValue; - } else - { ServerData.Remove(pair.Key); - } } } @@ -49,22 +41,14 @@ 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 (KeyValuePair pair in other) - { ServerData[pair.Key] = pair.Value; - } } public IObjectState MutatedClone(Action func) diff --git a/Parse/ParseObject.cs b/Parse/Platform/Objects/ParseObject.cs similarity index 99% rename from Parse/ParseObject.cs rename to Parse/Platform/Objects/ParseObject.cs index 44edf471..8f82c75a 100644 --- a/Parse/ParseObject.cs +++ b/Parse/Platform/Objects/ParseObject.cs @@ -7,11 +7,14 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Management; -using Parse.Utilities; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Internal; +using Parse.Infrastructure.Control; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure.Utilities; +using Parse.Platform.Objects; +using Parse.Infrastructure.Data; namespace Parse { diff --git a/Parse/Management/ParseObjectClass.cs b/Parse/Platform/Objects/ParseObjectClass.cs similarity index 90% rename from Parse/Management/ParseObjectClass.cs rename to Parse/Platform/Objects/ParseObjectClass.cs index 5a09827e..c7137fa0 100644 --- a/Parse/Management/ParseObjectClass.cs +++ b/Parse/Platform/Objects/ParseObjectClass.cs @@ -2,9 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using Parse.Common.Internal; +using Parse.Abstractions.Internal; +using Parse.Infrastructure.Utilities; -namespace Parse.Core.Internal +namespace Parse.Platform.Objects { internal class ParseObjectClass { diff --git a/Parse/Management/ParseObjectClassController.cs b/Parse/Platform/Objects/ParseObjectClassController.cs similarity index 94% rename from Parse/Management/ParseObjectClassController.cs rename to Parse/Platform/Objects/ParseObjectClassController.cs index 39ab042b..13cf1b15 100644 --- a/Parse/Management/ParseObjectClassController.cs +++ b/Parse/Platform/Objects/ParseObjectClassController.cs @@ -2,10 +2,11 @@ using System.Collections.Generic; using System.Reflection; using System.Threading; -using Parse.Abstractions.Library; -using Parse.Common.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure.Utilities; -namespace Parse.Core.Internal +namespace Parse.Platform.Objects { internal class ParseObjectClassController : IParseObjectClassController { @@ -45,9 +46,7 @@ public void AddValid(Type type) TypeInfo typeInfo = type.GetTypeInfo(); if (!typeof(ParseObject).GetTypeInfo().IsAssignableFrom(typeInfo)) - { throw new ArgumentException("Cannot register a type that is not a subclass of ParseObject"); - } string className = GetClassName(type); @@ -60,32 +59,24 @@ public void AddValid(Type type) Mutex.EnterWriteLock(); if (Classes.TryGetValue(className, out ParseObjectClass 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 ParseObject subclass of {className}. Cannot determine the right class to use because neither inherits from the other."); - } - } #warning Constructor detection may erroneously find a constructor which should not be used. ConstructorInfo constructor = type.FindConstructor() ?? type.FindConstructor(typeof(string), typeof(IServiceHub)); if (constructor is null) - { throw new ArgumentException("Cannot register a type that does not implement the default constructor!"); - } Classes[className] = new ParseObjectClass(type, constructor); } @@ -130,9 +121,7 @@ public IDictionary GetPropertyMappings(string className) Classes.TryGetValue(className, out ParseObjectClass info); if (info is null) - { Classes.TryGetValue(ReservedParseObjectClassName, out info); - } Mutex.ExitReadLock(); return info.PropertyMappings; diff --git a/Parse/Management/ParseObjectController.cs b/Parse/Platform/Objects/ParseObjectController.cs similarity index 95% rename from Parse/Management/ParseObjectController.cs rename to Parse/Platform/Objects/ParseObjectController.cs index e98c7d48..62363e3f 100644 --- a/Parse/Management/ParseObjectController.cs +++ b/Parse/Platform/Objects/ParseObjectController.cs @@ -5,11 +5,18 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Utilities; - -namespace Parse.Core.Internal +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure.Utilities; +using Parse.Infrastructure; +using Parse.Abstractions.Internal; +using Parse.Infrastructure.Execution; +using Parse.Infrastructure.Data; + +namespace Parse.Platform.Objects { public class ParseObjectController : IParseObjectController { @@ -49,7 +56,7 @@ internal IList>> ExecuteBatchRequests(IList remaining = requests; - + while (batchSize > MaximumBatchSize) { List process = remaining.Take(MaximumBatchSize).ToList(); @@ -87,9 +94,7 @@ IList>> ExecuteBatchRequest(IList }; if (request.DataObject != null) - { results["body"] = request.DataObject; - } return results; }).Cast().ToList(); @@ -101,16 +106,10 @@ IList>> ExecuteBatchRequest(IList if (task.IsFaulted || task.IsCanceled) { foreach (TaskCompletionSource> tcs in completionSources) - { if (task.IsFaulted) - { tcs.TrySetException(task.Exception); - } else if (task.IsCanceled) - { tcs.TrySetCanceled(); - } - } return; } @@ -121,9 +120,7 @@ IList>> ExecuteBatchRequest(IList if (resultLength != batchSize) { foreach (TaskCompletionSource> completionSource in completionSources) - { completionSource.TrySetException(new InvalidOperationException($"Batch command result count expected: {batchSize} but was: {resultLength}.")); - } return; } @@ -134,18 +131,14 @@ IList>> ExecuteBatchRequest(IList TaskCompletionSource> target = completionSources[i]; if (result.ContainsKey("success")) - { target.TrySetResult(result["success"] as IDictionary); - } else if (result.ContainsKey("error")) { IDictionary error = result["error"] as IDictionary; target.TrySetException(new ParseFailureException((ParseFailureException.ErrorCode) (long) error["code"], error[nameof(error)] as string)); } else - { target.TrySetException(new InvalidOperationException("Invalid batch command response.")); - } } }); diff --git a/Parse/ParseClient.cs b/Parse/Platform/ParseClient.cs similarity index 95% rename from Parse/ParseClient.cs rename to Parse/Platform/ParseClient.cs index 507635a5..b384d5c0 100644 --- a/Parse/ParseClient.cs +++ b/Parse/Platform/ParseClient.cs @@ -4,15 +4,12 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using Parse.Abstractions.Library; -using Parse.Abstractions.Management; -using Parse.Abstractions.Storage; -using Parse.Common.Internal; -using Parse.Library; -using Parse.Management; +using Parse.Abstractions.Infrastructure; +using Parse.Infrastructure.Utilities; +using Parse.Infrastructure; #if DEBUG -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Parse.Test")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Parse.Tests")] #endif namespace Parse @@ -128,7 +125,7 @@ public void Publicize() static object Mutex { get; } = new object { }; - internal static string BuildQueryString(IDictionary parameters) => String.Join("&", (from pair in parameters let valueString = pair.Value as string select $"{Uri.EscapeDataString(pair.Key)}={Uri.EscapeDataString(String.IsNullOrEmpty(valueString) ? Json.Encode(pair.Value) : valueString)}").ToArray()); + internal static string BuildQueryString(IDictionary parameters) => String.Join("&", (from pair in parameters let valueString = pair.Value as string select $"{Uri.EscapeDataString(pair.Key)}={Uri.EscapeDataString(String.IsNullOrEmpty(valueString) ? JsonUtilities.Encode(pair.Value) : valueString)}").ToArray()); internal static IDictionary DecodeQueryString(string queryString) { @@ -143,9 +140,9 @@ internal static IDictionary DecodeQueryString(string queryString return query; } - internal static IDictionary DeserializeJsonString(string jsonData) => Json.Parse(jsonData) as IDictionary; + internal static IDictionary DeserializeJsonString(string jsonData) => JsonUtilities.Parse(jsonData) as IDictionary; - internal static string SerializeJsonString(IDictionary jsonData) => Json.Encode(jsonData); + internal static string SerializeJsonString(IDictionary jsonData) => JsonUtilities.Encode(jsonData); public IServiceHub BuildHub(IMutableServiceHub target = default, IServiceHub extension = default, params IServiceHubMutator[] configurators) { diff --git a/Parse/Platform/Notifications/MutablePushState.cs b/Parse/Platform/Push/MutablePushState.cs similarity index 95% rename from Parse/Platform/Notifications/MutablePushState.cs rename to Parse/Platform/Push/MutablePushState.cs index e5ec7b57..0b942b4f 100644 --- a/Parse/Platform/Notifications/MutablePushState.cs +++ b/Parse/Platform/Push/MutablePushState.cs @@ -2,9 +2,10 @@ using System; using System.Collections.Generic; -using Parse.Common.Internal; +using Parse.Abstractions.Platform.Push; +using Parse.Infrastructure.Utilities; -namespace Parse.Push.Internal +namespace Parse.Platform.Push { public class MutablePushState : IPushState { @@ -37,9 +38,7 @@ public IPushState MutatedClone(Action func) public override bool Equals(object obj) { if (obj == null || !(obj is MutablePushState)) - { return false; - } MutablePushState other = obj as MutablePushState; return Equals(Query, other.Query) && diff --git a/Parse/ParsePush.cs b/Parse/Platform/Push/ParsePush.cs similarity index 98% rename from Parse/ParsePush.cs rename to Parse/Platform/Push/ParsePush.cs index 67582374..3c8f57d6 100644 --- a/Parse/ParsePush.cs +++ b/Parse/Platform/Push/ParsePush.cs @@ -4,8 +4,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Push.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Push; +using Parse.Platform.Push; namespace Parse { diff --git a/Parse/Platform/Notifications/ParsePushChannelsController.cs b/Parse/Platform/Push/ParsePushChannelsController.cs similarity index 87% rename from Parse/Platform/Notifications/ParsePushChannelsController.cs rename to Parse/Platform/Push/ParsePushChannelsController.cs index 1a9664ae..58377658 100644 --- a/Parse/Platform/Notifications/ParsePushChannelsController.cs +++ b/Parse/Platform/Push/ParsePushChannelsController.cs @@ -3,11 +3,13 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Library; +using Parse; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Installations; +using Parse.Abstractions.Platform.Push; +using Parse.Infrastructure.Utilities; -namespace Parse.Push.Internal +namespace Parse.Platform.Push { internal class ParsePushChannelsController : IParsePushChannelsController { diff --git a/Parse/Platform/Notifications/ParsePushController.cs b/Parse/Platform/Push/ParsePushController.cs similarity index 81% rename from Parse/Platform/Notifications/ParsePushController.cs rename to Parse/Platform/Push/ParsePushController.cs index 88f1beb9..52b912b4 100644 --- a/Parse/Platform/Notifications/ParsePushController.cs +++ b/Parse/Platform/Push/ParsePushController.cs @@ -2,11 +2,14 @@ using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Core.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Push; +using Parse.Abstractions.Platform.Users; +using Parse.Infrastructure.Execution; +using Parse.Infrastructure.Utilities; -namespace Parse.Push.Internal +namespace Parse.Platform.Push { internal class ParsePushController : IParsePushController { diff --git a/Parse/Platform/Notifications/ParsePushEncoder.cs b/Parse/Platform/Push/ParsePushEncoder.cs similarity index 89% rename from Parse/Platform/Notifications/ParsePushEncoder.cs rename to Parse/Platform/Push/ParsePushEncoder.cs index 410e1b73..a739df75 100644 --- a/Parse/Platform/Notifications/ParsePushEncoder.cs +++ b/Parse/Platform/Push/ParsePushEncoder.cs @@ -2,10 +2,10 @@ using System; using System.Collections.Generic; -using Parse.Common.Internal; -using Parse.Core.Internal; +using Parse.Abstractions.Platform.Push; +using Parse.Infrastructure.Utilities; -namespace Parse.Push.Internal +namespace Parse.Platform.Push { public class ParsePushEncoder { @@ -16,14 +16,10 @@ private ParsePushEncoder() { } public IDictionary Encode(IPushState state) { if (state.Alert is null && state.Data is null) - { throw new InvalidOperationException("A push must have either an Alert or Data"); - } if (state.Channels is null && state.Query is null) - { throw new InvalidOperationException("A push must have either Channels or a Query"); - } IDictionary data = state.Data ?? new Dictionary { @@ -35,9 +31,7 @@ public IDictionary Encode(IPushState state) ParseQuery query = state.Query ?? new ParseQuery(default, "_Installation") { }; if (state.Channels != null) - { query = query.WhereContainedIn("channels", state.Channels); - } Dictionary payload = new Dictionary { @@ -46,18 +40,12 @@ public IDictionary Encode(IPushState state) }; if (state.Expiration.HasValue) - { payload["expiration_time"] = state.Expiration.Value.ToString("yyyy-MM-ddTHH:mm:ssZ"); - } else if (state.ExpirationInterval.HasValue) - { payload["expiration_interval"] = state.ExpirationInterval.Value.TotalSeconds; - } if (state.PushTime.HasValue) - { payload["push_time"] = state.PushTime.Value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"); - } return payload; } diff --git a/Parse/Platform/Notifications/ParsePushModule.cs b/Parse/Platform/Push/ParsePushModule.cs similarity index 92% rename from Parse/Platform/Notifications/ParsePushModule.cs rename to Parse/Platform/Push/ParsePushModule.cs index f383c93a..321df179 100644 --- a/Parse/Platform/Notifications/ParsePushModule.cs +++ b/Parse/Platform/Push/ParsePushModule.cs @@ -1,6 +1,3 @@ -using Parse.Common.Internal; -using Parse.Management; - namespace Parse.Push.Internal { #warning Check if the logic in this class is necessary to run. diff --git a/Parse/Library/ParsePushNotificationEvent.cs b/Parse/Platform/Push/ParsePushNotificationEvent.cs similarity index 86% rename from Parse/Library/ParsePushNotificationEvent.cs rename to Parse/Platform/Push/ParsePushNotificationEvent.cs index bf4ae316..6e82c6bb 100644 --- a/Parse/Library/ParsePushNotificationEvent.cs +++ b/Parse/Platform/Push/ParsePushNotificationEvent.cs @@ -2,9 +2,9 @@ using System; using System.Collections.Generic; -using Parse.Common.Internal; +using Parse.Infrastructure.Utilities; -namespace Parse +namespace Parse.Platform.Push { /// /// A wrapper around Parse push notification payload. @@ -14,7 +14,7 @@ public class ParsePushNotificationEvent : EventArgs internal ParsePushNotificationEvent(IDictionary content) { Content = content; - TextContent = Json.Encode(content); + TextContent = JsonUtilities.Encode(content); } // TODO: (richardross) investigate this. @@ -24,7 +24,7 @@ internal ParsePushNotificationEvent(IDictionary content) internal ParsePushNotificationEvent(string stringPayload) { TextContent = stringPayload; - Content = Json.Parse(stringPayload) as IDictionary; + Content = JsonUtilities.Parse(stringPayload) as IDictionary; } /// diff --git a/Parse/ParseQuery.cs b/Parse/Platform/Queries/ParseQuery.cs similarity index 99% rename from Parse/ParseQuery.cs rename to Parse/Platform/Queries/ParseQuery.cs index a3d1d6c5..babad75d 100644 --- a/Parse/ParseQuery.cs +++ b/Parse/Platform/Queries/ParseQuery.cs @@ -7,9 +7,11 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Core.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure; +using Parse.Infrastructure.Data; +using Parse.Infrastructure.Utilities; namespace Parse { diff --git a/Parse/Query/ParseQueryController.cs b/Parse/Platform/Queries/ParseQueryController.cs similarity index 89% rename from Parse/Query/ParseQueryController.cs rename to Parse/Platform/Queries/ParseQueryController.cs index b0239c05..cc5d775c 100644 --- a/Parse/Query/ParseQueryController.cs +++ b/Parse/Platform/Queries/ParseQueryController.cs @@ -5,10 +5,15 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; - -namespace Parse.Core.Internal +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Objects; +using Parse.Abstractions.Platform.Queries; +using Parse.Infrastructure.Data; +using Parse.Infrastructure.Execution; +using Parse.Infrastructure.Utilities; + +namespace Parse.Platform.Queries { /// /// A straightforward implementation of that uses to decode raw server data when needed. diff --git a/Parse/ParseRelation.cs b/Parse/Platform/Relations/ParseRelation.cs similarity index 97% rename from Parse/ParseRelation.cs rename to Parse/Platform/Relations/ParseRelation.cs index 348861e2..d25c5a25 100644 --- a/Parse/ParseRelation.cs +++ b/Parse/Platform/Relations/ParseRelation.cs @@ -5,11 +5,9 @@ using System.ComponentModel; using System.Diagnostics; using System.Linq.Expressions; -using System.Reflection; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Management; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure.Control; namespace Parse { diff --git a/Parse/ParseRole.cs b/Parse/Platform/Roles/ParseRole.cs similarity index 100% rename from Parse/ParseRole.cs rename to Parse/Platform/Roles/ParseRole.cs diff --git a/Parse/Platform/Security/ParseACL.cs b/Parse/Platform/Security/ParseACL.cs index c858d177..432eb721 100644 --- a/Parse/Platform/Security/ParseACL.cs +++ b/Parse/Platform/Security/ParseACL.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; using System.Linq; -using Parse.Common.Internal; -using Parse.Core.Internal; +using Parse.Abstractions.Internal; +using Parse.Abstractions.Infrastructure; namespace Parse { diff --git a/Parse/ParseSession.cs b/Parse/Platform/Sessions/ParseSession.cs similarity index 95% rename from Parse/ParseSession.cs rename to Parse/Platform/Sessions/ParseSession.cs index 3bbe176b..b53923eb 100644 --- a/Parse/ParseSession.cs +++ b/Parse/Platform/Sessions/ParseSession.cs @@ -1,8 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. using System.Collections.Generic; -using System.Threading; -using Parse.Management; namespace Parse { diff --git a/Parse/Platform/Session/ParseSessionController.cs b/Parse/Platform/Sessions/ParseSessionController.cs similarity index 84% rename from Parse/Platform/Session/ParseSessionController.cs rename to Parse/Platform/Sessions/ParseSessionController.cs index 6f608e66..5f729045 100644 --- a/Parse/Platform/Session/ParseSessionController.cs +++ b/Parse/Platform/Sessions/ParseSessionController.cs @@ -3,10 +3,16 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; - -namespace Parse.Core.Internal +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Sessions; +using Parse.Infrastructure.Utilities; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure.Execution; +using Parse.Infrastructure.Data; + +namespace Parse.Platform.Sessions { public class ParseSessionController : IParseSessionController { diff --git a/Parse/Management/ParseCurrentUserController.cs b/Parse/Platform/Users/ParseCurrentUserController.cs similarity index 88% rename from Parse/Management/ParseCurrentUserController.cs rename to Parse/Platform/Users/ParseCurrentUserController.cs index 71b50fe5..7e9aa137 100644 --- a/Parse/Management/ParseCurrentUserController.cs +++ b/Parse/Platform/Users/ParseCurrentUserController.cs @@ -5,10 +5,15 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; - -namespace Parse.Core.Internal +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Objects; +using Parse.Abstractions.Platform.Users; +using Parse.Infrastructure.Utilities; +using Parse; +using Parse.Infrastructure.Data; + +namespace Parse.Platform.Users { #warning This class needs to be rewritten (PCuUsC). @@ -32,16 +37,12 @@ public ParseUser CurrentUser get { lock (Mutex) - { return currentUser; - } } set { lock (Mutex) - { currentUser = value; - } } } @@ -50,9 +51,7 @@ public Task SetAsync(ParseUser user, CancellationToken cancellationToken) => Tas Task saveTask = default; if (user is null) - { saveTask = StorageController.LoadAsync().OnSuccess(task => task.Result.RemoveAsync(nameof(CurrentUser))).Unwrap(); - } else { // TODO (hallucinogen): we need to use ParseCurrentCoder instead of this janky encoding @@ -61,15 +60,11 @@ public Task SetAsync(ParseUser user, CancellationToken cancellationToken) => Tas data["objectId"] = user.ObjectId; if (user.CreatedAt != null) - { data["createdAt"] = user.CreatedAt.Value.ToString(ParseClient.DateFormatStrings.First(), CultureInfo.InvariantCulture); - } if (user.UpdatedAt != null) - { data["updatedAt"] = user.UpdatedAt.Value.ToString(ParseClient.DateFormatStrings.First(), CultureInfo.InvariantCulture); - } - saveTask = StorageController.LoadAsync().OnSuccess(task => task.Result.AddAsync(nameof(CurrentUser), Json.Encode(data))).Unwrap(); + saveTask = StorageController.LoadAsync().OnSuccess(task => task.Result.AddAsync(nameof(CurrentUser), JsonUtilities.Encode(data))).Unwrap(); } CurrentUser = user; @@ -81,9 +76,7 @@ public Task GetAsync(IServiceHub serviceHub, CancellationToken cancel ParseUser cachedCurrent; lock (Mutex) - { cachedCurrent = CurrentUser; - } return cachedCurrent is { } ? Task.FromResult(cachedCurrent) : TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => StorageController.LoadAsync().OnSuccess(task => { @@ -91,9 +84,7 @@ public Task GetAsync(IServiceHub serviceHub, CancellationToken cancel ParseUser user = default; if (data is string { } serialization) - { - user = ClassController.GenerateObjectFromState(ParseObjectCoder.Instance.Decode(Json.Parse(serialization) as IDictionary, Decoder, serviceHub), "_User", serviceHub); - } + user = ClassController.GenerateObjectFromState(ParseObjectCoder.Instance.Decode(JsonUtilities.Parse(serialization) as IDictionary, Decoder, serviceHub), "_User", serviceHub); return CurrentUser = user; })).Unwrap(), cancellationToken); @@ -104,9 +95,7 @@ public Task GetAsync(IServiceHub serviceHub, CancellationToken cancel public bool IsCurrent(ParseUser user) { lock (Mutex) - { return CurrentUser == user; - } } public void ClearFromMemory() => CurrentUser = default; diff --git a/Parse/ParseUser.cs b/Parse/Platform/Users/ParseUser.cs similarity index 98% rename from Parse/ParseUser.cs rename to Parse/Platform/Users/ParseUser.cs index b4fbd5ee..365fa2c5 100644 --- a/Parse/ParseUser.cs +++ b/Parse/Platform/Users/ParseUser.cs @@ -4,9 +4,10 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Management; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Platform.Authentication; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure.Utilities; namespace Parse { diff --git a/Parse/Management/ParseUserController.cs b/Parse/Platform/Users/ParseUserController.cs similarity index 89% rename from Parse/Management/ParseUserController.cs rename to Parse/Platform/Users/ParseUserController.cs index 8d306bcd..635f1470 100644 --- a/Parse/Management/ParseUserController.cs +++ b/Parse/Platform/Users/ParseUserController.cs @@ -1,13 +1,19 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; - -namespace Parse.Core.Internal +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Users; +using Parse.Infrastructure.Utilities; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure.Execution; +using Parse.Infrastructure.Data; + +namespace Parse.Platform.Users { public class ParseUserController : IParseUserController { diff --git a/Parse/Properties/Resources.Designer.cs b/Parse/Resources.Designer.cs similarity index 85% rename from Parse/Properties/Resources.Designer.cs rename to Parse/Resources.Designer.cs index f66708a0..43abe2ab 100644 --- a/Parse/Properties/Resources.Designer.cs +++ b/Parse/Resources.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -8,10 +8,8 @@ // //------------------------------------------------------------------------------ -namespace Parse.Properties { - using System; - - +namespace Parse +{ /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,58 +20,70 @@ namespace Parse.Properties { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - + internal class Resources + { + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { + internal Resources() + { } - + /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Parse.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { + internal static global::System.Globalization.CultureInfo Culture + { + get + { return resourceCulture; } - set { + set + { resourceCulture = value; } } - + /// /// Looks up a localized string similar to This storage controller is not configured to use physical files to store information. Data is hosted in system memory.. /// - internal static string ConcurrentUserStorageControllerFileOperationNotSupportedMessage { - get { + internal static string ConcurrentUserStorageControllerFileOperationNotSupportedMessage + { + get + { return ResourceManager.GetString("ConcurrentUserStorageControllerFileOperationNotSupportedMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to Mutating a storage dictionary is an asynchronous operation as the storing file needs to be modified.. /// - internal static string StorageDictionarySynchronousMutationNotSupportedMessage { - get { + internal static string StorageDictionarySynchronousMutationNotSupportedMessage + { + get + { return ResourceManager.GetString("StorageDictionarySynchronousMutationNotSupportedMessage", resourceCulture); } } diff --git a/Parse/Properties/Resources.resx b/Parse/Resources.resx similarity index 100% rename from Parse/Properties/Resources.resx rename to Parse/Resources.resx diff --git a/Parse/AnalyticsServiceExtensions.cs b/Parse/Utilities/AnalyticsServiceExtensions.cs similarity index 96% rename from Parse/AnalyticsServiceExtensions.cs rename to Parse/Utilities/AnalyticsServiceExtensions.cs index 0418b50e..1e9e4d3b 100644 --- a/Parse/AnalyticsServiceExtensions.cs +++ b/Parse/Utilities/AnalyticsServiceExtensions.cs @@ -2,12 +2,9 @@ using System; using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Analytics.Internal; -using Parse.Common.Internal; -using Parse.Core.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Infrastructure.Utilities; namespace Parse { diff --git a/Parse/CloudCodeServiceExtensions.cs b/Parse/Utilities/CloudCodeServiceExtensions.cs similarity index 97% rename from Parse/CloudCodeServiceExtensions.cs rename to Parse/Utilities/CloudCodeServiceExtensions.cs index 841e5efe..cfcde189 100644 --- a/Parse/CloudCodeServiceExtensions.cs +++ b/Parse/Utilities/CloudCodeServiceExtensions.cs @@ -3,9 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Core.Internal; -using Parse.Management; +using Parse.Abstractions.Infrastructure; namespace Parse { diff --git a/Parse/ConfigurationServiceExtensions.cs b/Parse/Utilities/ConfigurationServiceExtensions.cs similarity index 95% rename from Parse/ConfigurationServiceExtensions.cs rename to Parse/Utilities/ConfigurationServiceExtensions.cs index d3d61ead..019b3299 100644 --- a/Parse/ConfigurationServiceExtensions.cs +++ b/Parse/Utilities/ConfigurationServiceExtensions.cs @@ -3,8 +3,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Core.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Platform.Configuration; namespace Parse { diff --git a/Parse/InstallationServiceExtensions.cs b/Parse/Utilities/InstallationServiceExtensions.cs similarity index 97% rename from Parse/InstallationServiceExtensions.cs rename to Parse/Utilities/InstallationServiceExtensions.cs index 9c9c549a..afd4b9ad 100644 --- a/Parse/InstallationServiceExtensions.cs +++ b/Parse/Utilities/InstallationServiceExtensions.cs @@ -1,8 +1,7 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; namespace Parse { diff --git a/Parse/ObjectServiceExtensions.cs b/Parse/Utilities/ObjectServiceExtensions.cs similarity index 99% rename from Parse/ObjectServiceExtensions.cs rename to Parse/Utilities/ObjectServiceExtensions.cs index 9a91c423..2c7c2765 100644 --- a/Parse/ObjectServiceExtensions.cs +++ b/Parse/Utilities/ObjectServiceExtensions.cs @@ -5,10 +5,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Core.Internal; -using Parse.Utilities; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Internal; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure.Utilities; +using Parse.Infrastructure.Data; namespace Parse { diff --git a/Parse/Utilities/ParseExtensions.cs b/Parse/Utilities/ParseExtensions.cs index 258a91df..682a1234 100644 --- a/Parse/Utilities/ParseExtensions.cs +++ b/Parse/Utilities/ParseExtensions.cs @@ -1,12 +1,8 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Library; +using Parse.Infrastructure.Utilities; namespace Parse { diff --git a/Parse/Utilities/ParseFileExtensions.cs b/Parse/Utilities/ParseFileExtensions.cs index 31288c3d..1f5f3e2e 100644 --- a/Parse/Utilities/ParseFileExtensions.cs +++ b/Parse/Utilities/ParseFileExtensions.cs @@ -2,7 +2,7 @@ using System; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Internal { /// /// So here's the deal. We have a lot of internal APIs for ParseObject, ParseUser, etc. diff --git a/Parse/Utilities/ParseQueryExtensions.cs b/Parse/Utilities/ParseQueryExtensions.cs index 70f2b2e9..b8e2d816 100644 --- a/Parse/Utilities/ParseQueryExtensions.cs +++ b/Parse/Utilities/ParseQueryExtensions.cs @@ -6,9 +6,9 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Parse.Common.Internal; +using Parse.Infrastructure.Data; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Internal { #warning Fully refactor at some point. diff --git a/Parse/Utilities/ParseRelationExtensions.cs b/Parse/Utilities/ParseRelationExtensions.cs index ed2511bf..42bd5558 100644 --- a/Parse/Utilities/ParseRelationExtensions.cs +++ b/Parse/Utilities/ParseRelationExtensions.cs @@ -1,6 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -namespace Parse.Core.Internal +namespace Parse.Abstractions.Internal { /// /// So here's the deal. We have a lot of internal APIs for ParseObject, ParseUser, etc. diff --git a/Parse/Utilities/ParseUserExtensions.cs b/Parse/Utilities/ParseUserExtensions.cs index 07694a61..30e3bd87 100644 --- a/Parse/Utilities/ParseUserExtensions.cs +++ b/Parse/Utilities/ParseUserExtensions.cs @@ -3,9 +3,8 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -namespace Parse.Core.Internal +namespace Parse.Abstractions.Internal { /// /// So here's the deal. We have a lot of internal APIs for ParseObject, ParseUser, etc. diff --git a/Parse/PushServiceExtensions.cs b/Parse/Utilities/PushServiceExtensions.cs similarity index 98% rename from Parse/PushServiceExtensions.cs rename to Parse/Utilities/PushServiceExtensions.cs index c2a0491c..ad94e282 100644 --- a/Parse/PushServiceExtensions.cs +++ b/Parse/Utilities/PushServiceExtensions.cs @@ -4,8 +4,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Infrastructure.Utilities; +using Parse.Platform.Push; namespace Parse { diff --git a/Parse/QueryServiceExtensions.cs b/Parse/Utilities/QueryServiceExtensions.cs similarity index 98% rename from Parse/QueryServiceExtensions.cs rename to Parse/Utilities/QueryServiceExtensions.cs index b5d6ef1f..c31b66e6 100644 --- a/Parse/QueryServiceExtensions.cs +++ b/Parse/Utilities/QueryServiceExtensions.cs @@ -4,7 +4,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; namespace Parse { diff --git a/Parse/RoleServiceExtensions.cs b/Parse/Utilities/RoleServiceExtensions.cs similarity index 93% rename from Parse/RoleServiceExtensions.cs rename to Parse/Utilities/RoleServiceExtensions.cs index 09b4ef6a..fcd5d05a 100644 --- a/Parse/RoleServiceExtensions.cs +++ b/Parse/Utilities/RoleServiceExtensions.cs @@ -1,6 +1,6 @@ // Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. -using Parse.Abstractions.Library; +using Parse.Abstractions.Infrastructure; namespace Parse { diff --git a/Parse/SessionsServiceExtensions.cs b/Parse/Utilities/SessionsServiceExtensions.cs similarity index 96% rename from Parse/SessionsServiceExtensions.cs rename to Parse/Utilities/SessionsServiceExtensions.cs index 62bb8301..a23aac95 100644 --- a/Parse/SessionsServiceExtensions.cs +++ b/Parse/Utilities/SessionsServiceExtensions.cs @@ -2,9 +2,8 @@ using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Core.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Infrastructure.Utilities; namespace Parse { diff --git a/Parse/UserServiceExtensions.cs b/Parse/Utilities/UserServiceExtensions.cs similarity index 98% rename from Parse/UserServiceExtensions.cs rename to Parse/Utilities/UserServiceExtensions.cs index e8305af2..26b29b34 100644 --- a/Parse/UserServiceExtensions.cs +++ b/Parse/Utilities/UserServiceExtensions.cs @@ -3,9 +3,10 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Parse.Abstractions.Library; -using Parse.Common.Internal; -using Parse.Core.Internal; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Internal; +using Parse.Abstractions.Platform.Authentication; +using Parse.Infrastructure.Utilities; namespace Parse { diff --git a/Parse/Utilities/XamarinAttributes.cs b/Parse/Utilities/XamarinAttributes.cs deleted file mode 100644 index eae589d1..00000000 --- a/Parse/Utilities/XamarinAttributes.cs +++ /dev/null @@ -1,406 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Parse.Common.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() => 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), - }; - } -} From a9a93e3103235f602e67b3ed0a0614b1201d24c1 Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Sun, 3 May 2020 19:39:54 -0700 Subject: [PATCH 11/24] Add late initialized IMutableServiceHub implementation, use IServiceHub implementation instance passed to ParseClient constructor as BuildHub call target if the implementation also implements IMutableServiceHub, make sure IMutableServiceHub-implementing IServiceHub implementations passed to ParseClient constructor are passed the given IServerConnectionData implementation instance if they have null ServerConnectionData properties, allow values to be set in LateInitializer, change LateInitializer to be an internal class, add tests for LateInitializer, rename Parse.Test to Parse.Tests, update appveyor build script to reflect Parse.Test rename and Visual Studio target version bump, add various syntax adjustments to make code more idiomatic, remove ParsePushModule.cs, and fix a race condition in ParseCommandRunner.cs. --- Parse.Tests/LateInitializerTests.cs | 43 +++++ .../Execution/ParseCommandRunner.cs | 69 ++++---- .../LateInitializedMutableServiceHub.cs | 167 ++++++++++++++++++ .../Utilities/LateInitializer.cs | 36 +++- .../ParseCurrentInstallationController.cs | 19 +- Parse/Platform/ParseClient.cs | 8 +- Parse/Platform/Push/ParsePushModule.cs | 20 --- 7 files changed, 296 insertions(+), 66 deletions(-) create mode 100644 Parse.Tests/LateInitializerTests.cs create mode 100644 Parse/Infrastructure/LateInitializedMutableServiceHub.cs delete mode 100644 Parse/Platform/Push/ParsePushModule.cs diff --git a/Parse.Tests/LateInitializerTests.cs b/Parse.Tests/LateInitializerTests.cs new file mode 100644 index 00000000..f57433ac --- /dev/null +++ b/Parse.Tests/LateInitializerTests.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Parse.Infrastructure.Utilities; + +namespace Parse.Tests +{ + // TODO: Add more tests. + + [TestClass] + public class LateInitializerTests + { + LateInitializer LateInitializer { get; } = new LateInitializer { }; + + [TestInitialize] + public void Clear() => LateInitializer.Reset(); + + [DataTestMethod, DataRow("Bruh", "Hello"), DataRow("Cheese", ""), DataRow("", "Waffle"), DataRow("Toaster", "Toad"), DataRow(default, "Duck"), DataRow("Dork", default)] + public void TestAlteredValueGetValuePostGenerationCall(string initialValue, string finalValue) + { + string GetValue() => LateInitializer.GetValue(() => initialValue); + bool SetValue() => LateInitializer.SetValue(finalValue); + + Assert.AreEqual(initialValue, GetValue()); + + Assert.IsTrue(SetValue()); + Assert.AreNotEqual(initialValue, GetValue()); + Assert.AreEqual(finalValue, GetValue()); + } + + [DataTestMethod, DataRow("Bruh", "Hello"), DataRow("Cheese", ""), DataRow("", "Waffle"), DataRow("Toaster", "Toad"), DataRow(default, "Duck"), DataRow("Dork", default)] + public void TestInitialGetValueCallPostSetValueCall(string initialValue, string finalValue) + { + string GetValue() => LateInitializer.GetValue(() => finalValue); + bool SetValue() => LateInitializer.SetValue(initialValue); + + Assert.IsTrue(SetValue()); + Assert.AreNotEqual(finalValue, GetValue()); + Assert.AreEqual(initialValue, GetValue()); + } + } +} diff --git a/Parse/Infrastructure/Execution/ParseCommandRunner.cs b/Parse/Infrastructure/Execution/ParseCommandRunner.cs index 1af00f9a..ab74abb4 100644 --- a/Parse/Infrastructure/Execution/ParseCommandRunner.cs +++ b/Parse/Infrastructure/Execution/ParseCommandRunner.cs @@ -100,49 +100,58 @@ Task PrepareCommand(ParseCommand command) Task installationIdFetchTask = InstallationController.GetAsync().ContinueWith(task => { - newCommand.Headers.Add(new KeyValuePair("X-Parse-Installation-Id", task.Result.ToString())); + lock (newCommand.Headers) + { + newCommand.Headers.Add(new KeyValuePair("X-Parse-Installation-Id", task.Result.ToString())); + } return newCommand; }); - newCommand.Headers.Add(new KeyValuePair("X-Parse-Application-Id", ServerConnectionData.ApplicationID)); - newCommand.Headers.Add(new KeyValuePair("X-Parse-Client-Version", ParseClient.Version.ToString())); + // Locks needed due to installationFetchTask continuation newCommand.Headers.Add-call-related race condition (occurred once in Unity). + // TODO: Consider removal of installationFetchTask variable. - if (ServerConnectionData.Headers != null) + lock (newCommand.Headers) { - foreach (KeyValuePair header in ServerConnectionData.Headers) + newCommand.Headers.Add(new KeyValuePair("X-Parse-Application-Id", ServerConnectionData.ApplicationID)); + newCommand.Headers.Add(new KeyValuePair("X-Parse-Client-Version", ParseClient.Version.ToString())); + + if (ServerConnectionData.Headers != null) { - newCommand.Headers.Add(header); + foreach (KeyValuePair header in ServerConnectionData.Headers) + { + newCommand.Headers.Add(header); + } } - } - if (!String.IsNullOrEmpty(MetadataController.HostManifestData.Version)) - { - newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Build-Version", MetadataController.HostManifestData.Version)); - } + if (!String.IsNullOrEmpty(MetadataController.HostManifestData.Version)) + { + newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Build-Version", MetadataController.HostManifestData.Version)); + } - if (!String.IsNullOrEmpty(MetadataController.HostManifestData.ShortVersion)) - { - newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Display-Version", MetadataController.HostManifestData.ShortVersion)); - } + if (!String.IsNullOrEmpty(MetadataController.HostManifestData.ShortVersion)) + { + newCommand.Headers.Add(new KeyValuePair("X-Parse-App-Display-Version", MetadataController.HostManifestData.ShortVersion)); + } - if (!String.IsNullOrEmpty(MetadataController.EnvironmentData.OSVersion)) - { - newCommand.Headers.Add(new KeyValuePair("X-Parse-OS-Version", MetadataController.EnvironmentData.OSVersion)); - } + if (!String.IsNullOrEmpty(MetadataController.EnvironmentData.OSVersion)) + { + newCommand.Headers.Add(new KeyValuePair("X-Parse-OS-Version", MetadataController.EnvironmentData.OSVersion)); + } - if (!String.IsNullOrEmpty(ServerConnectionData.MasterKey)) - { - newCommand.Headers.Add(new KeyValuePair("X-Parse-Master-Key", ServerConnectionData.MasterKey)); - } - else - { - newCommand.Headers.Add(new KeyValuePair("X-Parse-Windows-Key", ServerConnectionData.Key)); - } + if (!String.IsNullOrEmpty(ServerConnectionData.MasterKey)) + { + newCommand.Headers.Add(new KeyValuePair("X-Parse-Master-Key", ServerConnectionData.MasterKey)); + } + else + { + newCommand.Headers.Add(new KeyValuePair("X-Parse-Windows-Key", ServerConnectionData.Key)); + } - if (UserController.Value.RevocableSessionEnabled) - { - newCommand.Headers.Add(new KeyValuePair("X-Parse-Revocable-Session", "1")); + if (UserController.Value.RevocableSessionEnabled) + { + newCommand.Headers.Add(new KeyValuePair("X-Parse-Revocable-Session", "1")); + } } return installationIdFetchTask; diff --git a/Parse/Infrastructure/LateInitializedMutableServiceHub.cs b/Parse/Infrastructure/LateInitializedMutableServiceHub.cs new file mode 100644 index 00000000..da687735 --- /dev/null +++ b/Parse/Infrastructure/LateInitializedMutableServiceHub.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Installations; +using Parse.Abstractions.Platform.Objects; +using Parse.Abstractions.Platform.Cloud; +using Parse.Abstractions.Platform.Configuration; +using Parse.Abstractions.Platform.Files; +using Parse.Abstractions.Platform.Push; +using Parse.Abstractions.Platform.Queries; +using Parse.Abstractions.Platform.Sessions; +using Parse.Abstractions.Platform.Users; +using Parse.Abstractions.Platform.Analytics; +using Parse.Infrastructure.Execution; +using Parse.Platform.Objects; +using Parse.Platform.Installations; +using Parse.Platform.Cloud; +using Parse.Platform.Configuration; +using Parse.Platform.Files; +using Parse.Platform.Queries; +using Parse.Platform.Sessions; +using Parse.Platform.Users; +using Parse.Platform.Analytics; +using Parse.Platform.Push; +using Parse.Infrastructure.Data; +using Parse.Infrastructure.Utilities; + +namespace Parse.Infrastructure +{ + public class LateInitializedMutableServiceHub : IMutableServiceHub + { + LateInitializer LateInitializer { get; } = new LateInitializer { }; + + public IServiceHubCloner Cloner { get; set; } + + public IMetadataController MetadataController + { + get => LateInitializer.GetValue(() => new MetadataController { EnvironmentData = EnvironmentData.Inferred, HostManifestData = HostManifestData.Inferred }); + set => LateInitializer.SetValue(value); + } + + public IWebClient WebClient + { + get => LateInitializer.GetValue(() => new UniversalWebClient { }); + set => LateInitializer.SetValue(value); + } + + public IStorageController StorageController + { + get => LateInitializer.GetValue(() => new StorageController { }); + set => LateInitializer.SetValue(value); + } + + public IParseObjectClassController ClassController + { + get => LateInitializer.GetValue(() => new ParseObjectClassController { }); + set => LateInitializer.SetValue(value); + } + + public IParseInstallationController InstallationController + { + get => LateInitializer.GetValue(() => new ParseInstallationController(StorageController)); + set => LateInitializer.SetValue(value); + } + + public IParseCommandRunner CommandRunner + { + get => LateInitializer.GetValue(() => new ParseCommandRunner(WebClient, InstallationController, MetadataController, ServerConnectionData, new Lazy(() => UserController))); + set => LateInitializer.SetValue(value); + } + + public IParseCloudCodeController CloudCodeController + { + get => LateInitializer.GetValue(() => new ParseCloudCodeController(CommandRunner, Decoder)); + set => LateInitializer.SetValue(value); + } + + public IParseConfigurationController ConfigurationController + { + get => LateInitializer.GetValue(() => new ParseConfigurationController(CommandRunner, StorageController, Decoder)); + set => LateInitializer.SetValue(value); + } + + public IParseFileController FileController + { + get => LateInitializer.GetValue(() => new ParseFileController(CommandRunner)); + set => LateInitializer.SetValue(value); + } + + public IParseObjectController ObjectController + { + get => LateInitializer.GetValue(() => new ParseObjectController(CommandRunner, Decoder, ServerConnectionData)); + set => LateInitializer.SetValue(value); + } + + public IParseQueryController QueryController + { + get => LateInitializer.GetValue(() => new ParseQueryController(CommandRunner, Decoder)); + set => LateInitializer.SetValue(value); + } + + public IParseSessionController SessionController + { + get => LateInitializer.GetValue(() => new ParseSessionController(CommandRunner, Decoder)); + set => LateInitializer.SetValue(value); + } + + public IParseUserController UserController + { + get => LateInitializer.GetValue(() => new ParseUserController(CommandRunner, Decoder)); + set => LateInitializer.SetValue(value); + } + + public IParseCurrentUserController CurrentUserController + { + get => LateInitializer.GetValue(() => new ParseCurrentUserController(StorageController, ClassController, Decoder)); + set => LateInitializer.SetValue(value); + } + + public IParseAnalyticsController AnalyticsController + { + get => LateInitializer.GetValue(() => new ParseAnalyticsController(CommandRunner)); + set => LateInitializer.SetValue(value); + } + + public IParseInstallationCoder InstallationCoder + { + get => LateInitializer.GetValue(() => new ParseInstallationCoder(Decoder, ClassController)); + set => LateInitializer.SetValue(value); + } + + public IParsePushChannelsController PushChannelsController + { + get => LateInitializer.GetValue(() => new ParsePushChannelsController(CurrentInstallationController)); + set => LateInitializer.SetValue(value); + } + + public IParsePushController PushController + { + get => LateInitializer.GetValue(() => new ParsePushController(CommandRunner, CurrentUserController)); + set => LateInitializer.SetValue(value); + } + + public IParseCurrentInstallationController CurrentInstallationController + { + get => LateInitializer.GetValue(() => new ParseCurrentInstallationController(InstallationController, StorageController, InstallationCoder, ClassController)); + set => LateInitializer.SetValue(value); + } + + public IParseDataDecoder Decoder + { + get => LateInitializer.GetValue(() => new ParseDataDecoder(ClassController)); + set => LateInitializer.SetValue(value); + } + + public IParseInstallationDataFinalizer InstallationDataFinalizer + { + get => LateInitializer.GetValue(() => new ParseInstallationDataFinalizer { }); + set => LateInitializer.SetValue(value); + } + + public IServerConnectionData ServerConnectionData { get; set; } + } +} diff --git a/Parse/Infrastructure/Utilities/LateInitializer.cs b/Parse/Infrastructure/Utilities/LateInitializer.cs index 68faf9b2..f3ddd129 100644 --- a/Parse/Infrastructure/Utilities/LateInitializer.cs +++ b/Parse/Infrastructure/Utilities/LateInitializer.cs @@ -7,15 +7,18 @@ namespace Parse.Infrastructure.Utilities /// /// A wrapper over a dictionary from value generator to value. Uses the fact that lambda expressions in a specific location are cached, so the cost of instantiating a generator delegate is only incurred once at the call site of and subsequent calls look up the result of the first generation from the dictionary based on the hash of the generator delegate. This is effectively a lazy initialization mechanism that allows the member type to remain unchanged. /// - public class LateInitializer + internal class LateInitializer { Lazy, object>> Storage { get; set; } = new Lazy, object>> { }; public TData GetValue(Func generator) { lock (generator) - if (Storage.Value.TryGetValue(generator as Func, out object data)) + { + if (Storage.IsValueCreated && Storage.Value.Keys.OfType>().FirstOrDefault() is { } key && Storage.Value.TryGetValue(key as Func, out object data)) + { return (TData) data; + } else { TData result = generator.Invoke(); @@ -23,17 +26,44 @@ public TData GetValue(Func generator) Storage.Value.Add(generator as Func, result); return result; } + } } public bool ClearValue() { lock (Storage) + { if (Storage.IsValueCreated && Storage.Value.Keys.OfType>().FirstOrDefault() is { } key) + { lock (key) { Storage.Value.Remove(key as Func); return true; } + } + } + + return false; + } + + public bool SetValue(TData value, bool initialize = true) + { + lock (Storage) + { + if (Storage.IsValueCreated && Storage.Value.Keys.OfType>().FirstOrDefault() is { } key) + { + lock (key) + { + Storage.Value[key as Func] = value; + return true; + } + } + else if (initialize) + { + Storage.Value[new Func(() => value) as Func] = value; + return true; + } + } return false; } @@ -41,11 +71,13 @@ public bool ClearValue() public bool Reset() { lock (Storage) + { if (Storage.IsValueCreated) { Storage.Value.Clear(); return true; } + } return false; } diff --git a/Parse/Platform/Installations/ParseCurrentInstallationController.cs b/Parse/Platform/Installations/ParseCurrentInstallationController.cs index b1a5c5a1..62888309 100644 --- a/Parse/Platform/Installations/ParseCurrentInstallationController.cs +++ b/Parse/Platform/Installations/ParseCurrentInstallationController.cs @@ -41,12 +41,16 @@ internal ParseInstallation CurrentInstallation get { lock (Mutex) + { return CurrentInstallationValue; + } } set { lock (Mutex) + { CurrentInstallationValue = value; + } } } @@ -63,10 +67,7 @@ public Task GetAsync(IServiceHub serviceHub, CancellationToke ParseInstallation cachedCurrent; cachedCurrent = CurrentInstallation; - if (cachedCurrent != null) - return Task.FromResult(cachedCurrent); - - return TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => StorageController.LoadAsync().OnSuccess(stroage => + return cachedCurrent is { } ? Task.FromResult(cachedCurrent) : TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => StorageController.LoadAsync().OnSuccess(stroage => { Task fetchTask; stroage.Result.TryGetValue(ParseInstallationKey, out object temp); @@ -90,17 +91,11 @@ public Task GetAsync(IServiceHub serviceHub, CancellationToke })).Unwrap().Unwrap(), cancellationToken); } - public Task ExistsAsync(CancellationToken cancellationToken) - { - if (CurrentInstallation != null) - return Task.FromResult(true); - - return TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => StorageController.LoadAsync().OnSuccess(s => s.Result.ContainsKey(ParseInstallationKey))).Unwrap(), cancellationToken); - } + public Task ExistsAsync(CancellationToken cancellationToken) => CurrentInstallation is { } ? Task.FromResult(true) : TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => StorageController.LoadAsync().OnSuccess(storageTask => storageTask.Result.ContainsKey(ParseInstallationKey))).Unwrap(), cancellationToken); public bool IsCurrent(ParseInstallation installation) => CurrentInstallation == installation; - public void ClearFromMemory() => CurrentInstallation = null; + public void ClearFromMemory() => CurrentInstallation = default; public void ClearFromDisk() { diff --git a/Parse/Platform/ParseClient.cs b/Parse/Platform/ParseClient.cs index b384d5c0..92751154 100644 --- a/Parse/Platform/ParseClient.cs +++ b/Parse/Platform/ParseClient.cs @@ -65,7 +65,7 @@ public class ParseClient : CustomServiceHub, IServiceHubComposer /// The server URI provided in the Parse dashboard. /// /// A service hub to override internal services and thereby make the Parse SDK operate in a custom manner. - public ParseClient(string application, string serverURI, IServiceHub serviceHub = default, params IServiceHubMutator[] configurators) : this(new ServerConnectionData { ApplicationID = application, ServerURI = serverURI }, serviceHub, configurators) { } + public ParseClient(string application, string serverURI, string key, IServiceHub serviceHub = default, params IServiceHubMutator[] configurators) : this(new ServerConnectionData { ApplicationID = application, ServerURI = serverURI, Key = key }, serviceHub, configurators) { } /// /// Authenticates this client as belonging to your application. This must be @@ -100,7 +100,11 @@ public ParseClient(IServerConnectionData configuration, IServiceHub serviceHub = if (configurators is { Length: int length } && length > 0) { - Services = BuildHub(default, Services, configurators); + Services = serviceHub switch + { + IMutableServiceHub { } mutableServiceHub => BuildHub((Hub: mutableServiceHub, mutableServiceHub.ServerConnectionData = serviceHub.ServerConnectionData ?? Services.ServerConnectionData).Hub, Services, configurators), + { } => BuildHub(default, Services, configurators) + }; } Services.ClassController.AddIntrinsic(); diff --git a/Parse/Platform/Push/ParsePushModule.cs b/Parse/Platform/Push/ParsePushModule.cs deleted file mode 100644 index 321df179..00000000 --- a/Parse/Platform/Push/ParsePushModule.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Parse.Push.Internal -{ -#warning Check if the logic in this class is necessary to run. - - //public class ParsePushModule : IParseModule - //{ - // public void ExecuteModuleRegistrationHook() - // { - // } - - // public void ExecuteLibraryInitializationHook() - // { - // ParseObject.RegisterDerivative(); - - // ParseCorePlugins.Instance.SubclassingController.AddRegisterHook(typeof(ParseInstallation), () => ParsePushPlugins.Instance.CurrentInstallationController.ClearFromMemory()); - - // ParsePushPlugins.Instance.DeviceInfoController.Initialize(); - // } - //} -} \ No newline at end of file From f1e3068d8b6577bbf3faf24f301a468d53ba98b9 Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Mon, 4 May 2020 06:02:10 -0700 Subject: [PATCH 12/24] Update README.md, add and document SignUpAsync IServiceHub extension method, and update documentation of non-cloning ParseClient constructors and LogInAsync IServiceHub extension method. --- Parse/Platform/ParseClient.cs | 24 ++-- Parse/Utilities/UserServiceExtensions.cs | 28 +++- README.md | 155 +++++++++++++++++++---- 3 files changed, 165 insertions(+), 42 deletions(-) diff --git a/Parse/Platform/ParseClient.cs b/Parse/Platform/ParseClient.cs index 92751154..f40919ca 100644 --- a/Parse/Platform/ParseClient.cs +++ b/Parse/Platform/ParseClient.cs @@ -55,27 +55,21 @@ public class ParseClient : CustomServiceHub, IServiceHubComposer // TODO: Implement IServiceHubMutator in all IServiceHub-implementing classes in Parse.Library and possibly require all implementations to do so as an efficiency improvement over instantiating an OrchestrationServiceHub, only for another one to be possibly instantiated when configurators are specified. /// - /// Authenticates this client as belonging to your application. This must be - /// called before your application can use the Parse library. The recommended - /// way is to put a call to ParseClient.Initialize in your - /// Application startup. + /// Creates a new and authenticates it as belonging to your application. This class is a hub for interacting with the SDK. The recommended way to use this class on client applications is to instantiate it, then call on it in your application entry point. This allows you to access . /// - /// The Application ID provided in the Parse dashboard. - /// - /// The server URI provided in the Parse dashboard. - /// + /// The Application ID provided in the Parse dashboard. + /// The server URI provided in the Parse dashboard. + /// The .NET Key provided in the Parse dashboard. /// A service hub to override internal services and thereby make the Parse SDK operate in a custom manner. - public ParseClient(string application, string serverURI, string key, IServiceHub serviceHub = default, params IServiceHubMutator[] configurators) : this(new ServerConnectionData { ApplicationID = application, ServerURI = serverURI, Key = key }, serviceHub, configurators) { } + /// A set of implementation instances to tweak the behaviour of the SDK. + public ParseClient(string applicationID, string serverURI, string key, IServiceHub serviceHub = default, params IServiceHubMutator[] configurators) : this(new ServerConnectionData { ApplicationID = applicationID, ServerURI = serverURI, Key = key }, serviceHub, configurators) { } /// - /// Authenticates this client as belonging to your application. This must be - /// called before your application can use the Parse library. The recommended - /// way is to put a call to ParseClient.Initialize in your - /// Application startup. + /// Creates a new and authenticates it as belonging to your application. This class is a hub for interacting with the SDK. The recommended way to use this class on client applications is to instantiate it, then call on it in your application entry point. This allows you to access . /// - /// The configuration to initialize Parse with. - /// + /// The configuration to initialize Parse with. /// A service hub to override internal services and thereby make the Parse SDK operate in a custom manner. + /// A set of implementation instances to tweak the behaviour of the SDK. public ParseClient(IServerConnectionData configuration, IServiceHub serviceHub = default, params IServiceHubMutator[] configurators) { Services = serviceHub is { } ? new OrchestrationServiceHub { Custom = serviceHub, Default = new ServiceHub { ServerConnectionData = GenerateServerConnectionData() } } : new ServiceHub { ServerConnectionData = GenerateServerConnectionData() } as IServiceHub; diff --git a/Parse/Utilities/UserServiceExtensions.cs b/Parse/Utilities/UserServiceExtensions.cs index 26b29b34..fff1167a 100644 --- a/Parse/Utilities/UserServiceExtensions.cs +++ b/Parse/Utilities/UserServiceExtensions.cs @@ -21,10 +21,34 @@ internal static string GetCurrentSessionToken(this IServiceHub serviceHub) internal static Task GetCurrentSessionTokenAsync(this IServiceHub serviceHub, CancellationToken cancellationToken = default) => serviceHub.CurrentUserController.GetCurrentSessionTokenAsync(serviceHub, cancellationToken); + // TODO: Consider renaming SignUpAsync and LogInAsync to SignUpWithAsync and LogInWithAsync, respectively. + // TODO: Consider returning the created user from the SignUpAsync overload that accepts a username and password. + /// - /// 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 . + /// Creates a new , saves it with the target Parse Server instance, and then authenticates it on the target client. + /// + /// The instance to target when creating the user and authenticating. + /// The value that should be used for . + /// The value that should be used for . + /// The cancellation token. + public static Task SignUpAsync(this IServiceHub serviceHub, string username, string password, CancellationToken cancellationToken = default) => serviceHub.SignUpAsync(new ParseUser { Username = username, Password = password }, cancellationToken); + + /// + /// Saves the provided instance with the target Parse Server instance and then authenticates it on the target client. + /// + /// The instance to target when creating the user and authenticating. + /// The instance to save on the target Parse Server instance and authenticate. + /// The cancellation token. + public static Task SignUpAsync(this IServiceHub serviceHub, ParseUser user, CancellationToken cancellationToken = default) + { + user.Bind(serviceHub); + return user.SignUpAsync(cancellationToken); + } + + /// + /// Logs in a user with a username and password. On success, this saves the session to disk or to memory so you can retrieve the currently logged in user using . /// + /// The instance to target when logging in. /// The username to log in with. /// The password to log in with. /// The cancellation token. diff --git a/README.md b/README.md index 3466feea..ea82bf9c 100644 --- a/README.md +++ b/README.md @@ -9,51 +9,156 @@ ![Twitter Follow](https://img.shields.io/twitter/follow/ParsePlatform.svg?label=Follow%20us%20on%20Twitter&style=social) ## Getting Started -The latest stable release of the SDK is available as a [NuGet package][nuget-link]. Note that the latest package currently available on the official distribution channel is quite old. -To use the most up-to-date code, build this project and reference the generated NuGet package. +The latest stable release of the SDK is available as [a NuGet package][nuget-link]. Note that the latest package currently available on the official distribution channel is quite old; to use the most up-to-date code, build this project and reference the generated NuGet package. ## Using the Code -Make sure you are using the project's root namespace: +Make sure you are using the project's root namespace. -```cs +```csharp using Parse; ``` -Then, in your program's entry point, paste the following code, with the text reflecting your application and Parse Server setup emplaced between the quotation marks. +The `ParseClient` class has three constructors, one allowing you to specify your Application ID, Server URI, and .NET Key as well as some configuration items, one for creating clones of `ParseClient.Instance` if `Publicize` was called on an instance previously, and another, accepting an `IServerConnectionData` implementation instance which exposes the data needed for the SDK to connect to a Parse Server instance, like the first constructor, but with a few extra options. You can create your own `IServerConnectionData` implementation or use the `ServerConnectionData` struct. `IServerConnectionData` allows you to expose a value the SDK should use as the master key, as well as some extra request headers if needed. -```cs -ParseClient.Initialize(new ParseClient.Configuration +```csharp +ParseClient client = new ParseClient("Your Application ID", "The Parse Server Instance Host URI", "Your .NET Key"); +``` + +```csharp +ParseClient client = new ParseClient(new ServerConnectionData { - ApplicationID = "", - Key = "", - ServerURI = "" + ApplicationID = "Your Application ID", + ServerURI = "The Parse Server Instance Host URI", + Key = "Your .NET Key" }); ``` -`ApplicationID` is your app's `ApplicationId` field from your Parse Server. -`Key` is your app's `DotNetKey` field from your Parse Server. -`ServerURI` is the full URL to your web-hosted Parse Server. +The two non-cloning `ParseClient` constructors contain optional parameters for an `IServiceHub` implementation instance and an array of `IServiceHubMutator`s. These should only be used when the behaviour of the SDK needs to be changed such as [when it is used with the Unity game engine](#use-in-unity-client). + +To find full usage instructions for the latest stable release, please visit the [Parse docs website][parse-docs-link]. Please note that the latest stable release is quite old and does not reflect the work being done at the moment. -If you would like to, you can also set the `MasterKey` property, which will allow the SDK to bypass any CLPs and object permissions that are set. This property should be compatible with read-only master keys as well. +### Common Definitions -There are also a few optional parameters you can choose to set if you prefer or are experiencing issues with the SDK; sometimes the operation that generates values for these properties automatically can fail unexpectedly, causing the SDK to not be able to initialize, so these properties are provided to give you the ability to bypass that operation by providing the details outright. +- `Application ID`: +Your app's `ApplicationId` field from your Parse Server. +- `Key`: +Your app's `.NET Key` field from your Parse Server. +- `Master Key`: +Your app's `Master Key` field from your Parse Server. Using this key with the SDK will allow it to bypass any CLPs and object permissions that are set. This also should be compatible with read-only master keys as well. +- `Server URI`: +The full URL to your web-hosted Parse Server. -`StorageConfiguration` represents some metadata information usually collected reflectively about the project for the purpose of data caching. -`VersionInfo` represents some version information usually collected reflectively about the project for the purposes of data caching and metadata collection for installation object creation. +### Client-Side Use -To find full usage instructions for the latest stable release, please visit the [Parse docs website][parse-docs-link]. Please note that the latest stable release is quite old and does not reflect the work being done at the moment. +In your program's entry point, instantiate a `ParseClient` with all the parameters needed to connect to your target Parse Server instance, then call `Publicize` on it. This will light up the static `ParseClient.Instance` property with the newly-instantiated `ParseClient`, so you can perform operations with the SDK. + +```csharp +new ParseClient(/* Parameters */).Publicize(); +``` + +### Use In Unity Client + +In Unity, the same logic applies to use the SDK as in [any other client](#client-side-use), except that a special `IServiceHub` impelementation instance and a `MetadataMutator` need to be passed in to one of the non-cloning `ParseClient` constructors in order to specify the environment and platform metadata manually. This step is needed because the logic that creates these values automatically will fail. The functionality to do this automatically may eventually be provided as a Unity package in the future, but for now, the following code can be used. + +```csharp +using System; +using UnityEngine; +using Parse.Infrastructure; +``` + +```csharp +new ParseClient(/* Parameters */, new LateInitializedMutableServiceHub { }, new MetadataMutator { EnvironmentData = new EnvironmentData { OSVersion = SystemInfo.operatingSystem, Platform = $"Unity {Application.unityVersion} on {SystemInfo.operatingSystemFamily}", TimeZone = TimeZoneInfo.Local.StandardName }, HostManifestData = new HostManifestData { Name = Application.productName, Identifier = Application.productName, ShortVersion = Application.version, Version = Application.version } }).Publicize(); +``` + +Other `IServiceHubMutator` implementations are available that do different things, such as the `CacheLocationMutator`, which allows a custom cache location to be specified. + +### Server-Side Use + +The SDK can be set up in a way such that every new `ParseClient` instance can authenticate a different user concurrently. This is enabled by an `IServiceHubMutator` implementation which adds itself as an `IServiceHubCloner` implementation to the service hub which, making it so that consecutive calls to the cloning `ParseClient` constructor (the one without parameters) will clone the publicized `ParseClient` instance, exposed by `ParseClient.Instance`, replacing the `IParseCurrentUserController` implementation instance with a fresh one with no caching every time. This allows you to configure the original instance, and have the clones retain the general behaviour, while also allowing the differnt users to be signed into the their respective clones and execute requests concurrently, without causing race conditions. To use this feature of the SDK, the first `ParseClient` instance must be constructued and publicized as follows once, before any other `ParseClient` instantiations. Any classes that need to be registered must be done so with the original instance. + +```csharp +new ParseClient(/* Parameters */, default, new ConcurrentUserServiceHubCloner { }).Publicize(); +``` + +Consecutive instantiations can be done via the cloning constructor for simplicity's sake. + +```csharp +ParseClient client = new ParseClient { }; +``` + +### Basic Demonstration + +The following code shows how to use the Parse .NET SDK to create a new user, save and authenticate the user, deauthenticate the user, re-authenticate the user, create an object with permissions that allow only the user to modify it, save the object, update the object, delete the object, and deauthenticate the user once more. -## Building The Library -You can build the library from Visual Studio Code (with the proper extensions), Visual Studio 2017 Community and higher, or Visual Studio for Mac 7 and higher. You can also build the library using the command line: +```csharp +// Instantiate a ParseClient. +ParseClient client = new ParseClient(/* Parameters */); + +// Create a user, save it, and authenticate with it. +await client.SignUpAsync(username: "Test", password: "Test"); + +// Get the authenticated user. This is can also be done with a variable that stores the ParseUser instance before the SignUp overload that accepts a ParseUser is called. +Console.WriteLine(client.GetCurrentUser().SessionToken); + +// Deauthenticate the user. +await client.LogOutAsync(); + +// Authenticate the user. +ParseUser user = await client.LogInAsync(username: "Test", password: "Test"); + +// Create a new object with permessions that allow only the user to modify it. +ParseObject testObject = new ParseObject("TestClass") { ACL = new ParseACL(user) }; + +// Bind the ParseObject to the target ParseClient instance. This is unnecessary if Publicize is called on the client. +testObject.Bind(client); + +// Set some value on the object. +testObject.Set("someValue", "This is a value."); + +// See that the ObjectId of an unsaved object is null; +Console.WriteLine(testObject.ObjectId); + +// Save the object to the target Parse Server instance. +await testObject.SaveAsync(); + +// See that the ObjectId of a saved object is non-null; +Console.WriteLine(testObject.ObjectId); + +// Query the object back down from the server to check that it was actually saved. +Console.WriteLine((await client.GetQuery("TestClass").WhereEqualTo("objectId", testObject.ObjectId).FirstAsync()).Get("someValue")); + +// Mutate some value on the object. +testObject.Set("someValue", "This is another value."); + +// Save the object again. +await testObject.SaveAsync(); + +// Query the object again to see that the change was made. +Console.WriteLine((await client.GetQuery("TestClass").WhereEqualTo("objectId", testObject.ObjectId).FirstAsync()).Get("someValue")); + +// Store the object's objectId so it can be verified that it was deleted later. +var testObjectId = testObject.ObjectId; + +// Delete the object. +await testObject.DeleteAsync(); + +// Check that the object was deleted from the server. +Console.WriteLine(await client.GetQuery("TestClass").WhereEqualTo("objectId", testObjectId).FirstOrDefaultAsync() == null); + +// Deauthenticate the user again. +await client.LogOutAsync(); +``` + +## Local Builds +You can build the SDK on any system with the MSBuild or .NET Core CLI installed. Results can be found under either the `Release/netstandard` or `Debug/netstandard` in the `bin` folder unless a non-standard build configuration is used. + +## .NET Core CLI -### On Windows or any .NET Core compatible Unix-based system with the .NET Core SDK installed: ```batch dotnet build Parse.sln ``` -Results can be found in either `Parse/bin/Release/netstandard2.0/` or `Parse/bin/Debug/netstandard2.0/` relative to the root project directory, where `/` is the path separator character for your system. - -## How Do I Contribute? +## Contributions We want to make contributing to this project as easy and transparent as possible. Please refer to the [Contribution Guidelines][contributing]. ## License @@ -72,4 +177,4 @@ of patent rights can be found in the PATENTS file in the same directory. [license-link]: https://github.com/parse-community/Parse-SDK-dotNET/blob/master/LICENSE [nuget-link]: http://nuget.org/packages/parse [nuget-svg]: https://img.shields.io/nuget/v/parse.svg - [parse-docs-link]: http://docs.parseplatform.org/ + [parse-docs-link]: http://docs.parseplatform.org/ \ No newline at end of file From a746e2ed1f8ba57fefab92e66cd2e1ae93df73b7 Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Mon, 4 May 2020 10:23:19 -0700 Subject: [PATCH 13/24] Rename IStorageController and related items to derivative names of ICacheController, add IDiskFileCacheController, rename ICacheLocationConfiguration and related items to derivative names of IRelativeCacheLocationGenerator, rename various items within renamed IStorageController, allow IDiskFileCacheController to regenerate tracked file wrappers and memory cache when requested storage paths change, make CacheController not auto-create files until needed, rename CacheLocationMutator to RelativeCacheLocationMutato, make AbsoluteCachePathMutator for changing the absolute cache path from a file in subfolder of LocalApplicationData or equivalent on other platforms, document various items, rename ParseClient.Configuration to ServerConnectionData and remove it to use IServiceHub-inherited member, rename IStorageDictionary to IDataCache, rename VirtualStorageDictionary to FileBackedCache, remove Parse.Abstractions.Storage and Parse.Abstractions.Internal namespaces, and update README.md to reflect changes. --- Parse.Tests/CurrentUserControllerTests.cs | 20 +-- Parse.Tests/InstallationIdControllerTests.cs | 10 +- Parse.Tests/LateInitializerTests.cs | 3 - Parse.Tests/SessionControllerTests.cs | 1 - Parse.Tests/SessionTests.cs | 1 - Parse.Tests/UserTests.cs | 1 - .../Infrastructure/CustomServiceHub.cs | 2 +- ...orageController.cs => ICacheController.cs} | 33 +---- .../Abstractions/Infrastructure/IDataCache.cs | 30 +++++ .../IDiskFileCacheController.cs | 26 ++++ .../Infrastructure/IMutableServiceHub.cs | 2 +- ....cs => IRelativeCacheLocationGenerator.cs} | 10 +- .../Infrastructure/IServiceHub.cs | 5 +- .../Infrastructure/IServiceHubMutator.cs | 8 ++ .../AbsoluteCacheLocationMutator.cs | 34 +++++ ...torageController.cs => CacheController.cs} | 122 ++++++++---------- Parse/Infrastructure/CacheLocationMutator.cs | 22 ---- .../ConcurrentUserStorageController.cs | 45 +++++++ Parse/Infrastructure/HostManifestData.cs | 2 +- ...ierBasedRelativeCacheLocationGenerator.cs} | 15 +-- .../LateInitializedMutableServiceHub.cs | 14 +- ...ataBasedRelativeCacheLocationGenerator.cs} | 11 +- Parse/Infrastructure/MutableServiceHub.cs | 12 +- .../Infrastructure/OrchestrationServiceHub.cs | 2 +- .../RelativeCacheLocationMutator.cs | 32 +++++ Parse/Infrastructure/ServiceHub.cs | 10 +- .../ParseConfigurationController.cs | 2 +- .../ParseCurrentConfigurationController.cs | 4 +- .../ParseCurrentInstallationController.cs | 4 +- .../Installations/ParseInstallationCoder.cs | 1 - .../ParseInstallationController.cs | 4 +- Parse/Platform/ParseClient.cs | 5 - .../Users/ParseCurrentUserController.cs | 5 +- README.md | 8 +- 34 files changed, 308 insertions(+), 198 deletions(-) rename Parse/Abstractions/Infrastructure/{IStorageController.cs => ICacheController.cs} (57%) create mode 100644 Parse/Abstractions/Infrastructure/IDataCache.cs create mode 100644 Parse/Abstractions/Infrastructure/IDiskFileCacheController.cs rename Parse/Abstractions/Infrastructure/{IStorageConfiguration.cs => IRelativeCacheLocationGenerator.cs} (51%) create mode 100644 Parse/Infrastructure/AbsoluteCacheLocationMutator.cs rename Parse/Infrastructure/{StorageController.cs => CacheController.cs} (66%) delete mode 100644 Parse/Infrastructure/CacheLocationMutator.cs create mode 100644 Parse/Infrastructure/ConcurrentUserStorageController.cs rename Parse/Infrastructure/{IdentifierBasedCacheLocationConfiguration.cs => IdentifierBasedRelativeCacheLocationGenerator.cs} (62%) rename Parse/Infrastructure/{MetadataBasedCacheLocationConfiguration.cs => MetadataBasedRelativeCacheLocationGenerator.cs} (60%) create mode 100644 Parse/Infrastructure/RelativeCacheLocationMutator.cs diff --git a/Parse.Tests/CurrentUserControllerTests.cs b/Parse.Tests/CurrentUserControllerTests.cs index 7785a161..5de620d8 100644 --- a/Parse.Tests/CurrentUserControllerTests.cs +++ b/Parse.Tests/CurrentUserControllerTests.cs @@ -25,7 +25,7 @@ public class CurrentUserControllerTests public void TearDown() => (Client.Services as ServiceHub).Reset(); [TestMethod] - public void TestConstructor() => Assert.IsNull(new ParseCurrentUserController(new Mock { }.Object, Client.ClassController, Client.Decoder).CurrentUser); + public void TestConstructor() => Assert.IsNull(new ParseCurrentUserController(new Mock { }.Object, Client.ClassController, Client.Decoder).CurrentUser); [TestMethod] [AsyncStateMachine(typeof(CurrentUserControllerTests))] @@ -33,8 +33,8 @@ public Task TestGetSetAsync() { #warning This method may need a fully custom ParseClient setup. - Mock storageController = new Mock(MockBehavior.Strict); - Mock> mockedStorage = new Mock>(); + Mock storageController = new Mock(MockBehavior.Strict); + Mock> mockedStorage = new Mock>(); ParseCurrentUserController controller = new ParseCurrentUserController(storageController.Object, Client.ClassController, Client.Decoder); @@ -78,8 +78,8 @@ public Task TestGetSetAsync() [AsyncStateMachine(typeof(CurrentUserControllerTests))] public Task TestExistsAsync() { - Mock storageController = new Mock(); - Mock> mockedStorage = new Mock>(); + Mock storageController = new Mock(); + Mock> mockedStorage = new Mock>(); ParseCurrentUserController controller = new ParseCurrentUserController(storageController.Object, Client.ClassController, Client.Decoder); ParseUser user = new ParseUser { }.Bind(Client) as ParseUser; @@ -120,13 +120,13 @@ public Task TestExistsAsync() [AsyncStateMachine(typeof(CurrentUserControllerTests))] public Task TestIsCurrent() { - Mock storageController = new Mock(MockBehavior.Strict); + Mock storageController = new Mock(MockBehavior.Strict); ParseCurrentUserController controller = new ParseCurrentUserController(storageController.Object, Client.ClassController, Client.Decoder); ParseUser user = new ParseUser { }.Bind(Client) as ParseUser; ParseUser user2 = new ParseUser { }.Bind(Client) as ParseUser; - storageController.Setup(storage => storage.LoadAsync()).Returns(Task.FromResult(new Mock>().Object)); + storageController.Setup(storage => storage.LoadAsync()).Returns(Task.FromResult(new Mock>().Object)); return controller.SetAsync(user, CancellationToken.None).OnSuccess(task => { @@ -159,8 +159,8 @@ public Task TestIsCurrent() [AsyncStateMachine(typeof(CurrentUserControllerTests))] public Task TestCurrentSessionToken() { - Mock storageController = new Mock(); - Mock> mockedStorage = new Mock>(); + Mock storageController = new Mock(); + Mock> mockedStorage = new Mock>(); ParseCurrentUserController controller = new ParseCurrentUserController(storageController.Object, Client.ClassController, Client.Decoder); storageController.Setup(c => c.LoadAsync()).Returns(Task.FromResult(mockedStorage.Object)); @@ -180,7 +180,7 @@ public Task TestCurrentSessionToken() public Task TestLogOut() { - ParseCurrentUserController controller = new ParseCurrentUserController(new Mock(MockBehavior.Strict).Object, Client.ClassController, Client.Decoder); + ParseCurrentUserController controller = new ParseCurrentUserController(new Mock(MockBehavior.Strict).Object, Client.ClassController, Client.Decoder); ParseUser user = new ParseUser { }.Bind(Client) as ParseUser; return controller.SetAsync(user, CancellationToken.None).OnSuccess(_ => diff --git a/Parse.Tests/InstallationIdControllerTests.cs b/Parse.Tests/InstallationIdControllerTests.cs index f4326e76..99dc0d66 100644 --- a/Parse.Tests/InstallationIdControllerTests.cs +++ b/Parse.Tests/InstallationIdControllerTests.cs @@ -22,7 +22,7 @@ public class InstallationIdControllerTests [TestMethod] public void TestConstructor() { - Mock storageMock = new Mock(MockBehavior.Strict); + Mock storageMock = new Mock(MockBehavior.Strict); ParseInstallationController controller = new ParseInstallationController(storageMock.Object); // Make sure it didn't touch storageMock. @@ -34,8 +34,8 @@ public void TestConstructor() [AsyncStateMachine(typeof(InstallationIdControllerTests))] public Task TestGet() { - Mock storageMock = new Mock(MockBehavior.Strict); - Mock> storageDictionary = new Mock>(); + Mock storageMock = new Mock(MockBehavior.Strict); + Mock> storageDictionary = new Mock>(); storageMock.Setup(s => s.LoadAsync()).Returns(Task.FromResult(storageDictionary.Object)); @@ -81,8 +81,8 @@ public Task TestGet() [AsyncStateMachine(typeof(InstallationIdControllerTests))] public Task TestSet() { - Mock storageMock = new Mock(MockBehavior.Strict); - Mock> storageDictionary = new Mock>(); + Mock storageMock = new Mock(MockBehavior.Strict); + Mock> storageDictionary = new Mock>(); storageMock.Setup(s => s.LoadAsync()).Returns(Task.FromResult(storageDictionary.Object)); diff --git a/Parse.Tests/LateInitializerTests.cs b/Parse.Tests/LateInitializerTests.cs index f57433ac..e4e51436 100644 --- a/Parse.Tests/LateInitializerTests.cs +++ b/Parse.Tests/LateInitializerTests.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using Parse.Infrastructure.Utilities; diff --git a/Parse.Tests/SessionControllerTests.cs b/Parse.Tests/SessionControllerTests.cs index 18d16156..454ae0ac 100644 --- a/Parse.Tests/SessionControllerTests.cs +++ b/Parse.Tests/SessionControllerTests.cs @@ -9,7 +9,6 @@ using Moq; using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Infrastructure.Execution; -using Parse.Abstractions.Internal; using Parse.Abstractions.Platform.Objects; using Parse.Abstractions.Platform.Sessions; using Parse.Infrastructure; diff --git a/Parse.Tests/SessionTests.cs b/Parse.Tests/SessionTests.cs index deb7fe24..2bff624e 100644 --- a/Parse.Tests/SessionTests.cs +++ b/Parse.Tests/SessionTests.cs @@ -5,7 +5,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Parse.Abstractions.Infrastructure; -using Parse.Abstractions.Internal; using Parse.Abstractions.Platform.Objects; using Parse.Abstractions.Platform.Sessions; using Parse.Abstractions.Platform.Users; diff --git a/Parse.Tests/UserTests.cs b/Parse.Tests/UserTests.cs index 9e799457..4e205ff2 100644 --- a/Parse.Tests/UserTests.cs +++ b/Parse.Tests/UserTests.cs @@ -6,7 +6,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Parse.Abstractions.Infrastructure; -using Parse.Abstractions.Internal; using Parse.Infrastructure; using Parse.Abstractions.Infrastructure.Control; using Parse.Abstractions.Platform.Objects; diff --git a/Parse/Abstractions/Infrastructure/CustomServiceHub.cs b/Parse/Abstractions/Infrastructure/CustomServiceHub.cs index 493e240f..49689c94 100644 --- a/Parse/Abstractions/Infrastructure/CustomServiceHub.cs +++ b/Parse/Abstractions/Infrastructure/CustomServiceHub.cs @@ -23,7 +23,7 @@ public abstract class CustomServiceHub : ICustomServiceHub public virtual IWebClient WebClient => Services.WebClient; - public virtual IStorageController StorageController => Services.StorageController; + public virtual ICacheController CacheController => Services.CacheController; public virtual IParseObjectClassController ClassController => Services.ClassController; diff --git a/Parse/Abstractions/Infrastructure/IStorageController.cs b/Parse/Abstractions/Infrastructure/ICacheController.cs similarity index 57% rename from Parse/Abstractions/Infrastructure/IStorageController.cs rename to Parse/Abstractions/Infrastructure/ICacheController.cs index e82fb0ba..e3445deb 100644 --- a/Parse/Abstractions/Infrastructure/IStorageController.cs +++ b/Parse/Abstractions/Infrastructure/ICacheController.cs @@ -4,10 +4,12 @@ namespace Parse.Abstractions.Infrastructure { + // TODO: Move TransferAsync to IDiskFileCacheController and find viable alternative for use in ICacheController if needed. + /// /// An abstraction for accessing persistent storage in the Parse SDK. /// - public interface IStorageController + public interface ICacheController { /// /// Cleans up any temporary files and/or directories created during SDK operation. @@ -19,7 +21,7 @@ public interface IStorageController /// /// The relative path to the target file /// An instance of wrapping the the value - FileInfo GetWrapperForRelativePersistentStorageFilePath(string path); + FileInfo GetRelativeFile(string path); /// /// Transfers a file from to . @@ -33,36 +35,13 @@ public interface IStorageController /// Load the contents of this storage controller asynchronously. /// /// - Task> LoadAsync(); + 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 : IDictionary - { - /// - /// 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); + Task> SaveAsync(IDictionary contents); } } \ No newline at end of file diff --git a/Parse/Abstractions/Infrastructure/IDataCache.cs b/Parse/Abstractions/Infrastructure/IDataCache.cs new file mode 100644 index 00000000..047d489f --- /dev/null +++ b/Parse/Abstractions/Infrastructure/IDataCache.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Parse.Abstractions.Infrastructure +{ + // IGeneralizedDataCache + + /// + /// 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 IDataCache : IDictionary + { + /// + /// 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/Parse/Abstractions/Infrastructure/IDiskFileCacheController.cs b/Parse/Abstractions/Infrastructure/IDiskFileCacheController.cs new file mode 100644 index 00000000..dff5a045 --- /dev/null +++ b/Parse/Abstractions/Infrastructure/IDiskFileCacheController.cs @@ -0,0 +1,26 @@ +using System; + +namespace Parse.Abstractions.Infrastructure +{ + /// + /// An which stores the cache on disk via a file. + /// + public interface IDiskFileCacheController : ICacheController + { + /// + /// The path to a persistent user-specific storage location specific to the final client assembly of the Parse library. + /// + public string AbsoluteCacheFilePath { get; set; } + + /// + /// The relative path from the on the device an to application-specific persistent storage folder. + /// + public string RelativeCacheFilePath { get; set; } + + /// + /// Refreshes this cache controller's internal tracked cache file to reflect the and/or . + /// + /// This will not delete the active tracked cache file that will be un-tracked after a call to this method. To do so, call . + void RefreshPaths(); + } +} \ No newline at end of file diff --git a/Parse/Abstractions/Infrastructure/IMutableServiceHub.cs b/Parse/Abstractions/Infrastructure/IMutableServiceHub.cs index 895c8a71..7282985f 100644 --- a/Parse/Abstractions/Infrastructure/IMutableServiceHub.cs +++ b/Parse/Abstractions/Infrastructure/IMutableServiceHub.cs @@ -23,7 +23,7 @@ public interface IMutableServiceHub : IServiceHub IServiceHubCloner Cloner { set; } IWebClient WebClient { set; } - IStorageController StorageController { set; } + ICacheController CacheController { set; } IParseObjectClassController ClassController { set; } IParseDataDecoder Decoder { set; } diff --git a/Parse/Abstractions/Infrastructure/IStorageConfiguration.cs b/Parse/Abstractions/Infrastructure/IRelativeCacheLocationGenerator.cs similarity index 51% rename from Parse/Abstractions/Infrastructure/IStorageConfiguration.cs rename to Parse/Abstractions/Infrastructure/IRelativeCacheLocationGenerator.cs index 6f40e5b2..7ceabe26 100644 --- a/Parse/Abstractions/Infrastructure/IStorageConfiguration.cs +++ b/Parse/Abstractions/Infrastructure/IRelativeCacheLocationGenerator.cs @@ -1,15 +1,13 @@ -using Parse.Abstractions.Infrastructure; - -namespace Parse.Abstractions.Storage +namespace Parse.Abstractions.Infrastructure { /// /// A unit that can generate a relative path to a persistent storage file. /// - public interface ICacheLocationConfiguration + public interface IRelativeCacheLocationGenerator { /// - /// The corresponding relative path generated by this . + /// The corresponding relative path generated by this . /// - string GetRelativeStorageFilePath(IServiceHub plugins); + string GetRelativeCacheFilePath(IServiceHub serviceHub); } } diff --git a/Parse/Abstractions/Infrastructure/IServiceHub.cs b/Parse/Abstractions/Infrastructure/IServiceHub.cs index a81891c0..c7e65992 100644 --- a/Parse/Abstractions/Infrastructure/IServiceHub.cs +++ b/Parse/Abstractions/Infrastructure/IServiceHub.cs @@ -22,13 +22,16 @@ namespace Parse.Abstractions.Infrastructure /// public interface IServiceHub { + /// + /// The current server connection data that the the Parse SDK has been initialized with. + /// IServerConnectionData ServerConnectionData { get; } IMetadataController MetadataController { get; } IServiceHubCloner Cloner { get; } IWebClient WebClient { get; } - IStorageController StorageController { get; } + ICacheController CacheController { get; } IParseObjectClassController ClassController { get; } IParseDataDecoder Decoder { get; } diff --git a/Parse/Abstractions/Infrastructure/IServiceHubMutator.cs b/Parse/Abstractions/Infrastructure/IServiceHubMutator.cs index 8a4b4490..6ce77d67 100644 --- a/Parse/Abstractions/Infrastructure/IServiceHubMutator.cs +++ b/Parse/Abstractions/Infrastructure/IServiceHubMutator.cs @@ -7,8 +7,16 @@ namespace Parse.Abstractions.Infrastructure /// public interface IServiceHubMutator { + /// + /// A value which dictates whether or not the should be considered in a valid state. + /// bool Valid { get; } + /// + /// A method which mutates an implementation instance. + /// + /// The target implementation instance + /// A hub which the is composed onto that should be used when needs to access services. void Mutate(ref IMutableServiceHub target, in IServiceHub composedHub); } } diff --git a/Parse/Infrastructure/AbsoluteCacheLocationMutator.cs b/Parse/Infrastructure/AbsoluteCacheLocationMutator.cs new file mode 100644 index 00000000..416aad1a --- /dev/null +++ b/Parse/Infrastructure/AbsoluteCacheLocationMutator.cs @@ -0,0 +1,34 @@ +using Parse.Abstractions.Infrastructure; + +namespace Parse.Infrastructure +{ + /// + /// An implementation which changes the 's if available. + /// + public class AbsoluteCacheLocationMutator : IServiceHubMutator + { + /// + /// A custom absolute cache file path to be set on the active if it implements . + /// + public string CustomAbsoluteCacheFilePath { get; set; } + + /// + /// + /// + public bool Valid => CustomAbsoluteCacheFilePath is { }; + + /// + /// + /// + /// + /// + public void Mutate(ref IMutableServiceHub target, in IServiceHub composedHub) + { + if ((target as IServiceHub).CacheController is IDiskFileCacheController { } diskFileCacheController) + { + diskFileCacheController.AbsoluteCacheFilePath = CustomAbsoluteCacheFilePath; + diskFileCacheController.RefreshPaths(); + } + } + } +} diff --git a/Parse/Infrastructure/StorageController.cs b/Parse/Infrastructure/CacheController.cs similarity index 66% rename from Parse/Infrastructure/StorageController.cs rename to Parse/Infrastructure/CacheController.cs index 9f3d4104..0ffb66b1 100644 --- a/Parse/Infrastructure/StorageController.cs +++ b/Parse/Infrastructure/CacheController.cs @@ -12,56 +12,21 @@ namespace Parse.Infrastructure { - public class ConcurrentUserStorageController : IStorageController - { - class VirtualStorageDictionary : Dictionary, IStorageDictionary - { - public Task AddAsync(string key, object value) - { - Add(key, value); - return Task.CompletedTask; - } - - public Task RemoveAsync(string key) - { - Remove(key); - return Task.CompletedTask; - } - } - - VirtualStorageDictionary Storage { get; } = new VirtualStorageDictionary { }; - - public void Clear() => Storage.Clear(); - - public FileInfo GetWrapperForRelativePersistentStorageFilePath(string path) => throw new NotSupportedException(ConcurrentUserStorageControllerFileOperationNotSupportedMessage); - - public Task> LoadAsync() => Task.FromResult>(Storage); - - public Task> SaveAsync(IDictionary contents) - { - foreach (KeyValuePair pair in contents) - ((IDictionary) Storage).Add(pair); - - return Task.FromResult>(Storage); - } - - public Task TransferAsync(string originFilePath, string targetFilePath) => Task.FromException(new NotSupportedException(ConcurrentUserStorageControllerFileOperationNotSupportedMessage)); - } - /// /// Implements `IStorageController` for PCL targets, based off of PCLStorage. /// - public class StorageController : IStorageController + public class CacheController : IDiskFileCacheController { - class StorageDictionary : IStorageDictionary + class FileBackedCache : IDataCache { - public StorageDictionary(FileInfo file) => File = file; + public FileBackedCache(FileInfo file) => File = file; internal Task SaveAsync() => Lock(() => File.WriteContentAsync(JsonUtilities.Encode(Storage))); internal Task LoadAsync() => File.ReadAllTextAsync().ContinueWith(task => { lock (Mutex) + { try { Storage = JsonUtilities.Parse(task.Result) as Dictionary; @@ -70,6 +35,7 @@ internal Task LoadAsync() => File.ReadAllTextAsync().ContinueWith(task => { Storage = new Dictionary { }; } + } }); // TODO: Check if the call to ToDictionary is necessary here considering contents is IDictionary. @@ -107,7 +73,9 @@ public Task RemoveAsync(string key) public bool TryGetValue(string key, out object value) { lock (Mutex) + { return (Result: Storage.TryGetValue(key, out object found), value = found).Result; + } } public void Clear() => Lock(() => Storage.Clear()); @@ -129,13 +97,17 @@ public bool TryGetValue(string key, out object value) TResult Lock(Func operation) { lock (Mutex) + { return operation.Invoke(); + } } void Lock(Action operation) { lock (Mutex) + { operation.Invoke(); + } } ICollection> Elements => Storage as ICollection>; @@ -157,80 +129,98 @@ public object this[string key] } } - FileInfo File { get; } - StorageDictionary Storage { get; set; } + FileInfo File { get; set; } + FileBackedCache Cache { get; set; } TaskQueue Queue { get; } = new TaskQueue { }; /// /// Creates a Parse storage controller and attempts to extract a previously created settings storage file from the persistent storage location. /// - public StorageController() => Storage = new StorageDictionary(File = PersistentStorageFileWrapper); + public CacheController() { } /// /// Creates a Parse storage controller with the provided wrapper. /// /// The file wrapper that the storage controller instance should target - public StorageController(FileInfo file) => File = file; + public CacheController(FileInfo file) => EnsureCacheExists(file); + + FileBackedCache EnsureCacheExists(FileInfo file = default) => Cache ??= new FileBackedCache(file ?? (File ??= PersistentCacheFile)); /// /// Loads a settings dictionary from the file wrapped by . /// - /// A storage dictionary containing the deserialized content of the storage file targeted by the instance - public Task> LoadAsync() + /// A storage dictionary containing the deserialized content of the storage file targeted by the instance + public Task> LoadAsync() { // Check if storage dictionary is already created from the controllers file (create if not) - Storage ??= new StorageDictionary(File); + EnsureCacheExists(); // Load storage dictionary content async and return the resulting dictionary type - return Queue.Enqueue(toAwait => toAwait.ContinueWith(_ => Storage.LoadAsync().OnSuccess(__ => Storage as IStorageDictionary)).Unwrap(), CancellationToken.None); + return Queue.Enqueue(toAwait => toAwait.ContinueWith(_ => Cache.LoadAsync().OnSuccess(__ => Cache as IDataCache)).Unwrap(), CancellationToken.None); } /// - /// + /// Saves the requested data. /// - /// - /// - public Task> SaveAsync(IDictionary contents) => Queue.Enqueue(toAwait => toAwait.ContinueWith(_ => + /// The data to be saved. + /// A data cache containing the saved data. + public Task> SaveAsync(IDictionary contents) => Queue.Enqueue(toAwait => toAwait.ContinueWith(_ => { - (Storage ??= new StorageDictionary(File)).Update(contents); - return Storage.SaveAsync().OnSuccess(__ => Storage as IStorageDictionary); + EnsureCacheExists().Update(contents); + return Cache.SaveAsync().OnSuccess(__ => Cache as IDataCache); }).Unwrap()); + /// + /// + /// + public void RefreshPaths() => Cache = new FileBackedCache(File = PersistentCacheFile); + // TODO: Attach the following method to AppDomain.CurrentDomain.ProcessExit if that actually ever made sense for anything except randomly generated file names, otherwise attach the delegate when it is known the file name is a randomly generated string. + /// + /// Clears the data controlled by this class. + /// public void Clear() { - if (new FileInfo(FallbackPersistentStorageFilePath) is { Exists: true } file) + if (new FileInfo(FallbackRelativeCacheFilePath) is { Exists: true } file) + { file.Delete(); + } } /// - /// The relative path from the on the device an to application-specific persistent storage folder. + /// /// - public string RelativeStorageFilePath { get; set; } + public string RelativeCacheFilePath { get; set; } /// - /// The path to a persistent user-specific storage location specific to the final client assembly of the Parse library. + /// /// - public string PersistentStorageFilePath => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), RelativeStorageFilePath ?? FallbackPersistentStorageFilePath)); + public string AbsoluteCacheFilePath + { + get => StoredAbsoluteCacheFilePath ?? Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), RelativeCacheFilePath ?? FallbackRelativeCacheFilePath)); + set => StoredAbsoluteCacheFilePath = value; + } + + string StoredAbsoluteCacheFilePath { get; set; } /// /// Gets the calculated persistent storage file fallback path for this app execution. /// - public string FallbackPersistentStorageFilePath => StoredFallbackPersistentStorageFilePath ??= IdentifierBasedCacheLocationConfiguration.Fallback.GetRelativeStorageFilePath(new MutableServiceHub { StorageController = this }); + public string FallbackRelativeCacheFilePath => StoredFallbackRelativeCacheFilePath ??= IdentifierBasedRelativeCacheLocationGenerator.Fallback.GetRelativeCacheFilePath(new MutableServiceHub { CacheController = this }); - string StoredFallbackPersistentStorageFilePath { get; set; } + string StoredFallbackRelativeCacheFilePath { get; set; } /// - /// Gets or creates the file pointed to by and returns it's wrapper as a instance. + /// Gets or creates the file pointed to by and returns it's wrapper as a instance. /// - public FileInfo PersistentStorageFileWrapper + public FileInfo PersistentCacheFile { get { - Directory.CreateDirectory(PersistentStorageFilePath.Substring(0, PersistentStorageFilePath.LastIndexOf(Path.DirectorySeparatorChar))); + Directory.CreateDirectory(AbsoluteCacheFilePath.Substring(0, AbsoluteCacheFilePath.LastIndexOf(Path.DirectorySeparatorChar))); - FileInfo file = new FileInfo(PersistentStorageFilePath); + FileInfo file = new FileInfo(AbsoluteCacheFilePath); if (!file.Exists) using (file.Create()) ; // Hopefully the JIT doesn't no-op this. The behaviour of the "using" clause should dictate how the stream is closed, to make sure it happens properly. @@ -244,12 +234,14 @@ public FileInfo PersistentStorageFileWrapper /// /// The relative path to the target file /// An instance of wrapping the the value - public FileInfo GetWrapperForRelativePersistentStorageFilePath(string path) + public FileInfo GetRelativeFile(string path) { Directory.CreateDirectory((path = Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), path))).Substring(0, path.LastIndexOf(Path.DirectorySeparatorChar))); return new FileInfo(path); } + // MoveAsync + /// /// Transfers a file from to . /// diff --git a/Parse/Infrastructure/CacheLocationMutator.cs b/Parse/Infrastructure/CacheLocationMutator.cs deleted file mode 100644 index c8552759..00000000 --- a/Parse/Infrastructure/CacheLocationMutator.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Parse.Abstractions.Infrastructure; -using Parse.Abstractions.Storage; - -namespace Parse.Infrastructure -{ - /// - /// An for the relative storagecache location. - /// - public class CacheLocationMutator : IServiceHubMutator - { - ICacheLocationConfiguration CacheLocationConfiguration { get; set; } - - public bool Valid => CacheLocationConfiguration is { }; - - public void Mutate(ref IMutableServiceHub target, in IServiceHub referenceHub) => target.StorageController = (target as IServiceHub).StorageController switch - { - null => new StorageController { RelativeStorageFilePath = CacheLocationConfiguration.GetRelativeStorageFilePath(referenceHub) }, - StorageController { } controller => (Controller: controller, controller.RelativeStorageFilePath = CacheLocationConfiguration.GetRelativeStorageFilePath(referenceHub)).Controller, - { } controller => controller - }; - } -} diff --git a/Parse/Infrastructure/ConcurrentUserStorageController.cs b/Parse/Infrastructure/ConcurrentUserStorageController.cs new file mode 100644 index 00000000..2835fd0e --- /dev/null +++ b/Parse/Infrastructure/ConcurrentUserStorageController.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Parse.Abstractions.Infrastructure; +using static Parse.Resources; + +namespace Parse.Infrastructure +{ + public class ConcurrentUserStorageController : ICacheController + { + class VirtualStorageDictionary : Dictionary, IDataCache + { + public Task AddAsync(string key, object value) + { + Add(key, value); + return Task.CompletedTask; + } + + public Task RemoveAsync(string key) + { + Remove(key); + return Task.CompletedTask; + } + } + + VirtualStorageDictionary Storage { get; } = new VirtualStorageDictionary { }; + + public void Clear() => Storage.Clear(); + + public FileInfo GetRelativeFile(string path) => throw new NotSupportedException(ConcurrentUserStorageControllerFileOperationNotSupportedMessage); + + public Task> LoadAsync() => Task.FromResult>(Storage); + + public Task> SaveAsync(IDictionary contents) + { + foreach (KeyValuePair pair in contents) + ((IDictionary) Storage).Add(pair); + + return Task.FromResult>(Storage); + } + + public Task TransferAsync(string originFilePath, string targetFilePath) => Task.FromException(new NotSupportedException(ConcurrentUserStorageControllerFileOperationNotSupportedMessage)); + } +} diff --git a/Parse/Infrastructure/HostManifestData.cs b/Parse/Infrastructure/HostManifestData.cs index 2eb27961..75e38b86 100644 --- a/Parse/Infrastructure/HostManifestData.cs +++ b/Parse/Infrastructure/HostManifestData.cs @@ -55,7 +55,7 @@ public class HostManifestData : IHostManifestData public bool IsDefault => Version is null && ShortVersion is null && Identifier is null && Name is null; /// - /// Gets a value for whether or not this instance of can currently be used for the generation of . + /// Gets a value for whether or not this instance of can currently be used for the generation of . /// public bool CanBeUsedForInference => !(IsDefault || String.IsNullOrWhiteSpace(ShortVersion)); } diff --git a/Parse/Infrastructure/IdentifierBasedCacheLocationConfiguration.cs b/Parse/Infrastructure/IdentifierBasedRelativeCacheLocationGenerator.cs similarity index 62% rename from Parse/Infrastructure/IdentifierBasedCacheLocationConfiguration.cs rename to Parse/Infrastructure/IdentifierBasedRelativeCacheLocationGenerator.cs index 9e188e2e..0a076266 100644 --- a/Parse/Infrastructure/IdentifierBasedCacheLocationConfiguration.cs +++ b/Parse/Infrastructure/IdentifierBasedRelativeCacheLocationGenerator.cs @@ -1,19 +1,18 @@ using System; using System.IO; using Parse.Abstractions.Infrastructure; -using Parse.Abstractions.Storage; namespace Parse.Infrastructure { /// /// A configuration of the Parse SDK persistent storage location based on an identifier. /// - public struct IdentifierBasedCacheLocationConfiguration : ICacheLocationConfiguration + public struct IdentifierBasedRelativeCacheLocationGenerator : IRelativeCacheLocationGenerator { - internal static IdentifierBasedCacheLocationConfiguration Fallback { get; } = new IdentifierBasedCacheLocationConfiguration { IsFallback = true }; + internal static IdentifierBasedRelativeCacheLocationGenerator Fallback { get; } = new IdentifierBasedRelativeCacheLocationGenerator { IsFallback = true }; /// - /// Dictates whether or not this instance should act as a fallback for when has not yet been initialized but the storage path is needed. + /// Dictates whether or not this instance should act as a fallback for when has not yet been initialized but the storage path is needed. /// internal bool IsFallback { get; set; } @@ -23,21 +22,21 @@ public struct IdentifierBasedCacheLocationConfiguration : ICacheLocationConfigur public string Identifier { get; set; } /// - /// The corresponding relative path generated by this . + /// The corresponding relative path generated by this . /// /// This will cause a .cachefile file extension to be added to the cache file in order to prevent the creation of files with unwanted extensions due to the value of containing periods. - public string GetRelativeStorageFilePath(IServiceHub serviceHub) + public string GetRelativeCacheFilePath(IServiceHub serviceHub) { FileInfo file; - while ((file = serviceHub.StorageController.GetWrapperForRelativePersistentStorageFilePath(GeneratePath())).Exists && IsFallback) + while ((file = serviceHub.CacheController.GetRelativeFile(GeneratePath())).Exists && IsFallback) ; return file.FullName; } /// - /// Generates a path for use in the method. + /// Generates a path for use in the method. /// /// A potential path to the cachefile string GeneratePath() => Path.Combine(nameof(Parse), IsFallback ? "_fallback" : "_global", $"{(IsFallback ? new Random { }.Next().ToString() : Identifier)}.cachefile"); diff --git a/Parse/Infrastructure/LateInitializedMutableServiceHub.cs b/Parse/Infrastructure/LateInitializedMutableServiceHub.cs index da687735..a5e58e4e 100644 --- a/Parse/Infrastructure/LateInitializedMutableServiceHub.cs +++ b/Parse/Infrastructure/LateInitializedMutableServiceHub.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; using Parse.Abstractions.Infrastructure.Data; using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Infrastructure.Execution; @@ -48,9 +46,9 @@ public IWebClient WebClient set => LateInitializer.SetValue(value); } - public IStorageController StorageController + public ICacheController CacheController { - get => LateInitializer.GetValue(() => new StorageController { }); + get => LateInitializer.GetValue(() => new CacheController { }); set => LateInitializer.SetValue(value); } @@ -62,7 +60,7 @@ public IParseObjectClassController ClassController public IParseInstallationController InstallationController { - get => LateInitializer.GetValue(() => new ParseInstallationController(StorageController)); + get => LateInitializer.GetValue(() => new ParseInstallationController(CacheController)); set => LateInitializer.SetValue(value); } @@ -80,7 +78,7 @@ public IParseCloudCodeController CloudCodeController public IParseConfigurationController ConfigurationController { - get => LateInitializer.GetValue(() => new ParseConfigurationController(CommandRunner, StorageController, Decoder)); + get => LateInitializer.GetValue(() => new ParseConfigurationController(CommandRunner, CacheController, Decoder)); set => LateInitializer.SetValue(value); } @@ -116,7 +114,7 @@ public IParseUserController UserController public IParseCurrentUserController CurrentUserController { - get => LateInitializer.GetValue(() => new ParseCurrentUserController(StorageController, ClassController, Decoder)); + get => LateInitializer.GetValue(() => new ParseCurrentUserController(CacheController, ClassController, Decoder)); set => LateInitializer.SetValue(value); } @@ -146,7 +144,7 @@ public IParsePushController PushController public IParseCurrentInstallationController CurrentInstallationController { - get => LateInitializer.GetValue(() => new ParseCurrentInstallationController(InstallationController, StorageController, InstallationCoder, ClassController)); + get => LateInitializer.GetValue(() => new ParseCurrentInstallationController(InstallationController, CacheController, InstallationCoder, ClassController)); set => LateInitializer.SetValue(value); } diff --git a/Parse/Infrastructure/MetadataBasedCacheLocationConfiguration.cs b/Parse/Infrastructure/MetadataBasedRelativeCacheLocationGenerator.cs similarity index 60% rename from Parse/Infrastructure/MetadataBasedCacheLocationConfiguration.cs rename to Parse/Infrastructure/MetadataBasedRelativeCacheLocationGenerator.cs index 2e16b8f6..9eadc5d1 100644 --- a/Parse/Infrastructure/MetadataBasedCacheLocationConfiguration.cs +++ b/Parse/Infrastructure/MetadataBasedRelativeCacheLocationGenerator.cs @@ -1,20 +1,19 @@ using System.IO; using System.Reflection; using Parse.Abstractions.Infrastructure; -using Parse.Abstractions.Storage; namespace Parse.Infrastructure { /// /// A configuration of the Parse SDK persistent storage location based on product metadata such as company name and product name. /// - public struct MetadataBasedCacheLocationConfiguration : ICacheLocationConfiguration + public struct MetadataBasedRelativeCacheLocationGenerator : IRelativeCacheLocationGenerator { /// - /// An instance of with inferred values based on the entry assembly. Should be used with and . + /// An instance of with inferred values based on the entry assembly. Should be used with and . /// /// Should not be used with Unity. - public static MetadataBasedCacheLocationConfiguration Inferred => new MetadataBasedCacheLocationConfiguration + public static MetadataBasedRelativeCacheLocationGenerator Inferred => new MetadataBasedRelativeCacheLocationGenerator { Company = Assembly.GetExecutingAssembly()?.GetCustomAttribute()?.Company, Product = Assembly.GetEntryAssembly()?.GetCustomAttribute()?.Product ?? Assembly.GetEntryAssembly()?.GetName()?.Name @@ -31,8 +30,8 @@ public struct MetadataBasedCacheLocationConfiguration : ICacheLocationConfigurat public string Product { get; set; } /// - /// The corresponding relative path generated by this . + /// The corresponding relative path generated by this . /// - public string GetRelativeStorageFilePath(IServiceHub serviceHub) => Path.Combine(Company ?? nameof(Parse), Product ?? "_global", $"{serviceHub.MetadataController.HostManifestData.ShortVersion ?? "1.0.0.0"}.pc"); + public string GetRelativeCacheFilePath(IServiceHub serviceHub) => Path.Combine(Company ?? nameof(Parse), Product ?? "_global", $"{serviceHub.MetadataController.HostManifestData.ShortVersion ?? "1.0.0.0"}.pc"); } } diff --git a/Parse/Infrastructure/MutableServiceHub.cs b/Parse/Infrastructure/MutableServiceHub.cs index f01ad617..4c63669b 100644 --- a/Parse/Infrastructure/MutableServiceHub.cs +++ b/Parse/Infrastructure/MutableServiceHub.cs @@ -39,7 +39,7 @@ public class MutableServiceHub : IMutableServiceHub public IServiceHubCloner Cloner { get; set; } public IWebClient WebClient { get; set; } - public IStorageController StorageController { get; set; } + public ICacheController CacheController { get; set; } public IParseObjectClassController ClassController { get; set; } public IParseDataDecoder Decoder { get; set; } @@ -77,29 +77,29 @@ public MutableServiceHub SetDefaults(IServerConnectionData connectionData = defa Cloner ??= new ConcurrentUserServiceHubCloner { }; WebClient ??= new UniversalWebClient { }; - StorageController ??= new StorageController { }; + CacheController ??= new CacheController { }; ClassController ??= new ParseObjectClassController { }; Decoder ??= new ParseDataDecoder(ClassController); - InstallationController ??= new ParseInstallationController(StorageController); + InstallationController ??= new ParseInstallationController(CacheController); CommandRunner ??= new ParseCommandRunner(WebClient, InstallationController, MetadataController, ServerConnectionData, new Lazy(() => UserController)); CloudCodeController ??= new ParseCloudCodeController(CommandRunner, Decoder); - ConfigurationController ??= new ParseConfigurationController(CommandRunner, StorageController, Decoder); + ConfigurationController ??= new ParseConfigurationController(CommandRunner, CacheController, Decoder); FileController ??= new ParseFileController(CommandRunner); ObjectController ??= new ParseObjectController(CommandRunner, Decoder, ServerConnectionData); QueryController ??= new ParseQueryController(CommandRunner, Decoder); SessionController ??= new ParseSessionController(CommandRunner, Decoder); UserController ??= new ParseUserController(CommandRunner, Decoder); - CurrentUserController ??= new ParseCurrentUserController(StorageController, ClassController, Decoder); + CurrentUserController ??= new ParseCurrentUserController(CacheController, ClassController, Decoder); AnalyticsController ??= new ParseAnalyticsController(CommandRunner); InstallationCoder ??= new ParseInstallationCoder(Decoder, ClassController); PushController ??= new ParsePushController(CommandRunner, CurrentUserController); - CurrentInstallationController ??= new ParseCurrentInstallationController(InstallationController, StorageController, InstallationCoder, ClassController); + CurrentInstallationController ??= new ParseCurrentInstallationController(InstallationController, CacheController, InstallationCoder, ClassController); PushChannelsController ??= new ParsePushChannelsController(CurrentInstallationController); InstallationDataFinalizer ??= new ParseInstallationDataFinalizer { }; diff --git a/Parse/Infrastructure/OrchestrationServiceHub.cs b/Parse/Infrastructure/OrchestrationServiceHub.cs index 2ac8d29e..8aa99a9e 100644 --- a/Parse/Infrastructure/OrchestrationServiceHub.cs +++ b/Parse/Infrastructure/OrchestrationServiceHub.cs @@ -26,7 +26,7 @@ public class OrchestrationServiceHub : IServiceHub public IWebClient WebClient => Custom.WebClient ?? Default.WebClient; - public IStorageController StorageController => Custom.StorageController ?? Default.StorageController; + public ICacheController CacheController => Custom.CacheController ?? Default.CacheController; public IParseObjectClassController ClassController => Custom.ClassController ?? Default.ClassController; diff --git a/Parse/Infrastructure/RelativeCacheLocationMutator.cs b/Parse/Infrastructure/RelativeCacheLocationMutator.cs new file mode 100644 index 00000000..fe332424 --- /dev/null +++ b/Parse/Infrastructure/RelativeCacheLocationMutator.cs @@ -0,0 +1,32 @@ +using Parse.Abstractions.Infrastructure; + +namespace Parse.Infrastructure +{ + /// + /// An for the relative cache file location. This should be used if the relative cache file location is not created correctly by the SDK, such as platforms on which it is not possible to gather metadata about the client assembly, or ones on which is inaccsessible. + /// + public class RelativeCacheLocationMutator : IServiceHubMutator + { + /// + /// An implementation instance which creates a path that should be used as the -relative cache location. + /// + public IRelativeCacheLocationGenerator RelativeCacheLocationGenerator { get; set; } + + /// + /// + /// + public bool Valid => RelativeCacheLocationGenerator is { }; + + /// + /// + /// + /// + /// + public void Mutate(ref IMutableServiceHub target, in IServiceHub referenceHub) => target.CacheController = (target as IServiceHub).CacheController switch + { + null => new CacheController { RelativeCacheFilePath = RelativeCacheLocationGenerator.GetRelativeCacheFilePath(referenceHub) }, + IDiskFileCacheController { } controller => (Controller: controller, controller.RelativeCacheFilePath = RelativeCacheLocationGenerator.GetRelativeCacheFilePath(referenceHub)).Controller, + { } controller => controller + }; + } +} diff --git a/Parse/Infrastructure/ServiceHub.cs b/Parse/Infrastructure/ServiceHub.cs index cec0e064..564b7bf6 100644 --- a/Parse/Infrastructure/ServiceHub.cs +++ b/Parse/Infrastructure/ServiceHub.cs @@ -42,22 +42,22 @@ public class ServiceHub : IServiceHub public IServiceHubCloner Cloner => LateInitializer.GetValue(() => new { } as object as IServiceHubCloner); public IWebClient WebClient => LateInitializer.GetValue(() => new UniversalWebClient { }); - public IStorageController StorageController => LateInitializer.GetValue(() => new StorageController { }); + public ICacheController CacheController => LateInitializer.GetValue(() => new CacheController { }); public IParseObjectClassController ClassController => LateInitializer.GetValue(() => new ParseObjectClassController { }); public IParseDataDecoder Decoder => LateInitializer.GetValue(() => new ParseDataDecoder(ClassController)); - public IParseInstallationController InstallationController => LateInitializer.GetValue(() => new ParseInstallationController(StorageController)); + public IParseInstallationController InstallationController => LateInitializer.GetValue(() => new ParseInstallationController(CacheController)); public IParseCommandRunner CommandRunner => LateInitializer.GetValue(() => new ParseCommandRunner(WebClient, InstallationController, MetadataController, ServerConnectionData, new Lazy(() => UserController))); public IParseCloudCodeController CloudCodeController => LateInitializer.GetValue(() => new ParseCloudCodeController(CommandRunner, Decoder)); - public IParseConfigurationController ConfigurationController => LateInitializer.GetValue(() => new ParseConfigurationController(CommandRunner, StorageController, Decoder)); + public IParseConfigurationController ConfigurationController => LateInitializer.GetValue(() => new ParseConfigurationController(CommandRunner, CacheController, Decoder)); public IParseFileController FileController => LateInitializer.GetValue(() => new ParseFileController(CommandRunner)); public IParseObjectController ObjectController => LateInitializer.GetValue(() => new ParseObjectController(CommandRunner, Decoder, ServerConnectionData)); public IParseQueryController QueryController => LateInitializer.GetValue(() => new ParseQueryController(CommandRunner, Decoder)); public IParseSessionController SessionController => LateInitializer.GetValue(() => new ParseSessionController(CommandRunner, Decoder)); public IParseUserController UserController => LateInitializer.GetValue(() => new ParseUserController(CommandRunner, Decoder)); - public IParseCurrentUserController CurrentUserController => LateInitializer.GetValue(() => new ParseCurrentUserController(StorageController, ClassController, Decoder)); + public IParseCurrentUserController CurrentUserController => LateInitializer.GetValue(() => new ParseCurrentUserController(CacheController, ClassController, Decoder)); public IParseAnalyticsController AnalyticsController => LateInitializer.GetValue(() => new ParseAnalyticsController(CommandRunner)); @@ -65,7 +65,7 @@ public class ServiceHub : IServiceHub public IParsePushChannelsController PushChannelsController => LateInitializer.GetValue(() => new ParsePushChannelsController(CurrentInstallationController)); public IParsePushController PushController => LateInitializer.GetValue(() => new ParsePushController(CommandRunner, CurrentUserController)); - public IParseCurrentInstallationController CurrentInstallationController => LateInitializer.GetValue(() => new ParseCurrentInstallationController(InstallationController, StorageController, InstallationCoder, ClassController)); + public IParseCurrentInstallationController CurrentInstallationController => LateInitializer.GetValue(() => new ParseCurrentInstallationController(InstallationController, CacheController, InstallationCoder, ClassController)); public IParseInstallationDataFinalizer InstallationDataFinalizer => LateInitializer.GetValue(() => new ParseInstallationDataFinalizer { }); public bool Reset() => LateInitializer.Used && LateInitializer.Reset(); diff --git a/Parse/Platform/Configuration/ParseConfigurationController.cs b/Parse/Platform/Configuration/ParseConfigurationController.cs index f4f6210c..7cfe3d1e 100644 --- a/Parse/Platform/Configuration/ParseConfigurationController.cs +++ b/Parse/Platform/Configuration/ParseConfigurationController.cs @@ -26,7 +26,7 @@ internal class ParseConfigurationController : IParseConfigurationController /// /// Initializes a new instance of the class. /// - public ParseConfigurationController(IParseCommandRunner commandRunner, IStorageController storageController, IParseDataDecoder decoder) + public ParseConfigurationController(IParseCommandRunner commandRunner, ICacheController storageController, IParseDataDecoder decoder) { CommandRunner = commandRunner; CurrentConfigurationController = new ParseCurrentConfigurationController(storageController, decoder); diff --git a/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs b/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs index 092564f5..de21b64e 100644 --- a/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs +++ b/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs @@ -20,14 +20,14 @@ internal class ParseCurrentConfigurationController : IParseCurrentConfigurationC ParseConfiguration CurrentConfiguration { get; set; } - IStorageController StorageController { get; } + ICacheController StorageController { get; } IParseDataDecoder Decoder { get; } /// /// Initializes a new instance of the class. /// - public ParseCurrentConfigurationController(IStorageController storageController, IParseDataDecoder decoder) + public ParseCurrentConfigurationController(ICacheController storageController, IParseDataDecoder decoder) { StorageController = storageController; Decoder = decoder; diff --git a/Parse/Platform/Installations/ParseCurrentInstallationController.cs b/Parse/Platform/Installations/ParseCurrentInstallationController.cs index 62888309..c9b0f0fd 100644 --- a/Parse/Platform/Installations/ParseCurrentInstallationController.cs +++ b/Parse/Platform/Installations/ParseCurrentInstallationController.cs @@ -20,13 +20,13 @@ internal class ParseCurrentInstallationController : IParseCurrentInstallationCon IParseInstallationController InstallationController { get; } - IStorageController StorageController { get; } + ICacheController StorageController { get; } IParseInstallationCoder InstallationCoder { get; } IParseObjectClassController ClassController { get; } - public ParseCurrentInstallationController(IParseInstallationController installationIdController, IStorageController storageController, IParseInstallationCoder installationCoder, IParseObjectClassController classController) + public ParseCurrentInstallationController(IParseInstallationController installationIdController, ICacheController storageController, IParseInstallationCoder installationCoder, IParseObjectClassController classController) { InstallationController = installationIdController; StorageController = storageController; diff --git a/Parse/Platform/Installations/ParseInstallationCoder.cs b/Parse/Platform/Installations/ParseInstallationCoder.cs index 1744f32e..25263db6 100644 --- a/Parse/Platform/Installations/ParseInstallationCoder.cs +++ b/Parse/Platform/Installations/ParseInstallationCoder.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using Parse; using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Infrastructure.Data; using Parse.Abstractions.Platform.Installations; diff --git a/Parse/Platform/Installations/ParseInstallationController.cs b/Parse/Platform/Installations/ParseInstallationController.cs index 31f1a1ef..80e0fc14 100644 --- a/Parse/Platform/Installations/ParseInstallationController.cs +++ b/Parse/Platform/Installations/ParseInstallationController.cs @@ -16,9 +16,9 @@ public class ParseInstallationController : IParseInstallationController Guid? InstallationId { get; set; } - IStorageController StorageController { get; } + ICacheController StorageController { get; } - public ParseInstallationController(IStorageController storageController) => StorageController = storageController; + public ParseInstallationController(ICacheController storageController) => StorageController = storageController; public Task SetAsync(Guid? installationId) { diff --git a/Parse/Platform/ParseClient.cs b/Parse/Platform/ParseClient.cs index f40919ca..68535c72 100644 --- a/Parse/Platform/ParseClient.cs +++ b/Parse/Platform/ParseClient.cs @@ -40,11 +40,6 @@ public class ParseClient : CustomServiceHub, IServiceHubComposer /// public static ParseClient Instance { get; private set; } - /// - /// The current configuration that parse has been initialized with. - /// - public IServerConnectionData Configuration => Services.ServerConnectionData; - internal static string Version => typeof(ParseClient)?.Assembly?.GetCustomAttribute()?.InformationalVersion ?? typeof(ParseClient)?.Assembly?.GetName()?.Version?.ToString(); /// diff --git a/Parse/Platform/Users/ParseCurrentUserController.cs b/Parse/Platform/Users/ParseCurrentUserController.cs index 7e9aa137..680110a5 100644 --- a/Parse/Platform/Users/ParseCurrentUserController.cs +++ b/Parse/Platform/Users/ParseCurrentUserController.cs @@ -10,7 +10,6 @@ using Parse.Abstractions.Platform.Objects; using Parse.Abstractions.Platform.Users; using Parse.Infrastructure.Utilities; -using Parse; using Parse.Infrastructure.Data; namespace Parse.Platform.Users @@ -23,13 +22,13 @@ public class ParseCurrentUserController : IParseCurrentUserController TaskQueue TaskQueue { get; } = new TaskQueue { }; - IStorageController StorageController { get; } + ICacheController StorageController { get; } IParseObjectClassController ClassController { get; } IParseDataDecoder Decoder { get; } - public ParseCurrentUserController(IStorageController storageController, IParseObjectClassController classController, IParseDataDecoder decoder) => (StorageController, ClassController, Decoder) = (storageController, classController, decoder); + public ParseCurrentUserController(ICacheController storageController, IParseObjectClassController classController, IParseDataDecoder decoder) => (StorageController, ClassController, Decoder) = (storageController, classController, decoder); ParseUser currentUser; public ParseUser CurrentUser diff --git a/README.md b/README.md index ea82bf9c..36b9c3c6 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ new ParseClient(/* Parameters */).Publicize(); ### Use In Unity Client -In Unity, the same logic applies to use the SDK as in [any other client](#client-side-use), except that a special `IServiceHub` impelementation instance and a `MetadataMutator` need to be passed in to one of the non-cloning `ParseClient` constructors in order to specify the environment and platform metadata manually. This step is needed because the logic that creates these values automatically will fail. The functionality to do this automatically may eventually be provided as a Unity package in the future, but for now, the following code can be used. +In Unity, the same logic applies to use the SDK as in [any other client](#client-side-use), except that a special `IServiceHub` impelementation instance, a `MetadataMutator`, and an `AbsoluteCacheLocationMutator` need to be passed in to one of the non-cloning `ParseClient` constructors in order to specify the environment and platform metadata, as well as the absolute cache location manually. This step is needed because the logic that creates these values automatically will fail and create incorrect values. The functionality to do this automatically may eventually be provided as a Unity package in the future, but for now, the following code can be used. ```csharp using System; @@ -67,10 +67,12 @@ using Parse.Infrastructure; ``` ```csharp -new ParseClient(/* Parameters */, new LateInitializedMutableServiceHub { }, new MetadataMutator { EnvironmentData = new EnvironmentData { OSVersion = SystemInfo.operatingSystem, Platform = $"Unity {Application.unityVersion} on {SystemInfo.operatingSystemFamily}", TimeZone = TimeZoneInfo.Local.StandardName }, HostManifestData = new HostManifestData { Name = Application.productName, Identifier = Application.productName, ShortVersion = Application.version, Version = Application.version } }).Publicize(); +new ParseClient(/* Parameters */, new LateInitializedMutableServiceHub { }, new MetadataMutator { EnvironmentData = new EnvironmentData { OSVersion = SystemInfo.operatingSystem, Platform = $"Unity {Application.unityVersion} on {SystemInfo.operatingSystemFamily}", TimeZone = TimeZoneInfo.Local.StandardName }, HostManifestData = new HostManifestData { Name = Application.productName, Identifier = Application.productName, ShortVersion = Application.version, Version = Application.version } }, new AbsoluteCacheLocationMutator { CustomAbsoluteCacheFilePath = $"{Application.persistentDataPath.Replace('/', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}Parse.cache" }).Publicize(); ``` -Other `IServiceHubMutator` implementations are available that do different things, such as the `CacheLocationMutator`, which allows a custom cache location to be specified. +Other `IServiceHubMutator` implementations are available that do different things, such as the `RelativeCacheLocationMutator`, which allows a custom cache location relative to the default base folder (`System.Environment.SpecialFolder.LocalApplicationData`) to be specified. + +If you are having trouble getting the SDK to work on other platforms, try to use the above code to control what values for various metadata information items the SDK will use, to see if that fixes the issue. ### Server-Side Use From f58c2480fab8211e1b4089236e14c8f0420db8ae Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Mon, 4 May 2020 18:23:51 -0700 Subject: [PATCH 14/24] Rename ConcurrentUserStorageController to VirtualCacheController, and VirtualStorageDictionary to VirtualDataStore. --- .../ConcurrentUserServiceHubCloner.cs | 2 +- .../ConcurrentUserStorageController.cs | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs b/Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs index 527a7d81..829901fe 100644 --- a/Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs +++ b/Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs @@ -13,7 +13,7 @@ public class ConcurrentUserServiceHubCloner : IServiceHubCloner, IServiceHubMuta public void Mutate(ref IMutableServiceHub target, in IServiceHub composedHub) { target.Cloner = this; - target.CurrentUserController = new ParseCurrentUserController(new ConcurrentUserStorageController { }, composedHub.ClassController, composedHub.Decoder); + target.CurrentUserController = new ParseCurrentUserController(new VirtualCacheController { }, composedHub.ClassController, composedHub.Decoder); } } } diff --git a/Parse/Infrastructure/ConcurrentUserStorageController.cs b/Parse/Infrastructure/ConcurrentUserStorageController.cs index 2835fd0e..7f413a85 100644 --- a/Parse/Infrastructure/ConcurrentUserStorageController.cs +++ b/Parse/Infrastructure/ConcurrentUserStorageController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; @@ -7,9 +7,9 @@ namespace Parse.Infrastructure { - public class ConcurrentUserStorageController : ICacheController + public class VirtualCacheController : ICacheController { - class VirtualStorageDictionary : Dictionary, IDataCache + class VirtualDataStore : Dictionary, IDataCache { public Task AddAsync(string key, object value) { @@ -24,20 +24,22 @@ public Task RemoveAsync(string key) } } - VirtualStorageDictionary Storage { get; } = new VirtualStorageDictionary { }; + VirtualDataStore Cache { get; } = new VirtualDataStore { }; - public void Clear() => Storage.Clear(); + public void Clear() => Cache.Clear(); public FileInfo GetRelativeFile(string path) => throw new NotSupportedException(ConcurrentUserStorageControllerFileOperationNotSupportedMessage); - public Task> LoadAsync() => Task.FromResult>(Storage); + public Task> LoadAsync() => Task.FromResult>(Cache); public Task> SaveAsync(IDictionary contents) { foreach (KeyValuePair pair in contents) - ((IDictionary) Storage).Add(pair); + { + ((IDictionary) Cache).Add(pair); + } - return Task.FromResult>(Storage); + return Task.FromResult>(Cache); } public Task TransferAsync(string originFilePath, string targetFilePath) => Task.FromException(new NotSupportedException(ConcurrentUserStorageControllerFileOperationNotSupportedMessage)); From 5729f9b15a42f4b617991722faacbb5027a1329b Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Mon, 4 May 2020 18:34:44 -0700 Subject: [PATCH 15/24] Update exception messages, rename ConcurrentUserStorageController to TransientCacheController, and rename VirtualDataStore to VirtualCache. --- Parse/Infrastructure/CacheController.cs | 10 +-- .../ConcurrentUserServiceHubCloner.cs | 2 +- ...troller.cs => TransientCacheController.cs} | 10 +-- Parse/Resources.Designer.cs | 68 ++++++++----------- Parse/Resources.resx | 8 +-- 5 files changed, 44 insertions(+), 54 deletions(-) rename Parse/Infrastructure/{ConcurrentUserStorageController.cs => TransientCacheController.cs} (77%) diff --git a/Parse/Infrastructure/CacheController.cs b/Parse/Infrastructure/CacheController.cs index 0ffb66b1..f85d2d47 100644 --- a/Parse/Infrastructure/CacheController.cs +++ b/Parse/Infrastructure/CacheController.cs @@ -60,13 +60,13 @@ public Task RemoveAsync(string key) } } - public void Add(string key, object value) => throw new NotSupportedException(StorageDictionarySynchronousMutationNotSupportedMessage); + public void Add(string key, object value) => throw new NotSupportedException(FileBackedCacheSynchronousMutationNotSupportedMessage); - public bool Remove(string key) => throw new NotSupportedException(StorageDictionarySynchronousMutationNotSupportedMessage); + public bool Remove(string key) => throw new NotSupportedException(FileBackedCacheSynchronousMutationNotSupportedMessage); - public void Add(KeyValuePair item) => throw new NotSupportedException(StorageDictionarySynchronousMutationNotSupportedMessage); + public void Add(KeyValuePair item) => throw new NotSupportedException(FileBackedCacheSynchronousMutationNotSupportedMessage); - public bool Remove(KeyValuePair item) => throw new NotSupportedException(StorageDictionarySynchronousMutationNotSupportedMessage); + public bool Remove(KeyValuePair item) => throw new NotSupportedException(FileBackedCacheSynchronousMutationNotSupportedMessage); public bool ContainsKey(string key) => Lock(() => Storage.ContainsKey(key)); @@ -125,7 +125,7 @@ void Lock(Action operation) public object this[string key] { get => Storage[key]; - set => throw new NotSupportedException(StorageDictionarySynchronousMutationNotSupportedMessage); + set => throw new NotSupportedException(FileBackedCacheSynchronousMutationNotSupportedMessage); } } diff --git a/Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs b/Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs index 829901fe..44eb69ee 100644 --- a/Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs +++ b/Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs @@ -13,7 +13,7 @@ public class ConcurrentUserServiceHubCloner : IServiceHubCloner, IServiceHubMuta public void Mutate(ref IMutableServiceHub target, in IServiceHub composedHub) { target.Cloner = this; - target.CurrentUserController = new ParseCurrentUserController(new VirtualCacheController { }, composedHub.ClassController, composedHub.Decoder); + target.CurrentUserController = new ParseCurrentUserController(new TransientCacheController { }, composedHub.ClassController, composedHub.Decoder); } } } diff --git a/Parse/Infrastructure/ConcurrentUserStorageController.cs b/Parse/Infrastructure/TransientCacheController.cs similarity index 77% rename from Parse/Infrastructure/ConcurrentUserStorageController.cs rename to Parse/Infrastructure/TransientCacheController.cs index 7f413a85..ccfd62f7 100644 --- a/Parse/Infrastructure/ConcurrentUserStorageController.cs +++ b/Parse/Infrastructure/TransientCacheController.cs @@ -7,9 +7,9 @@ namespace Parse.Infrastructure { - public class VirtualCacheController : ICacheController + public class TransientCacheController : ICacheController { - class VirtualDataStore : Dictionary, IDataCache + class VirtualCache : Dictionary, IDataCache { public Task AddAsync(string key, object value) { @@ -24,11 +24,11 @@ public Task RemoveAsync(string key) } } - VirtualDataStore Cache { get; } = new VirtualDataStore { }; + VirtualCache Cache { get; } = new VirtualCache { }; public void Clear() => Cache.Clear(); - public FileInfo GetRelativeFile(string path) => throw new NotSupportedException(ConcurrentUserStorageControllerFileOperationNotSupportedMessage); + public FileInfo GetRelativeFile(string path) => throw new NotSupportedException(TransientCacheControllerDiskFileOperationNotSupportedMessage); public Task> LoadAsync() => Task.FromResult>(Cache); @@ -42,6 +42,6 @@ public Task> SaveAsync(IDictionary co return Task.FromResult>(Cache); } - public Task TransferAsync(string originFilePath, string targetFilePath) => Task.FromException(new NotSupportedException(ConcurrentUserStorageControllerFileOperationNotSupportedMessage)); + public Task TransferAsync(string originFilePath, string targetFilePath) => Task.FromException(new NotSupportedException(TransientCacheControllerDiskFileOperationNotSupportedMessage)); } } diff --git a/Parse/Resources.Designer.cs b/Parse/Resources.Designer.cs index 43abe2ab..3b6e39ff 100644 --- a/Parse/Resources.Designer.cs +++ b/Parse/Resources.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -8,8 +8,10 @@ // //------------------------------------------------------------------------------ -namespace Parse -{ +namespace Parse { + using System; + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -20,71 +22,59 @@ namespace Parse [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - + internal class Resources { + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { + internal Resources() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if (object.ReferenceEquals(resourceMan, null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Parse.Properties.Resources", typeof(Resources).Assembly); + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Parse.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { + internal static global::System.Globalization.CultureInfo Culture { + get { return resourceCulture; } - set - { + set { resourceCulture = value; } } - + /// - /// Looks up a localized string similar to This storage controller is not configured to use physical files to store information. Data is hosted in system memory.. + /// Looks up a localized string similar to Mutation of a file-backed cache is an asynchronous operation as the tracked file needs to be modified.. /// - internal static string ConcurrentUserStorageControllerFileOperationNotSupportedMessage - { - get - { - return ResourceManager.GetString("ConcurrentUserStorageControllerFileOperationNotSupportedMessage", resourceCulture); + internal static string FileBackedCacheSynchronousMutationNotSupportedMessage { + get { + return ResourceManager.GetString("FileBackedCacheSynchronousMutationNotSupportedMessage", resourceCulture); } } - + /// - /// Looks up a localized string similar to Mutating a storage dictionary is an asynchronous operation as the storing file needs to be modified.. + /// Looks up a localized string similar to Cannot perform file operations with in-memory cache controller.. /// - internal static string StorageDictionarySynchronousMutationNotSupportedMessage - { - get - { - return ResourceManager.GetString("StorageDictionarySynchronousMutationNotSupportedMessage", resourceCulture); + internal static string TransientCacheControllerDiskFileOperationNotSupportedMessage { + get { + return ResourceManager.GetString("TransientCacheControllerDiskFileOperationNotSupportedMessage", resourceCulture); } } } diff --git a/Parse/Resources.resx b/Parse/Resources.resx index eb8d7799..69ace474 100644 --- a/Parse/Resources.resx +++ b/Parse/Resources.resx @@ -117,10 +117,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - This storage controller is not configured to use physical files to store information. Data is hosted in system memory. + + Mutation of a file-backed cache is an asynchronous operation as the tracked file needs to be modified. - - Mutating a storage dictionary is an asynchronous operation as the storing file needs to be modified. + + Cannot perform file operations with in-memory cache controller. \ No newline at end of file From 3f1154f4c76e268d9482512087f262712a7f5ed7 Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Mon, 4 May 2020 18:44:53 -0700 Subject: [PATCH 16/24] Update README.md. --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 36b9c3c6..9cc48d22 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,17 @@ ParseClient client = new ParseClient(new ServerConnectionData { ApplicationID = "Your Application ID", ServerURI = "The Parse Server Instance Host URI", - Key = "Your .NET Key" + Key = "Your .NET Key", // This is unnecessary if a value for MasterKey is specified. + MasterKey = "Your Master Key", + Headers = new Dictionary + { + ["X-Extra-Header"] = "Some Value" + } }); ``` +`ServerConnectionData` is available in the `Parse.Infrastructure` namespace. + The two non-cloning `ParseClient` constructors contain optional parameters for an `IServiceHub` implementation instance and an array of `IServiceHubMutator`s. These should only be used when the behaviour of the SDK needs to be changed such as [when it is used with the Unity game engine](#use-in-unity-client). To find full usage instructions for the latest stable release, please visit the [Parse docs website][parse-docs-link]. Please note that the latest stable release is quite old and does not reflect the work being done at the moment. From d77ffe5e05f4c8cdeda94e0993c7148c79ceffb0 Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Mon, 4 May 2020 19:45:14 -0700 Subject: [PATCH 17/24] Update SignUpAsync IServiceHub extension method logic, and make ParseObject.Services publicly settable. --- Parse/Platform/Objects/ParseObject.cs | 6 +++--- Parse/Utilities/UserServiceExtensions.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Parse/Platform/Objects/ParseObject.cs b/Parse/Platform/Objects/ParseObject.cs index 8f82c75a..dea9db3e 100644 --- a/Parse/Platform/Objects/ParseObject.cs +++ b/Parse/Platform/Objects/ParseObject.cs @@ -41,13 +41,13 @@ public class ParseObject : IEnumerable>, INotifyPro internal TaskQueue TaskQueue { get; } = new TaskQueue { }; /// - /// The instance being targeted. + /// The instance being targeted. This should generally not be set except when an object is being constructed, as otherwise race conditions may occur. The preferred method to set this property is via calling . /// - internal IServiceHub Services { get; private protected set; } + public IServiceHub Services { get; set; } /// /// Constructs a new ParseObject with no data in it. A ParseObject constructed in this way will - /// not have an ObjectId and will not persist to the database until + /// not have an ObjectId and will not persist to the database until /// is called. /// /// diff --git a/Parse/Utilities/UserServiceExtensions.cs b/Parse/Utilities/UserServiceExtensions.cs index fff1167a..c4188193 100644 --- a/Parse/Utilities/UserServiceExtensions.cs +++ b/Parse/Utilities/UserServiceExtensions.cs @@ -31,10 +31,10 @@ internal static string GetCurrentSessionToken(this IServiceHub serviceHub) /// The value that should be used for . /// The value that should be used for . /// The cancellation token. - public static Task SignUpAsync(this IServiceHub serviceHub, string username, string password, CancellationToken cancellationToken = default) => serviceHub.SignUpAsync(new ParseUser { Username = username, Password = password }, cancellationToken); + public static Task SignUpAsync(this IServiceHub serviceHub, string username, string password, CancellationToken cancellationToken = default) => new ParseUser { Services = serviceHub, Username = username, Password = password }.SignUpAsync(cancellationToken); /// - /// Saves the provided instance with the target Parse Server instance and then authenticates it on the target client. + /// Saves the provided instance with the target Parse Server instance and then authenticates it on the target client. This method should only be used once has been called and is the wanted bind target, or if has already been set or has already been called on the . /// /// The instance to target when creating the user and authenticating. /// The instance to save on the target Parse Server instance and authenticate. From fe205ed98940a7c4056c88b34d4373dbe19915f2 Mon Sep 17 00:00:00 2001 From: Tobias Pott Date: Sun, 28 Jun 2020 19:49:11 +0200 Subject: [PATCH 18/24] Updated code sample formatting for readability. --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a1a3dbd4..e01da8a7 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,18 @@ using Parse.Infrastructure; ``` ```csharp -new ParseClient(/* Parameters */, new LateInitializedMutableServiceHub { }, new MetadataMutator { EnvironmentData = new EnvironmentData { OSVersion = SystemInfo.operatingSystem, Platform = $"Unity {Application.unityVersion} on {SystemInfo.operatingSystemFamily}", TimeZone = TimeZoneInfo.Local.StandardName }, HostManifestData = new HostManifestData { Name = Application.productName, Identifier = Application.productName, ShortVersion = Application.version, Version = Application.version } }, new AbsoluteCacheLocationMutator { CustomAbsoluteCacheFilePath = $"{Application.persistentDataPath.Replace('/', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}Parse.cache" }).Publicize(); +new ParseClient(/* Parameters */, + new LateInitializedMutableServiceHub { }, + new MetadataMutator + { + EnvironmentData = new EnvironmentData { OSVersion = SystemInfo.operatingSystem, Platform = $"Unity {Application.unityVersion} on {SystemInfo.operatingSystemFamily}", TimeZone = TimeZoneInfo.Local.StandardName }, + HostManifestData = new HostManifestData { Name = Application.productName, Identifier = Application.productName, ShortVersion = Application.version, Version = Application.version } + }, + new AbsoluteCacheLocationMutator + { + CustomAbsoluteCacheFilePath = $"{Application.persistentDataPath.Replace('/', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}Parse.cache" + } + ).Publicize(); ``` Other `IServiceHubMutator` implementations are available that do different things, such as the `RelativeCacheLocationMutator`, which allows a custom cache location relative to the default base folder (`System.Environment.SpecialFolder.LocalApplicationData`) to be specified. From 1e2a51b833166b375e085d91ce20f48792464c2f Mon Sep 17 00:00:00 2001 From: Tobias Pott Date: Sun, 28 Jun 2020 19:59:39 +0200 Subject: [PATCH 19/24] Added link.xml hint for iOS platform to the Unity3D section Added internet access settings requirement for Android platform to the Unity3D section --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index e01da8a7..38d7f854 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,27 @@ Other `IServiceHubMutator` implementations are available that do different thing If you are having trouble getting the SDK to work on other platforms, try to use the above code to control what values for various metadata information items the SDK will use, to see if that fixes the issue. +#### Unity3D on iOS + +When using the Parse SDK on iOS/iPadOS target platforms you may encounter issues during runtime where the creation of ParseObjects using subclassing or other Parse methods fail. This occurs due to the fact that Unity strips code from the project and it will most likely do so for some parts of the Parse.dll assembly file. + +To prevent Unity to remove necessary code from the assembly it is necessary to include a link.xml file in your project which tells Unity to not touch anything from the Parse.dll. + +```xml + + + +``` +Save the above xml code to a file called 'link.xml' and place it in the Assets folder of your project. + +#### Unity3D on Android + +When using the Parse SDK on Android target platform you may encounter an issue related to network communication and resolution of host addresses when using the Parse SDK. This occurs in situations where you might use the Parse SDK but did not configure your Android app to require internet access. Whenever a project does not explicitly state it requires internet access Unity will try to remove classes and system assemblies during the build process, causing Parse-calls to fail with different exceptions. +This may not be the case if your project uses any Unity specific web/networking code, as this will be detected by the engine and the code stripping will not be done. + +To set your project, navigate to `Project Settings -> Player -> Other Settings -> Internet Access` and switch it to Require. +Depending on the version of Unity you are using this setting may be found in a slightly different location or with slightly different naming, use the above path as a guidance. + ### Server-Side Use The SDK can be set up in a way such that every new `ParseClient` instance can authenticate a different user concurrently. This is enabled by an `IServiceHubMutator` implementation which adds itself as an `IServiceHubCloner` implementation to the service hub which, making it so that consecutive calls to the cloning `ParseClient` constructor (the one without parameters) will clone the publicized `ParseClient` instance, exposed by `ParseClient.Instance`, replacing the `IParseCurrentUserController` implementation instance with a fresh one with no caching every time. This allows you to configure the original instance, and have the clones retain the general behaviour, while also allowing the differnt users to be signed into the their respective clones and execute requests concurrently, without causing race conditions. To use this feature of the SDK, the first `ParseClient` instance must be constructued and publicized as follows once, before any other `ParseClient` instantiations. Any classes that need to be registered must be done so with the original instance. From 07fb388627c25db2d890286443e6e31df2df088a Mon Sep 17 00:00:00 2001 From: Tobias Pott Date: Mon, 29 Jun 2020 12:19:46 +0200 Subject: [PATCH 20/24] Removed -develop suffix of package id Added nuget package icon (using parse website logo) Added license file reference to nuget package --- Parse/Parse.csproj | 23 ++++++++++++++++++++--- Parse/parse-logo.png | Bin 0 -> 5982 bytes 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 Parse/parse-logo.png diff --git a/Parse/Parse.csproj b/Parse/Parse.csproj index e2d9d5b9..fb1465ca 100644 --- a/Parse/Parse.csproj +++ b/Parse/Parse.csproj @@ -3,26 +3,36 @@ netstandard2.0 bin\Release\netstandard2.0\Parse.xml - 2.0.0-develop-0001 + 2.0.0 latest Parse https://parseplatform.org/ https://github.com/parse-community/Parse-SDK-dotNET/ - https://raw.githubusercontent.com/parse-community/parse-community.github.io/master/img/favicon/favicon-194x194.png + GitHub This is the official package for the Parse .NET Standard SDK. Add a cloud backend to any platform supporting .NET Standard 2.0 with this simple-to-use SDK. Copyright © Parse 2020. All rights reserved. - Parse;netstandard2.0;parse-platform;backend;sdk;netstandard;app + Parse;parse-platform;netstandard;netstandard2.0;backend;sdk;app false true + parse-logo.png + LICENSE + + + + True + + + + True @@ -38,4 +48,11 @@ + + + True + + + + diff --git a/Parse/parse-logo.png b/Parse/parse-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..87526d324c2ef61e58d1262d952cdd8bcc0298ae GIT binary patch literal 5982 zcmai2c_5T)+n+&*vZTcp(~u?1V#_jv?8+d6Y{_8kGsDba$TFi-3fcE1m8C4n5|yn9 zMb;#;9*R`To;B)w)OpW&&v)MU`~G-l=DvT|^}Da-zOLuK=bD9?(S9yrE)WQ`-^5to z68NU?KAh~pXNwlD69i(b^|G?1+G0#KTuB5N&W+@PhtUXr01g77v}t}gS06kT;)3_^ zBBG%S^-rJ>FE=#woC*ek@zcY5dKrh1@z@YEE7uSoS9Le2wiXwPrU4KT@KhXxM(`z4 zG-zn(PhJh+_wFW=nb#*vG5w57H07xiMf{9cdO@T;}*roWzp^vAylD+(>UL+!9 zmlNkg3ZSB)PymPgL5`<+{l!kC{NX+j2>31nN5T;Be;22Ex&IgOUF09)ex4*MiQ-A} z`_qBHE%7J(4@Dq+|5=5G^ZQT27|j1IP9Xfv4GPsD5D3H{LH(Oe|5|`z73_zHTjD9C z0J1CIAP`TaO8kt(Zd^1hyl8k|TYWDAo=5?ri-sZ<)&3W1_uo)Gk}rvj1-2C)Q2VL0 z8#@gX0?q?}&dZJJ`4`(?fGM8n@egFzI^TfZ^jZ_W0Dlz!Vzd1VLqpY7fsM7B^q+io z{}K8#7aBk9!;stn^OSyS{RNrm=~<9T?q0qCMzJ)~g_s!VsUp=?RTUIrzgz(lrvd1C zxd-dxsDP9rLJ^^W0C06Hq@o5AsiBONLnvw>e%bR=8mI?192NKf$?xuJ2nx`^U^I-q zC{z+T_~+EGt&YX}{~G-o`g;A`;}FQtOl#m=cY}n6`jJU)0j_wrpKbwkzfcOvof?EA z<4<`2Nk&6Yxx0G-;S7cV<>Tc+#FHUNm>TR?DE`z5^27st|J^(IAGX1F>+WC1q2T{8 zAN5n<-wGAb`!xqN0HBk=|7<0|;Lm1+CjuRa478eTcnuf?0!MlQiTOuDKp>gKfW-PU ztf%;*o=AF2p5U^#m0`O!B6*uXQuXlOCLM(&&ZeyCB9l9X(fE$;dZ_!%9vUyX*&gcYO9&J}RddI??pc)-){sw)pzAlS=TJM;H?WmuBf^ zA_gWC>N?wZKs!4}e(BB&do@9jPfUf9-P1lU8%ujbTcxLtg1tRgBm=Jn z-4t*W)i#q7I!=!VU%{`veR?LZ>~7M;k7R8u68;WJv?Dhj;fBi=}7qpC@edz)noF0h(<`dE(d4G!Pa@~0 zJtiJ9W^I9&#$G>TI|>NI)4KbBQ{{L>Kp;M*iT)`oTFzYV#gu~snaqfttaXHi&XS?A z8|@l1`r5Lgey9QF-Int;WYqE(;ea4kspr>CR6VM-5WZb7q)%;C8$^0Wr^gPN1X>on z%B&M&lZr$nAWrlO%VY$to0_kGKdbT*5#Ka^{=V2&kXc^;KI`@H^AqPD62sQ(CqCpK zftGGToaHTu;fAbuo#IVdlMSAi&f&rWO>e*|Ox#W3yMqTmJ#f`-J{C&kPLQoC>0~-O zi}|r$Y3}r$Ef;GMTmy~LxiE=6L$A{%+;YL~B~I*PF5xp^f$`L<@rYQQNQD8mUgK2bn(8W=F9)PF1Oy`Loc3Xx7*Q)suVt2S9Gtd3&}GW=t1}!pNL7U2MTU z;@-( zx160AP|_=p*Q{tS?qe?KOIMZjvaTg!n>Z=-u&NR`^|q3O5s1?MLu~Q9sA8>?zUWly zk~${wc3^-@HV`Tp*asQ9rvA!9dhs|WQCU;?;WNNAaj-Aj^U=QV*6sYxw~CD0%7{bD zI6IrwB_@pA@h}O=G_xC!b{MZHWQUY)VVD=0esZJTU?!h)ec3Ff%=fh-E5hTR1L#GG zDusG`-w+^#wo&nXAV!Q@6hW&5>Z;C|<$HSsr)etT3@zWqeXiNCRhxHBC&0 z>$vkw9a@U$dK8^v62!J4rp}v}o&nxIqaj^{;y@e;R83x)yLesAFRI8`1gkE4oE5>U zzJ^0x3{j9cv(qJdz{RgJp0Ou<8Y)h{CI0xOxf7`8%9Lj}(%a$P>)KYeeo@!;Pcj_T zH&bXIhHSC?ac&WM*{oKq$ppcRbH*)hK7v~+MU+puqrSZq2AT@YCqimSDBzTD0* zxes`uMAyD5`WYX}q?|mwJiWT7ykn_!mUCic*fd+o{Bm#>)cPw`C;oJ*HJV;|Xk9RWNFgK(>l3X>Ut#(eCE}5&~^4!#z#8?m`vUzTYk9-uK*aWUiX=R#3Adyl)^PP`2{ZS9P7k zZL48G8YX@?;!c0a@rql@Z^R9X5ekoIWf^j@Xo;QZbsc`qGTr<-cHSd`Yo+(co1|Cj zvSc>FJF=E{OpJn(!V~3fzGE(33iZf(w49yq@3eLEa`xb0^{j;?{{5w;tF)bb&O8Tf z?@FqIzU8Ar@jNT$(KkaHMnbIH0q1`7<$_T@dGc$PqRUGo4K9(9C*Qs(5)9g@WkigM z6shljcERL~a*z31)Z?Hq*VEP83#M|dkzNMo)Z%`#bf5})S^IqtJu?(Ln%+AJMb1Ug z%5ya*h?6(>$EaQO$(MJO{(UW@2|WKqwU?}ZAVa=%>#N+4p@Gz`=v>PolW=C+RB~$r z!3g$%varQ&rZTz?3$K!Wt|ua*E-}>BkLBJwT;Kk?$;0#j5BI5%mp1&qZc9Tbb}mz< z{cZm=U&8G^#z37?H&SeLMBj?ZZkV>UnO#|17bBivy;Fso=_n1*L@>?vet}L$NwbYN z^v)A!7K1+w&QE9y;5Rv3s<)#$2`Zqy663!clq=Jp1+EdL*TbaZanD{Xe4mpZp;eD; zZkuXr^YPmszJY>3l%w%XyhhVda@lMcs53A}tvfQ&<^I|GUwB95P2N1vpDHVhmPGe( zj47h-ZJ3>%PqW^6*Fn5p+|4m|{=^vpafFXCnRVl)YDYP?mQZ8f0p~Ti>x0o+$k1-K z$S555hXV^hC5at#zN6-`pU(cfmYr; z+CRdRtsSs1zi{)NK3S+^In)vB{TVfRyL<;U)00#-K8^J8T=sCs6O@mym!0<&5Cntj zr6UEp5l>w?{#z14CYtrP~NU7leghu zr`jmg1ynl`PkEaJ9`g($1s)R~vOE7_tLS=6&VK22j)J;^9QOXnByh^;c=q{ydWm%Rk1QNCcskt?;C$|EX4&keZMUUy zUYB_Kidfw@F#Dn7>5pxkZ#aYBhgUVIU6QOQ{2bb<_fQ+c-72T{Ah!(l*@%t;r`weV z%|xVJ2~FACP^0Dsa@`R$I7Crv9ee8%9v&qI&foc(gpL?))@4|C)(VD>d~D~mm}!&A z&uRg)XxTz4UOoIq6~XjOAC_6A6o(UevLF8#LES>JWo!9&`Pd~Xfuh=ZH)u2Lm5p3H z!a82_{F8h@yX3xHW_`i)!xVd)lLJM9E)ctLWb#w7%TpWD&a*z+GfRg{y*ZP%VVWXZ z^TF+?(5Gl4CS6y&lq7dcEoFN+0w(&QhBvuwzr6{cM4suOREX1s{^wi53-YT{mIuEs zZEYX8l798xmwQ8_@7kR%vk7nO1(8E=NOzzXlK;ePYsmH#Sn zB*97#oXKy*KFDF>!dW%)8FK8WojjY0vl;ps$rL zm^Vavivw?y03C7Zi?Gg>%K3TA=XWx*wX=uF4cFg`|2BN?^Yw8Pseml#>YN|eddk`5 zZi2YelbV7zauJ`GRKp*f>=Nt7MlakHhT*yHwVq<7y`6y;@xIs!xC`rR@jqeqU@7Jj z|B1%p4W1J~4OoF9KIq?1-r}%D^;9-&*7l1h53-DftX1ujFJ<(}Q zwfwWVb7zUdzihnx0i@7Ix(6ppR9Yak+$%XPg; zczAH>c;9;)T+n@PBdLf9QhW<`G#fZErANoXud!DM<--~k*mnGO=+pPZeuN>Ko2tv} zjV~(kJD+LOh6&S)jE~S)16MUFm~iFI(ZI3nx91zL2AQ5`&6T@S#@^&+biaz3=1GsY zG<~b_s69_Pe{j<{$9w&VQ$zD?YlNd0=rj1JNTj|&8YLg8Hjpw>{o}=FZkn%4Z}{a4 z)D4Fo|s9b4t!`p2$2l#S9kulngp43yMY%s#UJ))Rs z`uo-yx%h@r87n=bl(?6l#lQCr>UIq`X9FiMNljy_M&mva)kNY}!Sy|)ft4qBl=2EA z#`vaUK8tL;_+H_Ia}~P{*7Gpo=jlzh4wJ2bp1+oaqgPM*o|w;$Mjf1z^2spgR2rxyy&}z@QW`pw$4;_Gf|boB-XO>CMdB;bF%V! zMn7nYelFkLAz8X5>U`AB@#?Fo*SN9GG#PQvQttdFgo0xPqA!>zzR7LyxWX&w3*d2U@Pp-HCR6Y}Z{leUdS3wwhV!%W=4{ zt|T26hsUc%yi{OXl2y3sZiA;rx`Iw`PEK+rTvZn?1BsFIBY2mkb}xol;9}?jM#9S6lVw!JKiDM7!e?;`r=T(U^e5{v6v0(Nr$ga z9Iwt!9(Nu{&D0ocR$_n25DMD{Ey^Qa;Qh;PbGb%*x5p%+YA$qB*b}bqUJsrLvL_M- zFafbB?$Yiuxf`whfX*iE5qFE=JbQz^OTi%;hs2TIg$Bj0b$uH_9cWd_dds48my&kv z%*n%(5X1R#0yC{uGIm&JP>7idTsvLM)Vo-TppHto*exCjP73dpX1Okv__E`l_urHQ zQvMr`^F7nnS=kU%V7Y=#T0FWB?#OB=G#6! S*}nU?xQT(8ez~r5 Date: Fri, 4 Sep 2020 09:41:34 -0700 Subject: [PATCH 21/24] Refactor and rename numerous items, add Facebook authentication via FacebookAuthenticationUtilities and FacebookAuthenticationProvider/FacebookAuthenticationMutator, add WebUtilities for web utilities contained within ParseClient, move JSON utilities contained within ParseClient into JsonUtilities, use latest C# version (9), add various documentation text, remove ParseUserExtensions as it was made redundant, and make IServiceHubMutator have access to a stack of future mutators that will be executed so that said stack can be modified as well as service-hub-related items. --- Parse.Tests/ConfigTests.cs | 2 +- Parse.Tests/UserTests.cs | 8 +- .../Infrastructure/IJsonConvertible.cs | 2 +- .../Infrastructure/IServiceHubBuilder.cs | 11 ++ .../Infrastructure/IServiceHubCloner.cs | 12 +- .../Infrastructure/IServiceHubComposer.cs | 9 - .../Infrastructure/IServiceHubMutator.cs | 16 +- .../IParseAuthenticationProvider.cs | 5 +- .../AbsoluteCacheLocationMutator.cs | 5 +- .../ConcurrentUserServiceHubCloner.cs | 22 ++- Parse/Infrastructure/Data/ParseDataEncoder.cs | 2 +- .../FacebookAuthenticationMutator.cs | 26 +++ Parse/Infrastructure/MetadataMutator.cs | 11 +- .../RelativeCacheLocationMutator.cs | 10 +- .../FacebookAuthenticationUtilities.cs | 94 +++++++++ .../Infrastructure/Utilities/JsonUtilities.cs | 69 ++++--- .../Infrastructure/Utilities/WebUtilities.cs | 25 +++ Parse/Parse.csproj | 4 +- .../FacebookAuthenticationProvider.cs | 187 ++++++++++++++++++ .../Configuration/ParseConfiguration.cs | 2 +- .../ParseCurrentConfigurationController.cs | 12 +- Parse/Platform/Files/ParseFile.cs | 2 +- Parse/Platform/Location/ParseGeoPoint.cs | 2 +- Parse/Platform/ParseClient.cs | 61 +++--- Parse/Platform/Relations/ParseRelation.cs | 2 +- Parse/Platform/Security/ParseACL.cs | 2 +- Parse/Platform/Users/ParseUser.cs | 57 ++++-- Parse/Platform/Users/ParseUserController.cs | 2 +- Parse/Utilities/ParseUserExtensions.cs | 29 --- Parse/Utilities/PushServiceExtensions.cs | 3 + Parse/Utilities/UserServiceExtensions.cs | 31 ++- 31 files changed, 541 insertions(+), 184 deletions(-) create mode 100644 Parse/Abstractions/Infrastructure/IServiceHubBuilder.cs delete mode 100644 Parse/Abstractions/Infrastructure/IServiceHubComposer.cs create mode 100644 Parse/Infrastructure/FacebookAuthenticationMutator.cs create mode 100644 Parse/Infrastructure/Utilities/FacebookAuthenticationUtilities.cs create mode 100644 Parse/Infrastructure/Utilities/WebUtilities.cs create mode 100644 Parse/Platform/Authentication/FacebookAuthenticationProvider.cs delete mode 100644 Parse/Utilities/ParseUserExtensions.cs diff --git a/Parse.Tests/ConfigTests.cs b/Parse.Tests/ConfigTests.cs index a6c45f3c..191f8b65 100644 --- a/Parse.Tests/ConfigTests.cs +++ b/Parse.Tests/ConfigTests.cs @@ -61,7 +61,7 @@ public void TestCurrentConfig() public void TestToJSON() { IDictionary expectedJson = new Dictionary { { "params", new Dictionary { { "testKey", "testValue" } } } }; - Assert.AreEqual(JsonConvert.SerializeObject((Client.GetCurrentConfiguration() as IJsonConvertible).ConvertToJSON()), JsonConvert.SerializeObject(expectedJson)); + Assert.AreEqual(JsonConvert.SerializeObject((Client.GetCurrentConfiguration() as IJsonConvertible).ConvertToJson()), JsonConvert.SerializeObject(expectedJson)); } [TestMethod] diff --git a/Parse.Tests/UserTests.cs b/Parse.Tests/UserTests.cs index 4e205ff2..4795647e 100644 --- a/Parse.Tests/UserTests.cs +++ b/Parse.Tests/UserTests.cs @@ -602,7 +602,7 @@ public Task TestLink() hub.ObjectController = mockObjectController.Object; hub.CurrentUserController = new Mock { }.Object; - return user.LinkWithAsync("parse", new Dictionary { }, CancellationToken.None).ContinueWith(task => + return user.LinkToServiceAsync("parse", new Dictionary { }, CancellationToken.None).ContinueWith(task => { Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); @@ -656,7 +656,7 @@ public Task TestUnlink() hub.ObjectController = mockObjectController.Object; hub.CurrentUserController = mockCurrentUserController.Object; - return user.UnlinkFromAsync("parse", CancellationToken.None).ContinueWith(task => + return user.UnlinkFromServiceAsync("parse", CancellationToken.None).ContinueWith(task => { Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); @@ -710,7 +710,7 @@ public Task TestUnlinkNonCurrentUser() hub.ObjectController = mockObjectController.Object; hub.CurrentUserController = mockCurrentUserController.Object; - return user.UnlinkFromAsync("parse", CancellationToken.None).ContinueWith(task => + return user.UnlinkFromServiceAsync("parse", CancellationToken.None).ContinueWith(task => { Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); @@ -747,7 +747,7 @@ public Task TestLogInWith() hub.UserController = mockController.Object; - return client.LogInWithAsync("parse", new Dictionary { }, CancellationToken.None).ContinueWith(task => + return client.AuthenticateWithServiceAsync("parse", new Dictionary { }, CancellationToken.None).ContinueWith(task => { Assert.IsFalse(task.IsFaulted); Assert.IsFalse(task.IsCanceled); diff --git a/Parse/Abstractions/Infrastructure/IJsonConvertible.cs b/Parse/Abstractions/Infrastructure/IJsonConvertible.cs index 6071d8bb..c5138f60 100644 --- a/Parse/Abstractions/Infrastructure/IJsonConvertible.cs +++ b/Parse/Abstractions/Infrastructure/IJsonConvertible.cs @@ -13,6 +13,6 @@ public interface IJsonConvertible /// Converts the object to a data structure that can be converted to JSON. /// /// An object to be JSONified. - IDictionary ConvertToJSON(); + IDictionary ConvertToJson(); } } diff --git a/Parse/Abstractions/Infrastructure/IServiceHubBuilder.cs b/Parse/Abstractions/Infrastructure/IServiceHubBuilder.cs new file mode 100644 index 00000000..23ce1d76 --- /dev/null +++ b/Parse/Abstractions/Infrastructure/IServiceHubBuilder.cs @@ -0,0 +1,11 @@ +#nullable enable + +namespace Parse.Abstractions.Infrastructure +{ + // ALTERNATE NAME: IServiceHubRebuilder, IServiceHubConstructor, IServiceHubBuilder, IServiceHubStacker, ISericeHubCreator, IClient, IDataContainmentHub, IResourceContainmentHub, IDataContainer, IServiceHubComposer + + public interface IServiceHubBuilder + { + public IServiceHub BuildHub(IMutableServiceHub? serviceHub = default, IServiceHub? extension = default, params IServiceHubMutator[] mutators); + } +} diff --git a/Parse/Abstractions/Infrastructure/IServiceHubCloner.cs b/Parse/Abstractions/Infrastructure/IServiceHubCloner.cs index d88dea9e..dd7c5401 100644 --- a/Parse/Abstractions/Infrastructure/IServiceHubCloner.cs +++ b/Parse/Abstractions/Infrastructure/IServiceHubCloner.cs @@ -1,7 +1,17 @@ namespace Parse.Abstractions.Infrastructure { + /// + /// Generates an as a clone of another. This clone could be mutated. + /// public interface IServiceHubCloner { - public IServiceHub BuildHub(in IServiceHub reference, IServiceHubComposer composer, params IServiceHubMutator[] requestedMutators); + /// + /// Clones , via the if needed, and should execute the . The clone could be observed in a mutated state versus . + /// + /// The hub to use as a reference or combination extension to new hub. + /// The builder which could be used to create the clone. + /// Additional mutators to execute on the cloned hub. + /// A service hub which could be both based on , and observed in a mutated state versus . + public IServiceHub CloneHub(in IServiceHub hub, IServiceHubBuilder builder, params IServiceHubMutator[] mutators); } } diff --git a/Parse/Abstractions/Infrastructure/IServiceHubComposer.cs b/Parse/Abstractions/Infrastructure/IServiceHubComposer.cs deleted file mode 100644 index 73dc3597..00000000 --- a/Parse/Abstractions/Infrastructure/IServiceHubComposer.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Parse.Abstractions.Infrastructure -{ - // ALTERNATE NAME: IClient, IDataContainmentHub, IResourceContainmentHub, IDataContainer, IServiceHubComposer - - public interface IServiceHubComposer - { - public IServiceHub BuildHub(IMutableServiceHub serviceHub = default, IServiceHub extension = default, params IServiceHubMutator[] configurators); - } -} diff --git a/Parse/Abstractions/Infrastructure/IServiceHubMutator.cs b/Parse/Abstractions/Infrastructure/IServiceHubMutator.cs index 6ce77d67..acd7f5a5 100644 --- a/Parse/Abstractions/Infrastructure/IServiceHubMutator.cs +++ b/Parse/Abstractions/Infrastructure/IServiceHubMutator.cs @@ -1,9 +1,11 @@ +using System.Collections.Generic; + namespace Parse.Abstractions.Infrastructure { // IServiceHubComposer, IServiceHubMutator, IServiceHubConfigurator, IClientConfigurator, IServiceConfigurationLayer /// - /// A class which makes a deliberate mutation to a service. + /// A definition of a deliberate mutation to a service hub. /// public interface IServiceHubMutator { @@ -12,11 +14,17 @@ public interface IServiceHubMutator /// bool Valid { get; } + //IServiceHubMutator[] Mutators => new[] { this }; + + // customHub, mutableHub, slice, target + // combinedHub, stackedHub, overallHub, resultantHub, composedHub + /// /// A method which mutates an implementation instance. /// - /// The target implementation instance - /// A hub which the is composed onto that should be used when needs to access services. - void Mutate(ref IMutableServiceHub target, in IServiceHub composedHub); + /// The target implementation instance + /// A hub which the is combined onto that should be used when needs to access services. + /// The mutators that will be executed after this one. + void Mutate(ref IMutableServiceHub mutableHub, in IServiceHub consumableHub, Stack futureMutators); } } diff --git a/Parse/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs b/Parse/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs index d177ee9d..6fb52c02 100644 --- a/Parse/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs +++ b/Parse/Abstractions/Platform/Authentication/IParseAuthenticationProvider.cs @@ -6,6 +6,9 @@ namespace Parse.Abstractions.Platform.Authentication { + /// + /// An authenticator service for the Parse SDK. + /// public interface IParseAuthenticationProvider { /// @@ -35,6 +38,6 @@ public interface IParseAuthenticationProvider /// Provides a unique name for the type of authentication the provider does. /// For example, the FacebookAuthenticationProvider would return "facebook". /// - string AuthType { get; } + string Name { get; } } } diff --git a/Parse/Infrastructure/AbsoluteCacheLocationMutator.cs b/Parse/Infrastructure/AbsoluteCacheLocationMutator.cs index 416aad1a..829285a0 100644 --- a/Parse/Infrastructure/AbsoluteCacheLocationMutator.cs +++ b/Parse/Infrastructure/AbsoluteCacheLocationMutator.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + using Parse.Abstractions.Infrastructure; namespace Parse.Infrastructure @@ -22,7 +24,8 @@ public class AbsoluteCacheLocationMutator : IServiceHubMutator /// /// /// - public void Mutate(ref IMutableServiceHub target, in IServiceHub composedHub) + /// + public void Mutate(ref IMutableServiceHub target, in IServiceHub composedHub, Stack futureMutators) { if ((target as IServiceHub).CacheController is IDiskFileCacheController { } diskFileCacheController) { diff --git a/Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs b/Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs index 44eb69ee..67542ff2 100644 --- a/Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs +++ b/Parse/Infrastructure/ConcurrentUserServiceHubCloner.cs @@ -1,19 +1,33 @@ +using System.Collections.Generic; using System.Linq; using Parse.Abstractions.Infrastructure; using Parse.Platform.Users; namespace Parse.Infrastructure { + /// + /// Makes it so that clones can be made each with a reset , but otherwise identical services unless modified. + /// public class ConcurrentUserServiceHubCloner : IServiceHubCloner, IServiceHubMutator { + /// public bool Valid { get; } = true; - public IServiceHub BuildHub(in IServiceHub reference, IServiceHubComposer composer, params IServiceHubMutator[] requestedMutators) => composer.BuildHub(default, reference, requestedMutators.Concat(new[] { this }).ToArray()); + /// + /// Mutators which need to be executed on each new hub for a user. + /// + public List BoundMutators { get; set; } = new List { }; - public void Mutate(ref IMutableServiceHub target, in IServiceHub composedHub) + /// + public IServiceHub CloneHub(in IServiceHub hub, IServiceHubBuilder builder, params IServiceHubMutator[] mutators) => builder.BuildHub(default, hub, mutators.Concat(new[] { this }).ToArray()); + + /// + public void Mutate(ref IMutableServiceHub mutableHub, in IServiceHub consumableHub, Stack futureMutators) { - target.Cloner = this; - target.CurrentUserController = new ParseCurrentUserController(new TransientCacheController { }, composedHub.ClassController, composedHub.Decoder); + mutableHub.Cloner = this; + mutableHub.CurrentUserController = new ParseCurrentUserController(new TransientCacheController { }, consumableHub.ClassController, consumableHub.Decoder); + + BoundMutators.ForEach(mutator => futureMutators.Push(mutator)); } } } diff --git a/Parse/Infrastructure/Data/ParseDataEncoder.cs b/Parse/Infrastructure/Data/ParseDataEncoder.cs index 8cee8060..b595e66c 100644 --- a/Parse/Infrastructure/Data/ParseDataEncoder.cs +++ b/Parse/Infrastructure/Data/ParseDataEncoder.cs @@ -34,7 +34,7 @@ public abstract class ParseDataEncoder ["base64"] = Convert.ToBase64String(bytes) }, ParseObject { } entity => EncodeObject(entity), - IJsonConvertible { } jsonConvertible => jsonConvertible.ConvertToJSON(), + IJsonConvertible { } jsonConvertible => jsonConvertible.ConvertToJson(), { } when Conversion.As>(value) is { } dictionary => dictionary.ToDictionary(pair => pair.Key, pair => Encode(pair.Value, serviceHub)), { } when Conversion.As>(value) is { } list => EncodeList(list, serviceHub), diff --git a/Parse/Infrastructure/FacebookAuthenticationMutator.cs b/Parse/Infrastructure/FacebookAuthenticationMutator.cs new file mode 100644 index 00000000..705f9001 --- /dev/null +++ b/Parse/Infrastructure/FacebookAuthenticationMutator.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using Parse.Abstractions.Infrastructure; +using Parse.Infrastructure.Utilities; + +namespace Parse.Infrastructure +{ + /// + /// Enables Facebook authentication on the service hub. + /// + public class FacebookAuthenticationMutator : IServiceHubMutator + { + /// + public bool Valid => Caller is { Length: > 0 }; + + /// + /// The identifier to make API calls to Facebook with. This is in the Facebook Developer Dashboard as some variant of "Application ID" or "API Key". + /// + public string Caller { get; set; } + + /// + public void Mutate(ref IMutableServiceHub mutableHub, in IServiceHub consumableHub, Stack futureMutators) => mutableHub.InitializeFacebookAuthenticationProvider(Caller); + } +} diff --git a/Parse/Infrastructure/MetadataMutator.cs b/Parse/Infrastructure/MetadataMutator.cs index 351f779b..36fd506d 100644 --- a/Parse/Infrastructure/MetadataMutator.cs +++ b/Parse/Infrastructure/MetadataMutator.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + using Parse.Abstractions.Infrastructure; namespace Parse.Infrastructure @@ -13,10 +15,11 @@ public class MetadataMutator : MetadataController, IServiceHubMutator public bool Valid => this is { EnvironmentData: { OSVersion: { }, Platform: { }, TimeZone: { } }, HostManifestData: { Identifier: { }, Name: { }, ShortVersion: { }, Version: { } } }; /// - /// Sets the to the instance. + /// Sets the 's to the instance. /// - /// The to compose the information onto. - /// Thhe to use if a default service instance is required. - public void Mutate(ref IMutableServiceHub target, in IServiceHub referenceHub) => target.MetadataController = this; + /// The to compose the information onto. + /// The to use if a default service instance is required. + /// The mutators that will be executed after this one. + public void Mutate(ref IMutableServiceHub mutableHub, in IServiceHub consumableHub, Stack futureMutators) => mutableHub.MetadataController = this; } } diff --git a/Parse/Infrastructure/RelativeCacheLocationMutator.cs b/Parse/Infrastructure/RelativeCacheLocationMutator.cs index fe332424..93e4090e 100644 --- a/Parse/Infrastructure/RelativeCacheLocationMutator.cs +++ b/Parse/Infrastructure/RelativeCacheLocationMutator.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + using Parse.Abstractions.Infrastructure; namespace Parse.Infrastructure @@ -12,17 +14,11 @@ public class RelativeCacheLocationMutator : IServiceHubMutator /// public IRelativeCacheLocationGenerator RelativeCacheLocationGenerator { get; set; } - /// /// - /// public bool Valid => RelativeCacheLocationGenerator is { }; - /// /// - /// - /// - /// - public void Mutate(ref IMutableServiceHub target, in IServiceHub referenceHub) => target.CacheController = (target as IServiceHub).CacheController switch + public void Mutate(ref IMutableServiceHub target, in IServiceHub referenceHub, Stack futureMutators) => target.CacheController = (target as IServiceHub).CacheController switch { null => new CacheController { RelativeCacheFilePath = RelativeCacheLocationGenerator.GetRelativeCacheFilePath(referenceHub) }, IDiskFileCacheController { } controller => (Controller: controller, controller.RelativeCacheFilePath = RelativeCacheLocationGenerator.GetRelativeCacheFilePath(referenceHub)).Controller, diff --git a/Parse/Infrastructure/Utilities/FacebookAuthenticationUtilities.cs b/Parse/Infrastructure/Utilities/FacebookAuthenticationUtilities.cs new file mode 100644 index 00000000..6cfb7a83 --- /dev/null +++ b/Parse/Infrastructure/Utilities/FacebookAuthenticationUtilities.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using Parse.Abstractions.Infrastructure; +using Parse.Platform.Authentication; + +namespace Parse.Infrastructure.Utilities +{ + // TODO: Add convenience methods for individual environments back. + + /// + /// Provides a set of utilities for the use of Facebook as an authenticator for Parse. + /// + public static partial class FacebookAuthenticationUtilities + { + static FacebookAuthenticationProvider Provider { get; set; } = new FacebookAuthenticationProvider { }; + + /// + /// Gets the Facebook Application ID as supplied to . + /// + public static string Identifier => Provider.Caller; + + /// + /// Gets the access token for the currently logged in Facebook user. This can be used with a + /// Facebook SDK to get access to Facebook user data. + /// + public static string AccessToken => Provider.AccessToken; + + /// + /// Initializes Facebook for use with Parse. + /// + /// The service hub to use. + /// Your Facebook application ID. + public static void InitializeFacebookAuthenticationProvider(this IServiceHub serviceHub, string identifier) + { + Provider.Caller = identifier; + serviceHub.SetAuthenticationProvider(Provider); + } + + /// + /// Initializes Facebook for use with Parse. + /// + /// The service hub to use. + /// The instance to use for Facebook authentication. + public static void InitializeFacebookAuthentication(this IServiceHub serviceHub, FacebookAuthenticationProvider authenticator) + { + if (authenticator is IServiceHubMutator { Valid: true }) + { + serviceHub.SetAuthenticationProvider(Provider = authenticator); + } + } + + /// + /// Logs in a using Facebook for authentication. If a user for the + /// given Facebook credentials does not already exist, a new user will be created. + /// + /// The service hub to use. + /// The user's Facebook ID. + /// A valid access token for the user. + /// The expiration date of the access token. + /// The cancellation token. + /// The user that was either logged in or created. + public static Task AuthenticateWithFacebookAsync(this IServiceHub serviceHub, string facebookId, string accessToken, DateTime expiration, CancellationToken cancellationToken = default) => serviceHub.AuthenticateWithServiceAsync("facebook", Provider.GetAuthenticationData(facebookId, accessToken, expiration), cancellationToken); + + /// + /// Links a to a Facebook account, allowing you to use Facebook + /// for authentication, and providing access to Facebook data for the user. + /// + /// The user to link to a Facebook account. + /// The user's Facebook ID. + /// A valid access token for the user. + /// The expiration date of the access token. + /// The cancellation token. + public static Task LinkToFacebookAsync(this ParseUser user, string facebookId, string accessToken, DateTime expiration, CancellationToken cancellationToken = default) => user.LinkToServiceAsync("facebook", Provider.GetAuthenticationData(facebookId, accessToken, expiration), cancellationToken); + + /// + /// Gets whether the given user is linked to a Facebook account. This can only be used on + /// the currently authorized user. + /// + /// The user to check. + /// true if the user is linked to a Facebook account. + public static bool CheckLinkedToFacebook(this ParseUser user) => user.CheckLinkedToService("facebook"); + + /// + /// Unlinks a user from a Facebook account. Unlinking a user will save the user's data. + /// + /// The user to unlink. + /// The cancellation token. + public static Task UnlinkFromFacebookAsync(this ParseUser user, CancellationToken cancellationToken = default) => user.UnlinkFromServiceAsync("facebook", cancellationToken); + } +} diff --git a/Parse/Infrastructure/Utilities/JsonUtilities.cs b/Parse/Infrastructure/Utilities/JsonUtilities.cs index 40c8da3c..fdc68c66 100644 --- a/Parse/Infrastructure/Utilities/JsonUtilities.cs +++ b/Parse/Infrastructure/Utilities/JsonUtilities.cs @@ -13,34 +13,49 @@ namespace Parse.Infrastructure.Utilities /// 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 JsonUtilities + public static class JsonUtilities { + internal static IDictionary DeserializeJsonText(string text) => Parse(text) as IDictionary; + + internal static string SerializeToJsonText(IDictionary data) => Encode(data); + /// /// 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 + static readonly string startOfString = "\\G"; + + static readonly char startObject = '{'; + + static readonly char endObject = '}'; + + static readonly char startArray = '['; + + static readonly char endArray = ']'; + + static readonly char valueSeparator = ','; + + static readonly char nameSeparator = ':'; + + static readonly char[] falseValue = "false".ToCharArray(); + + static readonly char[] trueValue = "true".ToCharArray(); + + static readonly char[] nullValue = "null".ToCharArray(); + + static readonly Regex numberValue = new Regex(startOfString + @"-?(?:0|[1-9]\d*)(?\.\d+)?(?(?:e|E)(?:-|\+)?\d+)?"); + + static readonly Regex stringValue = new Regex(startOfString + "\"(?(?:[^\\\\\"]|(?\\\\(?:[\\\\\"/bfnrt]|u[0-9a-fA-F]{4})))*)\"", RegexOptions.Multiline); + + static readonly Regex escapePattern = new Regex("\\\\|\"|[\u0000-\u001F]"); + + class JsonStringParser { - public string Input { get; private set; } + public string Input { get; set; } - public char[] InputAsArray { get; private set; } - public int CurrentIndex { get; private set; } + public char[] InputAsArray { get; set; } + public int CurrentIndex { get; set; } public void Skip(int skip) => CurrentIndex += skip; @@ -80,7 +95,7 @@ internal bool ParseObject(out object output) /// /// Parses JSON member syntax (e.g. '"keyname" : null') /// - private bool ParseMember(out object output) + bool ParseMember(out object output) { output = null; if (!ParseString(out object key)) @@ -120,7 +135,7 @@ internal bool ParseArray(out object output) /// 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) + bool ParseValue(out object output) { if (Accept(falseValue)) { @@ -146,7 +161,7 @@ private bool ParseValue(out object output) /// /// Parses a JSON string (e.g. '"foo\u1234bar\n"') /// - private bool ParseString(out object output) + bool ParseString(out object output) { output = null; if (!Accept(stringValue, out Match m)) @@ -201,7 +216,7 @@ private bool ParseString(out object output) /// 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) + bool ParseNumber(out object output) { output = null; if (!Accept(numberValue, out Match m)) @@ -222,7 +237,7 @@ private bool ParseNumber(out object output) /// /// Matches the string to a regex, consuming part of the string and returning the match. /// - private bool Accept(Regex matcher, out Match match) + bool Accept(Regex matcher, out Match match) { match = matcher.Match(Input, CurrentIndex); if (match.Success) @@ -233,7 +248,7 @@ private bool Accept(Regex matcher, out Match match) /// /// Find the first occurrences of a character, consuming part of the string. /// - private bool Accept(char condition) + bool Accept(char condition) { int step = 0; int strLen = InputAsArray.Length; @@ -276,7 +291,7 @@ private bool Accept(char condition) /// /// Find the first occurrences of a string, consuming part of the string. /// - private bool Accept(char[] condition) + bool Accept(char[] condition) { int step = 0; int strLen = InputAsArray.Length; diff --git a/Parse/Infrastructure/Utilities/WebUtilities.cs b/Parse/Infrastructure/Utilities/WebUtilities.cs new file mode 100644 index 00000000..e90f8e77 --- /dev/null +++ b/Parse/Infrastructure/Utilities/WebUtilities.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Parse.Infrastructure.Utilities +{ + internal static class WebUtilities + { + internal static string BuildQueryString(IDictionary parameters) => String.Join("&", (from pair in parameters let valueString = pair.Value as string select $"{Uri.EscapeDataString(pair.Key)}={Uri.EscapeDataString(String.IsNullOrEmpty(valueString) ? JsonUtilities.Encode(pair.Value) : valueString)}").ToArray()); + + internal static IDictionary DecodeQueryString(string queryString) + { + Dictionary query = new Dictionary { }; + + foreach (string pair in queryString.Split('&')) + { + string[] parts = pair.Split(new char[] { '=' }, 2); + query[parts[0]] = parts.Length == 2 ? Uri.UnescapeDataString(parts[1].Replace("+", " ")) : null; + } + + return query; + } + } +} diff --git a/Parse/Parse.csproj b/Parse/Parse.csproj index e2d9d5b9..528c26cd 100644 --- a/Parse/Parse.csproj +++ b/Parse/Parse.csproj @@ -4,8 +4,8 @@ netstandard2.0 bin\Release\netstandard2.0\Parse.xml 2.0.0-develop-0001 - latest - + preview + Parse https://parseplatform.org/ https://github.com/parse-community/Parse-SDK-dotNET/ diff --git a/Parse/Platform/Authentication/FacebookAuthenticationProvider.cs b/Parse/Platform/Authentication/FacebookAuthenticationProvider.cs new file mode 100644 index 00000000..43b52e85 --- /dev/null +++ b/Parse/Platform/Authentication/FacebookAuthenticationProvider.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Authentication; +using Parse.Infrastructure; +using Parse.Infrastructure.Execution; +using Parse.Infrastructure.Utilities; + +namespace Parse.Platform.Authentication +{ + /// + /// Provides an authenticator service for the Parse SDK which uses Facebook to authorize linked Parse user access. + /// + public class FacebookAuthenticationProvider : IParseAuthenticationProvider, IServiceHubMutator + { + static Uri TokenExtensionRoute { get; } = new Uri("https://graph.facebook.com/oauth/access_token", UriKind.Absolute); + + static Uri ProfileRoute { get; } = new Uri("https://graph.facebook.com/me", UriKind.Absolute); + + TaskCompletionSource> SubmissionTaskSource { get; set; } + + CancellationToken SubmissionCancellationToken { get; set; } + + /// + /// Instantiates a . + /// + public FacebookAuthenticationProvider() + { + } + + internal Uri AuthenticationRoute { get; set; } = new Uri("https://www.facebook.com/dialog/oauth", UriKind.Absolute); + + internal Uri SuccessRoute { get; set; } = new Uri("https://www.facebook.com/connect/login_success.html", UriKind.Absolute); + + /// + /// Permissions the should ask for from Facebook, in terms of access to or mutation of user data. + /// + public IEnumerable Permissions { get; set; } + + /// + /// The identifier to make API calls to Facebook with. This is in the Facebook Developer Dashboard as some variant of "Application ID" or "API Key". + /// + public string Caller { get; set; } + + /// + /// The token for access to an individual Facebook user account. + /// + public string AccessToken { get; set; } + + /// + /// An event for when a Facebook-authentication-related web resource is loaded. + /// + public event Action Load; + + /// + /// Parses a uri, looking for a base uri that represents facebook login completion, and then + /// converting the query string into a dictionary of key-value pairs. (e.g. access_token) + /// + bool CheckIsOAuthCallback(Uri uri, out IDictionary result) + { + if (!uri.AbsoluteUri.StartsWith(SuccessRoute.AbsoluteUri) || uri.Fragment == null) + { + result = default; + return default; + } + + string fragmentOrQuery = uri.Fragment is null or { Length: 0 } ? uri.Query : uri.Fragment; + result = WebUtilities.DecodeQueryString(fragmentOrQuery.Substring(1)); + + return true; + } + + public IDictionary GetAuthenticationData(string facebookId, string accessToken, DateTime expiration) => new Dictionary + { + ["id"] = facebookId, + ["access_token"] = accessToken, + ["expiration_date"] = expiration.ToString(ParseClient.DateFormatStrings[0]) + }; + + public bool ExtractUser(IServiceHub serviceHub, Uri oAuthCallback) + { + if (CheckIsOAuthCallback(oAuthCallback, out IDictionary result)) + { + void GetUser() + { + try + { + if (result.ContainsKey("error")) + { + SubmissionTaskSource.TrySetException(new ParseFailureException(ParseFailureException.ErrorCode.OtherCause, $"{result["error_description"]}: {result["error"]}")); + return; + } + + serviceHub.WebClient.ExecuteAsync(new WebRequest { Resource = $"{ProfileRoute}?{WebUtilities.BuildQueryString(new Dictionary { ["access_token"] = result["access_token"], ["fields"] = "id" })}", Method = "GET" }, default, default, CancellationToken.None).OnSuccess(task => SubmissionTaskSource.TrySetResult(GetAuthenticationData(JsonUtilities.DeserializeJsonText(task.Result.Item2)["id"] as string, result["access_token"], DateTime.Now + TimeSpan.FromSeconds(Int32.Parse(result["expires_in"]))))).ContinueWith(task => + { + if (task.IsFaulted) + { + SubmissionTaskSource.TrySetException(task.Exception); + } + }); + } + catch (Exception e) + { + SubmissionTaskSource.TrySetException(e); + } + } + + GetUser(); + return true; + } + + return default; + } + + /// + public Task> AuthenticateAsync(CancellationToken cancellationToken) + { + if (Caller is null) + { + throw new InvalidOperationException("You must initialize FacebookUtilities or provide a FacebookAuthenticationMutator instance to the ParseClient customization constructor before attempting a Facebook login."); + } + + if (SubmissionTaskSource != null) + { + SubmissionTaskSource.TrySetCanceled(); + } + + TaskCompletionSource> tcs = new TaskCompletionSource> { }; + + SubmissionCancellationToken = cancellationToken; + SubmissionTaskSource = tcs; + + cancellationToken.Register(() => tcs.TrySetCanceled()); + + Action navigateHandler = Load; + + if (navigateHandler != null) + { + Dictionary parameters = new Dictionary() + { + ["redirect_uri"] = SuccessRoute.AbsoluteUri, + ["response_type"] = "token", + ["display"] = "popup", + ["client_id"] = Caller + }; + + if (Permissions != null) + { + parameters["scope"] = String.Join(",", Permissions.ToArray()); + } + + navigateHandler(new Uri(AuthenticationRoute, $"?{WebUtilities.BuildQueryString(parameters)}")); + } + return tcs.Task; + } + + /// + public void Deauthenticate() => AccessToken = default; + + /// + public bool RestoreAuthentication(IDictionary authData) + { + if (authData == null) + { + Deauthenticate(); + } + else + { + AccessToken = authData["access_token"] as string; + } + + return true; + } + + /// + public string Name => "facebook"; + + void IServiceHubMutator.Mutate(ref IMutableServiceHub mutableHub, in IServiceHub consumableHub, Stack futureMutators) => mutableHub.InitializeFacebookAuthentication(authenticator: this); + + bool IServiceHubMutator.Valid => Caller is { Length: > 0 }; + } +} diff --git a/Parse/Platform/Configuration/ParseConfiguration.cs b/Parse/Platform/Configuration/ParseConfiguration.cs index 67ccd10b..676d10a3 100644 --- a/Parse/Platform/Configuration/ParseConfiguration.cs +++ b/Parse/Platform/Configuration/ParseConfiguration.cs @@ -72,7 +72,7 @@ public bool TryGetValue(string key, out T result) /// The value for the key. virtual public object this[string key] => Properties[key]; - IDictionary IJsonConvertible.ConvertToJSON() => new Dictionary + IDictionary IJsonConvertible.ConvertToJson() => new Dictionary { ["params"] = NoObjectsEncoder.Instance.Encode(Properties, Services) }; diff --git a/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs b/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs index de21b64e..1530a7e1 100644 --- a/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs +++ b/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs @@ -20,7 +20,7 @@ internal class ParseCurrentConfigurationController : IParseCurrentConfigurationC ParseConfiguration CurrentConfiguration { get; set; } - ICacheController StorageController { get; } + ICacheController CacheController { get; } IParseDataDecoder Decoder { get; } @@ -29,27 +29,27 @@ internal class ParseCurrentConfigurationController : IParseCurrentConfigurationC /// public ParseCurrentConfigurationController(ICacheController storageController, IParseDataDecoder decoder) { - StorageController = storageController; + CacheController = storageController; Decoder = decoder; TaskQueue = new TaskQueue { }; } - public Task GetCurrentConfigAsync(IServiceHub serviceHub) => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => CurrentConfiguration is { } ? Task.FromResult(CurrentConfiguration) : StorageController.LoadAsync().OnSuccess(task => + public Task GetCurrentConfigAsync(IServiceHub serviceHub) => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => CurrentConfiguration is { } ? Task.FromResult(CurrentConfiguration) : CacheController.LoadAsync().OnSuccess(task => { task.Result.TryGetValue(CurrentConfigurationKey, out object data); - return CurrentConfiguration = data is string { } configuration ? Decoder.BuildConfiguration(ParseClient.DeserializeJsonString(configuration), serviceHub) : new ParseConfiguration(serviceHub); + return CurrentConfiguration = data is string { } configuration ? Decoder.BuildConfiguration(JsonUtilities.DeserializeJsonText(configuration), serviceHub) : new ParseConfiguration(serviceHub); })), CancellationToken.None).Unwrap(); public Task SetCurrentConfigAsync(ParseConfiguration target) => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => { CurrentConfiguration = target; - return StorageController.LoadAsync().OnSuccess(task => task.Result.AddAsync(CurrentConfigurationKey, ParseClient.SerializeJsonString(((IJsonConvertible) target).ConvertToJSON()))); + return CacheController.LoadAsync().OnSuccess(task => task.Result.AddAsync(CurrentConfigurationKey, JsonUtilities.SerializeToJsonText(((IJsonConvertible) target).ConvertToJson()))); }).Unwrap().Unwrap(), CancellationToken.None); public Task ClearCurrentConfigAsync() => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => { CurrentConfiguration = null; - return StorageController.LoadAsync().OnSuccess(task => task.Result.RemoveAsync(CurrentConfigurationKey)); + return CacheController.LoadAsync().OnSuccess(task => task.Result.RemoveAsync(CurrentConfigurationKey)); }).Unwrap().Unwrap(), CancellationToken.None); public Task ClearCurrentConfigInMemoryAsync() => TaskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => CurrentConfiguration = null), CancellationToken.None); diff --git a/Parse/Platform/Files/ParseFile.cs b/Parse/Platform/Files/ParseFile.cs index 4d7602bb..1e54480f 100644 --- a/Parse/Platform/Files/ParseFile.cs +++ b/Parse/Platform/Files/ParseFile.cs @@ -140,7 +140,7 @@ public ParseFile(string name, Stream data, string mimeType = null) #endregion - IDictionary IJsonConvertible.ConvertToJSON() + IDictionary IJsonConvertible.ConvertToJson() { if (IsDirty) { diff --git a/Parse/Platform/Location/ParseGeoPoint.cs b/Parse/Platform/Location/ParseGeoPoint.cs index eead203a..c667b0f8 100644 --- a/Parse/Platform/Location/ParseGeoPoint.cs +++ b/Parse/Platform/Location/ParseGeoPoint.cs @@ -90,7 +90,7 @@ public ParseGeoDistance DistanceTo(ParseGeoPoint point) return new ParseGeoDistance(2 * Math.Asin(Math.Sqrt(a))); } - IDictionary IJsonConvertible.ConvertToJSON() => new Dictionary { + IDictionary IJsonConvertible.ConvertToJson() => new Dictionary { {"__type", "GeoPoint"}, {nameof(latitude), Latitude}, {nameof(longitude), Longitude} diff --git a/Parse/Platform/ParseClient.cs b/Parse/Platform/ParseClient.cs index 68535c72..2ebefec0 100644 --- a/Parse/Platform/ParseClient.cs +++ b/Parse/Platform/ParseClient.cs @@ -18,7 +18,7 @@ namespace Parse /// ParseClient contains static functions that handle global /// configuration for the Parse library. /// - public class ParseClient : CustomServiceHub, IServiceHubComposer + public class ParseClient : CustomServiceHub, IServiceHubBuilder { /// /// Contains, in order, the official ISO date and time format strings, and two modified versions that account for the possibility that the server-side string processing mechanism removed trailing zeroes. @@ -56,22 +56,22 @@ public class ParseClient : CustomServiceHub, IServiceHubComposer /// The server URI provided in the Parse dashboard. /// The .NET Key provided in the Parse dashboard. /// A service hub to override internal services and thereby make the Parse SDK operate in a custom manner. - /// A set of implementation instances to tweak the behaviour of the SDK. - public ParseClient(string applicationID, string serverURI, string key, IServiceHub serviceHub = default, params IServiceHubMutator[] configurators) : this(new ServerConnectionData { ApplicationID = applicationID, ServerURI = serverURI, Key = key }, serviceHub, configurators) { } + /// A set of implementation instances to tweak the behaviour of the SDK. + public ParseClient(string applicationID, string serverURI, string key, IServiceHub serviceHub = default, params IServiceHubMutator[] mutators) : this(new ServerConnectionData { ApplicationID = applicationID, ServerURI = serverURI, Key = key }, serviceHub, mutators) { } /// /// Creates a new and authenticates it as belonging to your application. This class is a hub for interacting with the SDK. The recommended way to use this class on client applications is to instantiate it, then call on it in your application entry point. This allows you to access . /// - /// The configuration to initialize Parse with. + /// The configuration to initialize Parse with. /// A service hub to override internal services and thereby make the Parse SDK operate in a custom manner. - /// A set of implementation instances to tweak the behaviour of the SDK. - public ParseClient(IServerConnectionData configuration, IServiceHub serviceHub = default, params IServiceHubMutator[] configurators) + /// A set of implementation instances to tweak the behaviour of the SDK. + public ParseClient(IServerConnectionData serverConnectionData, IServiceHub serviceHub = default, params IServiceHubMutator[] mutators) { Services = serviceHub is { } ? new OrchestrationServiceHub { Custom = serviceHub, Default = new ServiceHub { ServerConnectionData = GenerateServerConnectionData() } } : new ServiceHub { ServerConnectionData = GenerateServerConnectionData() } as IServiceHub; - IServerConnectionData GenerateServerConnectionData() => configuration switch + IServerConnectionData GenerateServerConnectionData() => serverConnectionData switch { - null => throw new ArgumentNullException(nameof(configuration)), + null => throw new ArgumentNullException(nameof(serverConnectionData)), ServerConnectionData { Test: true, ServerURI: { } } data => data, ServerConnectionData { Test: true } data => new ServerConnectionData { @@ -82,17 +82,17 @@ public ParseClient(IServerConnectionData configuration, IServiceHub serviceHub = Key = data.Key, ServerURI = "https://api.parse.com/1/" }, - { ServerURI: "https://api.parse.com/1/" } => throw new InvalidOperationException("Since the official parse server has shut down, you must specify a URI that points to a hosted instance."), + { ServerURI: "https://api.parse.com/1/" } => throw new InvalidOperationException("Since the official Parse server has shut down, you must specify a URI that points to a hosted instance."), { ApplicationID: { }, ServerURI: { }, Key: { } } data => data, _ => throw new InvalidOperationException("The IServerConnectionData implementation instance provided to the ParseClient constructor must be populated with the information needed to connect to a Parse server instance.") }; - if (configurators is { Length: int length } && length > 0) + if (mutators is { Length: int length } && length > 0) { Services = serviceHub switch { - IMutableServiceHub { } mutableServiceHub => BuildHub((Hub: mutableServiceHub, mutableServiceHub.ServerConnectionData = serviceHub.ServerConnectionData ?? Services.ServerConnectionData).Hub, Services, configurators), - { } => BuildHub(default, Services, configurators) + IMutableServiceHub { } mutableServiceHub => BuildHub((Hub: mutableServiceHub, mutableServiceHub.ServerConnectionData = serviceHub.ServerConnectionData ?? Services.ServerConnectionData).Hub, Services, mutators), + { } => BuildHub(default, Services, mutators) }; } @@ -102,12 +102,11 @@ public ParseClient(IServerConnectionData configuration, IServiceHub serviceHub = /// /// Initializes a instance using the set on the 's implementation instance. /// - public ParseClient() => Services = (Instance ?? throw new InvalidOperationException("A ParseClient instance with an initializer service must first be publicized in order for the default constructor to be used.")).Services.Cloner.BuildHub(Instance.Services, this); + public ParseClient() => Services = (Instance ?? throw new InvalidOperationException("A ParseClient instance with an initializer service must first be publicized in order for the default constructor to be used.")).Services.Cloner.CloneHub(Instance.Services, this); /// /// Sets this instance as the template to create new instances from. /// - ///// Declares that the current instance should be the publicly-accesible . public void Publicize() { lock (Mutex) @@ -118,36 +117,22 @@ public void Publicize() static object Mutex { get; } = new object { }; - internal static string BuildQueryString(IDictionary parameters) => String.Join("&", (from pair in parameters let valueString = pair.Value as string select $"{Uri.EscapeDataString(pair.Key)}={Uri.EscapeDataString(String.IsNullOrEmpty(valueString) ? JsonUtilities.Encode(pair.Value) : valueString)}").ToArray()); - - internal static IDictionary DecodeQueryString(string queryString) + public IServiceHub BuildHub(IMutableServiceHub baseHub = default, IServiceHub extension = default, params IServiceHubMutator[] mutators) { - Dictionary query = new Dictionary { }; + OrchestrationServiceHub orchestrationServiceHub = new OrchestrationServiceHub { Custom = baseHub ??= new MutableServiceHub { }, Default = extension ?? new ServiceHub { } }; - foreach (string pair in queryString.Split('&')) + Stack validMutators = new Stack(mutators.Where(mutator => mutator.Valid).Reverse()); + while (validMutators.Count > 0 && GetMutator() is { } mutator) { - string[] parts = pair.Split(new char[] { '=' }, 2); - query[parts[0]] = parts.Length == 2 ? Uri.UnescapeDataString(parts[1].Replace("+", " ")) : null; - } - - return query; - } - - internal static IDictionary DeserializeJsonString(string jsonData) => JsonUtilities.Parse(jsonData) as IDictionary; - - internal static string SerializeJsonString(IDictionary jsonData) => JsonUtilities.Encode(jsonData); - - public IServiceHub BuildHub(IMutableServiceHub target = default, IServiceHub extension = default, params IServiceHubMutator[] configurators) - { - OrchestrationServiceHub orchestrationServiceHub = new OrchestrationServiceHub { Custom = target ??= new MutableServiceHub { }, Default = extension ?? new ServiceHub { } }; - - foreach (IServiceHubMutator mutator in configurators.Where(configurator => configurator.Valid)) - { - mutator.Mutate(ref target, orchestrationServiceHub); - orchestrationServiceHub.Custom = target; + if (mutator is { Valid: true }) + { + mutator.Mutate(ref baseHub, orchestrationServiceHub, validMutators); + orchestrationServiceHub.Custom = baseHub; + } } return orchestrationServiceHub; + IServiceHubMutator GetMutator() => validMutators.Pop(); } } } diff --git a/Parse/Platform/Relations/ParseRelation.cs b/Parse/Platform/Relations/ParseRelation.cs index d25c5a25..a7d35ddc 100644 --- a/Parse/Platform/Relations/ParseRelation.cs +++ b/Parse/Platform/Relations/ParseRelation.cs @@ -66,7 +66,7 @@ internal void Remove(ParseObject entity) TargetClassName = change.TargetClassName; } - IDictionary IJsonConvertible.ConvertToJSON() => new Dictionary + IDictionary IJsonConvertible.ConvertToJson() => new Dictionary { ["__type"] = "Relation", ["className"] = TargetClassName diff --git a/Parse/Platform/Security/ParseACL.cs b/Parse/Platform/Security/ParseACL.cs index 432eb721..d831e55d 100644 --- a/Parse/Platform/Security/ParseACL.cs +++ b/Parse/Platform/Security/ParseACL.cs @@ -53,7 +53,7 @@ public ParseACL(ParseUser owner) SetWriteAccess(owner, true); } - IDictionary IJsonConvertible.ConvertToJSON() + IDictionary IJsonConvertible.ConvertToJson() { Dictionary result = new Dictionary(); foreach (string user in readers.Union(writers)) diff --git a/Parse/Platform/Users/ParseUser.cs b/Parse/Platform/Users/ParseUser.cs index 365fa2c5..42aec932 100644 --- a/Parse/Platform/Users/ParseUser.cs +++ b/Parse/Platform/Users/ParseUser.cs @@ -179,16 +179,20 @@ internal Task LogOutAsync(Task toAwait, CancellationToken cancellationToken) return Task.FromResult(0); } - // Cleanup in-memory session. + // TODO: Consider use of toAwait to make sure all tasks are finished before the revokeSession task is executed. + // Cleans the stored session. MutateState(mutableClone => mutableClone.ServerData.Remove("sessionToken")); Task revokeSessionTask = Services.RevokeSessionAsync(oldSessionToken, cancellationToken); return Task.WhenAll(revokeSessionTask, Services.CurrentUserController.LogOutAsync(Services, cancellationToken)); } - internal Task UpgradeToRevocableSessionAsync() => UpgradeToRevocableSessionAsync(CancellationToken.None); - - internal Task UpgradeToRevocableSessionAsync(CancellationToken cancellationToken) => TaskQueue.Enqueue(toAwait => UpgradeToRevocableSessionAsync(toAwait, cancellationToken), cancellationToken); + /// + /// Uses a revocable session token for this instance. + /// + /// The cancellation token to use to halt this task if needed before it has finished. + /// A task which will be finished once the move to a revocable session token has occurred. + public Task UpgradeToRevocableSessionAsync(CancellationToken cancellationToken = default) => TaskQueue.Enqueue(toAwait => UpgradeToRevocableSessionAsync(toAwait, cancellationToken), cancellationToken); internal Task UpgradeToRevocableSessionAsync(Task toAwait, CancellationToken cancellationToken) { @@ -259,12 +263,12 @@ internal void SynchronizeAllAuthData() foreach (KeyValuePair> pair in authData) { - SynchronizeAuthData(GetProvider(pair.Key)); + SynchronizeAuthenticationData(GetProvider(pair.Key)); } } } - internal void SynchronizeAuthData(IParseAuthenticationProvider provider) + internal void SynchronizeAuthenticationData(IParseAuthenticationProvider authenticator) { bool restorationSuccess = false; @@ -272,57 +276,72 @@ internal void SynchronizeAuthData(IParseAuthenticationProvider provider) { IDictionary> authData = AuthData; - if (authData == null || provider == null) + if (authData == null || authenticator == null) { return; } - if (authData.TryGetValue(provider.AuthType, out IDictionary data)) + if (authData.TryGetValue(authenticator.Name, out IDictionary data)) { - restorationSuccess = provider.RestoreAuthentication(data); + restorationSuccess = authenticator.RestoreAuthentication(data); } } if (!restorationSuccess) { - UnlinkFromAsync(provider.AuthType, CancellationToken.None); + // TODO: Check if lack of await here causes issues. + + UnlinkFromServiceAsync(authenticator.Name, CancellationToken.None); } } - internal Task LinkWithAsync(string authType, IDictionary data, CancellationToken cancellationToken) => TaskQueue.Enqueue(toAwait => + /// + /// Links a user to a service. + /// + /// The name of the service to link the user to. + /// The authentication data for the service. + /// The cancellation token which should be used if the link task needs to be halted. + /// + public Task LinkToServiceAsync(string name, IDictionary data, CancellationToken cancellationToken = default) => TaskQueue.Enqueue(toAwait => { IDictionary> authData = AuthData; if (authData == null) { - authData = AuthData = new Dictionary>(); + authData = AuthData = new Dictionary> { }; } - authData[authType] = data; + authData[name] = data; AuthData = authData; return SaveAsync(cancellationToken); }, cancellationToken); - internal Task LinkWithAsync(string authType, CancellationToken cancellationToken) + /// + /// Links a user to a service if the SDK was initialized with an with the data needed to authenticate this user. + /// + /// The name of the service to link the user to. + /// The cancellation token which should be used if the link task needs to be halted. + /// A task which completes once the user has been linked to the service + public Task LinkToServiceWithInitializedAuthenticationProviderAsync(string name, CancellationToken cancellationToken = default) { - IParseAuthenticationProvider provider = GetProvider(authType); - return provider.AuthenticateAsync(cancellationToken).OnSuccess(t => LinkWithAsync(authType, t.Result, cancellationToken)).Unwrap(); + IParseAuthenticationProvider authenticator = GetProvider(name); + return authenticator.AuthenticateAsync(cancellationToken).OnSuccess(task => LinkToServiceAsync(name, task.Result, cancellationToken)).Unwrap(); } /// /// Unlinks a user from a service. /// - internal Task UnlinkFromAsync(string authType, CancellationToken cancellationToken) => LinkWithAsync(authType, null, cancellationToken); + public Task UnlinkFromServiceAsync(string name, CancellationToken cancellationToken = default) => LinkToServiceAsync(name, default, cancellationToken); /// /// Checks whether a user is linked to a service. /// - internal bool IsLinked(string authType) + public bool CheckLinkedToService(string name) { lock (Mutex) { - return AuthData != null && AuthData.ContainsKey(authType) && AuthData[authType] != null; + return AuthData != null && AuthData.ContainsKey(name) && AuthData[name] != null; } } } diff --git a/Parse/Platform/Users/ParseUserController.cs b/Parse/Platform/Users/ParseUserController.cs index 635f1470..dfe1aff0 100644 --- a/Parse/Platform/Users/ParseUserController.cs +++ b/Parse/Platform/Users/ParseUserController.cs @@ -29,7 +29,7 @@ public class ParseUserController : IParseUserController public Task SignUpAsync(IObjectState state, IDictionary operations, IServiceHub serviceHub, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand("classes/_User", method: "POST", data: serviceHub.GenerateJSONObjectForSaving(operations)), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder, serviceHub).MutatedClone(mutableClone => mutableClone.IsNew = true)); - public Task LogInAsync(string username, string password, IServiceHub serviceHub, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand($"login?{ParseClient.BuildQueryString(new Dictionary { [nameof(username)] = username, [nameof(password)] = password })}", method: "GET", data: null), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder, serviceHub).MutatedClone(mutableClone => mutableClone.IsNew = task.Result.Item1 == System.Net.HttpStatusCode.Created)); + public Task LogInAsync(string username, string password, IServiceHub serviceHub, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand($"login?{WebUtilities.BuildQueryString(new Dictionary { [nameof(username)] = username, [nameof(password)] = password })}", method: "GET", data: null), cancellationToken: cancellationToken).OnSuccess(task => ParseObjectCoder.Instance.Decode(task.Result.Item2, Decoder, serviceHub).MutatedClone(mutableClone => mutableClone.IsNew = task.Result.Item1 == System.Net.HttpStatusCode.Created)); public Task LogInAsync(string authType, IDictionary data, IServiceHub serviceHub, CancellationToken cancellationToken = default) { diff --git a/Parse/Utilities/ParseUserExtensions.cs b/Parse/Utilities/ParseUserExtensions.cs deleted file mode 100644 index 30e3bd87..00000000 --- a/Parse/Utilities/ParseUserExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Parse.Abstractions.Internal -{ - /// - /// So here's the deal. We have a lot of internal APIs for ParseObject, ParseUser, 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 ParseUserExtensions - { - public static Task UnlinkFromAsync(this ParseUser user, string authType, CancellationToken cancellationToken) => user.UnlinkFromAsync(authType, cancellationToken); - - public static Task LinkWithAsync(this ParseUser user, string authType, CancellationToken cancellationToken) => user.LinkWithAsync(authType, cancellationToken); - - public static Task LinkWithAsync(this ParseUser user, string authType, IDictionary data, CancellationToken cancellationToken) => user.LinkWithAsync(authType, data, cancellationToken); - - public static Task UpgradeToRevocableSessionAsync(this ParseUser user, CancellationToken cancellationToken) => user.UpgradeToRevocableSessionAsync(cancellationToken); - } -} diff --git a/Parse/Utilities/PushServiceExtensions.cs b/Parse/Utilities/PushServiceExtensions.cs index ad94e282..7d354c14 100644 --- a/Parse/Utilities/PushServiceExtensions.cs +++ b/Parse/Utilities/PushServiceExtensions.cs @@ -10,6 +10,9 @@ namespace Parse { + /// + /// Utilities to control and interact with the Parse Push service via this SDK. + /// public static class PushServiceExtensions { /// diff --git a/Parse/Utilities/UserServiceExtensions.cs b/Parse/Utilities/UserServiceExtensions.cs index c4188193..49ffbb46 100644 --- a/Parse/Utilities/UserServiceExtensions.cs +++ b/Parse/Utilities/UserServiceExtensions.cs @@ -177,26 +177,19 @@ internal static bool GetIsRevocableSessionEnabled(this IServiceHub serviceHub) #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 Parse site. - /// - /// The email address associated with the user that forgot their password. - public static Task RequestPasswordResetAsync(this IServiceHub serviceHub, string email) => RequestPasswordResetAsync(serviceHub, 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 Parse site. /// /// The email address associated with the user that forgot their password. /// The cancellation token. - public static Task RequestPasswordResetAsync(this IServiceHub serviceHub, string email, CancellationToken cancellationToken) => serviceHub.UserController.RequestPasswordResetAsync(email, cancellationToken); + public static Task RequestPasswordResetAsync(this IServiceHub serviceHub, string email, CancellationToken cancellationToken = default) => serviceHub.UserController.RequestPasswordResetAsync(email, cancellationToken); - public static Task LogInWithAsync(this IServiceHub serviceHub, string authType, IDictionary data, CancellationToken cancellationToken) + public static Task AuthenticateWithServiceAsync(this IServiceHub serviceHub, string authenticationServiceName, IDictionary data, CancellationToken cancellationToken) { ParseUser user = null; - return serviceHub.UserController.LogInAsync(authType, data, serviceHub, cancellationToken).OnSuccess(task => + return serviceHub.UserController.LogInAsync(authenticationServiceName, data, serviceHub, cancellationToken).OnSuccess(task => { user = serviceHub.GenerateObjectFromState(task.Result, "_User"); @@ -207,7 +200,7 @@ public static Task LogInWithAsync(this IServiceHub serviceHub, string user.AuthData = new Dictionary>(); } - user.AuthData[authType] = data; + user.AuthData[authenticationServiceName] = data; #warning Check if SynchronizeAllAuthData should accept an IServiceHub for consistency on which actions take place on which IServiceHub implementation instance. @@ -218,22 +211,22 @@ public static Task LogInWithAsync(this IServiceHub serviceHub, string }).Unwrap().OnSuccess(t => user); } - public static Task LogInWithAsync(this IServiceHub serviceHub, string authType, CancellationToken cancellationToken) + public static Task AuthenticateWithServiceAsync(this IServiceHub serviceHub, string authenticationServiceName, CancellationToken cancellationToken) { - IParseAuthenticationProvider provider = ParseUser.GetProvider(authType); - return provider.AuthenticateAsync(cancellationToken).OnSuccess(authData => LogInWithAsync(serviceHub, authType, authData.Result, cancellationToken)).Unwrap(); + IParseAuthenticationProvider provider = ParseUser.GetProvider(authenticationServiceName); + return provider.AuthenticateAsync(cancellationToken).OnSuccess(authData => AuthenticateWithServiceAsync(serviceHub, authenticationServiceName, authData.Result, cancellationToken)).Unwrap(); } - internal static void RegisterProvider(this IServiceHub serviceHub, IParseAuthenticationProvider provider) + internal static void SetAuthenticationProvider(this IServiceHub serviceHub, IParseAuthenticationProvider provider) { - ParseUser.Authenticators[provider.AuthType] = provider; - ParseUser curUser = GetCurrentUser(serviceHub); + ParseUser.Authenticators[provider.Name] = provider; + ParseUser currentUser = GetCurrentUser(serviceHub); - if (curUser != null) + if (currentUser != null) { #warning Check if SynchronizeAllAuthData should accept an IServiceHub for consistency on which actions take place on which IServiceHub implementation instance. - curUser.SynchronizeAuthData(provider); + currentUser.SynchronizeAuthenticationData(provider); } } } From 0320b87ea6a01baa1f2e627b166133374dffa33d Mon Sep 17 00:00:00 2001 From: Alexander Fanat Date: Fri, 4 Sep 2020 10:00:25 -0700 Subject: [PATCH 22/24] Use web utilities for BuildQueryString instead of ParseClient. --- Parse/Platform/Queries/ParseQueryController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parse/Platform/Queries/ParseQueryController.cs b/Parse/Platform/Queries/ParseQueryController.cs index cc5d775c..b72bbb05 100644 --- a/Parse/Platform/Queries/ParseQueryController.cs +++ b/Parse/Platform/Queries/ParseQueryController.cs @@ -45,6 +45,6 @@ public Task FirstAsync(ParseQuery query, ParseUser user, Can return FindAsync(query.ClassName, parameters, user?.SessionToken, cancellationToken).OnSuccess(task => (task.Result["results"] as IList).FirstOrDefault() as IDictionary is Dictionary item && item != null ? ParseObjectCoder.Instance.Decode(item, Decoder, user.Services) : null); } - Task> FindAsync(string className, IDictionary parameters, string sessionToken, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand($"classes/{Uri.EscapeDataString(className)}?{ParseClient.BuildQueryString(parameters)}", method: "GET", sessionToken: sessionToken, data: null), cancellationToken: cancellationToken).OnSuccess(t => t.Result.Item2); + Task> FindAsync(string className, IDictionary parameters, string sessionToken, CancellationToken cancellationToken = default) => CommandRunner.RunCommandAsync(new ParseCommand($"classes/{Uri.EscapeDataString(className)}?{WebUtilities.BuildQueryString(parameters)}", method: "GET", sessionToken: sessionToken, data: null), cancellationToken: cancellationToken).OnSuccess(t => t.Result.Item2); } } From bb7bdaac240180efc29e28d358fb0d6f575a7e7d Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 24 May 2024 11:05:16 +0200 Subject: [PATCH 23/24] Update release.config.js --- release.config.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/release.config.js b/release.config.js index 4632c511..e330a175 100644 --- a/release.config.js +++ b/release.config.js @@ -90,11 +90,11 @@ async function config() { ] }], ["@semantic-release/exec", { - 'verifyConditionsCmd': 'ls -la /home/runner/work/Parse-SDK-dotNET/Parse-SDK-dotNET/Parse/bin/Release/netstandard2.0', - 'prepareCmd': 'ls -la /home/runner/work/Parse-SDK-dotNET/Parse-SDK-dotNET/Parse/bin/Release/netstandard2.0', - 'publishCmd': 'ls -la /home/runner/work/Parse-SDK-dotNET/Parse-SDK-dotNET/Parse/bin/Release/netstandard2.0', - 'successCmd': 'ls -la /home/runner/work/Parse-SDK-dotNET/Parse-SDK-dotNET/Parse/bin/Release/netstandard2.0', - 'failCmd': 'ls -la /home/runner/work/Parse-SDK-dotNET/Parse-SDK-dotNET/Parse/bin/Release/netstandard2.0', + 'verifyConditionsCmd': 'ls -la /home/runner/work/Parse-SDK-dotNET/Parse-SDK-dotNET/Parse/bin', + 'prepareCmd': 'ls -la /home/runner/work/Parse-SDK-dotNET/Parse-SDK-dotNET/Parse/bin', + 'publishCmd': 'ls -la /home/runner/work/Parse-SDK-dotNET/Parse-SDK-dotNET/Parse/bin', + 'successCmd': 'ls -la /home/runner/work/Parse-SDK-dotNET/Parse-SDK-dotNET/Parse/bin', + 'failCmd': 'ls -la /home/runner/work/Parse-SDK-dotNET/Parse-SDK-dotNET/Parse/bin', }], ['@droidsolutions-oss/semantic-release-nuget', { projectPath: './Parse/Parse.csproj', From 4c0f5b14e5ee49f3c1072aca0e2765064be7aeb2 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 24 May 2024 11:09:12 +0200 Subject: [PATCH 24/24] Update release.config.js --- release.config.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/release.config.js b/release.config.js index e330a175..f9e52ffe 100644 --- a/release.config.js +++ b/release.config.js @@ -89,6 +89,13 @@ async function config() { } ] }], + ["@semantic-release/exec", { + 'verifyConditionsCmd': 'find / -name "Parse.dll" 2>/dev/null', + 'prepareCmd': 'find / -name "Parse.dll" 2>/dev/null', + 'publishCmd': 'find / -name "Parse.dll" 2>/dev/null', + 'successCmd': 'find / -name "Parse.dll" 2>/dev/null', + 'failCmd': 'find / -name "Parse.dll" 2>/dev/null', + }], ["@semantic-release/exec", { 'verifyConditionsCmd': 'ls -la /home/runner/work/Parse-SDK-dotNET/Parse-SDK-dotNET/Parse/bin', 'prepareCmd': 'ls -la /home/runner/work/Parse-SDK-dotNET/Parse-SDK-dotNET/Parse/bin',