From 43808aa99c0f3f4ccbc11ef172c31618449285fd Mon Sep 17 00:00:00 2001 From: nirinchev Date: Mon, 19 Aug 2024 14:58:42 +0200 Subject: [PATCH 01/17] Create a local-only realm package --- Realm - Windows.sln | 51 - Realm.sln | 29 +- Realm/Realm.Weaver/Analytics/Analytics.cs | 4 - Realm/Realm.Weaver/ImportedReferences.cs | 4 - .../FlexibleSyncConfiguration.cs | 187 -- .../PartitionSyncConfiguration.cs | 150 - .../Configurations/SyncConfigurationBase.cs | 173 -- .../Exceptions/CompensatingWriteException.cs | 79 - .../RealmDecryptionFailedException.cs | 3 - Realm/Realm/Exceptions/RealmException.cs | 6 - .../Exceptions/RealmExceptionCategories.cs | 5 - Realm/Realm/Exceptions/Sync/AppException.cs | 51 - Realm/Realm/Exceptions/Sync/ClientError.cs | 35 - .../Exceptions/Sync/ClientResetException.cs | 61 - Realm/Realm/Exceptions/Sync/ErrorCode.cs | 220 -- .../Realm/Exceptions/Sync/SessionException.cs | 42 - .../Exceptions/Sync/SubscriptionException.cs | 32 - .../Realm/Extensions/CollectionExtensions.cs | 82 - Realm/Realm/Extensions/TestingExtensions.cs | 50 - .../Realm/Handles/AppHandle.EmailPassword.cs | 211 -- Realm/Realm/Handles/AppHandle.cs | 479 ---- Realm/Realm/Handles/AsyncOpenTaskHandle.cs | 93 - Realm/Realm/Handles/SessionHandle.cs | 472 ---- Realm/Realm/Handles/SharedRealmHandle.cs | 62 +- Realm/Realm/Handles/SubscriptionSetHandle.cs | 357 --- Realm/Realm/Handles/SyncUserHandle.cs | 470 ---- Realm/Realm/Helpers/SerializationHelper.cs | 3 - Realm/Realm/Native/AppConfiguration.cs | 99 - Realm/Realm/Native/AppError.cs | 44 - Realm/Realm/Native/Credentials.cs | 55 - Realm/Realm/Native/HttpClientTransport.cs | 234 -- Realm/Realm/Native/NativeCommon.cs | 9 - .../Realm/Native/SessionNotificationToken.cs | 28 - Realm/Realm/Native/Subscription.cs | 61 - Realm/Realm/Native/SyncConfiguration.cs | 63 - Realm/Realm/Native/SyncError.cs | 49 - .../Native/SyncSocketProvider.EventLoop.cs | 124 - .../Realm/Native/SyncSocketProvider.Native.cs | 98 - .../Native/SyncSocketProvider.WebSocket.cs | 264 -- Realm/Realm/Native/SyncSocketProvider.cs | 175 -- Realm/Realm/Native/UserApiKey.cs | 44 - Realm/Realm/Native/UserProfileField.cs | 33 - Realm/Realm/Realm.cs | 155 +- Realm/Realm/Sync/ApiKey.cs | 86 - Realm/Realm/Sync/App.cs | 511 ---- Realm/Realm/Sync/AppConfiguration.cs | 183 -- Realm/Realm/Sync/ClientResyncMode.cs | 28 - Realm/Realm/Sync/ConnectionState.cs | 41 - Realm/Realm/Sync/Credentials.cs | 251 -- .../ErrorHandling/ClientResetHandlerBase.cs | 77 - .../DiscardUnsyncedChangesHandler.cs | 62 - .../ErrorHandling/ManualRecoveryHandler.cs | 48 - .../RecoverOrDiscardUnsyncedChangesHandler.cs | 89 - .../RecoverUnsyncedChangesHandler.cs | 68 - Realm/Realm/Sync/FlexibleSync/Subscription.cs | 80 - .../Sync/FlexibleSync/SubscriptionOptions.cs | 55 - .../Sync/FlexibleSync/SubscriptionSet.cs | 395 --- .../Sync/FlexibleSync/SubscriptionSetState.cs | 57 - .../Sync/FlexibleSync/WaitForSyncMode.cs | 69 - Realm/Realm/Sync/GoogleCredentialType.cs | 36 - Realm/Realm/Sync/MetadataPersistenceMode.cs | 42 - Realm/Realm/Sync/MongoClient.cs | 586 ---- .../ProgressDirection.cs | 36 - .../ProgressNotifications/ProgressMode.cs | 40 - .../ProgressNotificationToken.cs | 78 - .../ProgressNotifications/SyncProgress.cs | 39 - .../SyncProgressObservable.cs | 48 - Realm/Realm/Sync/SchemaMode.cs | 29 - Realm/Realm/Sync/Session.cs | 232 -- Realm/Realm/Sync/SessionState.cs | 36 - Realm/Realm/Sync/SessionStopPolicy.cs | 27 - Realm/Realm/Sync/SyncTimeoutOptions.cs | 108 - Realm/Realm/Sync/User.cs | 501 ---- Realm/Realm/Sync/UserIdentity.cs | 70 - Realm/Realm/Sync/UserProfile.cs | 97 - Realm/Realm/Sync/UserState.cs | 41 - .../GuidRepresentationMigrationTests.cs | 75 +- .../EmbeddedGuidType_generated.cs | 1 - .../GuidType_generated.cs | 1 - Tests/Realm.Tests/Program.cs | 2 - Tests/Realm.Tests/Realm.Tests.csproj | 6 - .../Serialization/SerializationTests.cs | 16 - Tests/Realm.Tests/Sync/AppTests.cs | 442 --- .../Realm.Tests/Sync/AsymmetricObjectTests.cs | 792 ------ .../Sync/DataTypeSynchronizationTests.cs | 1112 -------- Tests/Realm.Tests/Sync/FlexibleSyncTests.cs | 2456 ----------------- Tests/Realm.Tests/Sync/FunctionsTests.cs | 669 ----- Tests/Realm.Tests/Sync/MongoClientTests.cs | 2336 ---------------- Tests/Realm.Tests/Sync/PartitionKeyTests.cs | 148 - Tests/Realm.Tests/Sync/SessionTests.cs | 1603 ----------- Tests/Realm.Tests/Sync/StaticQueriesTests.cs | 1348 --------- .../Sync/SyncConfigurationTests.cs | 146 - Tests/Realm.Tests/Sync/SyncMigrationTests.cs | 365 --- Tests/Realm.Tests/Sync/SyncTestBase.cs | 319 --- Tests/Realm.Tests/Sync/SyncTestHelpers.cs | 211 -- .../Sync/SynchronizedInstanceTests.cs | 946 ------- Tests/Realm.Tests/Sync/UserManagementTests.cs | 1213 -------- Tests/Tests.UWP/MainPage.xaml.cs | 3 +- Tests/Tests.XamarinMac/Main.cs | 3 +- Tests/Tests.iOS/AppDelegate.cs | 4 - Tools/DeployApps/BaasClient.cs | 1222 -------- Tools/DeployApps/DeployApps.csproj | 26 - Tools/DeployApps/GlobalSuppressions.cs | 17 - Tools/DeployApps/Program.cs | 48 - wrappers/src/CMakeLists.txt | 11 - wrappers/src/app_cs.cpp | 444 --- wrappers/src/app_cs.hpp | 228 -- wrappers/src/async_open_task_cs.cpp | 59 - wrappers/src/shared_realm_cs.cpp | 126 +- wrappers/src/shared_realm_cs.hpp | 19 - wrappers/src/subscription_set_cs.cpp | 358 --- wrappers/src/sync_session_cs.cpp | 213 -- wrappers/src/sync_session_cs.hpp | 38 - wrappers/src/sync_user_cs.cpp | 368 --- wrappers/src/transport_cs.cpp | 106 - wrappers/src/transport_cs.hpp | 39 - wrappers/src/websocket_cs.cpp | 194 -- wrappers/src/websocket_cs.hpp | 27 - 118 files changed, 10 insertions(+), 26842 deletions(-) delete mode 100644 Realm/Realm/Configurations/FlexibleSyncConfiguration.cs delete mode 100644 Realm/Realm/Configurations/PartitionSyncConfiguration.cs delete mode 100644 Realm/Realm/Configurations/SyncConfigurationBase.cs delete mode 100644 Realm/Realm/Exceptions/CompensatingWriteException.cs delete mode 100644 Realm/Realm/Exceptions/Sync/AppException.cs delete mode 100644 Realm/Realm/Exceptions/Sync/ClientError.cs delete mode 100644 Realm/Realm/Exceptions/Sync/ClientResetException.cs delete mode 100644 Realm/Realm/Exceptions/Sync/ErrorCode.cs delete mode 100644 Realm/Realm/Exceptions/Sync/SessionException.cs delete mode 100644 Realm/Realm/Exceptions/Sync/SubscriptionException.cs delete mode 100644 Realm/Realm/Extensions/TestingExtensions.cs delete mode 100644 Realm/Realm/Handles/AppHandle.EmailPassword.cs delete mode 100644 Realm/Realm/Handles/AppHandle.cs delete mode 100644 Realm/Realm/Handles/AsyncOpenTaskHandle.cs delete mode 100644 Realm/Realm/Handles/SessionHandle.cs delete mode 100644 Realm/Realm/Handles/SubscriptionSetHandle.cs delete mode 100644 Realm/Realm/Handles/SyncUserHandle.cs delete mode 100644 Realm/Realm/Native/AppConfiguration.cs delete mode 100644 Realm/Realm/Native/AppError.cs delete mode 100644 Realm/Realm/Native/Credentials.cs delete mode 100644 Realm/Realm/Native/HttpClientTransport.cs delete mode 100644 Realm/Realm/Native/SessionNotificationToken.cs delete mode 100644 Realm/Realm/Native/Subscription.cs delete mode 100644 Realm/Realm/Native/SyncConfiguration.cs delete mode 100644 Realm/Realm/Native/SyncError.cs delete mode 100644 Realm/Realm/Native/SyncSocketProvider.EventLoop.cs delete mode 100644 Realm/Realm/Native/SyncSocketProvider.Native.cs delete mode 100644 Realm/Realm/Native/SyncSocketProvider.WebSocket.cs delete mode 100644 Realm/Realm/Native/SyncSocketProvider.cs delete mode 100644 Realm/Realm/Native/UserApiKey.cs delete mode 100644 Realm/Realm/Native/UserProfileField.cs delete mode 100644 Realm/Realm/Sync/ApiKey.cs delete mode 100644 Realm/Realm/Sync/App.cs delete mode 100644 Realm/Realm/Sync/AppConfiguration.cs delete mode 100644 Realm/Realm/Sync/ClientResyncMode.cs delete mode 100644 Realm/Realm/Sync/ConnectionState.cs delete mode 100644 Realm/Realm/Sync/Credentials.cs delete mode 100644 Realm/Realm/Sync/ErrorHandling/ClientResetHandlerBase.cs delete mode 100644 Realm/Realm/Sync/ErrorHandling/DiscardUnsyncedChangesHandler.cs delete mode 100644 Realm/Realm/Sync/ErrorHandling/ManualRecoveryHandler.cs delete mode 100644 Realm/Realm/Sync/ErrorHandling/RecoverOrDiscardUnsyncedChangesHandler.cs delete mode 100644 Realm/Realm/Sync/ErrorHandling/RecoverUnsyncedChangesHandler.cs delete mode 100644 Realm/Realm/Sync/FlexibleSync/Subscription.cs delete mode 100644 Realm/Realm/Sync/FlexibleSync/SubscriptionOptions.cs delete mode 100644 Realm/Realm/Sync/FlexibleSync/SubscriptionSet.cs delete mode 100644 Realm/Realm/Sync/FlexibleSync/SubscriptionSetState.cs delete mode 100644 Realm/Realm/Sync/FlexibleSync/WaitForSyncMode.cs delete mode 100644 Realm/Realm/Sync/GoogleCredentialType.cs delete mode 100644 Realm/Realm/Sync/MetadataPersistenceMode.cs delete mode 100644 Realm/Realm/Sync/MongoClient.cs delete mode 100644 Realm/Realm/Sync/ProgressNotifications/ProgressDirection.cs delete mode 100644 Realm/Realm/Sync/ProgressNotifications/ProgressMode.cs delete mode 100644 Realm/Realm/Sync/ProgressNotifications/ProgressNotificationToken.cs delete mode 100644 Realm/Realm/Sync/ProgressNotifications/SyncProgress.cs delete mode 100644 Realm/Realm/Sync/ProgressNotifications/SyncProgressObservable.cs delete mode 100644 Realm/Realm/Sync/SchemaMode.cs delete mode 100644 Realm/Realm/Sync/Session.cs delete mode 100644 Realm/Realm/Sync/SessionState.cs delete mode 100644 Realm/Realm/Sync/SessionStopPolicy.cs delete mode 100644 Realm/Realm/Sync/SyncTimeoutOptions.cs delete mode 100644 Realm/Realm/Sync/User.cs delete mode 100644 Realm/Realm/Sync/UserIdentity.cs delete mode 100644 Realm/Realm/Sync/UserProfile.cs delete mode 100644 Realm/Realm/Sync/UserState.cs delete mode 100644 Tests/Realm.Tests/Sync/AppTests.cs delete mode 100644 Tests/Realm.Tests/Sync/AsymmetricObjectTests.cs delete mode 100644 Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs delete mode 100644 Tests/Realm.Tests/Sync/FlexibleSyncTests.cs delete mode 100644 Tests/Realm.Tests/Sync/FunctionsTests.cs delete mode 100644 Tests/Realm.Tests/Sync/MongoClientTests.cs delete mode 100644 Tests/Realm.Tests/Sync/PartitionKeyTests.cs delete mode 100644 Tests/Realm.Tests/Sync/SessionTests.cs delete mode 100644 Tests/Realm.Tests/Sync/StaticQueriesTests.cs delete mode 100644 Tests/Realm.Tests/Sync/SyncConfigurationTests.cs delete mode 100644 Tests/Realm.Tests/Sync/SyncMigrationTests.cs delete mode 100644 Tests/Realm.Tests/Sync/SyncTestBase.cs delete mode 100644 Tests/Realm.Tests/Sync/SyncTestHelpers.cs delete mode 100644 Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs delete mode 100644 Tests/Realm.Tests/Sync/UserManagementTests.cs delete mode 100644 Tools/DeployApps/BaasClient.cs delete mode 100644 Tools/DeployApps/DeployApps.csproj delete mode 100644 Tools/DeployApps/GlobalSuppressions.cs delete mode 100644 Tools/DeployApps/Program.cs delete mode 100644 wrappers/src/app_cs.cpp delete mode 100644 wrappers/src/app_cs.hpp delete mode 100644 wrappers/src/async_open_task_cs.cpp delete mode 100644 wrappers/src/subscription_set_cs.cpp delete mode 100644 wrappers/src/sync_session_cs.cpp delete mode 100644 wrappers/src/sync_session_cs.hpp delete mode 100644 wrappers/src/sync_user_cs.cpp delete mode 100644 wrappers/src/transport_cs.cpp delete mode 100644 wrappers/src/transport_cs.hpp delete mode 100644 wrappers/src/websocket_cs.cpp delete mode 100644 wrappers/src/websocket_cs.hpp diff --git a/Realm - Windows.sln b/Realm - Windows.sln index df7daff957..4687c9a975 100644 --- a/Realm - Windows.sln +++ b/Realm - Windows.sln @@ -89,8 +89,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Maui", "Tests\Tests.M EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnalyticsAssembly", "Tests\Weaver\AnalyticsAssembly\AnalyticsAssembly.csproj", "{1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeployApps", "Tools\DeployApps\DeployApps.csproj", "{10026D09-FC37-48B3-AAEA-B322AA6D89CE}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Realm.PlatformHelpers", "Realm\Realm.PlatformHelpers\Realm.PlatformHelpers.csproj", "{536C3309-F848-4485-ABF3-56DCD9C9F9E8}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.XamarinTVOS", "Tests\Tests.XamarinTVOS\Tests.XamarinTVOS.csproj", "{80B9697D-0C57-40E8-A71A-F5E81C7BF467}" @@ -1381,54 +1379,6 @@ Global {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|x86.Build.0 = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|ARM.ActiveCfg = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|ARM.Build.0 = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|iPhone.Build.0 = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|x64.ActiveCfg = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|x64.Build.0 = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|x86.ActiveCfg = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|x86.Build.0 = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.MinSizeRel|ARM.ActiveCfg = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.MinSizeRel|ARM.Build.0 = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.MinSizeRel|iPhone.ActiveCfg = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.MinSizeRel|iPhone.Build.0 = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.MinSizeRel|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.MinSizeRel|iPhoneSimulator.Build.0 = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.MinSizeRel|x64.Build.0 = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.MinSizeRel|x86.Build.0 = Debug|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Release|Any CPU.Build.0 = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Release|ARM.ActiveCfg = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Release|ARM.Build.0 = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Release|iPhone.ActiveCfg = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Release|iPhone.Build.0 = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Release|x64.ActiveCfg = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Release|x64.Build.0 = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Release|x86.ActiveCfg = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Release|x86.Build.0 = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.RelWithDebInfo|iPhone.ActiveCfg = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.RelWithDebInfo|iPhone.Build.0 = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.RelWithDebInfo|iPhoneSimulator.ActiveCfg = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.RelWithDebInfo|iPhoneSimulator.Build.0 = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.RelWithDebInfo|x64.Build.0 = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU - {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.RelWithDebInfo|x86.Build.0 = Release|Any CPU {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|Any CPU.Build.0 = Debug|Any CPU {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -1558,7 +1508,6 @@ Global {3C3CEB09-94C5-4FE4-BF75-1CEF4EAF6E47} = {EC97E75C-3A79-4B00-95BD-218D71C58746} {C84EBA8B-5F7F-4519-BB34-EDE93E275D66} = {D10BE048-9C20-4B8B-BE5B-48CC55F8BB07} {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5} = {4FF9AAE6-210E-41F0-A5E1-8D7C4A864F51} - {10026D09-FC37-48B3-AAEA-B322AA6D89CE} = {073F6C2D-FECB-41E3-BEA3-1685FAA580DB} {536C3309-F848-4485-ABF3-56DCD9C9F9E8} = {50F058DF-2B41-403C-BB73-8B4180D1CF39} {80B9697D-0C57-40E8-A71A-F5E81C7BF467} = {D10BE048-9C20-4B8B-BE5B-48CC55F8BB07} EndGlobalSection diff --git a/Realm.sln b/Realm.sln index 065cfc45ea..8a089d30e6 100644 --- a/Realm.sln +++ b/Realm.sln @@ -44,8 +44,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceGenerators", "SourceG EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.XamarinTVOS", "Tests\Tests.XamarinTVOS\Tests.XamarinTVOS.csproj", "{80B9697D-0C57-40E8-A71A-F5E81C7BF467}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeployApps", "Tools\DeployApps\DeployApps.csproj", "{56029D1A-0AF6-4535-9D6A-CD5CD61325C6}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Realm.Fody", "Realm\Realm.Fody\Realm.Fody.csproj", "{2AC1DA0E-2B6E-4DBA-A73F-6784BA0A7DB4}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.XamarinMac", "Tests\Tests.XamarinMac\Tests.XamarinMac.csproj", "{3A6CC342-40D0-4487-9964-DDAEBA628199}" @@ -85,7 +83,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SetupUnityPackage", "Tools\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Realm.PlatformHelpers", "Realm\Realm.PlatformHelpers\Realm.PlatformHelpers.csproj", "{D08C71CE-0F5B-4855-A77C-58062A5ECB78}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnalyticsAssembly", "Tests\Weaver\AnalyticsAssembly\AnalyticsAssembly.csproj", "{1E392D99-D783-4122-8B31-A4CA19ABADEE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnalyticsAssembly", "Tests\Weaver\AnalyticsAssembly\AnalyticsAssembly.csproj", "{1E392D99-D783-4122-8B31-A4CA19ABADEE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -271,30 +269,6 @@ Global {80B9697D-0C57-40E8-A71A-F5E81C7BF467}.Release|x64.Build.0 = Release|iPhone {80B9697D-0C57-40E8-A71A-F5E81C7BF467}.Release|x86.ActiveCfg = Release|iPhone {80B9697D-0C57-40E8-A71A-F5E81C7BF467}.Release|x86.Build.0 = Release|iPhone - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Debug|ARM.ActiveCfg = Debug|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Debug|ARM.Build.0 = Debug|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Debug|iPhone.Build.0 = Debug|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Debug|x64.ActiveCfg = Debug|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Debug|x64.Build.0 = Debug|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Debug|x86.ActiveCfg = Debug|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Debug|x86.Build.0 = Debug|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Release|Any CPU.Build.0 = Release|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Release|ARM.ActiveCfg = Release|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Release|ARM.Build.0 = Release|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Release|iPhone.ActiveCfg = Release|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Release|iPhone.Build.0 = Release|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Release|x64.ActiveCfg = Release|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Release|x64.Build.0 = Release|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Release|x86.ActiveCfg = Release|Any CPU - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6}.Release|x86.Build.0 = Release|Any CPU {2AC1DA0E-2B6E-4DBA-A73F-6784BA0A7DB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2AC1DA0E-2B6E-4DBA-A73F-6784BA0A7DB4}.Debug|Any CPU.Build.0 = Debug|Any CPU {2AC1DA0E-2B6E-4DBA-A73F-6784BA0A7DB4}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -767,7 +741,6 @@ Global {1104E44A-93C2-4B01-9330-6555C08ABFC1} = {50F058DF-2B41-403C-BB73-8B4180D1CF39} {CC933D98-B002-4306-89C4-B1905658D9DE} = {D10BE048-9C20-4B8B-BE5B-48CC55F8BB07} {80B9697D-0C57-40E8-A71A-F5E81C7BF467} = {D10BE048-9C20-4B8B-BE5B-48CC55F8BB07} - {56029D1A-0AF6-4535-9D6A-CD5CD61325C6} = {A25317DE-BB3A-47CC-8E65-F96C9B6AD984} {2AC1DA0E-2B6E-4DBA-A73F-6784BA0A7DB4} = {50F058DF-2B41-403C-BB73-8B4180D1CF39} {3A6CC342-40D0-4487-9964-DDAEBA628199} = {D10BE048-9C20-4B8B-BE5B-48CC55F8BB07} {7174D68E-E76D-4A4F-9F9A-601F5FB2DB65} = {4FF9AAE6-210E-41F0-A5E1-8D7C4A864F51} diff --git a/Realm/Realm.Weaver/Analytics/Analytics.cs b/Realm/Realm.Weaver/Analytics/Analytics.cs index f0d2e7ccd3..0a83896d66 100644 --- a/Realm/Realm.Weaver/Analytics/Analytics.cs +++ b/Realm/Realm.Weaver/Analytics/Analytics.cs @@ -165,10 +165,6 @@ public Analytics(Config config, ImportedReferences references, ILogger logger, M { key = Feature.ObjectNotification; } - else if (reference.DeclaringType.IsSameAs(_references.SyncSession)) - { - key = Feature.ConnectionNotification; - } } if (key == null) diff --git a/Realm/Realm.Weaver/ImportedReferences.cs b/Realm/Realm.Weaver/ImportedReferences.cs index 1aa113a0f2..8004090561 100644 --- a/Realm/Realm.Weaver/ImportedReferences.cs +++ b/Realm/Realm.Weaver/ImportedReferences.cs @@ -140,8 +140,6 @@ internal abstract class ImportedReferences public MethodReference CollectionExtensions_PopulateDictionary { get; private set; } - public TypeReference SyncSession { get; private set; } - protected ModuleDefinition Module { get; } public TypeSystem Types { get; } @@ -347,8 +345,6 @@ private void InitializeRealm(IMetadataScope realmAssembly) RealmSchema_AddDefaultTypes.Parameters.Add(new ParameterDefinition(ienumerableOfType)); } - SyncSession = new TypeReference("Realms.Sync", "Session", Module, realmAssembly); - var collectionExtensions = new TypeReference("Realms", "CollectionExtensions", Module, realmAssembly); CollectionExtensions_PopulateCollection = new MethodReference("PopulateCollection", Types.Void, collectionExtensions) { HasThis = false }; { diff --git a/Realm/Realm/Configurations/FlexibleSyncConfiguration.cs b/Realm/Realm/Configurations/FlexibleSyncConfiguration.cs deleted file mode 100644 index 1e4b610951..0000000000 --- a/Realm/Realm/Configurations/FlexibleSyncConfiguration.cs +++ /dev/null @@ -1,187 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Threading; -using System.Threading.Tasks; - -namespace Realms.Sync -{ - /// - /// A is used to setup a whose data can be synchronized - /// between devices using Atlas Device Sync. Unlike , a Realm opened with - /// will be initially empty until one or more subscriptions are added - /// via . - /// - /// Device Sync Docs - public class FlexibleSyncConfiguration : SyncConfigurationBase - { - /// - /// A delegate invoked when a flexible sync Realm is first opened. - /// - /// The realm that has just been opened. - public delegate void InitialSubscriptionsDelegate(Realm realm); - - /// - /// Initializes a new instance of the class. - /// - /// - /// A valid . - /// - /// - /// Path to the realm, must be a valid full path for the current platform, relative subdirectory, or just filename. - /// - public FlexibleSyncConfiguration(User user, string? optionalPath = null) - : base(user, GetPathToRealm(optionalPath ?? user?.Handle.GetRealmPath())) - { - } - - /// - /// Gets or sets a callback that will be invoked the first time a Realm is opened. - /// - /// - /// This callback allows you to populate an initial set of subscriptions, which will - /// then be awaited when is invoked. - ///
- /// The returned by is already - /// called in an block, so there's no need - /// to start one inside the callback. - ///
- /// - /// - /// var config = new FlexibleSyncConfiguration(user) - /// { - /// PopulateInitialSubscriptions = (realm) => - /// { - /// var myNotes = realm.All<Note>().Where(n => n.AuthorId == myUserId); - /// realm.Subscriptions.Add(myNotes); - /// } - /// }; - /// - /// // The task will complete when all the user notes have been downloaded. - /// var realm = await Realm.GetInstanceAsync(config); - /// - /// - /// - /// The that will be invoked the first time - /// a Realm is opened. - /// - public InitialSubscriptionsDelegate? PopulateInitialSubscriptions { get; set; } - - internal override async Task CreateRealmAsync(CancellationToken cancellationToken) - { - var tracker = InjectInitialSubscriptions(); - - var result = await base.CreateRealmAsync(cancellationToken); - - InvokeInitialSubscriptions(tracker, result); - - if (tracker.PopulateInitialDataInvoked) - { - await result.Subscriptions.WaitForSynchronizationAsync(); - - // TODO: remove the wait once https://github.com/realm/realm-core/issues/5705 is resolved - await result.SyncSession.WaitForDownloadAsync(); - } - - return result; - } - - internal override Realm CreateRealm() - { - var tracker = InjectInitialSubscriptions(); - - var result = base.CreateRealm(); - - InvokeInitialSubscriptions(tracker, result); - - return result; - } - - private InitialDataTracker InjectInitialSubscriptions() - { - var tracker = new InitialDataTracker - { - Callback = PopulateInitialSubscriptions - }; - - if (tracker.Callback != null) - { - var oldDataCallback = PopulateInitialData; - PopulateInitialData = (realm) => - { - // We can't run the PopulateInitialSubscriptions callback here because - // the Realm is already in a write transaction, so Subscriptions.Update - // will hang. We flag that with `shouldPopulate` and update the subscriptions - // after we open the realm. - oldDataCallback?.Invoke(realm); - tracker.PopulateInitialDataInvoked = true; - }; - } - - return tracker; - } - - private static void InvokeInitialSubscriptions(InitialDataTracker tracker, Realm realm) - { - if (!tracker.PopulateInitialDataInvoked) - { - return; - } - - try - { - realm.Subscriptions.Update(() => - { - // We're not guarded by a write lock between invoking the `InitialDataCallback` - // and getting here, so it's possible someone managed to insert subscriptions - // before us. If that's the case, then don't insert anything and just wait for - // sync. - if (realm.Subscriptions.Count == 0) - { - tracker.Callback(realm); - } - }); - } - catch (Exception ex) - { - // This needs to duplicate the logic in RealmConfigurationBase.CreateRealmAsync - throw new AggregateException("Exception occurred in a Realm.PopulateInitialSubscriptions callback. See inner exception for more details.", ex); - } - } - - internal override Native.SyncConfiguration CreateNativeSyncConfiguration() - { - var config = base.CreateNativeSyncConfiguration(); - config.is_flexible_sync = true; - return config; - } - - // This is a holder class to workaround the fact that we can't use ref booleans - // inside lambda functions. Its purpose is to server as a marker that PopulateInitialData - // was invoked, which means we need to then update subscriptions. - private class InitialDataTracker - { - [MemberNotNullWhen(returnValue: true, nameof(Callback))] - public bool PopulateInitialDataInvoked { get; set; } - - public InitialSubscriptionsDelegate? Callback { get; set; } - } - } -} diff --git a/Realm/Realm/Configurations/PartitionSyncConfiguration.cs b/Realm/Realm/Configurations/PartitionSyncConfiguration.cs deleted file mode 100644 index fbf2ffd483..0000000000 --- a/Realm/Realm/Configurations/PartitionSyncConfiguration.cs +++ /dev/null @@ -1,150 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using MongoDB.Bson; -using Realms.Helpers; - -namespace Realms.Sync -{ - /// - /// A is used to setup a that can be synchronized between devices using Atlas Device Sync. - /// - /// Device Sync Docs - public class PartitionSyncConfiguration : SyncConfigurationBase - { - /// - /// Gets or sets a callback that is invoked when download progress is made when using . - /// This will only be invoked for the initial download of the Realm and will not be invoked as further download - /// progress is made during the lifetime of the Realm. It is ignored when using - /// . - /// - /// A callback that will be periodically invoked as the Realm is downloaded. - public Action? OnProgress { get; set; } - - /// - /// Gets the partition identifying the Realm this configuration is describing. - /// - /// The partition value for the Realm. - public RealmValue Partition { get; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The partition identifying the remote Realm that will be synchronized. - /// - /// - /// A valid . - /// - /// - /// Path to the realm, must be a valid full path for the current platform, relative subdirectory, or just filename. - /// - public PartitionSyncConfiguration(string? partition, User user, string? optionalPath = null) - : this(user, partition, optionalPath) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The partition identifying the remote Realm that will be synchronized. - /// - /// - /// A valid . - /// - /// - /// Path to the realm, must be a valid full path for the current platform, relative subdirectory, or just filename. - /// - public PartitionSyncConfiguration(long? partition, User user, string? optionalPath = null) - : this(user, partition, optionalPath) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The partition identifying the remote Realm that will be synchronized. - /// - /// - /// A valid . - /// - /// - /// Path to the realm, must be a valid full path for the current platform, relative subdirectory, or just filename. - /// - public PartitionSyncConfiguration(ObjectId? partition, User user, string? optionalPath = null) - : this(user, partition, optionalPath) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The partition identifying the remote Realm that will be synchronized. - /// - /// - /// A valid . - /// - /// - /// Path to the realm, must be a valid full path for the current platform, relative subdirectory, or just filename. - /// - public PartitionSyncConfiguration(Guid? partition, User user, string? optionalPath = null) - : this(user, partition, optionalPath) - { - } - - private PartitionSyncConfiguration(User user, RealmValue partition, string? path) - : base(user, GetPathToRealm(path ?? user?.Handle.GetRealmPath(partition.ToNativeJson()))) - { - Partition = partition; - } - - internal override IDisposable? OnBeforeRealmOpen(AsyncOpenTaskHandle handle) - { - var onProgress = OnProgress; - if (onProgress is null) - { - return null; - } - - return new ProgressNotificationToken( - observer: (progress) => - { - onProgress(progress); - }, - register: handle.RegisterProgressNotifier, - unregister: (token) => - { - if (!handle.IsClosed) - { - handle.UnregisterProgressNotifier(token); - } - }); - } - - internal override Native.SyncConfiguration CreateNativeSyncConfiguration() - { - var config = base.CreateNativeSyncConfiguration(); - config.Partition = Partition.ToNativeJson(); - return config; - } - } -} diff --git a/Realm/Realm/Configurations/SyncConfigurationBase.cs b/Realm/Realm/Configurations/SyncConfigurationBase.cs deleted file mode 100644 index a552f114c7..0000000000 --- a/Realm/Realm/Configurations/SyncConfigurationBase.cs +++ /dev/null @@ -1,173 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using Realms.Helpers; -using Realms.Native; -using Realms.Sync.ErrorHandling; -using Realms.Sync.Exceptions; - -namespace Realms.Sync -{ - /// - /// A is used to setup a that can be synchronized between devices using Atlas Device Sync. - /// There are two synchronization modes with their respective configurations - "partition" sync with allows you - /// to split your data in separate partitions and synchronize an entire partition with an entire Realm; "flexible" sync with - /// allows you to start with an empty Realm and send the server a set of queries which it will run and - /// populate the Realm with all documents matching them. - /// - /// Device Sync Docs - public abstract class SyncConfigurationBase : RealmConfigurationBase - { - private ClientResetHandlerBase _clientResetHandler = new RecoverOrDiscardUnsyncedChangesHandler(); - - /// - /// Callback triggered when an error occurs in a session. - /// - /// - /// The where the error happened on. - /// - /// - /// The specific occurred on this . - /// - public delegate void SessionErrorCallback(Session session, SessionException error); - - /// - /// Gets the used to create this . - /// - /// The whose s will be synced. - public User User { get; } - - /// - /// Gets or sets a handler that will be invoked if a client reset error occurs for this Realm. Default is , - /// that attempts to automatically recover any unsynchronized changes and, if that fails, falls back to the discarding unsynced changes. - /// - /// The that will be used to handle a client reset. - /// - /// Supported values are instances of , , - /// and . - /// The default will have no custom actions set for the before and after callbacks. - /// - /// Client reset docs - public virtual ClientResetHandlerBase ClientResetHandler - { - get => _clientResetHandler; - set => _clientResetHandler = Argument.ValidateNotNull(value, nameof(value)); - } - - /// - /// Gets or sets a callback that will be invoked whenever a occurs for the synchronized Realm. - /// - /// The that will be used to report transient session errors. - /// - /// Client reset errors will not be reported through this callback as they are handled by the set . - /// - public SessionErrorCallback? OnSessionError { get; set; } - - /// - /// Gets or sets a value indicating whether async operations, such as , - /// , or should throw an - /// error whenever a non-fatal error, such as timeout occurs. - /// - /// - /// If set to false, non-fatal session errors will be ignored and sync will continue retrying the - /// connection under in the background. This means that in cases where the device is offline, these operations - /// may take an indeterminate time to complete. - /// - /// true to throw an error if a non-fatal session error occurs, false otherwise. - public bool CancelAsyncOperationsOnNonFatalErrors { get; set; } - - /// - /// Gets or sets the key, used to encrypt the entire Realm. Once set, must be specified each time the file is used. - /// - /// Full 64byte (512bit) key for AES-256 encryption. - public new byte[]? EncryptionKey - { - get => base.EncryptionKey; - set => base.EncryptionKey = value; - } - - internal SessionStopPolicy SessionStopPolicy { get; set; } = SessionStopPolicy.AfterChangesUploaded; - - private protected SyncConfigurationBase(User user, string databasePath) - : base(databasePath) - { - Argument.NotNull(user, nameof(user)); - - User = user; - } - - internal override SharedRealmHandle CreateHandle(in Configuration configuration) - { - var syncConfiguration = CreateNativeSyncConfiguration(); - return SharedRealmHandle.OpenWithSync(configuration, syncConfiguration); - } - - internal override Task CreateHandleAsync(in Configuration configuration, CancellationToken cancellationToken) - { - var syncConfiguration = CreateNativeSyncConfiguration(); - - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - var handle = SharedRealmHandle.OpenWithSyncAsync(configuration, syncConfiguration, GCHandle.ToIntPtr(tcsHandle)); - cancellationToken.Register(() => - { - if (!handle.IsClosed) - { - handle.Cancel(); - tcs.TrySetCanceled(); - } - }); - - async Task WaitForAsyncOpenTask() - { - using var progressToken = OnBeforeRealmOpen(handle); - - try - { - using var realmReference = await tcs.Task; - return SharedRealmHandle.ResolveFromReference(realmReference); - } - finally - { - tcsHandle.Free(); - handle.Dispose(); - } - } - - return WaitForAsyncOpenTask(); - } - - internal virtual IDisposable? OnBeforeRealmOpen(AsyncOpenTaskHandle handle) => null; - - internal virtual Native.SyncConfiguration CreateNativeSyncConfiguration() - { - return new Native.SyncConfiguration - { - SyncUserHandle = User.Handle, - session_stop_policy = SessionStopPolicy, - schema_mode = _schema is null ? SchemaMode.AdditiveDiscovered : SchemaMode.AdditiveExplicit, - client_resync_mode = ClientResetHandler.ClientResetMode, - cancel_waits_on_nonfatal_error = CancelAsyncOperationsOnNonFatalErrors, - }; - } - } -} diff --git a/Realm/Realm/Exceptions/CompensatingWriteException.cs b/Realm/Realm/Exceptions/CompensatingWriteException.cs deleted file mode 100644 index 984f7e64bb..0000000000 --- a/Realm/Realm/Exceptions/CompensatingWriteException.cs +++ /dev/null @@ -1,79 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Realms.Sync.Exceptions -{ - /// - /// An exception class that indicates that one more object changes have been reverted - /// by the server. - /// - /// - /// The two typical cases in which the server will revert a client write are: - /// 1. The client created an object that doesn't match any . - /// 2. The client created/updated an object it didn't have permissions to. - /// - public class CompensatingWriteException : SessionException - { - /// - /// Gets a list of the compensating writes performed by the server. - /// - /// The compensating writes performed by the server. - public IEnumerable CompensatingWrites { get; } - - internal CompensatingWriteException(string message, IEnumerable compensatingWrites) - : base(message, ErrorCode.CompensatingWrite) - { - CompensatingWrites = compensatingWrites; - } - } - - /// - /// A class containing the details for a compensating write performed by the server. - /// - [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "This is closely related to CompensatingWriteException")] - public class CompensatingWriteInfo - { - /// - /// Gets the type of the object which was affected by the compensating write. - /// - /// The object type. - public string ObjectType { get; } - - /// - /// Gets the reason for the server to perform a compensating write. - /// - /// The compensating write reason. - public string Reason { get; } - - /// - /// Gets the primary key of the object which was affected by the compensating write. - /// - /// The object primary key. - public RealmValue PrimaryKey { get; } - - internal CompensatingWriteInfo(string objectName, string reason, RealmValue primaryKey) - { - ObjectType = objectName; - Reason = reason; - PrimaryKey = primaryKey; - } - } -} diff --git a/Realm/Realm/Exceptions/RealmDecryptionFailedException.cs b/Realm/Realm/Exceptions/RealmDecryptionFailedException.cs index d0c8b7f7e0..32756df083 100644 --- a/Realm/Realm/Exceptions/RealmDecryptionFailedException.cs +++ b/Realm/Realm/Exceptions/RealmDecryptionFailedException.cs @@ -16,14 +16,11 @@ // //////////////////////////////////////////////////////////////////////////// -using Realms.Sync; - namespace Realms.Exceptions { /// /// An exception, raised when file decryption is unsuccessful, most likely due to invalid /// . - /// . /// public class RealmDecryptionFailedException : RealmFileAccessErrorException { diff --git a/Realm/Realm/Exceptions/RealmException.cs b/Realm/Realm/Exceptions/RealmException.cs index 29074660d9..04c57a1679 100644 --- a/Realm/Realm/Exceptions/RealmException.cs +++ b/Realm/Realm/Exceptions/RealmException.cs @@ -17,7 +17,6 @@ //////////////////////////////////////////////////////////////////////////// using System; -using Realms.Sync.Exceptions; namespace Realms.Exceptions { @@ -105,11 +104,6 @@ internal static Exception Create(RealmExceptionCodes exceptionCode, string messa return new RealmMigrationException(message); } - if (categories.HasFlag(RealmExceptionCategories.RLM_ERR_CAT_APP_ERROR)) - { - return new AppException(message, helpLink: null, httpStatusCode: 0); - } - if (categories.HasFlag(RealmExceptionCategories.RLM_ERR_CAT_FILE_ACCESS)) { return new RealmFileAccessErrorException(message); diff --git a/Realm/Realm/Exceptions/RealmExceptionCategories.cs b/Realm/Realm/Exceptions/RealmExceptionCategories.cs index 20c170e45b..b1d6d883a1 100644 --- a/Realm/Realm/Exceptions/RealmExceptionCategories.cs +++ b/Realm/Realm/Exceptions/RealmExceptionCategories.cs @@ -28,12 +28,7 @@ internal enum RealmExceptionCategories RLM_ERR_CAT_INVALID_ARG = 0x0008, RLM_ERR_CAT_FILE_ACCESS = 0x0010, RLM_ERR_CAT_SYSTEM_ERROR = 0x0020, - RLM_ERR_CAT_APP_ERROR = 0x0040, RLM_ERR_CAT_CLIENT_ERROR = 0x0080, - RLM_ERR_CAT_JSON_ERROR = 0x0100, - RLM_ERR_CAT_SERVICE_ERROR = 0x0200, - RLM_ERR_CAT_HTTP_ERROR = 0x0400, RLM_ERR_CAT_CUSTOM_ERROR = 0x0800, - RLM_ERR_CAT_WEBSOCKET_ERROR = 0x1000, } } diff --git a/Realm/Realm/Exceptions/Sync/AppException.cs b/Realm/Realm/Exceptions/Sync/AppException.cs deleted file mode 100644 index 10a166e878..0000000000 --- a/Realm/Realm/Exceptions/Sync/AppException.cs +++ /dev/null @@ -1,51 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Net; -using Realms.Sync.Native; - -namespace Realms.Sync.Exceptions -{ - /// - /// An exception thrown from operations interacting with a Atlas App Services app. - /// - public class AppException : Exception - { - /// - /// Gets the HTTP status code returned by the remote operation. - /// - /// The HTTP status code of the operation that failed or null if the error was not an http one. - public HttpStatusCode? StatusCode { get; } - - internal AppException(AppError appError) - : this($"{appError.ErrorCategory}: {appError.Message}", appError.LogsLink, appError.http_status_code) - { - } - - internal AppException(string message, string? helpLink, int httpStatusCode) - : base(message) - { - HelpLink = helpLink; - if (httpStatusCode != 0) - { - StatusCode = (HttpStatusCode)httpStatusCode; - } - } - } -} diff --git a/Realm/Realm/Exceptions/Sync/ClientError.cs b/Realm/Realm/Exceptions/Sync/ClientError.cs deleted file mode 100644 index 6cf93d3133..0000000000 --- a/Realm/Realm/Exceptions/Sync/ClientError.cs +++ /dev/null @@ -1,35 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Exceptions.Sync -{ - internal enum ClientError - { - /// - /// A fatal error was encountered which prevents the completion of a client reset. - /// - AutoClientResetFailed = 132, - } - - internal enum ServerRequestsAction - { - NoAction = 0, - ApplicationBug = 2, - ClientReset = 6 - } -} diff --git a/Realm/Realm/Exceptions/Sync/ClientResetException.cs b/Realm/Realm/Exceptions/Sync/ClientResetException.cs deleted file mode 100644 index 10752ff871..0000000000 --- a/Realm/Realm/Exceptions/Sync/ClientResetException.cs +++ /dev/null @@ -1,61 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Generic; -using System.IO; - -namespace Realms.Sync.Exceptions -{ - /// - /// An exception describing a condition where a reset of the local Realm is required. - /// - public class ClientResetException : SessionException - { - private readonly string _originalFilePath; - private readonly App _app; - - /// - /// Gets the path where the backup copy of the realm will be placed once the client reset process is complete. - /// - /// The path to the backup realm. - public string BackupFilePath { get; } - - internal ClientResetException(App app, string message, ErrorCode errorCode, IDictionary userInfo) - : base(message, errorCode) - { - // Using Path.GetFullPath to normalize path separators on Windows - _originalFilePath = Path.GetFullPath(userInfo[OriginalFilePathKey]!); - _app = app; - BackupFilePath = Path.GetFullPath(userInfo[BackupFilePathKey]!); - HelpLink = "https://www.mongodb.com/docs/realm/sdk/dotnet/sync/client-reset/"; - } - - /// - /// Initiates the client reset process. - /// - /// true if actions were run successfully, false otherwise. - /// - /// On Windows, all Realm instances for that path must be disposed before this method is called or an - /// Exception will be thrown. - /// - public bool InitiateClientReset() - { - return _app.Handle.ImmediatelyRunFileActions(_originalFilePath); - } - } -} diff --git a/Realm/Realm/Exceptions/Sync/ErrorCode.cs b/Realm/Realm/Exceptions/Sync/ErrorCode.cs deleted file mode 100644 index 066e8dc0cb..0000000000 --- a/Realm/Realm/Exceptions/Sync/ErrorCode.cs +++ /dev/null @@ -1,220 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using Realms.Sync.ErrorHandling; - -namespace Realms.Sync.Exceptions; - -/// -/// Error code enumeration, indicating the type of the session error. -/// -/// -public enum ErrorCode -{ - /// - /// Unrecognized error code. It usually indicates incompatibility between the App Services server and client SDK versions. - /// - RuntimeError = 1000, - - /// - /// The partition value specified by the user is not valid - i.e. its the wrong type or is encoded incorrectly. - /// - BadPartitionValue = 1029, - - /// - /// A fundamental invariant in the communication between the client and the server was not upheld. This typically indicates - /// a bug in the synchronization layer and should be reported at https://github.com/realm/realm-core/issues. - /// - ProtocolInvariantFailed = 1038, - - /// - /// The changeset is invalid. - /// - BadChangeset = 1015, - - /// - /// The client attempted to create a subscription which the server rejected. - /// - SubscriptionFailed = 1016, - - /// - /// The client attempted to create a subscription for a query is invalid/malformed. - /// - BadQuery = 1031, - - /// - /// A client reset has occurred. This error code will only be reported via a and only - /// in the case manual client reset handling is required - either via or when - /// ManualResetFallback is invoked on one of the automatic client reset handlers. - /// - /// - /// - ClientReset = 1032, - - /// - /// The client attempted to upload an invalid schema change - either an additive schema change - /// when developer mode is off or a destructive schema change. - /// - InvalidSchemaChange = 1036, - - /// - /// Permission to Realm has been denied. - /// - PermissionDenied = 1037, - - /// - /// The server permissions for this file have changed since the last time it was used. - /// - ServerPermissionsChanged = 1040, - - /// - /// The user for this session doesn't match the user who originally created the file. This can happen - /// if you explicitly specify the Realm file path in the configuration and you open the Realm first with - /// user A, then with user B without changing the on-disk path. - /// - UserMismatch = 1041, - - /// - /// Client attempted a write that is disallowed by permissions, or modifies an object - /// outside the current query - this will result in a . - /// - WriteNotAllowed = 1044, - - /// - /// Automatic client reset has failed. This will only be reported via - /// when an automatic client reset handler was used but it failed to perform the client reset operation - - /// typically due to a breaking schema change in the server schema or due to an exception occurring in the - /// before or after client reset callbacks. - /// - AutoClientResetFailed = 1028, - - /// - /// The wrong sync type was used to connect to the server. This means that you're using - /// to connect to an app configured for flexible sync or that you're using to connect - /// to an app configured to use partition sync. - /// - WrongSyncType = 1043, - - /// - /// Client attempted a write that is disallowed by permissions, or modifies an - /// object outside the current query, and the server undid the modification. - /// - /// - CompensatingWrite = 1033, - - /// - /// Unrecognized error code. It usually indicates incompatibility between the App Services server and client SDK versions. - /// - [Obsolete("Use RuntimeError instead.")] - Unknown = RuntimeError, - - /// - /// Other session level error has occurred. - /// - /// - /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. - /// - [Obsolete("Use RuntimeError instead.")] - OtherSessionError = RuntimeError, - - /// - /// Path to Realm is invalid. - /// - /// - /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. - /// - [Obsolete("Use BadPartitionValue instead")] - IllegalRealmPath = BadPartitionValue, - - /// - /// The client file identifier is invalid. - /// - /// - [Obsolete("Use ClientReset instead")] - BadClientFileIdentifier = ClientReset, - - /// - /// The server version is invalid. - /// - /// - /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. - /// - [Obsolete("Use ProtocolInvariantFailed instead")] - BadServerVersion = ProtocolInvariantFailed, - - /// - /// The client version is invalid. - /// - /// - /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. - /// - [Obsolete("Use ProtocolInvariantFailed instead")] - BadClientVersion = ProtocolInvariantFailed, - - /// - /// Histories have diverged and cannot be merged. - /// - /// - [Obsolete("Use ClientReset instead")] - DivergingHistories = ClientReset, - - /// - /// The client file is invalid. - /// - /// - [Obsolete("Use ClientReset instead")] - BadClientFile = ClientReset, - - /// - /// Client file has expired likely due to history compaction on the server. - /// - /// - [Obsolete("Use ClientReset instead")] - ClientFileExpired = ClientReset, - - /// - /// The server has received too many sessions from this client. This is typically a transient error - /// but can also indicate that the client has too many Realms open at the same time. - /// - [Obsolete("This error code is no longer reported")] - TooManySessions = -2, - - /// - /// The client attempted to create an object that already exists outside their view. - /// - [Obsolete("This error code is no longer reported")] - ObjectAlreadyExists = -3, - - /// - /// The client tried to synchronize before initial sync has completed. Please wait for - /// the server process to complete and try again. - /// - [Obsolete("This error code is no longer reported")] - InitialSyncNotCompleted = -4, - - /// - /// An error sent by the server when its data structures used to track client progress - /// become corrupted. - /// - /// - /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. - /// - [Obsolete("Use ProtocolInvariantFailed instead")] - BadProgress = ProtocolInvariantFailed, -} diff --git a/Realm/Realm/Exceptions/Sync/SessionException.cs b/Realm/Realm/Exceptions/Sync/SessionException.cs deleted file mode 100644 index 540b9bab89..0000000000 --- a/Realm/Realm/Exceptions/Sync/SessionException.cs +++ /dev/null @@ -1,42 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; - -namespace Realms.Sync.Exceptions -{ - /// - /// An exception type that describes a session-level error condition. - /// - public class SessionException : Exception - { - internal const string OriginalFilePathKey = "ORIGINAL_FILE_PATH"; - internal const string BackupFilePathKey = "RECOVERY_FILE_PATH"; - - /// - /// Gets the error code that describes the session error this exception represents. - /// - /// An enum value, providing more detailed information for the cause of the error. - public ErrorCode ErrorCode { get; } - - internal SessionException(string message, ErrorCode errorCode, Exception? innerException = null) : base(message, innerException) - { - ErrorCode = errorCode; - } - } -} diff --git a/Realm/Realm/Exceptions/Sync/SubscriptionException.cs b/Realm/Realm/Exceptions/Sync/SubscriptionException.cs deleted file mode 100644 index b479d507e4..0000000000 --- a/Realm/Realm/Exceptions/Sync/SubscriptionException.cs +++ /dev/null @@ -1,32 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; - -namespace Realms.Sync.Exceptions -{ - /// - /// An exception that describes an issue with a Flexible Sync . - /// - public class SubscriptionException : Exception - { - internal SubscriptionException(string message) : base(message) - { - } - } -} diff --git a/Realm/Realm/Extensions/CollectionExtensions.cs b/Realm/Realm/Extensions/CollectionExtensions.cs index 54a7f81eee..bf3db48b0d 100644 --- a/Realm/Realm/Extensions/CollectionExtensions.cs +++ b/Realm/Realm/Extensions/CollectionExtensions.cs @@ -22,10 +22,7 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Realms.Helpers; -using Realms.Sync; namespace Realms; @@ -497,66 +494,6 @@ public static IQueryable Filter(this IDictionary dictionary, s return realmDictionary.GetFilteredValueResults(predicate, arguments); } - /// - /// Adds a query to the set of active flexible sync subscriptions. The query will be joined via an OR statement - /// with any existing queries for the same type. - /// - /// The query that will be matched on the server. - /// - /// The subscription options controlling the name and/or the type of insert that will be performed. - /// - /// - /// A parameter controlling when this method should asynchronously wait for the server to send the objects - /// matching the subscription. - /// - /// - /// An optional cancellation token to cancel waiting for synchronization with the server. Note that cancelling the - /// operation only cancels the wait itself and not the actual subscription, so the subscription will be added even - /// if the task is cancelled. To remove the subscription, you can use . - /// - /// The type of objects in the query results. - /// - /// Adding a query that already exists is a no-op. - ///
- /// This method is roughly equivalent to calling and then - /// . - ///
- /// The original query after it has been added to the subscription set. - /// - public static async Task> SubscribeAsync(this IQueryable query, - SubscriptionOptions? options = null, - WaitForSyncMode waitForSync = WaitForSyncMode.FirstTime, - CancellationToken? cancellationToken = null) - where T : IRealmObject - { - Argument.NotNull(query, nameof(query)); - - var realmResults = Argument.EnsureType>(query, $"{nameof(query)} must be a query obtained by calling Realm.All.", nameof(query)); - if (realmResults.Realm.Config is not FlexibleSyncConfiguration) - { - throw new NotSupportedException( - "SubscribeAsync can only be called on queries created in a flexible sync Realm (i.e. one open with a FlexibleSyncConfiguration)"); - } - - var subscriptions = realmResults.Realm.Subscriptions; - var existingSub = options?.Name == null ? subscriptions.Find(query) : subscriptions.Find(options.Name); - - Subscription newSub = null!; - - subscriptions.Update(() => - { - newSub = subscriptions.Add(realmResults, options); - }); - - if (ShouldWaitForSync(waitForSync, existingSub, newSub)) - { - await subscriptions.WaitForSynchronizationAsync(cancellationToken); - await realmResults.Realm.SyncSession.WaitForDownloadAsync(cancellationToken); - } - - return query; - } - [EditorBrowsable(EditorBrowsableState.Never)] [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "This is only used by the weaver/source generated classes and should not be exposed to users.")] @@ -569,25 +506,6 @@ public static void PopulateCollection(ICollection source, ICollection t public static void PopulateCollection(IDictionary source, IDictionary target, bool update, bool skipDefaults) => PopulateCollectionCore(source, target, update, skipDefaults, kvp => kvp.Value); - private static bool ShouldWaitForSync(WaitForSyncMode mode, Subscription? oldSub, Subscription newSub) - { - switch (mode) - { - case WaitForSyncMode.Never: - return false; - case WaitForSyncMode.FirstTime: - // For FirstTimeSync mode we want to wait for sync only if we're adding a brand new sub - // or if the sub changed object type/query. - return oldSub == null || - oldSub.ObjectType != newSub.ObjectType || - oldSub.Query != newSub.Query; - case WaitForSyncMode.Always: - return true; - default: - throw new ArgumentOutOfRangeException(nameof(mode), mode, null); - } - } - private static void PopulateCollectionCore(ICollection? source, ICollection target, bool update, bool skipDefaults, Func valueGetter) { Argument.NotNull(target, nameof(target)); diff --git a/Realm/Realm/Extensions/TestingExtensions.cs b/Realm/Realm/Extensions/TestingExtensions.cs deleted file mode 100644 index 8e9917138a..0000000000 --- a/Realm/Realm/Extensions/TestingExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using Realms.Exceptions.Sync; -using Realms.Helpers; -using Realms.Sync.Exceptions; - -namespace Realms.Sync.Testing -{ - /// - /// A set of extension methods to be used in unit-testing scenarios. Should not be used in production. - /// - public static class TestingExtensions - { - /// - /// Simulates a session error. - /// - /// The session where the simulated error will occur. - /// Error code. - /// Error message. - /// If set to true the error will be marked as fatal. - /// - /// Use this method to test your error handling code without connecting to Atlas Device Sync. - /// Some error codes, such as will be ignored and will not be reported - /// to the callback. - /// - public static void SimulateError(this Session session, ErrorCode errorCode, string message, bool isFatal = false) - { - Argument.NotNull(session, nameof(session)); - Argument.NotNull(message, nameof(message)); - - session.ReportErrorForTesting((int)errorCode, message, isFatal, ServerRequestsAction.ApplicationBug); - } - } -} diff --git a/Realm/Realm/Handles/AppHandle.EmailPassword.cs b/Realm/Realm/Handles/AppHandle.EmailPassword.cs deleted file mode 100644 index cd23cbf375..0000000000 --- a/Realm/Realm/Handles/AppHandle.EmailPassword.cs +++ /dev/null @@ -1,211 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace Realms.Sync -{ - internal partial class AppHandle : StandaloneHandle - { - private static class EmailNativeMethods - { - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_register_user", CallingConvention = CallingConvention.Cdecl)] - public static extern void register_user(AppHandle app, - [MarshalAs(UnmanagedType.LPWStr)] string username, IntPtr username_len, - [MarshalAs(UnmanagedType.LPWStr)] string password, IntPtr password_len, - IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_confirm_user", CallingConvention = CallingConvention.Cdecl)] - public static extern void confirm_user(AppHandle app, - [MarshalAs(UnmanagedType.LPWStr)] string token, IntPtr token_len, - [MarshalAs(UnmanagedType.LPWStr)] string token_id, IntPtr token_id_len, - IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_resend_confirmation_email", CallingConvention = CallingConvention.Cdecl)] - public static extern void resend_confirmation_email(AppHandle app, - [MarshalAs(UnmanagedType.LPWStr)] string email, IntPtr email_len, - IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_retry_custom_confirmation", CallingConvention = CallingConvention.Cdecl)] - public static extern void retry_custom_comfirmation(AppHandle app, - [MarshalAs(UnmanagedType.LPWStr)] string email, IntPtr email_len, - IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_send_reset_password_email", CallingConvention = CallingConvention.Cdecl)] - public static extern void send_reset_password_email(AppHandle app, - [MarshalAs(UnmanagedType.LPWStr)] string email, IntPtr email_len, - IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_reset_password", CallingConvention = CallingConvention.Cdecl)] - public static extern void reset_password(AppHandle app, - [MarshalAs(UnmanagedType.LPWStr)] string password, IntPtr password_len, - [MarshalAs(UnmanagedType.LPWStr)] string token, IntPtr token_len, - [MarshalAs(UnmanagedType.LPWStr)] string token_id, IntPtr token_id_len, - IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_call_reset_password_function", CallingConvention = CallingConvention.Cdecl)] - public static extern void call_reset_password_function(AppHandle app, - [MarshalAs(UnmanagedType.LPWStr)] string username, IntPtr username_len, - [MarshalAs(UnmanagedType.LPWStr)] string password, IntPtr password_len, - [MarshalAs(UnmanagedType.LPWStr)] string function_args, IntPtr function_args_len, - IntPtr tcs_ptr, out NativeException ex); - } - - public readonly EmailPasswordApi EmailPassword; - - public class EmailPasswordApi - { - private readonly AppHandle _appHandle; - - public EmailPasswordApi(AppHandle handle) - { - _appHandle = handle; - } - - public async Task RegisterUserAsync(string username, string password) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - - try - { - EmailNativeMethods.register_user(_appHandle, username, (IntPtr)username.Length, password, (IntPtr)password.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public async Task ConfirmUserAsync(string token, string tokenId) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - - try - { - EmailNativeMethods.confirm_user(_appHandle, token, (IntPtr)token.Length, tokenId, (IntPtr)tokenId.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public async Task ResendConfirmationEmailAsync(string email) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - - try - { - EmailNativeMethods.resend_confirmation_email(_appHandle, email, (IntPtr)email.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public async Task RetryCustomConfirmationAsync(string email) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - - try - { - EmailNativeMethods.retry_custom_comfirmation(_appHandle, email, (IntPtr)email.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public async Task SendResetPasswordEmailAsync(string username) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - - try - { - EmailNativeMethods.send_reset_password_email(_appHandle, username, (IntPtr)username.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public async Task ResetPasswordAsync(string password, string token, string tokenId) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - - try - { - EmailNativeMethods.reset_password( - _appHandle, - password, (IntPtr)password.Length, - token, (IntPtr)token.Length, - tokenId, (IntPtr)tokenId.Length, - GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public async Task CallResetPasswordFunctionAsync(string username, string password, string functionArgs) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - - try - { - EmailNativeMethods.call_reset_password_function(_appHandle, - username, (IntPtr)username.Length, - password, (IntPtr)password.Length, - functionArgs, (IntPtr)functionArgs.Length, - GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - } - } -} diff --git a/Realm/Realm/Handles/AppHandle.cs b/Realm/Realm/Handles/AppHandle.cs deleted file mode 100644 index 2a4746cc3e..0000000000 --- a/Realm/Realm/Handles/AppHandle.cs +++ /dev/null @@ -1,479 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Realms.Native; -using Realms.PlatformHelpers; -using Realms.Sync.Exceptions; -using Realms.Sync.Native; - -namespace Realms.Sync -{ - internal partial class AppHandle : StandaloneHandle - { - private static readonly List _appHandles = new(); - - private static class NativeMethods - { - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void UserCallback(IntPtr tcs_ptr, IntPtr user_ptr, AppError error); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void VoidTaskCallback(IntPtr tcs_ptr, AppError error); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void StringCallback(IntPtr tcs_ptr, PrimitiveValue response, AppError error); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void ApiKeysCallback(IntPtr tcs_ptr, /* UserApiKey[] */ IntPtr api_keys, IntPtr api_keys_len, AppError error); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_initialize", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr initialize( - [MarshalAs(UnmanagedType.LPWStr)] string framework, IntPtr framework_len, - [MarshalAs(UnmanagedType.LPWStr)] string framework_version, IntPtr framework_version_len, - [MarshalAs(UnmanagedType.LPWStr)] string sdk_version, IntPtr sdk_version_len, - [MarshalAs(UnmanagedType.LPWStr)] string platform_version, IntPtr platform_version_len, - [MarshalAs(UnmanagedType.LPWStr)] string device_name, IntPtr device_name_len, - [MarshalAs(UnmanagedType.LPWStr)] string device_version, IntPtr device_version_len, - [MarshalAs(UnmanagedType.LPWStr)] string bundle_id, IntPtr bundle_id_len, - UserCallback user_callback, VoidTaskCallback void_callback, StringCallback string_callback, ApiKeysCallback api_keys_callback); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_create", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr create_app(Native.AppConfiguration app_config, byte[]? encryptionKey, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_destroy", CallingConvention = CallingConvention.Cdecl)] - public static extern void destroy(IntPtr syncUserHandle); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_sync_immediately_run_file_actions", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.U1)] - public static extern bool immediately_run_file_actions(AppHandle app, [MarshalAs(UnmanagedType.LPWStr)] string path, IntPtr path_len, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_sync_reconnect", CallingConvention = CallingConvention.Cdecl)] - public static extern void reconnect(AppHandle app); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_current_user", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_current_user(AppHandle app, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_logged_in_users", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_logged_in_users(AppHandle app, [Out] IntPtr[] users, IntPtr bufsize, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_switch_user", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr switch_user(AppHandle app, SyncUserHandle user, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_login_user", CallingConvention = CallingConvention.Cdecl)] - public static extern void login_user(AppHandle app, Native.Credentials credentials, IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_remove_user", CallingConvention = CallingConvention.Cdecl)] - public static extern void remove_user(AppHandle app, SyncUserHandle user, IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_delete_user", CallingConvention = CallingConvention.Cdecl)] - public static extern void delete_user(AppHandle app, SyncUserHandle user, IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_reset_for_testing", CallingConvention = CallingConvention.Cdecl)] - public static extern void reset_for_testing(AppHandle app); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_user_for_testing", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_user_for_testing( - AppHandle app, - [MarshalAs(UnmanagedType.LPWStr)] string id_buf, IntPtr id_len, - [MarshalAs(UnmanagedType.LPWStr)] string refresh_token_buf, IntPtr refresh_token_len, - [MarshalAs(UnmanagedType.LPWStr)] string access_token_buf, IntPtr access_token_len, - out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_set_fake_sync_route_for_testing", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr set_fake_sync_route_for_testing(AppHandle app, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_clear_cached_apps", CallingConvention = CallingConvention.Cdecl)] - public static extern void clear_cached_apps(out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_base_file_path", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_base_file_path(AppHandle app, IntPtr buffer, IntPtr buffer_length, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_base_uri", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_base_uri(AppHandle app, IntPtr buffer, IntPtr buffer_length, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_id", CallingConvention = CallingConvention.Cdecl)] - public static extern StringValue get_id(AppHandle app, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_is_same_instance", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.U1)] - public static extern bool is_same_instance(AppHandle lhs, AppHandle rhs, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_default_url", CallingConvention = CallingConvention.Cdecl)] - public static extern StringValue get_default_url(out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_update_base_url", CallingConvention = CallingConvention.Cdecl)] - public static extern void update_base_uri(AppHandle appHandle, - [MarshalAs(UnmanagedType.LPWStr)] string url_buf, IntPtr url_len, - IntPtr tcs_ptr, - out NativeException ex); - } - - static AppHandle() - { - NativeCommon.Initialize(); - } - - public static Uri DefaultBaseUri - { - get - { - var value = NativeMethods.get_default_url(out var ex); - ex.ThrowIfNecessary(); - return new(value!); - } - } - - public static void Initialize() - { - NativeMethods.UserCallback userLogin = HandleUserCallback; - NativeMethods.VoidTaskCallback taskCallback = HandleTaskCompletion; - NativeMethods.StringCallback stringCallback = HandleStringCallback; - NativeMethods.ApiKeysCallback apiKeysCallback = HandleApiKeysCallback; - - GCHandle.Alloc(userLogin); - GCHandle.Alloc(taskCallback); - GCHandle.Alloc(stringCallback); - GCHandle.Alloc(apiKeysCallback); - - var frameworkName = InteropConfig.FrameworkName; - var frameworkVersion = Environment.Version.ToString(); - - // TODO: https://github.com/realm/realm-dotnet/issues/2218 this doesn't handle prerelease versions. - var sdkVersion = InteropConfig.SDKVersion.ToString(3); - - var platformVersion = Environment.OSVersion.Version.ToString(); - - if (!string.IsNullOrEmpty(Environment.OSVersion.ServicePack)) - { - platformVersion += $" {Environment.OSVersion.ServicePack}"; - } - - string deviceName; - string deviceVersion; - string bundleId; - try - { - deviceName = Platform.DeviceInfo.Name; - deviceVersion = Platform.DeviceInfo.Version; - bundleId = Platform.BundleId; - } - catch - { - // If we can't get the device info, don't crash the app. - deviceName = Platform.Unknown; - deviceVersion = Platform.Unknown; - bundleId = Platform.Unknown; - -#if DEBUG - throw; -#endif - } - - NativeMethods.initialize( - frameworkName, frameworkName.IntPtrLength(), - frameworkVersion, frameworkVersion.IntPtrLength(), - sdkVersion, sdkVersion.IntPtrLength(), - platformVersion, platformVersion.IntPtrLength(), - deviceName, deviceName.IntPtrLength(), - deviceVersion, deviceVersion.IntPtrLength(), - bundleId, bundleId.IntPtrLength(), - userLogin, taskCallback, stringCallback, apiKeysCallback); - } - - internal AppHandle(IntPtr handle) : base(handle) - { - EmailPassword = new EmailPasswordApi(this); - - lock (_appHandles) - { - _appHandles.RemoveAll(a => !a.IsAlive); - _appHandles.Add(new WeakReference(this)); - } - } - - public static AppHandle CreateApp(Native.AppConfiguration config, byte[]? encryptionKey) - { - var handle = NativeMethods.create_app(config, encryptionKey, out var ex); - ex.ThrowIfNecessary(); - return new AppHandle(handle); - } - - public static void ForceCloseHandles(bool clearNativeCache = false) - { - lock (_appHandles) - { - foreach (var weakHandle in _appHandles) - { - var appHandle = (AppHandle?)weakHandle.Target; - appHandle?.Close(); - } - - _appHandles.Clear(); - } - - if (clearNativeCache) - { - NativeMethods.clear_cached_apps(out var ex); - ex.ThrowIfNecessary(); - } - } - - public bool ImmediatelyRunFileActions(string path) - { - var result = NativeMethods.immediately_run_file_actions(this, path, (IntPtr)path.Length, out var ex); - ex.ThrowIfNecessary(); - - return result; - } - - public void Reconnect() - { - NativeMethods.reconnect(this); - } - - public bool TryGetCurrentUser([MaybeNullWhen(false)] out SyncUserHandle userHandle) - { - var userPtr = NativeMethods.get_current_user(this, out var ex); - ex.ThrowIfNecessary(); - - if (userPtr == IntPtr.Zero) - { - userHandle = null; - return false; - } - - userHandle = new SyncUserHandle(userPtr); - return true; - } - - public IEnumerable GetAllLoggedInUsers() - { - return MarshalHelpers.GetCollection((IntPtr[] buf, IntPtr len, out NativeException ex) => NativeMethods.get_logged_in_users(this, buf, len, out ex), bufferSize: 8) - .Select(h => new SyncUserHandle(h)); - } - - public void SwitchUser(SyncUserHandle user) - { - NativeMethods.switch_user(this, user, out var ex); - ex.ThrowIfNecessary(); - } - - public async Task LogInAsync(Native.Credentials credentials) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - - try - { - NativeMethods.login_user(this, credentials, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - - return await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public async Task RemoveAsync(SyncUserHandle user) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - try - { - NativeMethods.remove_user(this, user, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public async Task DeleteUserAsync(SyncUserHandle user) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - try - { - NativeMethods.delete_user(this, user, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public string GetBaseFilePath() - { - return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => - { - isNull = false; - return NativeMethods.get_base_file_path(this, buffer, length, out ex); - })!; - } - - public Uri GetBaseUri() - { - var uriString = MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => - { - isNull = false; - return NativeMethods.get_base_uri(this, buffer, length, out ex); - })!; - - return new Uri(uriString); - } - - public async Task UpdateBaseUriAsync(Uri? newUri) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - try - { - var url = newUri?.ToString().TrimEnd('/') ?? string.Empty; - NativeMethods.update_base_uri(this, url, (IntPtr)url.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public string GetId() - { - var value = NativeMethods.get_id(this, out var ex); - ex.ThrowIfNecessary(); - return value!; - } - - public bool IsSameInstance(AppHandle other) - { - var result = NativeMethods.is_same_instance(this, other, out var ex); - ex.ThrowIfNecessary(); - return result; - } - - protected override void Unbind() => NativeMethods.destroy(handle); - - #region Testing - public void ResetForTesting() - { - NativeMethods.reset_for_testing(this); - } - - public SyncUserHandle GetUserForTesting(string id, string refreshToken, string accessToken) - { - var result = NativeMethods.get_user_for_testing( - this, - id, (IntPtr)id.Length, - refreshToken, (IntPtr)refreshToken.Length, - accessToken, (IntPtr)accessToken.Length, - out var ex); - ex.ThrowIfNecessary(); - return new SyncUserHandle(result); - } - - public void SetFakeSyncRouteForTesting() - { - NativeMethods.set_fake_sync_route_for_testing(this, out var ex); - ex.ThrowIfNecessary(); - } - - #endregion - - [MonoPInvokeCallback(typeof(NativeMethods.UserCallback))] - private static void HandleUserCallback(IntPtr tcs_ptr, IntPtr user_ptr, AppError error) - { - var tcsHandle = GCHandle.FromIntPtr(tcs_ptr); - var tcs = (TaskCompletionSource)tcsHandle.Target!; - if (error.is_null) - { - var userHandle = new SyncUserHandle(user_ptr); - tcs.TrySetResult(userHandle); - } - else - { - tcs.TrySetException(new AppException(error)); - } - } - - [MonoPInvokeCallback(typeof(NativeMethods.VoidTaskCallback))] - private static void HandleTaskCompletion(IntPtr tcs_ptr, AppError error) - { - var tcsHandle = GCHandle.FromIntPtr(tcs_ptr); - var tcs = (TaskCompletionSource)tcsHandle.Target!; - if (error.is_null) - { - tcs.TrySetResult(); - } - else - { - tcs.TrySetException(new AppException(error)); - } - } - - [MonoPInvokeCallback(typeof(NativeMethods.StringCallback))] - private static void HandleStringCallback(IntPtr tcs_ptr, PrimitiveValue response, AppError error) - { - var tcsHandle = GCHandle.FromIntPtr(tcs_ptr); - var tcs = (TaskCompletionSource)tcsHandle.Target!; - if (error.is_null) - { - tcs.TrySetResult(response.AsString()); - } - else - { - tcs.TrySetException(new AppException(error)); - } - } - - [MonoPInvokeCallback(typeof(NativeMethods.ApiKeysCallback))] - private static void HandleApiKeysCallback(IntPtr tcs_ptr, IntPtr api_keys, IntPtr api_keys_len, AppError error) - { - var tcsHandle = GCHandle.FromIntPtr(tcs_ptr); - var tcs = (TaskCompletionSource)tcsHandle.Target!; - if (error.is_null) - { - var result = new ApiKey[api_keys_len.ToInt32()]; - for (var i = 0; i < api_keys_len.ToInt32(); i++) - { - var nativeKey = Marshal.PtrToStructure(IntPtr.Add(api_keys, i * UserApiKey.Size)); - result[i] = new ApiKey(nativeKey); - } - - tcs.TrySetResult(result); - } - else - { - tcs.TrySetException(new AppException(error)); - } - } - } -} diff --git a/Realm/Realm/Handles/AsyncOpenTaskHandle.cs b/Realm/Realm/Handles/AsyncOpenTaskHandle.cs deleted file mode 100644 index 876c9b6dcb..0000000000 --- a/Realm/Realm/Handles/AsyncOpenTaskHandle.cs +++ /dev/null @@ -1,93 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Concurrent; -using System.Runtime.InteropServices; - -namespace Realms -{ - internal class AsyncOpenTaskHandle : StandaloneHandle - { - private static ConcurrentDictionary _handles = new(); - - private static class NativeMethods - { - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_asyncopentask_destroy", CallingConvention = CallingConvention.Cdecl)] - public static extern void destroy(IntPtr asyncTaskHandle); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_asyncopentask_cancel", CallingConvention = CallingConvention.Cdecl)] - public static extern void cancel(AsyncOpenTaskHandle handle, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_asyncopentask_register_progress_notifier", CallingConvention = CallingConvention.Cdecl)] - public static extern ulong register_progress_notifier(AsyncOpenTaskHandle handle, IntPtr token_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_asyncopentask_unregister_progress_notifier", CallingConvention = CallingConvention.Cdecl)] - public static extern void unregister_progress_notifier(AsyncOpenTaskHandle handle, ulong token, out NativeException ex); - } - - public AsyncOpenTaskHandle(IntPtr handle) : base(handle) - { - _handles.TryAdd(this, true); - } - - public void Cancel() - { - NativeMethods.cancel(this, out var ex); - ex.ThrowIfNecessary(); - } - - protected override void Unbind() - { - _handles.TryRemove(this, out _); - - NativeMethods.destroy(handle); - } - - /// - /// Cancels all in-flight async open tasks. This should only be used when the domain is being torn down. - /// The case this handles is: - /// 1. GetInstanceAsync. - /// 2. Domain Reload wipes all coordinator caches. - /// 3. AsyncOpen completes, calls back into managed (because s_can_call_managed is true again). - /// 4. Undefined behavior as the state from before the domain reload is no longer valid. - /// - /// This fixes the issue reported in https://github.com/realm/realm-dotnet/issues/3344. - public static void CancelInFlightTasks() - { - var keys = _handles.Keys; - foreach (var value in keys) - { - value.Cancel(); - } - } - - public ulong RegisterProgressNotifier(GCHandle managedHandle) - { - var token = NativeMethods.register_progress_notifier(this, GCHandle.ToIntPtr(managedHandle), out var ex); - ex.ThrowIfNecessary(); - return token; - } - - public void UnregisterProgressNotifier(ulong token) - { - NativeMethods.unregister_progress_notifier(this, token, out var ex); - ex.ThrowIfNecessary(); - } - } -} diff --git a/Realm/Realm/Handles/SessionHandle.cs b/Realm/Realm/Handles/SessionHandle.cs deleted file mode 100644 index 060f5daf9a..0000000000 --- a/Realm/Realm/Handles/SessionHandle.cs +++ /dev/null @@ -1,472 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Diagnostics; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using Realms.Exceptions; -using Realms.Exceptions.Sync; -using Realms.Logging; -using Realms.Native; -using Realms.Sync.ErrorHandling; -using Realms.Sync.Exceptions; -using Realms.Sync.Native; - -namespace Realms.Sync -{ - internal class SessionHandle : RealmHandle - { - private static class NativeMethods - { - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void SessionErrorCallback(IntPtr session_handle_ptr, - SyncError error, - IntPtr managed_sync_config_handle); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void SessionProgressCallback(IntPtr progress_token_ptr, double progressEstimate); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void SessionWaitCallback(IntPtr task_completion_source, int error_code, PrimitiveValue message); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void SessionPropertyChangedCallback(IntPtr managed_session, NotifiableProperty property); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate IntPtr NotifyBeforeClientReset(IntPtr before_frozen, IntPtr managed_sync_config_handle); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate IntPtr NotifyAfterClientReset(IntPtr before_frozen, IntPtr after, IntPtr managed_sync_config_handle, bool did_recover); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_install_callbacks", CallingConvention = CallingConvention.Cdecl)] - public static extern void install_syncsession_callbacks(SessionErrorCallback error_callback, - SessionProgressCallback progress_callback, - SessionWaitCallback wait_callback, - SessionPropertyChangedCallback property_changed_callback, - NotifyBeforeClientReset notify_before, - NotifyAfterClientReset notify_after); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_get_user", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_user(SessionHandle session); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_get_state", CallingConvention = CallingConvention.Cdecl)] - public static extern SessionState get_state(SessionHandle session, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_get_connection_state", CallingConvention = CallingConvention.Cdecl)] - public static extern ConnectionState get_connection_state(SessionHandle session, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_get_path", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_path(SessionHandle session, IntPtr buffer, IntPtr buffer_length, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_get_raw_pointer", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_raw_pointer(SessionHandle session); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_destroy", CallingConvention = CallingConvention.Cdecl)] - public static extern void destroy(IntPtr handle); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_register_progress_notifier", CallingConvention = CallingConvention.Cdecl)] - public static extern ulong register_progress_notifier(SessionHandle session, - IntPtr token_ptr, - ProgressDirection direction, - [MarshalAs(UnmanagedType.U1)] bool is_streaming, - out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_unregister_progress_notifier", CallingConvention = CallingConvention.Cdecl)] - public static extern void unregister_progress_notifier(SessionHandle session, ulong token, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_register_property_changed_callback", CallingConvention = CallingConvention.Cdecl)] - public static extern SessionNotificationToken register_property_changed_callback(SessionHandle session, IntPtr managed_session_handle, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_unregister_property_changed_callback", CallingConvention = CallingConvention.Cdecl)] - public static extern void unregister_property_changed_callback(IntPtr session, SessionNotificationToken token, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_wait", CallingConvention = CallingConvention.Cdecl)] - public static extern void wait(SessionHandle session, IntPtr task_completion_source, ProgressDirection direction, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_report_error_for_testing", CallingConvention = CallingConvention.Cdecl)] - public static extern void report_error_for_testing(SessionHandle session, int error_code, [MarshalAs(UnmanagedType.LPWStr)] string message, IntPtr message_len, [MarshalAs(UnmanagedType.U1)] bool is_fatal, int action); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_stop", CallingConvention = CallingConvention.Cdecl)] - public static extern void stop(SessionHandle session, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_shutdown_and_wait", CallingConvention = CallingConvention.Cdecl)] - public static extern void shutdown_and_wait(SessionHandle session, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_start", CallingConvention = CallingConvention.Cdecl)] - public static extern void start(SessionHandle session, out NativeException ex); - } - - private SessionNotificationToken? _notificationToken; - - public override bool ForceRootOwnership => true; - - [Preserve] - public SessionHandle(SharedRealmHandle? root, IntPtr handle) : base(root, handle) - { - } - - public static void Initialize() - { - NativeMethods.SessionErrorCallback error = HandleSessionError; - NativeMethods.SessionProgressCallback progress = HandleSessionProgress; - NativeMethods.SessionWaitCallback wait = HandleSessionWaitCallback; - NativeMethods.SessionPropertyChangedCallback propertyChanged = HandleSessionPropertyChangedCallback; - NativeMethods.NotifyBeforeClientReset beforeReset = NotifyBeforeClientReset; - NativeMethods.NotifyAfterClientReset afterReset = NotifyAfterClientReset; - - GCHandle.Alloc(error); - GCHandle.Alloc(progress); - GCHandle.Alloc(wait); - GCHandle.Alloc(propertyChanged); - GCHandle.Alloc(beforeReset); - GCHandle.Alloc(afterReset); - - NativeMethods.install_syncsession_callbacks(error, progress, wait, propertyChanged, beforeReset, afterReset); - } - - public SyncUserHandle GetUser() - { - var ptr = NativeMethods.get_user(this); - if (ptr == IntPtr.Zero) - { - throw new RealmException("Unable to obtain user for session. This likely means the session is being torn down."); - } - - return new(ptr); - } - - public SessionState GetState() - { - var state = NativeMethods.get_state(this, out var ex); - ex.ThrowIfNecessary(); - return state; - } - - public ConnectionState GetConnectionState() - { - var connectionState = NativeMethods.get_connection_state(this, out var ex); - ex.ThrowIfNecessary(); - return connectionState; - } - - public string GetPath() - { - return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => - { - isNull = false; - return NativeMethods.get_path(this, buffer, length, out ex); - })!; - } - - public ulong RegisterProgressNotifier(GCHandle managedHandle, ProgressDirection direction, ProgressMode mode) - { - var isStreaming = mode == ProgressMode.ReportIndefinitely; - var token = NativeMethods.register_progress_notifier(this, GCHandle.ToIntPtr(managedHandle), direction, isStreaming, out var ex); - ex.ThrowIfNecessary(); - return token; - } - - public void UnregisterProgressNotifier(ulong token) - { - NativeMethods.unregister_progress_notifier(this, token, out var ex); - ex.ThrowIfNecessary(); - } - - public void SubscribeNotifications(Session session) - { - Debug.Assert(!_notificationToken.HasValue, $"{nameof(_notificationToken)} must be null before subscribing."); - - var managedSessionHandle = GCHandle.Alloc(session, GCHandleType.Weak); - var sessionPointer = GCHandle.ToIntPtr(managedSessionHandle); - _notificationToken = NativeMethods.register_property_changed_callback(this, sessionPointer, out var ex); - ex.ThrowIfNecessary(); - } - - public void UnsubscribeNotifications() - { - if (_notificationToken.HasValue) - { - // This needs to use the handle directly because it's being called in Unbind. At this point the SafeHandle is closed, which means we'll - // get an error if we attempted to marshal it to native. The raw pointer is fine though and we can use it. - NativeMethods.unregister_property_changed_callback(handle, _notificationToken.Value, out var ex); - _notificationToken = null; - ex.ThrowIfNecessary(); - } - } - - public Task WaitAsync(ProgressDirection direction, CancellationToken? cancellationToken) - { - var tcs = new TaskCompletionSource(); - if (cancellationToken?.IsCancellationRequested == true) - { - tcs.TrySetCanceled(cancellationToken.Value); - return tcs.Task; - } - - // The tcsHandles is freed in HandleSessionWaitCallback. It's important that we don't free it on cancellation - // as the cancellation doesn't really cancel the native wait operation. That will eventually complete and it needs - // to have the GCHandle at this point, otherwise we'll get a hard crash on Mono. - var tcsHandle = GCHandle.Alloc(tcs); - - cancellationToken?.Register(() => tcs.TrySetCanceled(cancellationToken.Value)); - - try - { - NativeMethods.wait(this, GCHandle.ToIntPtr(tcsHandle), direction, out var ex); - ex.ThrowIfNecessary(); - } - catch - { - // If we failed to register a waiter, we can free the handle as we won't get a native callback here anyway - tcsHandle.Free(); - throw; - } - - return tcs.Task; - } - - public IntPtr GetRawPointer() - { - return NativeMethods.get_raw_pointer(this); - } - - public void ReportErrorForTesting(int errorCode, string errorMessage, bool isFatal, ServerRequestsAction action) - { - NativeMethods.report_error_for_testing(this, errorCode, errorMessage, (IntPtr)errorMessage.Length, isFatal, (int)action); - } - - public void Stop() - { - NativeMethods.stop(this, out var ex); - ex.ThrowIfNecessary(); - } - - public void Start() - { - NativeMethods.start(this, out var ex); - ex.ThrowIfNecessary(); - } - - /// - /// Terminates the sync session and releases the Realm file it was using. - /// - public void ShutdownAndWait() - { - NativeMethods.shutdown_and_wait(this, out var ex); - ex.ThrowIfNecessary(); - } - - public override void Unbind() - { - UnsubscribeNotifications(); - NativeMethods.destroy(handle); - } - - [MonoPInvokeCallback(typeof(NativeMethods.SessionErrorCallback))] - private static void HandleSessionError(IntPtr sessionHandlePtr, SyncError error, IntPtr managedSyncConfigurationBaseHandle) - { - try - { - // Filter out end of input, which the client seems to have started reporting - if (error.error_code == (ErrorCode)1) - { - return; - } - - using var handle = new SessionHandle(null, sessionHandlePtr); - var session = new Session(handle); - string messageString = error.message!; - var syncConfigHandle = GCHandle.FromIntPtr(managedSyncConfigurationBaseHandle); - var syncConfig = (SyncConfigurationBase)syncConfigHandle.Target!; - - if (error.is_client_reset) - { - var userInfo = error.user_info_pairs.ToEnumerable().ToDictionary(kvp => (string)kvp.Key!, kvp => (string?)kvp.Value); - var clientResetEx = new ClientResetException(session.User.App, messageString, error.error_code, userInfo); - - syncConfig.ClientResetHandler.ManualClientReset?.Invoke(clientResetEx); - return; - } - - SessionException exception; - if (error.error_code == ErrorCode.CompensatingWrite) - { - var compensatingWrites = error.compensating_writes - .ToEnumerable() - .Select(c => new CompensatingWriteInfo(c.object_name!, c.reason!, new RealmValue(c.primary_key))) - .ToArray(); - exception = new CompensatingWriteException(messageString, compensatingWrites); - } - else - { - exception = new SessionException(messageString, error.error_code); - } - - exception.HelpLink = error.log_url; - syncConfig.OnSessionError?.Invoke(session, exception); - } - catch (Exception ex) - { - RealmLogger.Default.Log(LogLevel.Warn, $"An error has occurred while handling a session error: {ex}"); - } - } - - [MonoPInvokeCallback(typeof(NativeMethods.NotifyBeforeClientReset))] - private static IntPtr NotifyBeforeClientReset(IntPtr beforeFrozen, IntPtr managedSyncConfigurationHandle) - { - SyncConfigurationBase? syncConfig = null; - - try - { - var syncConfigHandle = GCHandle.FromIntPtr(managedSyncConfigurationHandle); - syncConfig = (SyncConfigurationBase)syncConfigHandle.Target!; - - var cb = syncConfig.ClientResetHandler switch - { - DiscardUnsyncedChangesHandler handler => handler.OnBeforeReset, - RecoverUnsyncedChangesHandler handler => handler.OnBeforeReset, - RecoverOrDiscardUnsyncedChangesHandler handler => handler.OnBeforeReset, - _ => throw new NotSupportedException($"ClientResetHandlerBase of type {syncConfig.ClientResetHandler.GetType()} is not handled yet") - }; - - if (cb != null) - { - var schema = syncConfig.Schema; - using var realmBefore = new Realm(new UnownedRealmHandle(beforeFrozen), syncConfig, schema); - cb.Invoke(realmBefore); - } - - return IntPtr.Zero; - } - catch (Exception ex) - { - var handlerType = syncConfig is null ? "ClientResetHandler" : syncConfig.ClientResetHandler.GetType().Name; - RealmLogger.Default.Log(LogLevel.Error, $"An error has occurred while executing {handlerType}.OnBeforeReset during a client reset: {ex}"); - - var exHandle = GCHandle.Alloc(ex); - return GCHandle.ToIntPtr(exHandle); - } - } - - [MonoPInvokeCallback(typeof(NativeMethods.NotifyAfterClientReset))] - private static IntPtr NotifyAfterClientReset(IntPtr beforeFrozen, IntPtr after, IntPtr managedSyncConfigurationHandle, bool didRecover) - { - SyncConfigurationBase? syncConfig = null; - - try - { - var syncConfigHandle = GCHandle.FromIntPtr(managedSyncConfigurationHandle); - syncConfig = (SyncConfigurationBase)syncConfigHandle.Target!; - - var cb = syncConfig.ClientResetHandler switch - { - DiscardUnsyncedChangesHandler handler => handler.OnAfterReset, - RecoverUnsyncedChangesHandler handler => handler.OnAfterReset, - RecoverOrDiscardUnsyncedChangesHandler handler => didRecover ? handler.OnAfterRecovery : handler.OnAfterDiscard, - _ => throw new NotSupportedException($"ClientResetHandlerBase of type {syncConfig.ClientResetHandler.GetType()} is not handled yet") - }; - - if (cb != null) - { - var schema = syncConfig.Schema; - using var realmBefore = new Realm(new UnownedRealmHandle(beforeFrozen), syncConfig, schema); - using var realmAfter = new Realm(new UnownedRealmHandle(after), syncConfig, schema); - cb.Invoke(realmBefore, realmAfter); - } - - return IntPtr.Zero; - } - catch (Exception ex) - { - var handlerType = syncConfig is null ? "ClientResetHandler" : syncConfig.ClientResetHandler.GetType().Name; - RealmLogger.Default.Log(LogLevel.Error, $"An error has occurred while executing {handlerType}.OnAfterReset during a client reset: {ex}"); - - var exHandle = GCHandle.Alloc(ex); - return GCHandle.ToIntPtr(exHandle); - } - } - - [MonoPInvokeCallback(typeof(NativeMethods.SessionProgressCallback))] - private static void HandleSessionProgress(IntPtr tokenPtr, double progressEstimate) - { - var token = (ProgressNotificationToken?)GCHandle.FromIntPtr(tokenPtr).Target; - token?.Notify(progressEstimate); - } - - [MonoPInvokeCallback(typeof(NativeMethods.SessionWaitCallback))] - private static void HandleSessionWaitCallback(IntPtr taskCompletionSource, int error_code, PrimitiveValue message) - { - var handle = GCHandle.FromIntPtr(taskCompletionSource); - var tcs = (TaskCompletionSource)handle.Target!; - - if (error_code == 0) - { - tcs.TrySetResult(); - } - else - { - var inner = new SessionException(message.AsString(), (ErrorCode)error_code); - const string OuterMessage = "A system error occurred while waiting for completion. See InnerException for more details"; - tcs.TrySetException(new RealmException(OuterMessage, inner)); - } - - handle.Free(); - } - - [MonoPInvokeCallback(typeof(NativeMethods.SessionPropertyChangedCallback))] - private static void HandleSessionPropertyChangedCallback(IntPtr managedSessionHandle, NotifiableProperty property) - { - try - { - if (managedSessionHandle == IntPtr.Zero) - { - return; - } - - var propertyName = property switch - { - NotifiableProperty.ConnectionState => nameof(Session.ConnectionState), - _ => throw new NotSupportedException($"Unexpected notifiable property value: {property}") - }; - var session = (Session?)GCHandle.FromIntPtr(managedSessionHandle).Target; - if (session is null) - { - // We're taking a weak handle to the session, so it's possible that it's been collected - return; - } - - ThreadPool.QueueUserWorkItem(_ => - { - session.RaisePropertyChanged(propertyName); - }); - } - catch (Exception ex) - { - RealmLogger.Default.Log(LogLevel.Error, $"An error has occurred while raising a property changed event: {ex}"); - } - } - - private enum NotifiableProperty : byte - { - ConnectionState = 0 - } - } -} diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index 17962d92b2..16f8e043b7 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -27,7 +27,6 @@ using Realms.Logging; using Realms.Native; using Realms.Schema; -using Realms.Sync; namespace Realms { @@ -95,15 +94,6 @@ public struct CategoryNamesContainer [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_open", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr open(Configuration configuration, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_open_with_sync", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr open_with_sync(Configuration configuration, Sync.Native.SyncConfiguration sync_configuration, - out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_open_with_sync_async", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr open_with_sync_async(Configuration configuration, Sync.Native.SyncConfiguration sync_configuration, - IntPtr task_completion_source, - out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_set_managed_state_handle", CallingConvention = CallingConvention.Cdecl)] public static extern void set_managed_state_handle(SharedRealmHandle sharedRealm, IntPtr managedStateHandle, out NativeException ex); @@ -169,7 +159,7 @@ public static extern IntPtr open_with_sync_async(Configuration configuration, Sy public static extern IntPtr resolve_realm_reference(ThreadSafeReferenceHandle referenceHandle, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_write_copy", CallingConvention = CallingConvention.Cdecl)] - public static extern void write_copy(SharedRealmHandle sharedRealm, Configuration configuration, NativeBool useSync, out NativeException ex); + public static extern void write_copy(SharedRealmHandle sharedRealm, Configuration configuration, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_create_object", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr create_object(SharedRealmHandle sharedRealm, UInt32 table_key, out NativeException ex); @@ -223,15 +213,6 @@ public static extern void rename_property(SharedRealmHandle sharedRealm, [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_remove_all", CallingConvention = CallingConvention.Cdecl)] public static extern bool remove_all(SharedRealmHandle sharedRealm, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_sync_session", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_session(SharedRealmHandle realm, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_subscriptions", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_subscriptions(SharedRealmHandle realm, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_subscriptions_version", CallingConvention = CallingConvention.Cdecl)] - public static extern Int64 get_subscriptions_version(SharedRealmHandle realm, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_refresh_async", CallingConvention = CallingConvention.Cdecl)] public static extern bool refresh_async(SharedRealmHandle realm, IntPtr tcs_handle, out NativeException ex); @@ -423,21 +404,6 @@ public static SharedRealmHandle Open(Configuration configuration) return new SharedRealmHandle(result); } - public static SharedRealmHandle OpenWithSync(Configuration configuration, Sync.Native.SyncConfiguration syncConfiguration) - { - var result = NativeMethods.open_with_sync(configuration, syncConfiguration, out var nativeException); - nativeException.ThrowIfNecessary(); - - return new SharedRealmHandle(result); - } - - public static AsyncOpenTaskHandle OpenWithSyncAsync(Configuration configuration, Sync.Native.SyncConfiguration syncConfiguration, IntPtr tcsHandle) - { - var asyncTaskPtr = NativeMethods.open_with_sync_async(configuration, syncConfiguration, tcsHandle, out var nativeException); - nativeException.ThrowIfNecessary(); - return new AsyncOpenTaskHandle(asyncTaskPtr); - } - public static SharedRealmHandle ResolveFromReference(ThreadSafeReferenceHandle referenceHandle) { var result = NativeMethods.resolve_realm_reference(referenceHandle, out var nativeException); @@ -622,12 +588,11 @@ public IntPtr ResolveReference(ThreadSafeReference reference) public void WriteCopy(RealmConfigurationBase config) { - var useSync = config is SyncConfigurationBase; - using var arena = new Arena(); var nativeConfig = config.CreateNativeConfiguration(arena); - NativeMethods.write_copy(this, nativeConfig, useSync, out var nativeException); + // TODO: community: remote useSync + NativeMethods.write_copy(this, nativeConfig, out var nativeException); nativeException.ThrowIfNecessary(); } @@ -750,27 +715,6 @@ public ResultsHandle CreateResults(TableKey tableKey) return new ResultsHandle(this, result); } - public SessionHandle GetSession() - { - var ptr = NativeMethods.get_session(this, out var ex); - ex.ThrowIfNecessary(); - return new SessionHandle(this, ptr); - } - - public SubscriptionSetHandle GetSubscriptions() - { - var ptr = NativeMethods.get_subscriptions(this, out var ex); - ex.ThrowIfNecessary(); - return new SubscriptionSetHandle(this, ptr); - } - - public long GetSubscriptionsVersion() - { - var result = NativeMethods.get_subscriptions_version(this, out var ex); - ex.ThrowIfNecessary(); - return result; - } - public async Task RefreshAsync() { var tcs = new TaskCompletionSource(); diff --git a/Realm/Realm/Handles/SubscriptionSetHandle.cs b/Realm/Realm/Handles/SubscriptionSetHandle.cs deleted file mode 100644 index f44aa5d5a7..0000000000 --- a/Realm/Realm/Handles/SubscriptionSetHandle.cs +++ /dev/null @@ -1,357 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using MongoDB.Bson; -using Realms.Native; -using Realms.Sync.Exceptions; - -namespace Realms.Sync -{ - internal class SubscriptionSetHandle : RealmHandle - { -#pragma warning disable IDE0049 // Use built-in type alias -#pragma warning disable SA1121 // Use built-in type alias - - private static class NativeMethods - { - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void StateWaitCallback(IntPtr task_completion_source, SubscriptionSetState new_state, StringValue message, ErrorCode error_code); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void GetSubscriptionCallback(IntPtr managed_callback, Native.Subscription subscription); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_install_callbacks", CallingConvention = CallingConvention.Cdecl)] - public static extern void install_callbacks( - GetSubscriptionCallback get_subscription_callback, - StateWaitCallback state_wait_callback); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_get_count", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_count(SubscriptionSetHandle handle, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_get_version", CallingConvention = CallingConvention.Cdecl)] - public static extern Int64 get_version(SubscriptionSetHandle handle, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_get_state", CallingConvention = CallingConvention.Cdecl)] - public static extern SubscriptionSetState get_state(SubscriptionSetHandle handle, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_get_at_index", CallingConvention = CallingConvention.Cdecl)] - public static extern void get_at_index(SubscriptionSetHandle handle, IntPtr index, IntPtr callback, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_find_by_name", CallingConvention = CallingConvention.Cdecl)] - public static extern void find_by_name(SubscriptionSetHandle handle, [MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr name_len, IntPtr callback, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_find_by_query", CallingConvention = CallingConvention.Cdecl)] - public static extern void find_by_query(SubscriptionSetHandle handle, ResultsHandle results, IntPtr callback, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_add_results", CallingConvention = CallingConvention.Cdecl)] - public static extern void add(SubscriptionSetHandle handle, ResultsHandle results, - [MarshalAs(UnmanagedType.LPWStr)] string? name, IntPtr name_len, - [MarshalAs(UnmanagedType.I1)] bool update_existing, IntPtr callback, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_remove", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool remove(SubscriptionSetHandle handle, [MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr name_len, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_remove_by_id", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool remove(SubscriptionSetHandle handle, PrimitiveValue id, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_remove_by_query", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr remove(SubscriptionSetHandle handle, ResultsHandle results, [MarshalAs(UnmanagedType.I1)] bool remove_named, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_remove_by_type", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr remove_by_type(SubscriptionSetHandle handle, [MarshalAs(UnmanagedType.LPWStr)] string type, IntPtr type_len, [MarshalAs(UnmanagedType.I1)] bool remove_named, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_remove_all", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr remove_all(SubscriptionSetHandle handle, [MarshalAs(UnmanagedType.I1)] bool remove_named, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_destroy", CallingConvention = CallingConvention.Cdecl)] - public static extern void destroy(IntPtr handle); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_destroy_mutable", CallingConvention = CallingConvention.Cdecl)] - public static extern void destroy_mutable(IntPtr handle); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_wait_for_state", CallingConvention = CallingConvention.Cdecl)] - public static extern void wait_for_state(SubscriptionSetHandle handle, IntPtr task_completion_source, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_begin_write", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr begin_write(SubscriptionSetHandle handle, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_commit_write", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr commit_write(SubscriptionSetHandle handle, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_get_error", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_error_message(SubscriptionSetHandle handle, IntPtr buffer, IntPtr buffer_length, [MarshalAs(UnmanagedType.U1)] out bool isNull, out NativeException ex); - } - -#pragma warning restore IDE0049 // Use built-in type alias -#pragma warning restore SA1121 // Use built-in type alias - - private delegate void GetSubscriptionBase(IntPtr callback, out NativeException ex); - - public override bool ForceRootOwnership => true; - - public static void Initialize() - { - NativeMethods.GetSubscriptionCallback getSubscription = OnGetSubscription; - NativeMethods.StateWaitCallback waitState = HandleStateWaitCallback; - - GCHandle.Alloc(getSubscription); - GCHandle.Alloc(waitState); - - NativeMethods.install_callbacks(getSubscription, waitState); - } - - public bool IsReadonly { get; } - - public SubscriptionSetHandle(SharedRealmHandle root, IntPtr handle, bool isReadonly = true) : base(root, handle) - { - IsReadonly = isReadonly; - } - - public int GetCount() - { - EnsureIsOpen(); - - var result = NativeMethods.get_count(this, out var ex); - ex.ThrowIfNecessary(); - return (int)result; - } - - public SubscriptionSetState GetState() - { - EnsureIsOpen(); - - var state = NativeMethods.get_state(this, out var ex); - ex.ThrowIfNecessary(); - return state; - } - - public long GetVersion() - { - EnsureIsOpen(); - - var result = NativeMethods.get_version(this, out var ex); - ex.ThrowIfNecessary(); - return result; - } - - public string? GetErrorMessage() - { - EnsureIsOpen(); - - return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => - NativeMethods.get_error_message(this, buffer, length, out isNull, out ex)); - } - - public SubscriptionSetHandle BeginWrite() - { - EnsureIsOpen(); - - var result = NativeMethods.begin_write(this, out var ex); - ex.ThrowIfNecessary(); - return new SubscriptionSetHandle(Root!, result, isReadonly: false); - } - - public SubscriptionSetHandle CommitWrite() - { - EnsureIsOpen(); - - var result = NativeMethods.commit_write(this, out var ex); - ex.ThrowIfNecessary(); - - return new SubscriptionSetHandle(Root!, result, isReadonly: true); - } - - public Subscription GetAtIndex(int index) - { - EnsureIsOpen(); - - return GetSubscriptionCore((IntPtr callback, out NativeException ex) => NativeMethods.get_at_index(this, (IntPtr)index, callback, out ex))!; - } - - public Subscription? Find(string name) - { - EnsureIsOpen(); - - return GetSubscriptionCore((IntPtr callback, out NativeException ex) => NativeMethods.find_by_name(this, name, name.IntPtrLength(), callback, out ex)); - } - - public Subscription? Find(ResultsHandle results) - { - EnsureIsOpen(); - - return GetSubscriptionCore((IntPtr callback, out NativeException ex) => NativeMethods.find_by_query(this, results, callback, out ex)); - } - - public Subscription Add(ResultsHandle results, SubscriptionOptions options) - { - EnsureIsOpen(); - - return GetSubscriptionCore((IntPtr callback, out NativeException ex) => NativeMethods.add(this, results, options.Name, options.Name.IntPtrLength(), options.UpdateExisting, callback, out ex))!; - } - - public bool Remove(string name) - { - EnsureIsOpen(); - - var result = NativeMethods.remove(this, name, name.IntPtrLength(), out var ex); - ex.ThrowIfNecessary(); - return result; - } - - public bool Remove(ObjectId id) - { - EnsureIsOpen(); - - var subId = PrimitiveValue.ObjectId(id); - var result = NativeMethods.remove(this, subId, out var ex); - ex.ThrowIfNecessary(); - return result; - } - - public int Remove(ResultsHandle results, bool removeNamed) - { - EnsureIsOpen(); - - var result = NativeMethods.remove(this, results, removeNamed, out var ex); - ex.ThrowIfNecessary(); - return (int)result; - } - - public int RemoveAll(string type, bool removeNamed) - { - EnsureIsOpen(); - - var result = NativeMethods.remove_by_type(this, type, type.IntPtrLength(), removeNamed, out var ex); - ex.ThrowIfNecessary(); - return (int)result; - } - - public int RemoveAll(bool removeNamed) - { - EnsureIsOpen(); - - var result = NativeMethods.remove_all(this, removeNamed, out var ex); - ex.ThrowIfNecessary(); - return (int)result; - } - - public async Task WaitForStateChangeAsync(CancellationToken? cancellationToken) - { - EnsureIsOpen(); - - var tcs = new TaskCompletionSource(); - if (cancellationToken?.IsCancellationRequested == true) - { - tcs.TrySetCanceled(cancellationToken.Value); - return await tcs.Task; - } - - // The tcsHandles is freed in HandleStateWaitCallback. It's important that we don't free it on cancellation - // as the cancellation doesn't really cancel the native wait operation. That will eventually complete and it needs - // to have the GCHandle at this point, otherwise we'll get a hard crash on Mono. - var tcsHandle = GCHandle.Alloc(tcs); - cancellationToken?.Register(() => tcs.TrySetCanceled(cancellationToken.Value)); - - try - { - NativeMethods.wait_for_state(this, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - } - catch - { - // If we failed to register a waiter, we can free the handle as we won't get a native callback here anyway - tcsHandle.Free(); - throw; - } - - return await tcs.Task; - } - - private static Subscription? GetSubscriptionCore(GetSubscriptionBase getter) - { - Subscription? result = null; - Action callback = sub => result = sub.ManagedSubscription; - var callbackHandle = GCHandle.Alloc(callback); - try - { - getter(GCHandle.ToIntPtr(callbackHandle), out var ex); - ex.ThrowIfNecessary(); - } - finally - { - callbackHandle.Free(); - } - - return result; - } - - public override void Unbind() - { - if (IsReadonly) - { - NativeMethods.destroy(handle); - } - else - { - NativeMethods.destroy_mutable(handle); - } - - handle = IntPtr.Zero; - } - - [MonoPInvokeCallback(typeof(NativeMethods.GetSubscriptionCallback))] - private static void OnGetSubscription(IntPtr managedCallbackPtr, Native.Subscription subscription) - { - var handle = GCHandle.FromIntPtr(managedCallbackPtr); - var callback = (Action)handle.Target!; - callback(subscription); - } - - [MonoPInvokeCallback(typeof(NativeMethods.StateWaitCallback))] - private static void HandleStateWaitCallback(IntPtr taskCompletionSource, SubscriptionSetState state, StringValue message, ErrorCode error_code) - { - var handle = GCHandle.FromIntPtr(taskCompletionSource); - var tcs = (TaskCompletionSource)handle.Target!; - - switch (error_code) - { - case 0: // No error - tcs.TrySetResult(state); - break; - case (ErrorCode)1027: // OperationAborted - tcs.TrySetException(new TaskCanceledException("The SubscriptionSet was closed before the wait could complete. This is likely because the Realm it belongs to was disposed.")); - break; - case ErrorCode.BadQuery: - case ErrorCode.SubscriptionFailed: - tcs.TrySetException(new SubscriptionException(message!)); - break; - default: - tcs.TrySetException(new SessionException((string?)message ?? "Unknown error", error_code)); - break; - } - - handle.Free(); - } - } -} diff --git a/Realm/Realm/Handles/SyncUserHandle.cs b/Realm/Realm/Handles/SyncUserHandle.cs deleted file mode 100644 index fc385365d1..0000000000 --- a/Realm/Realm/Handles/SyncUserHandle.cs +++ /dev/null @@ -1,470 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using MongoDB.Bson; -using Realms.Logging; -using Realms.Native; - -namespace Realms.Sync -{ - internal class SyncUserHandle : StandaloneHandle - { - private static class NativeMethods - { - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void UserChangedCallback(IntPtr managed_user); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_id", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_user_id(SyncUserHandle user, IntPtr buffer, IntPtr buffer_length, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_refresh_token", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_refresh_token(SyncUserHandle user, IntPtr buffer, IntPtr buffer_length, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_access_token", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_access_token(SyncUserHandle user, IntPtr buffer, IntPtr buffer_length, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_device_id", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_device_id(SyncUserHandle user, IntPtr buffer, IntPtr buffer_length, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_state", CallingConvention = CallingConvention.Cdecl)] - public static extern UserState get_state(SyncUserHandle user, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_profile_data", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_profile_data(SyncUserHandle user, UserProfileField field, - IntPtr buffer, IntPtr buffer_length, [MarshalAs(UnmanagedType.U1)] out bool isNull, - out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_custom_data", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_custom_data(SyncUserHandle user, IntPtr buffer, IntPtr buffer_length, - [MarshalAs(UnmanagedType.U1)] out bool isNull, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_app", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_app(SyncUserHandle user, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_log_out", CallingConvention = CallingConvention.Cdecl)] - public static extern void log_out(SyncUserHandle user, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_refresh_custom_data", CallingConvention = CallingConvention.Cdecl)] - public static extern void refresh_custom_data(SyncUserHandle user, IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_destroy", CallingConvention = CallingConvention.Cdecl)] - public static extern void destroy(IntPtr syncuserHandle); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_call_function", CallingConvention = CallingConvention.Cdecl)] - public static extern void call_function(SyncUserHandle handle, AppHandle app, - [MarshalAs(UnmanagedType.LPWStr)] string function_name, IntPtr function_name_len, - [MarshalAs(UnmanagedType.LPWStr)] string args, IntPtr args_len, - [MarshalAs(UnmanagedType.LPWStr)] string? service_name, IntPtr service_name_len, - IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_link_credentials", CallingConvention = CallingConvention.Cdecl)] - public static extern void link_credentials(SyncUserHandle handle, AppHandle app, Native.Credentials credentials, IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_serialized_identities", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_identities(SyncUserHandle handle, IntPtr buffer, IntPtr bufsize, out NativeException ex); - - #region Api Keys - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_api_key_create", CallingConvention = CallingConvention.Cdecl)] - public static extern void create_api_key(SyncUserHandle handle, AppHandle app, - [MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr name_len, - IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_api_key_fetch", CallingConvention = CallingConvention.Cdecl)] - public static extern void fetch_api_key(SyncUserHandle handle, AppHandle app, PrimitiveValue id, IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_api_key_fetch_all", CallingConvention = CallingConvention.Cdecl)] - public static extern void fetch_api_keys(SyncUserHandle handle, AppHandle app, IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_api_key_delete", CallingConvention = CallingConvention.Cdecl)] - public static extern void delete_api_key(SyncUserHandle handle, AppHandle app, PrimitiveValue id, IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_api_key_disable", CallingConvention = CallingConvention.Cdecl)] - public static extern void disable_api_key(SyncUserHandle handle, AppHandle app, PrimitiveValue id, IntPtr tcs_ptr, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_api_key_enable", CallingConvention = CallingConvention.Cdecl)] - public static extern void enable_api_key(SyncUserHandle handle, AppHandle app, PrimitiveValue id, IntPtr tcs_ptr, out NativeException ex); - - #endregion - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_path_for_realm", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_path_for_realm(SyncUserHandle handle, [MarshalAs(UnmanagedType.LPWStr)] string? partition, IntPtr partition_len, IntPtr buffer, IntPtr bufsize, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_register_changed_callback", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr register_changed_callback(SyncUserHandle user, IntPtr managed_user_handle, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_unregister_property_changed_callback", CallingConvention = CallingConvention.Cdecl)] - public static extern void unregister_changed_callback(IntPtr user, IntPtr token, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_install_callbacks", CallingConvention = CallingConvention.Cdecl)] - public static extern void install_syncuser_callbacks(UserChangedCallback changed_callback); - } - - private (IntPtr NotificationToken, GCHandle UserHandle)? _changeSubscriptionInfo; - - [Preserve] - public SyncUserHandle(IntPtr handle) : base(handle) - { - } - - public static void Initialize() - { - NativeMethods.UserChangedCallback changed = HandleUserChanged; - - GCHandle.Alloc(changed); - - NativeMethods.install_syncuser_callbacks(changed); - } - - public string GetUserId() - { - return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => - { - isNull = false; - return NativeMethods.get_user_id(this, buffer, length, out ex); - })!; - } - - public string GetRefreshToken() - { - return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => - { - isNull = false; - return NativeMethods.get_refresh_token(this, buffer, length, out ex); - })!; - } - - public string GetAccessToken() - { - return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => - { - isNull = false; - return NativeMethods.get_access_token(this, buffer, length, out ex); - })!; - } - - public string GetDeviceId() - { - return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => - { - isNull = false; - return NativeMethods.get_device_id(this, buffer, length, out ex); - })!; - } - - public UserState GetState() - { - var result = NativeMethods.get_state(this, out var ex); - ex.ThrowIfNecessary(); - return result; - } - - public bool TryGetApp([MaybeNullWhen(false)] out AppHandle appHandle) - { - var result = NativeMethods.get_app(this, out var ex); - ex.ThrowIfNecessary(); - - if (result == IntPtr.Zero) - { - appHandle = null; - return false; - } - - appHandle = new AppHandle(result); - return true; - } - - public void LogOut() - { - NativeMethods.log_out(this, out var ex); - ex.ThrowIfNecessary(); - } - - public async Task RefreshCustomDataAsync() - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - try - { - NativeMethods.refresh_custom_data(this, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - - await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public string? GetProfileData(UserProfileField field) - { - return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) - => NativeMethods.get_profile_data(this, field, buffer, length, out isNull, out ex)); - } - - public string? GetCustomData() - { - return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) - => NativeMethods.get_custom_data(this, buffer, length, out isNull, out ex)); - } - - public async Task CallFunctionAsync(AppHandle app, string name, string args, string? service) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - - try - { - NativeMethods.call_function(this, app, name, name.IntPtrLength(), args, args.IntPtrLength(), service, service.IntPtrLength(), GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - - return await tcs.Task; // .NET Host locks the dll when there's an exception here (coming from native) - } - finally - { - tcsHandle.Free(); - } - } - - public async Task LinkCredentialsAsync(AppHandle app, Native.Credentials credentials) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - try - { - NativeMethods.link_credentials(this, app, credentials, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - - return await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public string GetIdentities() - { - return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => - { - isNull = false; - return NativeMethods.get_identities(this, buffer, length, out ex); - })!; - } - - #region Api Keys - - public async Task CreateApiKeyAsync(AppHandle app, string name) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - - try - { - NativeMethods.create_api_key(this, app, name, (IntPtr)name.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - - var result = await tcs.Task; - - Debug.Assert(result.Length == 1, "The result of Create should be exactly 1 ApiKey."); - - return result.Single(); - } - finally - { - tcsHandle.Free(); - } - } - - public async Task FetchApiKeyAsync(AppHandle app, ObjectId id) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - - try - { - var primitiveId = PrimitiveValue.ObjectId(id); - NativeMethods.fetch_api_key(this, app, primitiveId, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - - var result = await tcs.Task; - - Debug.Assert(result is null || result.Length <= 1, "The result of the fetch operation should be either null, or an array of 0 or 1 elements."); - - return result is null || result.Length == 0 ? null : result.Single(); - } - finally - { - tcsHandle.Free(); - } - } - - public async Task FetchAllApiKeysAsync(AppHandle app) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - - try - { - NativeMethods.fetch_api_keys(this, app, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - - return await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public async Task DeleteApiKeyAsync(AppHandle app, ObjectId id) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - try - { - var primitiveId = PrimitiveValue.ObjectId(id); - NativeMethods.delete_api_key(this, app, primitiveId, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - - await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public async Task DisableApiKeyAsync(AppHandle app, ObjectId id) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - - try - { - var primitiveId = PrimitiveValue.ObjectId(id); - NativeMethods.disable_api_key(this, app, primitiveId, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - - await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - public async Task EnableApiKeyAsync(AppHandle app, ObjectId id) - { - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - - try - { - var primitiveId = PrimitiveValue.ObjectId(id); - NativeMethods.enable_api_key(this, app, primitiveId, GCHandle.ToIntPtr(tcsHandle), out var ex); - ex.ThrowIfNecessary(); - - await tcs.Task; - } - finally - { - tcsHandle.Free(); - } - } - - #endregion - - protected override void Unbind() - { - UnsubscribeNotifications(); - NativeMethods.destroy(handle); - } - - public string GetRealmPath(string? partition = null) - { - return MarshalHelpers.GetString((IntPtr buffer, IntPtr bufferLength, out bool isNull, out NativeException ex) => - { - isNull = false; - return NativeMethods.get_path_for_realm(this, partition, partition.IntPtrLength(), buffer, bufferLength, out ex); - })!; - } - - public void SubscribeNotifications(User user) - { - Debug.Assert(_changeSubscriptionInfo == null, $"{nameof(_changeSubscriptionInfo)} must be null before subscribing."); - - var managedUserHandle = GCHandle.Alloc(user, GCHandleType.Weak); - var userPointer = GCHandle.ToIntPtr(managedUserHandle); - var token = NativeMethods.register_changed_callback(this, userPointer, out var ex); - ex.ThrowIfNecessary(); - - _changeSubscriptionInfo = (token, managedUserHandle); - } - - public void UnsubscribeNotifications() - { - if (_changeSubscriptionInfo != null) - { - // This needs to use the handle directly because it's being called in Unbind. At this point the SafeHandle is closed, which means we'll - // get an error if we attempted to marshal it to native. The raw pointer is fine though and we can use it. - NativeMethods.unregister_changed_callback(handle, _changeSubscriptionInfo.Value.NotificationToken, out var ex); - ex.ThrowIfNecessary(); - - _changeSubscriptionInfo.Value.UserHandle.Free(); - _changeSubscriptionInfo = null; - } - } - - [MonoPInvokeCallback(typeof(NativeMethods.UserChangedCallback))] - private static void HandleUserChanged(IntPtr managedUserHandle) - { - try - { - if (managedUserHandle == IntPtr.Zero) - { - return; - } - - var user = (User?)GCHandle.FromIntPtr(managedUserHandle).Target; - if (user is null) - { - // We're taking a weak handle to the user, so it's possible that it's been collected - return; - } - - ThreadPool.QueueUserWorkItem(_ => - { - user.RaiseChanged(); - }); - } - catch (Exception ex) - { - RealmLogger.Default.Log(LogLevel.Error, $"An error has occurred while raising User.Changed event: {ex}"); - } - } - } -} diff --git a/Realm/Realm/Helpers/SerializationHelper.cs b/Realm/Realm/Helpers/SerializationHelper.cs index 0734cbdb69..a5c48b4f20 100644 --- a/Realm/Realm/Helpers/SerializationHelper.cs +++ b/Realm/Realm/Helpers/SerializationHelper.cs @@ -28,7 +28,6 @@ using MongoDB.Bson.Serialization.Options; using MongoDB.Bson.Serialization.Serializers; using Realms.Serialization; -using Realms.Sync; namespace Realms.Helpers { @@ -80,8 +79,6 @@ internal static void PreserveSerializers() _ = new StringSerializer(); _ = new ByteArraySerializer(); - _ = new EnumSerializer(); - _ = new ArraySerializer(); _ = new ArraySerializer(); _ = new ArraySerializer(); diff --git a/Realm/Realm/Native/AppConfiguration.cs b/Realm/Realm/Native/AppConfiguration.cs deleted file mode 100644 index 8dc1c83ee7..0000000000 --- a/Realm/Realm/Native/AppConfiguration.cs +++ /dev/null @@ -1,99 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Runtime.InteropServices; - -namespace Realms.Sync.Native -{ - [StructLayout(LayoutKind.Sequential)] - internal struct AppConfiguration - { - [MarshalAs(UnmanagedType.LPWStr)] - private string app_id; - private IntPtr app_id_len; - - internal string AppId - { - set - { - app_id = value; - app_id_len = (IntPtr)value.Length; - } - } - - [MarshalAs(UnmanagedType.LPWStr)] - private string base_file_path; - private IntPtr base_file_path_len; - - internal string BaseFilePath - { - set - { - base_file_path = value; - base_file_path_len = (IntPtr)value.Length; - } - } - - [MarshalAs(UnmanagedType.LPWStr)] - private string base_url; - private IntPtr base_url_len; - - internal string BaseUrl - { - set - { - base_url = value; - base_url_len = value.IntPtrLength(); - } - } - - internal UInt64 default_request_timeout_ms; - - private MetadataPersistenceMode metadata_persistence; - - [MarshalAs(UnmanagedType.U1)] - private bool metadata_persistence_has_value; - - internal MetadataPersistenceMode? MetadataPersistence - { - set - { - metadata_persistence = value.HasValue ? value.Value : default; - metadata_persistence_has_value = value.HasValue; - } - } - - internal IntPtr managed_http_client; - - internal IntPtr managed_websocket_provider; - - internal UInt64 sync_connect_timeout_ms; - - internal UInt64 sync_connection_linger_time_ms; - - internal UInt64 sync_ping_keep_alive_period_ms; - - internal UInt64 sync_pong_keep_alive_timeout_ms; - - internal UInt64 sync_fast_reconnect_limit; - - [MarshalAs(UnmanagedType.U1)] - internal bool use_cache; - } -} diff --git a/Realm/Realm/Native/AppError.cs b/Realm/Realm/Native/AppError.cs deleted file mode 100644 index c2655d0c4e..0000000000 --- a/Realm/Realm/Native/AppError.cs +++ /dev/null @@ -1,44 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Runtime.InteropServices; -using Realms.Native; - -namespace Realms.Sync.Native -{ - [StructLayout(LayoutKind.Sequential)] - internal struct AppError - { - [MarshalAs(UnmanagedType.U1)] - public bool is_null; - - private PrimitiveValue message; - - private PrimitiveValue error_category; - - private PrimitiveValue logs_link; - - public int http_status_code; - - public string? Message => message.Type == RealmValueType.Null ? null : message.AsString(); - - public string? ErrorCategory => error_category.Type == RealmValueType.Null ? null : error_category.AsString(); - - public string? LogsLink => logs_link.Type == RealmValueType.Null ? null : logs_link.AsString(); - } -} diff --git a/Realm/Realm/Native/Credentials.cs b/Realm/Realm/Native/Credentials.cs deleted file mode 100644 index 2a4d9e5f97..0000000000 --- a/Realm/Realm/Native/Credentials.cs +++ /dev/null @@ -1,55 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Runtime.InteropServices; -using static Realms.Sync.Credentials; - -namespace Realms.Sync.Native -{ - internal struct Credentials - { - internal AuthProvider provider; - - [MarshalAs(UnmanagedType.LPWStr)] - private string? token; - private IntPtr token_len; - - internal string? Token - { - set - { - token = value; - token_len = value.IntPtrLength(); - } - } - - [MarshalAs(UnmanagedType.LPWStr)] - private string? additional_info; - private IntPtr additional_info_len; - - internal string? AdditionalInfo - { - set - { - additional_info = value; - additional_info_len = value.IntPtrLength(); - } - } - } -} diff --git a/Realm/Realm/Native/HttpClientTransport.cs b/Realm/Realm/Native/HttpClientTransport.cs deleted file mode 100644 index c435dbd915..0000000000 --- a/Realm/Realm/Native/HttpClientTransport.cs +++ /dev/null @@ -1,234 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net.Http; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Realms.Native -{ - internal static class HttpClientTransport - { - private enum CustomErrorCode - { - NoError = 0, - HttpClientDisposed = 997, - UnknownHttp = 998, - Unknown = 999, - Timeout = 1000, - } - -#pragma warning disable SA1300 // Element should begin with upper-case letter - - private enum NativeHttpMethod - { - get, - post, - patch, - put, - del - } - - [StructLayout(LayoutKind.Sequential)] - private readonly struct HttpClientRequest - { - public readonly NativeHttpMethod method; - - private readonly StringValue url; - - public readonly UInt64 timeout_ms; - - public readonly MarshaledVector> headers; - - private readonly StringValue body; - - private readonly IntPtr managed_http_client; - - public string Url => url!; - - public string? Body => body; - - public HttpClient HttpClient - { - get - { - if (managed_http_client != IntPtr.Zero && - GCHandle.FromIntPtr(managed_http_client).Target is HttpClient client) - { - return client; - } - - throw new ObjectDisposedException("HttpClient has been disposed, most likely because the app has been closed."); - } - } - } - - [StructLayout(LayoutKind.Sequential)] - private struct HttpClientResponse - { - public Int32 http_status_code; - - public CustomErrorCode custom_status_code; - - public MarshaledVector> headers; - - public StringValue body; - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void execute_request(HttpClientRequest request, IntPtr callback_ptr); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_http_transport_install_callbacks", CallingConvention = CallingConvention.Cdecl)] - private static extern void install_callbacks(execute_request execute); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_http_transport_respond", CallingConvention = CallingConvention.Cdecl)] - private static extern void respond(HttpClientResponse response, IntPtr callback_ptr); - -#pragma warning restore SA1300 // Element should begin with upper-case letter - - internal static void Initialize() - { - execute_request execute = ExecuteRequest; - - GCHandle.Alloc(execute); - - install_callbacks(execute); - } - - private static HttpRequestMessage BuildRequest(HttpClientRequest request) - { - var message = new HttpRequestMessage(request.method.ToHttpMethod(), request.Url); - foreach (var header in request.headers) - { - message.Headers.TryAddWithoutValidation(header.Key!, header.Value); - } - - if (request.method != NativeHttpMethod.get) - { - message.Content = new StringContent(request.Body!, Encoding.UTF8, "application/json"); - } - - return message; - } - - [MonoPInvokeCallback(typeof(execute_request))] - private static async void ExecuteRequest(HttpClientRequest request, IntPtr callback) - { - try - { - using var arena = new Arena(); - try - { - var httpClient = request.HttpClient; - - using var message = BuildRequest(request); - using var cts = new CancellationTokenSource((int)request.timeout_ms); - - var response = await httpClient.SendAsync(message, cts.Token).ConfigureAwait(false); - - var headers = response.Headers.Concat(response.Content.Headers) - .Select(h => new MarshaledPair(StringValue.AllocateFrom(h.Key, arena), StringValue.AllocateFrom(h.Value.FirstOrDefault(), arena))) - .ToArray(); - - var nativeResponse = new HttpClientResponse - { - http_status_code = (int)response.StatusCode, - headers = MarshaledVector>.AllocateFrom(headers, arena), - body = StringValue.AllocateFrom(await response.Content.ReadAsStringAsync().ConfigureAwait(false), arena), - }; - - respond(nativeResponse, callback); - } - catch (HttpRequestException rex) - { - var sb = new StringBuilder("An unexpected error occurred while sending the request"); - - // We're doing this because the message for the top-level exception is usually pretty useless. - // If there's inner exception, we want to skip it and directly go for the more specific messages. - var innerEx = rex.InnerException ?? rex; - while (innerEx != null) - { - sb.Append($": {innerEx.Message}"); - innerEx = innerEx.InnerException; - } - - var nativeResponse = new HttpClientResponse - { - custom_status_code = CustomErrorCode.UnknownHttp, - body = StringValue.AllocateFrom(sb.ToString(), arena), - }; - - respond(nativeResponse, callback); - } - catch (TaskCanceledException) - { - var nativeResponse = new HttpClientResponse - { - custom_status_code = CustomErrorCode.Timeout, - body = StringValue.AllocateFrom($"Operation failed to complete within {request.timeout_ms} ms.", arena), - }; - - respond(nativeResponse, callback); - } - catch (ObjectDisposedException ode) - { - var nativeResponse = new HttpClientResponse - { - custom_status_code = CustomErrorCode.HttpClientDisposed, - body = StringValue.AllocateFrom(ode.Message, arena), - }; - - respond(nativeResponse, callback); - } - catch (Exception ex) - { - var nativeResponse = new HttpClientResponse - { - custom_status_code = CustomErrorCode.Unknown, - body = StringValue.AllocateFrom(ex.Message, arena), - }; - - respond(nativeResponse, callback); - } - } - catch (Exception outerEx) - { - Debug.WriteLine($"Unexpected error occurred while trying to respond to a request: {outerEx}"); - } - } - - private static HttpMethod ToHttpMethod(this NativeHttpMethod nativeMethod) - { - return nativeMethod switch - { - NativeHttpMethod.get => HttpMethod.Get, - NativeHttpMethod.post => HttpMethod.Post, - NativeHttpMethod.patch => new HttpMethod("PATCH"), - NativeHttpMethod.put => HttpMethod.Put, - NativeHttpMethod.del => HttpMethod.Delete, - _ => throw new NotSupportedException($"Unsupported HTTP method: {nativeMethod}") - }; - } - } -} diff --git a/Realm/Realm/Native/NativeCommon.cs b/Realm/Realm/Native/NativeCommon.cs index 9dc192fc9e..f389d9e1b2 100644 --- a/Realm/Realm/Native/NativeCommon.cs +++ b/Realm/Realm/Native/NativeCommon.cs @@ -23,8 +23,6 @@ using System.Threading; using Realms.Helpers; using Realms.Logging; -using Realms.Native; -using Realms.Sync; #if !NET5_0_OR_GREATER using System.IO; #endif @@ -74,11 +72,6 @@ internal static void Initialize() SynchronizationContextScheduler.Initialize(); SharedRealmHandle.Initialize(); - SessionHandle.Initialize(); - SyncUserHandle.Initialize(); - HttpClientTransport.Initialize(); - AppHandle.Initialize(); - SubscriptionSetHandle.Initialize(); SerializationHelper.Initialize(); } @@ -101,8 +94,6 @@ public static void CleanupNativeResources(string reason) var sw = new Stopwatch(); sw.Start(); - AppHandle.ForceCloseHandles(); - AsyncOpenTaskHandle.CancelInFlightTasks(); SharedRealmHandle.ForceCloseNativeRealms(); sw.Stop(); diff --git a/Realm/Realm/Native/SessionNotificationToken.cs b/Realm/Realm/Native/SessionNotificationToken.cs deleted file mode 100644 index 3af87b1313..0000000000 --- a/Realm/Realm/Native/SessionNotificationToken.cs +++ /dev/null @@ -1,28 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Runtime.InteropServices; - -namespace Realms.Sync.Native -{ - [StructLayout(LayoutKind.Sequential)] - internal struct SessionNotificationToken - { - internal ulong connection_state; - } -} diff --git a/Realm/Realm/Native/Subscription.cs b/Realm/Realm/Native/Subscription.cs deleted file mode 100644 index f45665afe4..0000000000 --- a/Realm/Realm/Native/Subscription.cs +++ /dev/null @@ -1,61 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Runtime.InteropServices; -using Realms.Native; -using ManagedSubscription = Realms.Sync.Subscription; - -namespace Realms.Sync.Native -{ - [StructLayout(LayoutKind.Sequential)] - internal struct Subscription - { - private PrimitiveValue id; - - private PrimitiveValue name; - - private PrimitiveValue object_type; - - private PrimitiveValue query; - - private PrimitiveValue created_at; - - private PrimitiveValue updated_at; - - [MarshalAs(UnmanagedType.I1)] - private bool has_value; - - public ManagedSubscription? ManagedSubscription - { - get - { - if (!has_value) - { - return null; - } - - return new(id: id.AsObjectId(), - name: name.Type == RealmValueType.Null ? null : name.AsString(), - objectType: object_type.AsString(), - query: query.AsString(), - createdAt: created_at.AsDate(), - updatedAt: updated_at.AsDate()); - } - } - } -} diff --git a/Realm/Realm/Native/SyncConfiguration.cs b/Realm/Realm/Native/SyncConfiguration.cs deleted file mode 100644 index 18146d3b20..0000000000 --- a/Realm/Realm/Native/SyncConfiguration.cs +++ /dev/null @@ -1,63 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Runtime.InteropServices; - -namespace Realms.Sync.Native -{ - [StructLayout(LayoutKind.Sequential)] - internal struct SyncConfiguration - { - private IntPtr sync_user_ptr; - - internal SyncUserHandle SyncUserHandle - { - set - { - sync_user_ptr = value.DangerousGetHandle(); - } - } - - [MarshalAs(UnmanagedType.LPWStr)] - private string partition; - - private IntPtr partition_len; - - internal string Partition - { - set - { - partition = value; - partition_len = (IntPtr)value.Length; - } - } - - internal SessionStopPolicy session_stop_policy; - - internal SchemaMode schema_mode; - - [MarshalAs(UnmanagedType.I1)] - internal bool is_flexible_sync; - - internal ClientResyncMode client_resync_mode; - - [MarshalAs(UnmanagedType.I1)] - internal bool cancel_waits_on_nonfatal_error; - } -} diff --git a/Realm/Realm/Native/SyncError.cs b/Realm/Realm/Native/SyncError.cs deleted file mode 100644 index 09ecae53a5..0000000000 --- a/Realm/Realm/Native/SyncError.cs +++ /dev/null @@ -1,49 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Generic; -using System.Runtime.InteropServices; -using Realms.Sync.Exceptions; - -namespace Realms.Native -{ - [StructLayout(LayoutKind.Sequential)] - internal struct SyncError - { - public ErrorCode error_code; - - public StringValue message; - - public StringValue log_url; - - [MarshalAs(UnmanagedType.U1)] - public bool is_client_reset; - - public MarshaledVector> user_info_pairs; - - public MarshaledVector compensating_writes; - - [StructLayout(LayoutKind.Sequential)] - internal struct CompensatingWriteInfo - { - public StringValue reason; - public StringValue object_name; - public PrimitiveValue primary_key; - } - } -} diff --git a/Realm/Realm/Native/SyncSocketProvider.EventLoop.cs b/Realm/Realm/Native/SyncSocketProvider.EventLoop.cs deleted file mode 100644 index d74a5f5161..0000000000 --- a/Realm/Realm/Native/SyncSocketProvider.EventLoop.cs +++ /dev/null @@ -1,124 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using Realms.Logging; - -namespace Realms.Native; - -internal partial class SyncSocketProvider -{ - private class Timer - { - private readonly CancellationTokenSource _cts = new(); - - internal Timer(TimeSpan delay, IntPtr nativeCallback, ChannelWriter workQueue) - { - RealmLogger.Default.Log(LogLevel.Trace, $"Creating timer with delay {delay} and target {nativeCallback}."); - var cancellationToken = _cts.Token; - Task.Delay(delay, cancellationToken).ContinueWith(async _ => - { - await workQueue.WriteAsync(new Work(nativeCallback, cancellationToken)); - }); - } - - internal void Cancel() - { - RealmLogger.Default.Log(LogLevel.Trace, $"Canceling timer."); - _cts.Cancel(); - _cts.Dispose(); - } - - private class Work(IntPtr nativeCallback, CancellationToken cancellationToken) - : IWork - { - public void Execute() - { - var status = Status.OK; - if (cancellationToken.IsCancellationRequested) - { - status = new(ErrorCode.OperationAborted, "Timer canceled"); - } - - RunCallback(nativeCallback, status); - } - } - } - - // Belongs to SyncSocketProvider. When Native destroys the Provider we need to stop executing - // enqueued work, but we need to release all the callbacks we copied on the heap. - private class EventLoopWork(IntPtr nativeCallback, CancellationToken cancellationToken) - : IWork - { - public void Execute() - { - if (cancellationToken.IsCancellationRequested) - { - RealmLogger.Default.Log(LogLevel.Trace, "Deleting EventLoopWork callback only because event loop was cancelled."); - NativeMethods.delete_callback(nativeCallback); - return; - } - - RunCallback(nativeCallback, Status.OK); - } - } - - private static void RunCallback(IntPtr nativeCallback, Status status) - { - RealmLogger.Default.Log(LogLevel.Trace, $"SyncSocketProvider running native callback {nativeCallback} with status {status.Code} \"{status.Reason}\"."); - - using var arena = new Arena(); - NativeMethods.run_callback(nativeCallback, status.Code, StringValue.AllocateFrom(status.Reason, arena)); - } - - private async Task PostWorkAsync(IntPtr nativeCallback) - { - RealmLogger.Default.Log(LogLevel.Trace, "Posting work to SyncSocketProvider event loop."); - await _workQueue.Writer.WriteAsync(new EventLoopWork(nativeCallback, _cts.Token)); - } - - private async partial Task WorkThread() - { - RealmLogger.Default.Log(LogLevel.Trace, "Starting SyncSocketProvider event loop."); - try - { - while (await _workQueue.Reader.WaitToReadAsync()) - { - while (_workQueue.Reader.TryRead(out var work)) - { - work.Execute(); - } - } - } - catch (Exception e) - { - RealmLogger.Default.Log(LogLevel.Error, $"Error occurred in SyncSocketProvider event loop {e.GetType().FullName}: {e.Message}"); - if (!string.IsNullOrEmpty(e.StackTrace)) - { - RealmLogger.Default.Log(LogLevel.Trace, e.StackTrace); - } - - throw; - } - - RealmLogger.Default.Log(LogLevel.Trace, "Exiting SyncSocketProvider event loop."); - } -} diff --git a/Realm/Realm/Native/SyncSocketProvider.Native.cs b/Realm/Realm/Native/SyncSocketProvider.Native.cs deleted file mode 100644 index dfc1f9e1b2..0000000000 --- a/Realm/Realm/Native/SyncSocketProvider.Native.cs +++ /dev/null @@ -1,98 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Net.WebSockets; -using System.Runtime.InteropServices; - -namespace Realms.Native; - -internal partial class SyncSocketProvider -{ - // additional websocket close status codes that Sync understands - private const int RLM_ERR_WEBSOCKET_CONNECTION_FAILED = 4401; - private const int RLM_ERR_WEBSOCKET_READ_ERROR = 4402; - private const int RLM_ERR_WEBSOCKET_WRITE_ERROR = 4403; - - // equivalent to ErrorCodes::Error in - public enum ErrorCode : int - { - Ok = 0, - RuntimeError = 1000, - OperationAborted = 1027 - } - - [StructLayout(LayoutKind.Sequential)] - public readonly struct Endpoint - { - public readonly StringValue address; - - public readonly ushort port; - - public readonly StringValue path; - - public readonly MarshaledVector protocols; - - public readonly NativeBool is_ssl; - } - - private static class NativeMethods - { - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void post_work(IntPtr socket_provider, IntPtr native_callback); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void provider_dispose(IntPtr managed_provider); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate IntPtr create_timer(IntPtr socket_provider, UInt64 delay_miliseconds, IntPtr native_callback); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void cancel_timer(IntPtr timer); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate IntPtr websocket_connect(IntPtr socket_provider, IntPtr observer, Endpoint endpoint); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void websocket_write(IntPtr managed_websocket, BinaryValue data, IntPtr native_callback); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void websocket_close(IntPtr managed_websocket); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_websocket_install_callbacks", CallingConvention = CallingConvention.Cdecl)] - public static extern void install_callbacks(post_work post, provider_dispose dispose, create_timer create_timer, cancel_timer cancel_timer, websocket_connect connect, websocket_write write, websocket_close close); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_websocket_run_callback", CallingConvention = CallingConvention.Cdecl)] - public static extern void run_callback(IntPtr native_callback, ErrorCode result, StringValue reason); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_websocket_delete_callback", CallingConvention = CallingConvention.Cdecl)] - public static extern void delete_callback(IntPtr native_callback); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_websocket_observer_connected_handler", CallingConvention = CallingConvention.Cdecl)] - public static extern void observer_connected_handler(IntPtr observer, StringValue protocol); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_websocket_observer_error_handler", CallingConvention = CallingConvention.Cdecl)] - public static extern void observer_error_handler(IntPtr observer); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_websocket_observer_binary_message_received", CallingConvention = CallingConvention.Cdecl)] - public static extern void observer_binary_message_received(IntPtr observer, BinaryValue data); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_websocket_observer_closed_handler", CallingConvention = CallingConvention.Cdecl)] - public static extern void observer_closed_handler(IntPtr observer, NativeBool was_clean, WebSocketCloseStatus status, StringValue reason); - } -} diff --git a/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs b/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs deleted file mode 100644 index 5aa91cc811..0000000000 --- a/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs +++ /dev/null @@ -1,264 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Buffers; -using System.IO; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using Realms.Logging; - -namespace Realms.Native; - -internal partial class SyncSocketProvider -{ - private class Socket : IDisposable - { - private readonly ClientWebSocket _webSocket; - private readonly IntPtr _observer; - private readonly ChannelWriter _workQueue; - private readonly CancellationTokenSource _cts = new(); - private readonly CancellationToken _cancellationToken; - - private readonly Uri _uri; - private readonly Task _readThread; - - private MemoryStream _receiveBuffer = new(); - - internal Socket(ClientWebSocket webSocket, IntPtr observer, ChannelWriter workQueue, Uri uri) - { - RealmLogger.Default.Log(LogLevel.Trace, $"Creating a WebSocket to {uri.GetLeftPart(UriPartial.Path)}"); - _webSocket = webSocket; - _observer = observer; - _workQueue = workQueue; - _uri = uri; - _cancellationToken = _cts.Token; - _readThread = Task.Run(ReadThread); - } - - private async Task ReadThread() - { - RealmLogger.Default.Log(LogLevel.Trace, "Entering WebSocket event loop."); - - try - { - await _webSocket.ConnectAsync(_uri, _cancellationToken); - await _workQueue.WriteAsync(new WebSocketConnectedWork(_webSocket.SubProtocol!, _observer, _cancellationToken)); - } - catch (Exception e) - { - var builder = new StringBuilder(); - FormatExceptionForLogging(e, builder); - RealmLogger.Default.Log(LogLevel.Error, $"Error establishing WebSocket connection {builder}"); - - await _workQueue.WriteAsync(new WebSocketClosedWork(false, (WebSocketCloseStatus)RLM_ERR_WEBSOCKET_CONNECTION_FAILED, e.Message, _observer, _cancellationToken)); - return; - } - - var buffer = new byte[32 * 1024]; - while (_webSocket.State == WebSocketState.Open) - { - try - { - var result = await _webSocket.ReceiveAsync(new ArraySegment(buffer), _cancellationToken); - switch (result.MessageType) - { - case WebSocketMessageType.Binary: - await _receiveBuffer.WriteAsync(buffer, 0, result.Count); - if (result.EndOfMessage) - { - var currentBuffer = _receiveBuffer; - _receiveBuffer = new MemoryStream(); - await _workQueue.WriteAsync(new BinaryMessageReceivedWork(currentBuffer, _observer, _cancellationToken)); - } - - break; - case WebSocketMessageType.Close: - RealmLogger.Default.Log(LogLevel.Trace, $"WebSocket closed with status {result.CloseStatus!.Value} and description \"{result.CloseStatusDescription}\""); - await _workQueue.WriteAsync(new WebSocketClosedWork(clean: true, result.CloseStatus!.Value, result.CloseStatusDescription!, _observer, _cancellationToken)); - break; - default: - RealmLogger.Default.Log(LogLevel.Trace, $"Received unexpected text WebSocket message: {Encoding.UTF8.GetString(buffer, 0, result.Count)}"); - break; - } - } - catch (Exception e) - { - var builder = new StringBuilder(); - FormatExceptionForLogging(e, builder); - RealmLogger.Default.Log(LogLevel.Error, $"Error reading from WebSocket {builder}"); - - await _workQueue.WriteAsync(new WebSocketClosedWork(false, (WebSocketCloseStatus)RLM_ERR_WEBSOCKET_READ_ERROR, e.Message, _observer, _cancellationToken)); - return; - } - } - } - - public async void Write(BinaryValue data, IntPtr native_callback) - { - if (_webSocket.State == WebSocketState.Aborted || _cancellationToken.IsCancellationRequested) - { - NativeMethods.delete_callback(native_callback); - return; - } - - var buffer = data.AsBytes(usePooledArray: true); - - try - { - await _webSocket.SendAsync(new(buffer), WebSocketMessageType.Binary, true, _cancellationToken); - } - catch (Exception e) - { - var builder = new StringBuilder(); - FormatExceptionForLogging(e, builder); - RealmLogger.Default.Log(LogLevel.Error, $"Error writing to WebSocket {builder}"); - - // in case of errors notify the websocket observer and just dispose the callback - await _workQueue.WriteAsync(new WebSocketClosedWork(false, (WebSocketCloseStatus)RLM_ERR_WEBSOCKET_WRITE_ERROR, e.Message, _observer, _cancellationToken)); - NativeMethods.delete_callback(native_callback); - return; - } - finally - { - ArrayPool.Shared.Return(buffer); - } - - await _workQueue.WriteAsync(new EventLoopWork(native_callback, _cancellationToken)); - } - - public async void Dispose() - { - _cts.Cancel(); - - if (_webSocket.State == WebSocketState.Open) - { - try - { - // If the websocket is still open close it, but throw away any close errors. - await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default); - } - catch - { - } - } - - _webSocket.Dispose(); - _receiveBuffer.Dispose(); - _cts.Dispose(); - RealmLogger.Default.Log(LogLevel.Trace, "Disposing WebSocket."); - - try - { - await _readThread; - } - catch(TaskCanceledException) - { - } - catch(ChannelClosedException) - { - } - } - - private static void FormatExceptionForLogging(Exception ex, StringBuilder builder, int nesting = 0) - { - var indentation = new string('\t', nesting); - builder.Append(indentation); - - builder.AppendFormat("{0}: {1}", ex.GetType().FullName, ex.Message); - builder.AppendLine(); - if (RealmLogger.GetLogLevel(LogCategory.Realm.SDK) >= LogLevel.Trace && !string.IsNullOrEmpty(ex.StackTrace)) - { - builder.Append(indentation); - var indentedTrace = ex.StackTrace.Replace(Environment.NewLine, Environment.NewLine + indentation); - builder.AppendLine(indentedTrace); - } - - if (ex is AggregateException aggregateException) - { - foreach (var inner in aggregateException.InnerExceptions) - { - FormatExceptionForLogging(inner, builder, nesting + 1); - } - } - else if (ex.InnerException is { } inner) - { - FormatExceptionForLogging(inner, builder, nesting + 1); - } - } - } - - private abstract class WebSocketWork(IntPtr observer, CancellationToken cancellationToken) - : IWork - { - // Belongs to the Socket and canceled when Native destroys the socket. - // If it's canceled we shouldn't call any observer methods. - private readonly CancellationToken _cancellationToken = cancellationToken; - - protected abstract void Execute(IntPtr observer); - - void IWork.Execute() - { - if (!_cancellationToken.IsCancellationRequested) - { - Execute(observer); - } - } - } - - private sealed class WebSocketConnectedWork(string protocol, IntPtr observer, CancellationToken cancellationToken) - : WebSocketWork(observer, cancellationToken) - { - protected override void Execute(IntPtr observer) - { - using var arena = new Arena(); - NativeMethods.observer_connected_handler(observer, StringValue.AllocateFrom(protocol, arena)); - } - } - - private sealed class BinaryMessageReceivedWork(MemoryStream receiveBuffer, IntPtr observer, CancellationToken cancellationToken) - : WebSocketWork(observer, cancellationToken) - { - protected unsafe override void Execute(IntPtr observer) - { - using var buffer = receiveBuffer; - fixed (byte* data = buffer.GetBuffer()) - { - NativeMethods.observer_binary_message_received(observer, new() { data = data, size = (IntPtr)buffer.Length }); - } - } - } - - private sealed class WebSocketClosedWork(bool clean, WebSocketCloseStatus status, string description, IntPtr observer, CancellationToken cancellationToken) - : WebSocketWork(observer, cancellationToken) - { - protected override void Execute(IntPtr observer) - { - if (!clean) - { - NativeMethods.observer_error_handler(observer); - } - - using var arena = new Arena(); - NativeMethods.observer_closed_handler(observer, clean, status, StringValue.AllocateFrom(description, arena)); - } - } -} diff --git a/Realm/Realm/Native/SyncSocketProvider.cs b/Realm/Realm/Native/SyncSocketProvider.cs deleted file mode 100644 index e281847a9f..0000000000 --- a/Realm/Realm/Native/SyncSocketProvider.cs +++ /dev/null @@ -1,175 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Linq; -using System.Net.WebSockets; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using Realms.Logging; - -namespace Realms.Native; - -internal partial class SyncSocketProvider : IDisposable -{ - private static void PostWork(IntPtr managed_provider, IntPtr native_callback) - { - var provider = (SyncSocketProvider)GCHandle.FromIntPtr(managed_provider).Target!; - _ = provider.PostWorkAsync(native_callback); - } - - [MonoPInvokeCallback(typeof(NativeMethods.provider_dispose))] - private static void ProviderDispose(IntPtr managed_provider) - { - var handle = GCHandle.FromIntPtr(managed_provider); - ((SyncSocketProvider)handle.Target!).Dispose(); - handle.Free(); - } - - [MonoPInvokeCallback(typeof(NativeMethods.create_timer))] - private static IntPtr CreateTimer(IntPtr managed_provider, ulong delay_milliseconds, IntPtr native_callback) - { - var provider = (SyncSocketProvider)GCHandle.FromIntPtr(managed_provider).Target!; - var timer = new Timer(TimeSpan.FromMilliseconds(delay_milliseconds), native_callback, provider._workQueue); - return GCHandle.ToIntPtr(GCHandle.Alloc(timer)); - } - - [MonoPInvokeCallback(typeof(NativeMethods.cancel_timer))] - private static void CancelTimer(IntPtr managed_timer) - { - var handle = GCHandle.FromIntPtr(managed_timer); - try - { - ((Timer)handle.Target!).Cancel(); - } - finally - { - handle.Free(); - } - } - - [MonoPInvokeCallback(typeof(NativeMethods.websocket_connect))] - private static IntPtr WebSocketConnect(IntPtr managed_provider, IntPtr observer, Endpoint endpoint) - { - var provider = (SyncSocketProvider)GCHandle.FromIntPtr(managed_provider).Target!; - var webSocket = new ClientWebSocket(); - foreach (string? subProtocol in endpoint.protocols) - { - webSocket.Options.AddSubProtocol(subProtocol!); - } - - provider._onWebSocketConnection?.Invoke(webSocket.Options); - - var builder = new UriBuilder - { - Scheme = endpoint.is_ssl ? "wss" : "ws", - Host = endpoint.address!, - Port = endpoint.port - }; - - if (endpoint.path) - { - var pathAndQuery = ((string)endpoint.path)!.Split('?'); - builder.Path = pathAndQuery.ElementAtOrDefault(0) ?? string.Empty; - builder.Query = pathAndQuery.ElementAtOrDefault(1) ?? String.Empty; - } - - var socket = new Socket(webSocket, observer, provider._workQueue, builder.Uri); - return GCHandle.ToIntPtr(GCHandle.Alloc(socket)); - } - - [MonoPInvokeCallback(typeof(NativeMethods.websocket_write))] - private static void WebSocketWrite(IntPtr managed_socket, BinaryValue data, IntPtr native_callback) - { - var socket = (Socket)GCHandle.FromIntPtr(managed_socket).Target!; - socket.Write(data, native_callback); - } - - [MonoPInvokeCallback(typeof(NativeMethods.websocket_close))] - private static void WebSocketClose(IntPtr managed_websocket) - { - var handle = GCHandle.FromIntPtr(managed_websocket); - ((Socket)handle.Target!).Dispose(); - handle.Free(); - } - - static SyncSocketProvider() - { - NativeMethods.post_work post = PostWork; - NativeMethods.provider_dispose dispose = ProviderDispose; - NativeMethods.create_timer create_timer = CreateTimer; - NativeMethods.cancel_timer cancel_timer = CancelTimer; - NativeMethods.websocket_connect websocket_connect = WebSocketConnect; - NativeMethods.websocket_write websocket_write = WebSocketWrite; - NativeMethods.websocket_close websocket_close = WebSocketClose; - - GCHandle.Alloc(post); - GCHandle.Alloc(dispose); - GCHandle.Alloc(create_timer); - GCHandle.Alloc(cancel_timer); - GCHandle.Alloc(websocket_connect); - GCHandle.Alloc(websocket_write); - GCHandle.Alloc(websocket_close); - - NativeMethods.install_callbacks(post, dispose, create_timer, cancel_timer, websocket_connect, websocket_write, websocket_close); - } - - private struct Status(ErrorCode code, string? reason) - { - internal readonly string? Reason = reason; - internal readonly ErrorCode Code = code; - internal static readonly Status OK = new(ErrorCode.Ok, null); - } - - /// - /// Basic unit of work for the provider's event loop. - /// - private interface IWork - { - /// - /// Execute the outstanding work. - /// - void Execute(); - } - - private readonly Channel _workQueue; - private readonly Task _workThread; - private readonly Action? _onWebSocketConnection; - private readonly CancellationTokenSource _cts = new(); - - internal SyncSocketProvider(Action? onWebSocketConnection) - { - RealmLogger.Default.Log(LogLevel.Debug, "Creating SyncSocketProvider."); - _onWebSocketConnection = onWebSocketConnection; - _workQueue = Channel.CreateUnbounded(new() { SingleReader = true }); - _workThread = Task.Run(WorkThread); - } - - private partial Task WorkThread(); - - public void Dispose() - { - RealmLogger.Default.Log(LogLevel.Debug, "Destroying SyncSocketProvider."); - _workQueue.Writer.Complete(); - _cts.Cancel(); - _cts.Dispose(); - _workThread.GetAwaiter().GetResult(); - } -} diff --git a/Realm/Realm/Native/UserApiKey.cs b/Realm/Realm/Native/UserApiKey.cs deleted file mode 100644 index 3f27423d27..0000000000 --- a/Realm/Realm/Native/UserApiKey.cs +++ /dev/null @@ -1,44 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Runtime.InteropServices; -using MongoDB.Bson; - -namespace Realms.Native -{ - [StructLayout(LayoutKind.Sequential)] - internal struct UserApiKey - { - internal static readonly int Size = Marshal.SizeOf(); - - private PrimitiveValue id; - - private PrimitiveValue key; - - private PrimitiveValue name; - - [MarshalAs(UnmanagedType.U1)] - public bool disabled; - - public ObjectId Id => ObjectId.Parse(id.AsString()); - - public string? Key => key.Type == RealmValueType.Null ? null : key.AsString(); - - public string Name => name.AsString(); - } -} diff --git a/Realm/Realm/Native/UserProfileField.cs b/Realm/Realm/Native/UserProfileField.cs deleted file mode 100644 index 14d2f7f5f0..0000000000 --- a/Realm/Realm/Native/UserProfileField.cs +++ /dev/null @@ -1,33 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Native -{ - internal enum UserProfileField : byte - { - Name, - Email, - PictureUrl, - FirstName, - LastName, - Gender, - Birthday, - MinAge, - MaxAge, - } -} diff --git a/Realm/Realm/Realm.cs b/Realm/Realm/Realm.cs index 1aa7bdb83a..48a3c4e498 100644 --- a/Realm/Realm/Realm.cs +++ b/Realm/Realm/Realm.cs @@ -33,7 +33,6 @@ using Realms.Logging; using Realms.Native; using Realms.Schema; -using Realms.Sync; using Realms.Weaving; namespace Realms @@ -108,9 +107,7 @@ public static Realm GetInstance(RealmConfigurationBase? config = null) /// Factory for asynchronously obtaining a instance. /// /// - /// If the configuration is , the realm will be downloaded and fully - /// synchronized with the server prior to the completion of the returned Task object. - /// Otherwise this method will perform any migrations on a background thread before returning an + /// This method will perform any migrations on a background thread before returning an /// opened instance to the calling thread. /// /// @@ -140,13 +137,6 @@ public static Task GetInstanceAsync(RealmConfigurationBase? config = null public static bool Compact(RealmConfigurationBase? config = null) { using var realm = GetInstance(config); - if (config is SyncConfigurationBase) - { - // For synchronized Realms, shutdown the session, otherwise Compact will fail. - var session = realm.SyncSession; - session.CloseHandle(waitForShutdown: true); - } - return realm.SharedRealmHandle.Compact(); } @@ -166,28 +156,9 @@ public static void DeleteRealm(RealmConfigurationBase configuration) SharedRealmHandle.DeleteFiles(configuration.DatabasePath); } - /// - /// Sets the serializer to use the legacy serialization. - /// - /// - /// In version 12.0.0 it was introduced a new automatic serialization and deserialization of Realm classes when using methods - /// on , without the need to annotate classes with attributes. - /// This new serialization changed the default serializer for various types ( for instance), so - /// if you need to call this method if you prefer to use the old serialization. - /// Please remember to call this method before any kind of serialization is needed, otherwise it is not guaranteed to work as expected. - /// - [Obsolete("It is recommended to use new serialization.")] - public static void SetLegacySerialization() - { - SerializationHelper.SetLegacySerialization(); - } - #endregion static - private WeakReference? _subscriptionRef; - private State _state; - private SessionProvider? _sessionProvider; private Transaction? _activeTransaction; internal readonly SharedRealmHandle SharedRealmHandle; @@ -239,74 +210,6 @@ public bool IsInTransaction /// The Realm's configuration. public RealmConfigurationBase Config { get; } - /// - /// Gets the for this . - /// - /// - /// Thrown if the Realm has not been opened with a or - /// . - /// - /// - /// The that is responsible for synchronizing with MongoDB Atlas - /// if the Realm instance was created with a or - /// . If this is a local or in-memory Realm, a - /// will be thrown. - /// - public Session SyncSession - { - get - { - ThrowIfDisposed(); - - if (Config is not SyncConfigurationBase) - { - throw new NotSupportedException("Realm.SyncSession is only valid for synchronized Realms (i.e. ones that are opened with FlexibleSyncConfiguration or PartitionSyncConfiguration)."); - } - - _sessionProvider ??= new SessionProvider(SharedRealmHandle); - - return _sessionProvider.GetSession(); - } - } - - /// - /// Gets the representing the active subscriptions for this . - /// - /// Thrown if the Realm has not been opened with a . - /// - /// The containing the query subscriptions that the server is using to decide which objects to - /// synchronize with the local . If the Realm was not created with a , - /// this will throw a . - /// - public SubscriptionSet Subscriptions - { - get - { - ThrowIfDisposed(); - - if (Config is not FlexibleSyncConfiguration) - { - throw new NotSupportedException("Realm.Subscriptions is only valid for flexible sync Realms (i.e. ones that are opened with FlexibleSyncConfiguration)."); - } - - // If the last subscription ref is alive and its version matches the current subscription - // version, we return it. Otherwise, we create a new set and replace the existing one. - if (_subscriptionRef != null && _subscriptionRef.TryGetTarget(out var existingSet)) - { - var currentVersion = SharedRealmHandle.GetSubscriptionsVersion(); - if (existingSet.Version >= currentVersion) - { - return existingSet; - } - } - - var handle = SharedRealmHandle.GetSubscriptions(); - var set = new SubscriptionSet(handle); - _subscriptionRef = new WeakReference(set); - return set; - } - } - internal Realm(SharedRealmHandle sharedRealmHandle, RealmConfigurationBase config, RealmSchema schema, RealmMetadata? metadata = null, bool isInMigration = false) { Config = config; @@ -1348,21 +1251,6 @@ public void WriteCopy(RealmConfigurationBase config) { Argument.NotNull(config, nameof(config)); - if (config is FlexibleSyncConfiguration && Config is not FlexibleSyncConfiguration) - { - throw new NotSupportedException("Writing a copy to a flexible sync realm is not supported unless flexible sync is already enabled"); - } - - if (config is PartitionSyncConfiguration && Config is FlexibleSyncConfiguration) - { - throw new NotSupportedException("Changing from flexible sync sync to partition based sync is not supported when writing a Realm copy."); - } - - if (Config is PartitionSyncConfiguration originalConfig && config is PartitionSyncConfiguration copiedConfig && originalConfig.Partition != copiedConfig.Partition) - { - throw new NotSupportedException($"Changing the partition to synchronize on is not supported when writing a Realm copy. Original partition: {originalConfig.Partition}, passed partition: {copiedConfig.Partition}"); - } - SharedRealmHandle.WriteCopy(config); } @@ -1388,47 +1276,6 @@ internal void ExecuteOutsideTransaction(Action action) #endregion Transactions - internal class SessionProvider - { - private readonly SharedRealmHandle _sharedRealmHandle; - private WeakReference? _weakSessionRef; - private Session? _strongSessionRef; - - public SessionProvider(SharedRealmHandle sharedRealmHandle) - { - _sharedRealmHandle = sharedRealmHandle; - } - - public Session GetSession() - { - if(_strongSessionRef?.IsClosed == false) - { - return _strongSessionRef; - } - - if (_weakSessionRef?.TryGetTarget(out var targetSession) == true && !targetSession.IsClosed) - { - return targetSession; - } - - var session = new Session(_sharedRealmHandle.GetSession(), OnSessionSubscribed, OnSessionUnsubscribed); - - _weakSessionRef = new WeakReference(session); - - return session; - } - - private void OnSessionSubscribed(Session session) - { - _strongSessionRef = session; - } - - private void OnSessionUnsubscribed() - { - _strongSessionRef = null; - } - } - internal class RealmMetadata { private readonly Dictionary stringToRealmObjectMetadataDict; diff --git a/Realm/Realm/Sync/ApiKey.cs b/Realm/Realm/Sync/ApiKey.cs deleted file mode 100644 index 3e3cf59ea2..0000000000 --- a/Realm/Realm/Sync/ApiKey.cs +++ /dev/null @@ -1,86 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using MongoDB.Bson; -using Realms.Native; - -namespace Realms.Sync -{ - /// - /// A class representing an API key for a . It can be used to represent the user when logging in - /// instead of their regular credentials. These keys are created or fetched through . - ///
- /// An API key's is only available when the key is created and cannot be obtained after that. - /// This means that it's the caller's responsibility to safely store an API key's value upon creation. - ///
- /// API Key Authentication Docs - public class ApiKey - { - /// - /// Gets the unique identifier for this key. - /// - /// The id uniquely identifying the key. - [Preserve] - public ObjectId Id { get; } - - /// - /// Gets the name of the key. - /// - /// The friendly name of the key, specified when calling . - [Preserve] - public string Name { get; } - - /// - /// Gets the value for the key. This is only returned when the key is created. After that, it will always be null. - /// - /// The value of the key that needs to be provided when constructing . - [Preserve] - public string? Value { get; } - - /// - /// Gets a value indicating whether or not this key is currently enabled. - /// - /// true if the key is enabled; false otherwise. - [Preserve] - public bool IsEnabled { get; } - - /// - public override bool Equals(object? obj) => (obj is ApiKey key) && key.Id == Id; - - /// - /// Gets the hash code. - /// - /// The hash code. - public override int GetHashCode() => Id.GetHashCode(); - - /// - /// Returns a string representation of the value. - /// - /// A string representation of the value. - public override string ToString() => $"ApiKey {Name} ({Id})"; - - [Preserve] - internal ApiKey(UserApiKey nativeKey) - { - Id = nativeKey.Id; - Name = nativeKey.Name; - Value = nativeKey.Key; - IsEnabled = !nativeKey.disabled; - } - } -} diff --git a/Realm/Realm/Sync/App.cs b/Realm/Realm/Sync/App.cs deleted file mode 100644 index 88036677bc..0000000000 --- a/Realm/Realm/Sync/App.cs +++ /dev/null @@ -1,511 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Net.Http; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Realms.Helpers; -using Realms.Native; - -namespace Realms.Sync -{ - /// - /// An is the main client-side entry point for interacting with a Atlas App Services application. - ///

- /// The App can be used to: - ///
- /// - /// - /// Register uses and perform various user-related operations through authentication providers (e.g. , ). - /// - /// - /// Synchronize data between the local device and a remote Realm App with Synchronized Realms (using ). - /// - /// - /// Invoke Realm App functions with Functions (using ). - /// - /// - /// Access remote data from MongoDB databases with a (using ). - /// - /// - ///
- /// To create an app that is linked with a remote Realm App initialize Realm and configure the App as shown below: - /// - /// var appConfig = new AppConfiguration("my-realm-app-id"); - /// var app = new App(appConfig); - /// - /// After configuring the App you can start managing users, configure Synchronized Realms, call remote Realm Functions, and access remote data through Mongo Collections. - ///
- /// To register a new user and/or login with an existing user do as shown below: - /// - /// await app.EmailPassword.RegisterUserAsync("foo@bar.com", "password"); - /// // Login with existing user - /// var user = app.LoginAsync(Credentials.EmailPassword("foo@bar.com", "password"); - /// - /// With an authorized user you can synchronize data between the local device and the remote Realm App by opening a Realm with a as indicated below: - /// - /// var syncConfig = new PartitionSyncConfiguration("some-partition-value", user); - /// using var realm = await Realm.GetInstanceAsync(syncConfig); - /// - /// realm.Write(() => - /// { - /// realm.Add(...); - /// }); - /// - /// await realm.GetSession().WaitForUploadAsync(); - /// - /// You can call remote Realm functions as shown below: - /// - /// var result = await user.Functions.CallAsync<int>("sum", 1, 2, 3, 4, 5); - /// - /// And access collections from the remote Realm App as shown here: - /// - /// var client = user.GetMongoClient("atlas-service"); - /// var db = client.GetDatabase("my-db"); - /// var collection = db.GetCollection("foos"); - /// var foosCount = await collection.CountAsync(); - /// - ///
- public class App - { - internal readonly AppHandle Handle; - - /// - /// Gets a instance that exposes API for interacting with the synchronization client for this . - /// - /// A instance scoped to this . - public SyncClient Sync => new(this); - - /// - /// Gets a instance that exposes functionality related to users either being created or logged in using - /// the provider. - /// - /// An instance scoped to this . - public EmailPasswordClient EmailPasswordAuth => new(this); - - /// - /// Gets the currently user. If none exists, null is returned. - /// - /// Valid user or null to indicate nobody logged in. - public User? CurrentUser => Handle.TryGetCurrentUser(out var userHandle) ? new User(userHandle, this) : null; - - /// - /// Gets all currently logged in users. - /// - /// An array of valid logged in users. - public User[] AllUsers => Handle.GetAllLoggedInUsers() - .Select(handle => new User(handle, this)) - .ToArray(); - - /// - /// Gets the root folder relative to which all local data for this application will be stored. This data includes - /// metadata for users and synchronized Realms. - /// - /// The app's base path. - /// - public string BaseFilePath => Handle.GetBaseFilePath(); - - /// - /// Gets the base url for this Realm application. - /// - /// The app's base url. - /// - public Uri BaseUri => Handle.GetBaseUri(); - - /// - /// Gets the unique app id that identifies the Realm application. - /// - /// The Atlas App Services App's id. - public string Id => Handle.GetId(); - - internal App(AppHandle handle) - { - Handle = handle; - } - - /// - /// A factory method for creating an app with a particular . - /// - /// The , specifying key parameters for the app behavior. - /// An instance can now be used to login users, call functions, or open synchronized Realms. - public static App Create(AppConfiguration config) - { - Argument.NotNull(config, nameof(config)); - var syncTimeouts = config.SyncTimeoutOptions; - - if (config.MetadataPersistenceMode.HasValue) - { - if (config.MetadataPersistenceMode == MetadataPersistenceMode.Encrypted && config.MetadataEncryptionKey == null) - { - throw new ArgumentException($"{nameof(AppConfiguration.MetadataEncryptionKey)} must be set when {nameof(AppConfiguration.MetadataPersistenceMode)} is set to {nameof(MetadataPersistenceMode.Encrypted)}."); - } - - if (config.MetadataPersistenceMode != MetadataPersistenceMode.Encrypted && config.MetadataEncryptionKey != null) - { - throw new ArgumentException($"{nameof(AppConfiguration.MetadataPersistenceMode)} must be set to {nameof(MetadataPersistenceMode.Encrypted)} when {nameof(AppConfiguration.MetadataEncryptionKey)} is set."); - } - } - - var httpClient = config.HttpClientHandler is null ? new HttpClient() : new HttpClient(config.HttpClientHandler); - var clientHandle = GCHandle.Alloc(httpClient); - var nativeConfig = new Native.AppConfiguration - { - AppId = config.AppId, - BaseFilePath = config.BaseFilePath, - BaseUrl = config.BaseUri.ToString().TrimEnd('/'), - MetadataPersistence = config.MetadataPersistenceMode, - default_request_timeout_ms = (ulong)config.DefaultRequestTimeout.TotalMilliseconds, - managed_http_client = GCHandle.ToIntPtr(clientHandle), - sync_connection_linger_time_ms = (ulong)syncTimeouts.ConnectionLingerTime.TotalMilliseconds, - sync_connect_timeout_ms = (ulong)syncTimeouts.ConnectTimeout.TotalMilliseconds, - sync_fast_reconnect_limit = (ulong)syncTimeouts.FastReconnectLimit.TotalMilliseconds, - sync_ping_keep_alive_period_ms = (ulong)syncTimeouts.PingKeepAlivePeriod.TotalMilliseconds, - sync_pong_keep_alive_timeout_ms = (ulong)syncTimeouts.PongKeepAliveTimeout.TotalMilliseconds, - use_cache = config.UseAppCache, - }; - - if (config.UseManagedWebSockets) - { - var provider = new SyncSocketProvider(config.OnSyncWebSocketConnection); - nativeConfig.managed_websocket_provider = GCHandle.ToIntPtr(GCHandle.Alloc(provider)); - } - else if (config.OnSyncWebSocketConnection is not null) - { - throw new ArgumentException($"{nameof(AppConfiguration.OnSyncWebSocketConnection)} cannot be used unless {nameof(AppConfiguration.UseManagedWebSockets)} is enabled."); - } - - var handle = AppHandle.CreateApp(nativeConfig, config.MetadataEncryptionKey); - return new App(handle); - } - - /// - /// A factory method for creating an app with a particular app Id. - /// - /// - /// This is a convenience method that creates an with the default parameters and the provided - /// and invokes . - /// - /// The application id of the Atlas App Services Application. - /// An instance can now be used to login users, call functions, or open synchronized Realms. - public static App Create(string appId) => Create(new AppConfiguration(appId)); - - /// - /// Logs in as a user with the given credentials associated with an authentication provider. - /// - /// - /// The last logged in user will be saved as . If there was already a current user, - /// that user is still logged in and can be found in the list returned by . It is also - /// possible to switch between which user is considered the current user by using . - /// - /// The representing the type of login. - /// - /// An awaitable that represents the asynchronous LogIn operation. - public async Task LogInAsync(Credentials credentials) - { - Argument.NotNull(credentials, nameof(credentials)); - - var handle = await Handle.LogInAsync(credentials.ToNative()); - - return new User(handle, this); - } - - /// - /// Switches the to the one specified in . - /// - /// The new current user. - public void SwitchUser(User user) - { - Argument.NotNull(user, nameof(user)); - - Handle.SwitchUser(user.Handle); - } - - /// - /// Removes a user and their local data from the device. If the user is logged in, they will be logged out in the process. - /// - /// - /// This is client operation and will not delete any data stored on the server for that user. - /// - /// The user to log out and remove. - /// - /// An awaitable that represents the asynchronous RemoveUser operation. Successful completion indicates that the user has been logged out, - /// their local data - removed, and the user's - revoked on the server. - /// - public Task RemoveUserAsync(User user) - { - Argument.NotNull(user, nameof(user)); - - return Handle.RemoveAsync(user.Handle); - } - - /// - /// Deletes a user from the server. The user is also removed from the device together with their local data. If the user is logged in, they will be logged out in the process. - /// - /// The user to remove from the server. - /// - /// An awaitable that represents the asynchronous deletion operation. - /// Successful completion indicates that the user has been removed, logged out and their local data has been removed. - /// - public Task DeleteUserFromServerAsync(User user) - { - Argument.NotNull(user, nameof(user)); - - return Handle.DeleteUserAsync(user.Handle); - } - - /// - /// Temporarily overrides the value from - /// with a new value used for communicating with the server. - /// - /// - /// The new uri that will be used for communicating with the server. If set to null, the base uri will - /// be reset to its default value. - /// - /// An awaitable that represents the asynchronous operation. - /// - /// The App will revert to using the value in [AppConfiguration] when it is restarted. - ///
- /// This API must be called after sync sessions have been manually stopped and at a point - /// where the server at is reachable. Once the base uri has been - /// updated, sync sessions should be resumed and the user needs to reauthenticate. - ///
- /// This API is experimental and subject to change without a major version increase. - ///
- [Experimental("Rlm001", UrlFormat = "www.mongodb.com/docs/atlas/app-services/edge-server/connect/#roaming-between-edge-servers")] - public Task UpdateBaseUriAsync(Uri? newUri) => Handle.UpdateBaseUriAsync(newUri); - - /// - public override bool Equals(object? obj) - { - if (obj is not App app) - { - return false; - } - - return Handle.IsSameInstance(app.Handle); - } - - /// - public override int GetHashCode() => Id.GetHashCode(); - - /// - /// Determines whether two instances are equal. - /// - /// The first app to compare. - /// The second app to compare. - /// true if the two instances are equal; false otherwise. - public static bool operator ==(App? app1, App? app2) - { - if (app1 is null || app2 is null) - { - return app1 is null && app2 is null; - } - - return app1.Equals(app2); - } - - /// - /// Determines whether two instances are different. - /// - /// The first app to compare. - /// The second app to compare. - /// true if the two instances are different; false otherwise. - public static bool operator !=(App? app1, App? app2) => !(app1 == app2); - - /// - /// A sync manager, handling synchronization of local Realm with MongoDB Atlas. It is always scoped to a - /// particular app and can only be accessed via . - /// - public readonly struct SyncClient - { - private readonly App _app; - - internal SyncClient(App app) - { - _app = app; - } - - /// - /// Attempt to reconnect all Sync sessions for the app. - /// - /// - /// Realm will automatically detect when a device gets connectivity after being offline and resume syncing. - /// However, some of these checks are performed using incremental backoff, which means that there are cases - /// when automatic reconnection doesn't happen immediately. In those cases, it can be beneficial to call - /// this method manually, which will force all sessions to attempt to reconnect and in the process, reset - /// any timers, that are used for incremental backoff. - /// - public void Reconnect() - { - _app.Handle.Reconnect(); - } - } - - /// - /// A class, encapsulating functionality for users, logged in with the provider. - /// It is always scoped to a particular app and can only be accessed via . - /// - public readonly struct EmailPasswordClient - { - private readonly App _app; - - internal EmailPasswordClient(App app) - { - _app = app; - } - - /// - /// Registers a new user with the given email and password. - /// - /// - /// The email to register with. This will be the user's username and, if user confirmation is enabled, this will be the address for - /// the confirmation email. - /// - /// The password to associate with the email. The password must be between 6 and 128 characters long. - /// - /// An awaitable representing the asynchronous RegisterUser operation. Successful completion indicates that the user has been - /// created on the server and can now be logged in calling with . - /// - public Task RegisterUserAsync(string email, string password) - { - Argument.NotNullOrEmpty(email, nameof(email)); - Argument.NotNullOrEmpty(password, nameof(password)); - - return _app.Handle.EmailPassword.RegisterUserAsync(email, password); - } - - /// - /// Confirms a user with the given token and token id. These are typically included in the email the user received - /// after registering. - /// - /// - /// While confirmation typically happens in a web app, mobile applications that have deep linking enabled can intercept the url - /// and complete the user confirmation flow in the app itself. - /// - /// The confirmation token. - /// The id of the confirmation token. - /// - /// An awaitable representing the asynchronous ConfirmUser operation. Successful completion indicates that the user has been - /// confirmed on the server. - /// - public Task ConfirmUserAsync(string token, string tokenId) - { - Argument.NotNullOrEmpty(token, nameof(token)); - Argument.NotNullOrEmpty(tokenId, nameof(tokenId)); - - return _app.Handle.EmailPassword.ConfirmUserAsync(token, tokenId); - } - - /// - /// Resends the confirmation email for a user to the given email. - /// - /// The email of the user. - /// - /// An awaitable representing the asynchronous request to the server that a confirmation email is sent. Successful - /// completion indicates that the server has accepted the request and will send a confirmation email to the specified address - /// if a user with that email exists. - /// - public Task ResendConfirmationEmailAsync(string email) - { - Argument.NotNullOrEmpty(email, nameof(email)); - - return _app.Handle.EmailPassword.ResendConfirmationEmailAsync(email); - } - - /// - /// Rerun the custom confirmation function for the given mail. - /// - /// The email of the user. - /// - /// An awaitable representing the asynchronous request to the server that the custom confirmation function is run again. Successful - /// completion indicates that the user has been confirmed on the server. - /// - public Task RetryCustomConfirmationAsync(string email) - { - Argument.NotNullOrEmpty(email, nameof(email)); - - return _app.Handle.EmailPassword.RetryCustomConfirmationAsync(email); - } - - /// - /// Sends a password reset email to the specified address. - /// - /// the email of the user. - /// - /// An awaitable representing the asynchronous request to the server that a reset password email is sent. Successful - /// completion indicates that the server has accepted the request and will send a password reset email to the specified - /// address if a user with that email exists. - /// - public Task SendResetPasswordEmailAsync(string email) - { - Argument.NotNullOrEmpty(email, nameof(email)); - - return _app.Handle.EmailPassword.SendResetPasswordEmailAsync(email); - } - - /// - /// Completes the reset password flow by providing the desired new password. - /// - /// - /// While the reset password flow is typically completed in the web app, mobile applications that have deep linking enabled can intercept the url - /// and complete the password reset flow in the app itself. - /// - /// The new password for the user. - /// The password reset token that was sent to the user's email address. - /// The password reset token id that was sent together with the to the user's email address. - /// - /// An awaitable representing the asynchronous request that a user's password is reset. Successful completion indicates that the user's password has been - /// reset and they can now use the new password to create credentials and call to login. - /// - public Task ResetPasswordAsync(string password, string token, string tokenId) - { - Argument.NotNullOrEmpty(token, nameof(token)); - Argument.NotNullOrEmpty(tokenId, nameof(tokenId)); - Argument.NotNullOrEmpty(password, nameof(password)); - - return _app.Handle.EmailPassword.ResetPasswordAsync(password, token, tokenId); - } - - /// - /// Calls the reset password function, configured on the server. - /// - /// The email of the user. - /// The new password of the user. - /// - /// Any additional arguments provided to the reset function. All arguments must be serializable to JSON - /// compatible values. - /// - /// - /// An awaitable representing the asynchronous request to call a password reset function. Successful completion indicates - /// that the user's password has been change and they can now use the new password to create - /// credentials and call to login. - /// - public Task CallResetPasswordFunctionAsync(string email, string password, params object?[] functionArgs) - { - Argument.NotNullOrEmpty(email, nameof(email)); - Argument.NotNullOrEmpty(password, nameof(password)); - Argument.NotNull(functionArgs, nameof(functionArgs)); - - return _app.Handle.EmailPassword.CallResetPasswordFunctionAsync(email, password, functionArgs.ToNativeJson()); - } - } - } -} diff --git a/Realm/Realm/Sync/AppConfiguration.cs b/Realm/Realm/Sync/AppConfiguration.cs deleted file mode 100644 index 2340eeb7ce..0000000000 --- a/Realm/Realm/Sync/AppConfiguration.cs +++ /dev/null @@ -1,183 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Net.Http; -using System.Net.WebSockets; -using Realms.Helpers; - -namespace Realms.Sync -{ - /// - /// A class exposing configuration options for a . - /// - public class AppConfiguration - { - private string? _baseFilePath; - - private byte[]? _metadataEncryptionKey; - - /// - /// Gets the unique app id that identifies the Realm application. - /// - /// The Atlas App Services App's id. - public string AppId { get; } - - /// - /// Gets or sets the root folder relative to which all local data for this application will be stored. This data includes - /// metadata for users and synchronized Realms. - /// - /// The app's base path. - public string BaseFilePath - { - get => _baseFilePath ?? InteropConfig.GetDefaultStorageFolder("Could not determine a writable folder to store app files (such as metadata and Realm files). When constructing the app, set AppConfiguration.BaseFilePath to an absolute path where the app is allowed to write."); - set - { - Argument.NotNull(value, nameof(value)); - _baseFilePath = value; - } - } - - /// - /// Gets or sets the base url for this Realm application. - /// - /// - /// This only needs to be set if for some reason your application isn't hosted on services.cloud.mongodb.com. - /// This is typically the case when synchronizing with an edge server. - /// - /// The app's base url. - public Uri BaseUri { get; set; } = AppHandle.DefaultBaseUri; - - /// - /// Gets or sets the local app's name. - /// - /// The friendly name identifying the current client application. - [Obsolete("This property has no effect and will be removed in a future version.")] - public string? LocalAppName { get; set; } - - /// - /// Gets or sets the local app's version. - /// - /// The client application's version. - /// - [Obsolete("This property has no effect and will be removed in a future version.")] - public string? LocalAppVersion { get; set; } - - /// - /// Gets or sets the persistence mode for user metadata on this device. - /// - /// - /// The default value is for iOS devices and - /// for all other platforms. On iOS we integrate with the system keychain to generate and store a random encryption key the first time the app - /// is launched. On other platforms, needs to be set if is - /// specified. - /// - /// The user metadata persistence mode. - public MetadataPersistenceMode? MetadataPersistenceMode { get; set; } - - /// - /// Gets or sets the encryption key for user metadata on this device. - /// - /// - /// This will not change the encryption key for individual Realms. This should still be set in - /// when opening the . - /// - /// The user metadata encryption key. - public byte[]? MetadataEncryptionKey - { - get => _metadataEncryptionKey; - set - { - if (value != null && value.Length != 64) - { - throw new FormatException("EncryptionKey must be 64 bytes"); - } - - _metadataEncryptionKey = value; - } - } - - /// - /// Gets or sets the default request timeout for HTTP requests to MongoDB Atlas. Default is 1 minute. - /// - /// The default HTTP request timeout. - public TimeSpan DefaultRequestTimeout { get; set; } = TimeSpan.FromMinutes(1); - - /// - /// Gets or sets the that will be used - /// for the http requests to MongoDB Atlas. - /// - /// The http client handler that configures things like certificates and proxy settings. - /// - /// You can use this to override the default http client handler and configure settings like proxies, - /// client certificates, and cookies. While these are not required to connect to MongoDB Atlas under - /// normal circumstances, they can be useful if client devices are behind a corporate firewall or use - /// a more complex networking setup. - /// - public HttpMessageHandler? HttpClientHandler { get; set; } - - /// - /// Gets or sets the delegate that will be used to configure outgoing WebSocket connections to MongoDB Atlas. - /// - /// The delegate that will be used to configure outgoing WebSocket connections. - /// - /// You can use this to modify the default behavior. Normally this is not - /// required to connect to MongoDB Atlas, but it can be useful if client devices are behind a corporate firewall - /// or use a more complex networking setup. Requires that is set to true. - /// - public Action? OnSyncWebSocketConnection { get; set; } - - /// - /// Gets or sets a value indicating whether the .NET WebSocket client or the built-in Realm WebSocket client - /// will be used for Sync traffic. - /// - /// true to use ; false to use the built-in Realm WebSocket client. - /// The default value is false, but this will change in a future version. - /// - public bool UseManagedWebSockets { get; set; } - - /// - /// Gets or sets the options for the assorted types of connection timeouts for sync connections - /// opened for this app. - /// - /// The sync timeout options applied to synchronized Realms. - public SyncTimeoutOptions SyncTimeoutOptions { get; set; } = new(); - - /// - /// Gets or sets a value indicating whether to cache app instances created with this configuration. - /// - /// - /// When an app is created using , the default behavior is - /// to get or add the instance from a cache keyed on the app id. This has certain - /// performance benefits when calling multiple times. - /// - /// true if the app should be cached; false otherwise. Default value is true. - public bool UseAppCache { get; set; } = true; - - /// - /// Initializes a new instance of the class with the specified . - /// - /// The Atlas App Services App id. - public AppConfiguration(string appId) - { - Argument.NotNullOrEmpty(appId, nameof(appId)); - - AppId = appId; - } - } -} diff --git a/Realm/Realm/Sync/ClientResyncMode.cs b/Realm/Realm/Sync/ClientResyncMode.cs deleted file mode 100644 index 1c4e928803..0000000000 --- a/Realm/Realm/Sync/ClientResyncMode.cs +++ /dev/null @@ -1,28 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - internal enum ClientResyncMode : byte - { - Manual = 0, - Discard = 1, - Recover = 2, - RecoverOrDiscard = 3, - } -} diff --git a/Realm/Realm/Sync/ConnectionState.cs b/Realm/Realm/Sync/ConnectionState.cs deleted file mode 100644 index f6ea4b8682..0000000000 --- a/Realm/Realm/Sync/ConnectionState.cs +++ /dev/null @@ -1,41 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - /// - /// The current connection state of a sync session object. - /// - public enum ConnectionState : byte - { - /// - /// The session is disconnected from Atlas Device Sync. - /// - Disconnected = 0, - - /// - /// The session is connecting to the Atlas Device Sync. - /// - Connecting = 1, - - /// - /// The session is connected to the Atlas Device Sync. - /// - Connected = 2 - } -} diff --git a/Realm/Realm/Sync/Credentials.cs b/Realm/Realm/Sync/Credentials.cs deleted file mode 100644 index 6e303eeca1..0000000000 --- a/Realm/Realm/Sync/Credentials.cs +++ /dev/null @@ -1,251 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using Realms.Helpers; - -namespace Realms.Sync -{ - /// - /// A class, representing the credentials used for authenticating a . - /// - public class Credentials - { - /// - /// An enum containing the possible authentication providers. These have to manually be enabled for - /// your app before they can be used. - /// - /// Authentication Providers Docs - public enum AuthProvider - { - /// - /// Mechanism for authenticating without credentials. - /// - Anonymous = 0, - - /// - /// OAuth2-based mechanism for logging in with an existing Facebook account. - /// - Facebook = 2, - - /// - /// Mechanism for logging in with an existing Google account using an auth code or Id token. - /// - Google = 3, - - /// - /// OAuth2-based mechanism for logging in with an Apple ID. - /// - Apple = 4, - - /// - /// Allow users to log in with JWT-based credentials generated by a service external to Realm. - /// - JWT = 5, - - /// - /// Mechanism for authenticating with an email and a password. - /// - EmailPassword = 6, - - /// - /// Allow users to log in with arbitrary credentials according to custom authentication logic that you define - /// on the server. - /// - Function = 7, - - /// - /// Mechanism for logging in with API keys generated by the client SDK. - /// - ApiKey = 8, - - /// - /// Mechanism for logging in with API keys generated in the server UI. - /// - [Obsolete("Use ApiKey instead.")] - ServerApiKey = 9, - - /// - /// A provider that is not among the well known provider types. This is most likely the result of the server - /// introducing a new provider type that this version of the SDK doesn't know about. - /// - Unknown = 999, - } - - /// - /// Creates credentials representing an anonymous user. - /// - /// - /// A value indicating whether anonymous users should be reused. Passing true means that multiple calls to - /// app.LoginAsync(Credentials.Anonymous()) will return the same anonymous user as long as that user hasn't - /// logged out. If is false, every time you call , - /// a new user will be created on the server. - /// - /// A Credentials that can be used to authenticate an anonymous user. - /// Anonymous Authentication Docs - public static Credentials Anonymous(bool reuseExisting = true) => new(AuthProvider.Anonymous, additionalInfo: reuseExisting.ToString().ToLower()); - - /// - /// Creates credentials representing a login using a Facebook access token. - /// - /// The OAuth 2.0 access token representing the Facebook user. - /// A Credentials that can be used to authenticate a user with Facebook. - /// Facebook Authentication Docs - public static Credentials Facebook(string accessToken) - { - Argument.NotNull(accessToken, nameof(accessToken)); - - return new Credentials(AuthProvider.Facebook, accessToken); - } - - /// - /// Creates credentials representing a login using a Google account. - /// - /// The credential representing the Google user. - /// The type of the credential. - /// A Credentials that can be used to authenticate a user with Google. - /// Google Authentication Docs - public static Credentials Google(string credential, GoogleCredentialType type) - { - Argument.NotNull(credential, nameof(credential)); - return new Credentials(AuthProvider.Google, credential, type.ToString()); - } - - /// - /// Creates credentials representing a login using an Apple ID access token. - /// - /// The OAuth 2.0 access token representing the user's Apple ID. - /// A Credentials that can be used to authenticate a user via an Apple ID. - /// Apple ID Authentication Docs - public static Credentials Apple(string accessToken) - { - Argument.NotNull(accessToken, nameof(accessToken)); - - return new Credentials(AuthProvider.Apple, accessToken); - } - - /// - /// Creates credentials representing a login using a JWT Token. - /// - /// The custom JWT token representing the user. - /// A Credentials that can be used to authenticate a user with a custom JWT Token. - /// Custom JWT Authentication Docs - public static Credentials JWT(string customToken) - { - Argument.NotNull(customToken, nameof(customToken)); - - return new Credentials(AuthProvider.JWT, customToken); - } - - /// - /// Creates credentials representing a login using an email and password. - /// - /// The user's email. - /// The user's password. - /// A Credentials that can be used to authenticate a user with their email and password. - /// - /// A user can login with email and password only after they've registered their account and verified their - /// email. To register an email/password user via the SDK, use . - /// To verify an email from the SDK, use . The email/password - /// provider can also be configured to automatically confirm users or to run a custom confirmation function upon - /// user registration. - /// - /// Email/Password Authentication Docs - public static Credentials EmailPassword(string email, string password) - { - Argument.NotNullOrEmpty(email, nameof(email)); - Argument.NotNullOrEmpty(password, nameof(password)); - - return new Credentials(AuthProvider.EmailPassword, email, password); - } - - /// - /// Creates credentials representing a login with Realm function. - /// - /// The payload that will be passed as an argument to the server function. - /// A Credentials that can be used to authenticate a user by invoking a server function. - /// - /// The payload object will be serialized and parsed when invoking the Realm function. This means that - /// unserializable values, such as references to functions or cyclic object graphs will not work. - /// Additionally, the names of the fields/properties must match exactly the names that your function - /// expects. - /// - /// Custom Function Authentication Docs - public static Credentials Function(object payload) - { - return new Credentials(AuthProvider.Function, payload.ToNativeJson()); - } - - /// - /// Creates credentials representing a login using an API key generated by a client SDK. - /// - /// The API key to use for login. - /// A Credentials that can be used to authenticate user with an API key. - /// API Key Authentication Docs - public static Credentials ApiKey(string key) - { - Argument.NotNull(key, nameof(key)); - - return new Credentials(AuthProvider.ApiKey, key); - } - - /// - /// Creates credentials representing a login using an API key generated in the server UI. - /// - /// The server API key to use for login. - /// A Credentials that can be used to authenticate user with an API key. - /// API Key Authentication Docs - [Obsolete("Use Credentials.ApiKey instead.")] - public static Credentials ServerApiKey(string serverApiKey) - { - Argument.NotNull(serverApiKey, nameof(serverApiKey)); - - return new Credentials(AuthProvider.ServerApiKey, serverApiKey); - } - - /// - /// Gets a value indicating which these Credentials are using. - /// - /// The these credentials use. - [Preserve] - public AuthProvider Provider { get; } - - [Preserve] - internal string? Token { get; } - - [Preserve] - internal string? AdditionalInfo { get; } - - private Credentials(AuthProvider provider, string? token = null, string? additionalInfo = null) - { - Provider = provider; - Token = token; - AdditionalInfo = additionalInfo; - } - - internal Native.Credentials ToNative() - { - return new Native.Credentials - { - provider = Provider, - Token = Token, - AdditionalInfo = AdditionalInfo, - }; - } - } -} diff --git a/Realm/Realm/Sync/ErrorHandling/ClientResetHandlerBase.cs b/Realm/Realm/Sync/ErrorHandling/ClientResetHandlerBase.cs deleted file mode 100644 index 2e16c16c74..0000000000 --- a/Realm/Realm/Sync/ErrorHandling/ClientResetHandlerBase.cs +++ /dev/null @@ -1,77 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using Realms.Sync.Exceptions; - -namespace Realms.Sync.ErrorHandling -{ - /// - /// The base class for the different types of client reset handlers. The possible implementations are , - /// , and . - /// To use either of them, create a new instance and assign it to on the configuration - /// you use to open the synchronized instance. - /// - /// Client Resets - .NET SDK - public abstract class ClientResetHandlerBase - { - /// - /// Callback triggered when a Client Reset error happens in a synchronized Realm. - /// - /// - /// The specific that holds useful data to be used when trying to manually recover from a client reset. - /// - public delegate void ClientResetCallback(ClientResetException clientResetException); - - /// - /// Callback that indicates a Client Reset is about to happen. - /// - /// - /// The frozen before the reset. - /// - /// - /// The lifetime of the Realm is tied to the callback, so don't store references to the Realm or objects - /// obtained from it for use outside of the callback. If you need to preserve the state as it was, use - /// to create a backup. - /// - public delegate void BeforeResetCallback(Realm beforeFrozen); - - /// - /// Callback that indicates a Client Reset has just happened. - /// - /// - /// The frozen as it was before the reset. - /// - /// - /// The after the client reset. In order to modify this realm a write transaction needs to be started. - /// - /// - /// The lifetime of the Realm instances supplied is tied to the callback, so don't store references to - /// the Realm or objects obtained from it for use outside of the callback. If you need to preserve the - /// state as it was, use to create a backup. - /// - public delegate void AfterResetCallback(Realm beforeFrozen, Realm after); - - internal abstract ClientResyncMode ClientResetMode { get; } - - internal ClientResetCallback? ManualClientReset { get; set; } - - internal ClientResetHandlerBase() - { - } - } -} diff --git a/Realm/Realm/Sync/ErrorHandling/DiscardUnsyncedChangesHandler.cs b/Realm/Realm/Sync/ErrorHandling/DiscardUnsyncedChangesHandler.cs deleted file mode 100644 index c1a4f69290..0000000000 --- a/Realm/Realm/Sync/ErrorHandling/DiscardUnsyncedChangesHandler.cs +++ /dev/null @@ -1,62 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync.ErrorHandling -{ - /// - /// A client reset strategy where all the not yet synchronized data is automatically discarded and a fresh copy of the synchronized Realm is obtained. - /// - /// - /// The freshly downloaded copy of the synchronized Realm triggers all change notifications as a write transaction is internally simulated. - /// This strategy supplies three callbacks: , , and . - /// The first two are invoked just before and after the client reset has happened, - /// while the last one will be invoked in case an error occurs during the automated process and the system needs to fallback to a manual mode. - /// The overall recommendation for using this strategy is that using the three available callbacks should only be considered when: - /// 1. the user needs to be notified (in ) of an incoming potential data loss of unsynced data - /// 2. the user needs to be notified (in ) that the reset process has completed - /// 3. advanced use cases for data-sensitive applications where the developer wants to recover in the most appropriate way the unsynced data - /// 4. backup the whole realm before the client reset happens (in ). Such backup could, for example, be used to restore the unsynced data (see 3.) - /// - /// Client Resets - .NET SDK - public sealed class DiscardUnsyncedChangesHandler : ClientResetHandlerBase - { - /// - /// Gets or sets the callback that indicates a Client Reset is about to happen. - /// - /// Callback invoked right before a Client Reset. - public BeforeResetCallback? OnBeforeReset { get; set; } - - /// - /// Gets or sets the callback that indicates a Client Reset just happened. - /// - /// Callback invoked right after a Client Reset. - public AfterResetCallback? OnAfterReset { get; set; } - - internal override ClientResyncMode ClientResetMode => ClientResyncMode.Discard; - - /// - /// Gets or sets the callback triggered when an error has occurred that makes the operation unable to complete, for example in the case of a destructive schema change. - /// - /// Callback invoked if automatic Client Reset handling fails. - public ClientResetCallback? ManualResetFallback - { - get => ManualClientReset; - set => ManualClientReset = value; - } - } -} diff --git a/Realm/Realm/Sync/ErrorHandling/ManualRecoveryHandler.cs b/Realm/Realm/Sync/ErrorHandling/ManualRecoveryHandler.cs deleted file mode 100644 index 7cd4e9c5d5..0000000000 --- a/Realm/Realm/Sync/ErrorHandling/ManualRecoveryHandler.cs +++ /dev/null @@ -1,48 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync.ErrorHandling -{ - /// - /// A client reset strategy where the user needs to fully take care of a client reset. - /// - /// Client Resets - .NET SDK - public sealed class ManualRecoveryHandler : ClientResetHandlerBase - { - /// - /// Gets the callback to manually handle a Client Reset. - /// A Client Reset should be handled as quickly as possible as any further changes to the Realm will not be synchronized with the server and - /// must be moved manually from the backup Realm to the new one. - /// - /// Callback invoked on Client Reset. - public ClientResetCallback OnClientReset => ManualClientReset!; - - internal override ClientResyncMode ClientResetMode => ClientResyncMode.Manual; - - /// - /// Initializes a new instance of the class with the supplied client reset handler. - /// - /// - /// Callback triggered when a manual client reset happens. - /// - public ManualRecoveryHandler(ClientResetCallback onClientReset) - { - ManualClientReset = onClientReset; - } - } -} diff --git a/Realm/Realm/Sync/ErrorHandling/RecoverOrDiscardUnsyncedChangesHandler.cs b/Realm/Realm/Sync/ErrorHandling/RecoverOrDiscardUnsyncedChangesHandler.cs deleted file mode 100644 index 32a6fa51b8..0000000000 --- a/Realm/Realm/Sync/ErrorHandling/RecoverOrDiscardUnsyncedChangesHandler.cs +++ /dev/null @@ -1,89 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync.ErrorHandling -{ - /// - /// A client reset strategy that attempts to automatically recover any unsynchronized changes. - /// If that fails, this handler falls back to the discard unsynced changes strategy. - /// - /// - /// The automatic recovery fails when a client that is configured for recovery is flagged on the server "as not allowed to execute automatic recovery". - /// In this situation this strategy falls back to the discard unsynced one. - /// To reiterate what it does: it discards all the unsynced local changes and uses the latest realm that is available on the remote sync server. - /// You can read more about the automatic merge rules at Client Resets - .NET SDK. - /// The automatic recovery mechanism creates write transactions meaning that all the changes that take place - /// are properly propagated through the standard Realm's change notifications. - /// The strategy supplies four callbacks: , , - /// and . - /// , is invoked just before the client reset happens. - /// , is invoke if and only if an automatic client reset succeeded. The callback is never called - /// if the automatic client reset fails. - /// is invoked if and only if an automatic client reset failed and instead the discard unsynced one succeeded. - /// The callback is never called if the discard unsynced client reset fails. - /// is invoked whenever an error occurs in either of the recovery strategies and the system needs to fallback to a manual mode. - /// The overall recommendation for using this strategy is that using the three available callbacks should only be considered when: - /// 1. The user needs to be notified (in ) of an incoming potential data loss - /// of unsynced data as a result of a merge or a complete discard of unsynced local changes - /// 2. The user needs to be notified (in or ) - /// that the reset process has completed - /// 3. Advanced use cases for data-sensitive applications where the developer wants - /// to recover in the most appropriate way the unsynced data - /// 4. Backup the whole realm before the client reset happens (in ). - /// Such backup could, for example, be used to restore the unsynced data (see 3.) - /// - /// Client Resets - .NET SDK - public sealed class RecoverOrDiscardUnsyncedChangesHandler : ClientResetHandlerBase - { - internal override ClientResyncMode ClientResetMode => ClientResyncMode.RecoverOrDiscard; - - /// - /// Gets or sets the callback that indicates a Client Reset is about to happen. - /// - /// Callback invoked right before a Client Reset. - public BeforeResetCallback? OnBeforeReset { get; set; } - - /// - /// Gets or sets the callback that indicates that an automatic Client Reset just happened. - /// - /// Callback invoked right after a Client Reset. - public AfterResetCallback? OnAfterRecovery { get; set; } - - /// - /// Gets or sets the callback that indicates that the discard unsynced changes fallback for a Client Reset just happened. - /// - /// - /// When a Client Reset with automatic recovery is attempted but the client is not allowed to use such strategy by the server, - /// then a Client Reset is re-tried with the fallback discard unsynced changes strategy. If this second attempt succeeds, - /// the callback is called. - /// - /// Callback invoked right after a Client Reset that fell back to discard unsynced changes. - public AfterResetCallback? OnAfterDiscard { get; set; } - - /// - /// Gets or sets the callback triggered when an error has occurred that makes the operation unable to complete, - /// for example in the case of a destructive schema change. - /// - /// Callback invoked if automatic Client Reset handling fails. - public ClientResetCallback? ManualResetFallback - { - get => ManualClientReset; - set => ManualClientReset = value; - } - } -} diff --git a/Realm/Realm/Sync/ErrorHandling/RecoverUnsyncedChangesHandler.cs b/Realm/Realm/Sync/ErrorHandling/RecoverUnsyncedChangesHandler.cs deleted file mode 100644 index 289c952029..0000000000 --- a/Realm/Realm/Sync/ErrorHandling/RecoverUnsyncedChangesHandler.cs +++ /dev/null @@ -1,68 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync.ErrorHandling -{ - /// - /// A client reset strategy that attempts to automatically recover any unsynchronized changes. - /// - /// - /// You can read more about the automatic merge rules at Client Resets - .NET SDK. - /// The automatic recovery mechanism creates write transactions meaning that all the changes that take place - /// are properly propagated through the standard Realm's change notifications. - /// The strategy supplies three callbacks: , and . - /// The first two are invoked just before and after the client reset has happened, while the last one is invoked - /// in case an error occurs during the automated process and the system needs to fallback to a manual mode. - /// The overall recommendation for using this strategy is that using the three available callbacks should only be considered when: - /// 1. The user needs to be notified (in ) of an incoming potential data loss - /// of unsynced data as a result of a merge or a complete discard of local changes - /// 2. The user needs to be notified (in ) that the reset process has completed - /// 3. Advanced use cases for data-sensitive applications where the developer wants - /// to recover in the most appropriate way the unsynced data - /// 4. Backup the whole realm before the client reset happens (in ). - /// Such backup could, for example, be used to restore the unsynced data (see 3.) - /// - /// Client Resets - .NET SDK - public sealed class RecoverUnsyncedChangesHandler : ClientResetHandlerBase - { - internal override ClientResyncMode ClientResetMode => ClientResyncMode.Recover; - - /// - /// Gets or sets the callback that indicates a Client Reset is about to happen. - /// - /// Callback invoked right before a Client Reset. - public BeforeResetCallback? OnBeforeReset { get; set; } - - /// - /// Gets or sets the callback that indicates a Client Reset just happened. - /// - /// Callback invoked right after a Client Reset. - public AfterResetCallback? OnAfterReset { get; set; } - - /// - /// Gets or sets the callback triggered when an error has occurred that makes the operation unable to complete, - /// for example in the case of a destructive schema change. - /// - /// Callback invoked if automatic Client Reset handling fails. - public ClientResetCallback? ManualResetFallback - { - get => ManualClientReset; - set => ManualClientReset = value; - } - } -} diff --git a/Realm/Realm/Sync/FlexibleSync/Subscription.cs b/Realm/Realm/Sync/FlexibleSync/Subscription.cs deleted file mode 100644 index 89e6261528..0000000000 --- a/Realm/Realm/Sync/FlexibleSync/Subscription.cs +++ /dev/null @@ -1,80 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using MongoDB.Bson; - -namespace Realms.Sync -{ - /// - /// A class representing a single query subscription. The server will continuously - /// evaluate the that the app subscribed to and will send data - /// that matches it as well as remove data that no longer does. - /// - public class Subscription - { - internal ObjectId Id { get; } - - /// - /// Gets the name of the subscription if one was provided at creation time. - /// If no name was provided, then this will return null. - /// - /// The subscription's name. - public string? Name { get; } - - /// - /// Gets the type of objects this subscription refers to. - /// - /// - /// If your types are remapped using , the value - /// returned will be the mapped-to value - i.e. the one that Realm uses internally - /// rather than the name of the C# class. - /// - /// The object type for the subscription. - public string ObjectType { get; } - - /// - /// Gets the query that describes the subscription. Objects matched by the query - /// will be sent to the device by the server. - /// - /// The subscription query. - public string Query { get; } - - /// - /// Gets a value indicating when this subscription was created. - /// - /// The creation date/time of the subscription. - public DateTimeOffset CreatedAt { get; } - - /// - /// Gets a value indicating when this subscription was last updated. - /// - /// The date/time of the last update to the subscription. - public DateTimeOffset UpdatedAt { get; } - - internal Subscription(ObjectId id, string? name, string objectType, string query, DateTimeOffset createdAt, DateTimeOffset updatedAt) - { - Id = id; - Name = name; - ObjectType = objectType; - Query = query; - CreatedAt = createdAt; - UpdatedAt = updatedAt; - } - } -} diff --git a/Realm/Realm/Sync/FlexibleSync/SubscriptionOptions.cs b/Realm/Realm/Sync/FlexibleSync/SubscriptionOptions.cs deleted file mode 100644 index 5d2d14e479..0000000000 --- a/Realm/Realm/Sync/FlexibleSync/SubscriptionOptions.cs +++ /dev/null @@ -1,55 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Linq; - -namespace Realms.Sync; - -/// -/// A class providing various options to . -/// All the properties in this class are optional. -/// -public class SubscriptionOptions -{ - /// - /// Gets or sets name of the subscription that is being added. This will - /// be reflected in . If not specified, - /// an automatic name will be generated from the query. - /// - /// The subscription's name. - public string? Name { get; set; } - - /// - /// Gets or sets a value indicating whether the operation should update - /// an existing subscription with the same name. The default is true. - /// - /// - /// true if - /// should have UPSERT semantics, false if you need it to be strictly an INSERT. - /// - /// - /// Adding a subscription with the same name and query string is a no-op, regardless - /// of the value of . This means that if - /// is not specified, - /// will always succeed since the name is derived from the query string. If - /// is set to a non-null value and is set to false, - /// may throw an exception - /// if the subscription set contains a subscription with the same name, but a different query string. - /// - public bool UpdateExisting { get; set; } = true; -} \ No newline at end of file diff --git a/Realm/Realm/Sync/FlexibleSync/SubscriptionSet.cs b/Realm/Realm/Sync/FlexibleSync/SubscriptionSet.cs deleted file mode 100644 index 130e59d07f..0000000000 --- a/Realm/Realm/Sync/FlexibleSync/SubscriptionSet.cs +++ /dev/null @@ -1,395 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Realms.Helpers; -using Realms.Sync.Exceptions; - -namespace Realms.Sync -{ - /// - /// A collection representing the set of active subscriptions for a - /// instance. This is used in combination with to - /// declare the set of queries you want to synchronize with the server. You can access and - /// read the subscription set freely, but mutating it must happen in an - /// block. - /// - /// - /// Any changes to the subscription set will be persisted locally and be available the next - /// time the application starts up - i.e. it's not necessary to subscribe for the same query - /// every time. Updating the subscription set can be done while offline, and only the latest - /// update will be sent to the server whenever connectivity is restored. - ///
- /// It is strongly recommended that you batch updates as much as possible and request the - /// dataset your application needs upfront. Updating the set of active subscriptions for a - /// Realm is an expensive operation serverside, even if there's very little data that needs - /// downloading. - ///
- public class SubscriptionSet : IReadOnlyList - { - private SubscriptionSetHandle _handle; - - /// - /// Gets the number of elements in the collection. - /// - /// The number of elements in the collection. - public int Count => _handle.GetCount(); - - /// - /// Gets the state of the subscription set. - /// - /// The subscription set's state. - public SubscriptionSetState State => _handle.GetState(); - - internal long Version => _handle.GetVersion(); - - /// - /// Gets the error associated with this subscription set, if any. This will - /// only be non-null if is . - /// - /// - /// The that provides more details for why the subscription set - /// was rejected by the server. - /// - public Exception? Error - { - get - { - var errorMessage = _handle.GetErrorMessage(); - return errorMessage == null ? null : new SubscriptionException(errorMessage); - } - } - - /// - /// Gets the at the specified index in the set. - /// - /// The zero-based index of the element to get. - /// The at the specified index in the set. - public Subscription this[int index] - { - get - { - if (index < 0) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - return _handle.GetAtIndex(index); - } - } - - internal SubscriptionSet(SubscriptionSetHandle handle) - { - _handle = handle; - } - - /// - /// Finds a subscription by name. - /// - /// The name of the subscription. - /// - /// A instance where is equal to - /// if the subscription set contains a subscription with the provided name; - /// null otherwise. - /// - public Subscription? Find(string name) => _handle.Find(name); - - /// - /// Finds a subscription by query. - /// - /// The type of objects in the query. - /// The query describing the subscription. - /// - /// A instance where matches - /// the provided ; null if the subscription set doesn't contain - /// a match. - /// - public Subscription? Find(IQueryable query) - where T : IRealmObject - { - var results = Argument.EnsureType>(query, $"{nameof(query)} must be a query obtained by calling Realm.All.", nameof(query)); - return _handle.Find(results.ResultsHandle); - } - - /// - /// Update the subscription set and send the request to the server in the background. - /// - /// - /// Calling is a prerequisite for - /// mutating the subscription set - e.g. by calling , - /// , or . - ///
- /// Calling this may update the content of this - e.g. if another - /// was called on a background thread or if the changed. - ///
- /// If you want to wait for the server to acknowledge and send back the data that matches the updated - /// subscriptions, use . - ///
- /// - /// Action to execute, adding or removing subscriptions to this set. - /// - public void Update(Action action) - { - EnsureReadonly(); - Argument.NotNull(action, nameof(action)); - - var oldHandle = _handle; - var writeableHandle = _handle.BeginWrite(); - - // We need to set the writable handle as the current subscription set handle - // to allow performing operations that modify it. - _handle = writeableHandle; - - try - { - action(); - - // Committing the write will generate a new readonly subscription set handle that - // we need to set to _handle. - _handle = _handle.CommitWrite(); - - oldHandle.Dispose(); - writeableHandle.Dispose(); - } - catch - { - // We need to immediately unbind the subscription set handle to rollback the transaction. If we - // don't do it, it will be unbound when the Realm closes or GC collects it which is too late. - // Using .Dispose here instead of .Unbind will only schedule it for unbinding which is not what - // we want. - writeableHandle.Unbind(); - - // If an error occurs - revert to the old subscription set handle. - _handle = oldHandle; - throw; - } - } - - /// - /// Adds a query to the set of active subscriptions. The query will be joined via an OR statement - /// with any existing queries for the same type. - /// - /// The type of objects in the query results. - /// The query that will be matched on the server. - /// - /// The subscription options controlling the name and/or the type of insert that will be performed. - /// - /// - /// Adding a query that already exists is a no-op and the existing subscription will be returned. - /// - /// The subscription that represents the specified query. - public Subscription Add(IQueryable query, SubscriptionOptions? options = null) - where T : IRealmObject - { - EnsureWritable(); - - var results = Argument.EnsureType>(query, $"{nameof(query)} must be a query obtained by calling Realm.All.", nameof(query)); - return _handle.Add(results.ResultsHandle, options ?? new()); - } - - /// - /// Removes a subscription with the specified . - /// - /// The name of the subscription to remove. - /// - /// true if the subscription existed in this subscription set and was removed; false otherwise. - /// - public bool Remove(string name) - { - EnsureWritable(); - - Argument.NotNullOrEmpty(name, nameof(name)); - return _handle.Remove(name); - } - - /// - /// Removes a subscription with the specified . - /// - /// The type of objects in the query results. - /// The query whose matching subscription should be removed. - /// A flag indicating whether to also remove named subscriptions. Default is false. - /// - /// true if the subscription existed in this subscription set and was removed; false otherwise. - /// - public int Remove(IQueryable query, bool removeNamed = false) - where T : IRealmObject - { - EnsureWritable(); - - var results = Argument.EnsureType>(query, $"{nameof(query)} must be a query obtained by calling Realm.All.", nameof(query)); - return _handle.Remove(results.ResultsHandle, removeNamed); - } - - /// - /// Removes the provided from this subscription set. - /// - /// The subscription to remove. - /// - /// true if the subscription existed in this subscription set and was removed; false otherwise. - /// - public bool Remove(Subscription subscription) - { - EnsureWritable(); - - Argument.NotNull(subscription, nameof(subscription)); - - return _handle.Remove(subscription.Id); - } - - /// - /// Removes all subscriptions for a specified type. - /// - /// The type of objects whose subscriptions should be removed. - /// A flag indicating whether to also remove named subscriptions. Default is false. - /// The number of subscriptions that existed for this type and were removed. - public int RemoveAll(bool removeNamed = false) - where T : IRealmObject => RemoveAll(typeof(T).GetMappedOrOriginalName(), removeNamed); - - /// - /// Removes all subscriptions for the provided . - /// - /// The name of the type whose subscriptions are to be removed. - /// A flag indicating whether to also remove named subscriptions. Default is false. - /// The number of subscriptions that existed for this type and were removed. - public int RemoveAll(string className, bool removeNamed = false) - { - EnsureWritable(); - - Argument.NotNullOrEmpty(className, nameof(className)); - return _handle.RemoveAll(className, removeNamed); - } - - /// - /// Removes all subscriptions from this subscription set. - /// - /// A flag indicating whether to also remove named subscriptions. Default is false. - /// The number of subscriptions that existed in the set and were removed. - public int RemoveAll(bool removeNamed = false) - { - EnsureWritable(); - - return _handle.RemoveAll(removeNamed); - } - - /// - /// Waits for the server to acknowledge the subscription set and return the matching objects. - /// - /// An optional cancellation token that can be used to cancel the wait operation. - /// - /// If the of the subscription set is - /// the returned will complete immediately. If the is - /// , the returned task will be immediately rejected with an - /// error. - ///
- /// If the change results in removing objects from the Realm - e.g. because subscriptions have been - /// removed, then those objects will have been removed prior to the returned task completing. - ///
- /// - /// An awaitable task, whose successful completion indicates that the server has processed the - /// subscription change and has sent all the data that matches the new subscriptions. - /// - public Task WaitForSynchronizationAsync(CancellationToken? cancellationToken = null) => _handle.WaitForStateChangeAsync(cancellationToken); - - /// - public IEnumerator GetEnumerator() => new Enumerator(this); - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private void EnsureWritable() - { - if (_handle.IsReadonly) - { - throw new InvalidOperationException("You can't mutate the subscription set outside of a Update or UpdateAsync callback."); - } - } - - private void EnsureReadonly() - { - if (!_handle.IsReadonly) - { - throw new InvalidOperationException("You can't Update/UpdateAsync on a subscription set that is already being updated."); - } - } - - private class Enumerator : IEnumerator - { - private SubscriptionSet? _enumerating; - private int _index; - - internal Enumerator(SubscriptionSet parent) - { - _index = -1; - _enumerating = parent; - } - - public Subscription Current - { - get - { - ThrowIfDisposed(); - - return _enumerating[_index]; - } - } - - object IEnumerator.Current => Current; - - public bool MoveNext() - { - ThrowIfDisposed(); - - var index = _index + 1; - if (index >= _enumerating.Count) - { - return false; - } - - _index = index; - return true; - } - - public void Reset() - { - _index = -1; // by definition BEFORE first item - } - - public void Dispose() - { - ThrowIfDisposed(); - - _enumerating = null; - } - - [MemberNotNull(nameof(_enumerating))] - private void ThrowIfDisposed() - { - if (_enumerating is null) - { - throw new ObjectDisposedException(nameof(Enumerator)); - } - } - } - } -} diff --git a/Realm/Realm/Sync/FlexibleSync/SubscriptionSetState.cs b/Realm/Realm/Sync/FlexibleSync/SubscriptionSetState.cs deleted file mode 100644 index e1b79860e1..0000000000 --- a/Realm/Realm/Sync/FlexibleSync/SubscriptionSetState.cs +++ /dev/null @@ -1,57 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - /// - /// An enum representing the state of a Realm's subscription set. - /// - public enum SubscriptionSetState : byte - { - /// - /// The subscription update has been persisted locally, but the server hasn't - /// yet returned all the data that matched the updated subscription queries. - /// - Pending, - - /// - /// The server has acknowledged the subscription and sent all the data that - /// matched the subscription queries at the time the subscription set was - /// updated. The server is now in steady-state synchronization mode where it - /// will stream updates as they come. - /// - Complete, - - /// - /// The server has returned an error and synchronization is paused for this - /// Realm. To view the actual error, use . - /// You can still use to update the - /// subscriptions and, if the new update doesn't trigger an error, synchronization - /// will be restarted. - /// - Error, - - /// - /// The subscription set has been superseded by an updated one. This typically means - /// that someone has called on a different instance - /// of the . You should not use a superseded subscription set - /// and instead obtain a new instance by calling . - /// - Superseded, - } -} diff --git a/Realm/Realm/Sync/FlexibleSync/WaitForSyncMode.cs b/Realm/Realm/Sync/FlexibleSync/WaitForSyncMode.cs deleted file mode 100644 index e7e453354c..0000000000 --- a/Realm/Realm/Sync/FlexibleSync/WaitForSyncMode.cs +++ /dev/null @@ -1,69 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Specialized; -using System.Linq; -using System.Threading; - -namespace Realms.Sync; - -/// -/// An enum controlling when query.SubscribeAsync will -/// wait for synchronization before returning. -/// -/// -/// When the [Subscription] is created for the first time, data needs to be downloaded from the -/// server before it becomes available, so depending on whether you run the query against the local -/// database before or after this has happened, you query results might not look correct. -///
-/// This enum thus defines the behaviour of when the query is run, so it possible to make the -/// appropriate tradeoff between correctness and availability. -///
-public enum WaitForSyncMode -{ - /// - /// This mode will wait for the server data the first time a subscription is created before - /// returning the local query. Later calls to query.SubscribeAsync - /// will detect that the subscription already exist and return immediately. - ///
- /// This is the default mode. - ///
- FirstTime, - - /// - /// With this mode enabled, Realm will always download the latest server state before returning from - /// query.SubscribeAsync. This means that your - /// query result is always seeing the latest data, but it also requires the app to be online. - /// - /// - /// When using this mode, it is strongly advised to supply a to - /// query.SubscribeAsync to make sure your - /// app doesn't wait forever. - /// - Always, - - /// - /// With this mode enabled, Realm will always return as soon as the the subscription is created - /// while any server data is being downloaded in the background. This update is not atomic, which - /// means that if you subscribe to notifications using - /// - /// or - /// you might see multiple events being fired as the server sends objects matching the subscription. - /// - Never -} diff --git a/Realm/Realm/Sync/GoogleCredentialType.cs b/Realm/Realm/Sync/GoogleCredentialType.cs deleted file mode 100644 index b0881d3421..0000000000 --- a/Realm/Realm/Sync/GoogleCredentialType.cs +++ /dev/null @@ -1,36 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - /// - /// The type of the Google credential. - /// - public enum GoogleCredentialType - { - /// - /// A credential representing an auth code. - /// - AuthCode, - - /// - /// A credential representing an Id token. - /// - IdToken, - } -} diff --git a/Realm/Realm/Sync/MetadataPersistenceMode.cs b/Realm/Realm/Sync/MetadataPersistenceMode.cs deleted file mode 100644 index c324c6b415..0000000000 --- a/Realm/Realm/Sync/MetadataPersistenceMode.cs +++ /dev/null @@ -1,42 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - /// - /// Enumeration that specifies how and if logged-in objects are persisted - /// across application launches. - /// - public enum MetadataPersistenceMode - { - /// - /// Persist objects, but do not encrypt them. - /// - NotEncrypted = 0, - - /// - /// Persist objects in an encrypted store. - /// - Encrypted, - - /// - /// Do not persist objects. - /// - Disabled - } -} diff --git a/Realm/Realm/Sync/MongoClient.cs b/Realm/Realm/Sync/MongoClient.cs deleted file mode 100644 index dd5f7dcb04..0000000000 --- a/Realm/Realm/Sync/MongoClient.cs +++ /dev/null @@ -1,586 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; -using Realms.Helpers; -using Realms.Schema; - -namespace Realms.Sync -{ - /// - /// The remote MongoClient used for working with data in MongoDB remotely via Realm. - /// - public class MongoClient - { - private static readonly char[] _invalidChars = new[] { '\0', '.' }; - - private static readonly ConcurrentDictionary _schemas = new(); - - private User User { get; } - - /// - /// Gets the service name for this client. - /// - /// The name of the remote MongoDB service. - public string ServiceName { get; } - - internal MongoClient(User user, string serviceName) - { - User = user; - ServiceName = serviceName; - } - - /// - /// Gets a collection of documents from MongoDB that can be deserialized in Realm objects. - /// - /// - /// This method is only supported for source-generated classes - i.e. ones that inherit from - /// rather than . - /// The collection and database name are automatically derived from the Realm object class. - /// - /// The Realm object type that matches the shape of the documents in the collection. - /// A instance that exposes an API for CRUD operations on its contents. - public Collection GetCollection() - where TRealmObject : class, IRealmObjectBase - { - var schema = _schemas.GetOrAdd(typeof(TRealmObject), - type => (ObjectSchema?)type.GetField("RealmSchema", BindingFlags.Static | BindingFlags.Public)?.GetValue(null) - ?? throw new NotSupportedException("This method is only supported for source-generated classes - i.e. ones that inherit from IRealmObject rather than RealmObject.")); - - return new Collection(this, schema.Name); - } - - /// - /// Gets a instance for the given database name. - /// - /// The name of the database to retrieve. - /// A instance that exposes an API for querying its collections. - public Database GetDatabase(string name) - { - Argument.Ensure(IsNameValid(name), "Database names must be non-empty and not contain '.' or the null character.", nameof(name)); - - return new Database(this, name); - } - - private static bool IsNameValid(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - return false; - } - - var index = name.IndexOfAny(_invalidChars); - return index == -1; - } - - /// - /// An object representing a remote MongoDB database. - /// - public class Database - { - private static readonly ConcurrentDictionary _schemas = new(); - - /// - /// Gets the that manages this database. - /// - /// The database's . - public MongoClient Client { get; } - - /// - /// Gets the name of the database. - /// - /// The database name. - public string Name { get; } - - internal Database(MongoClient client, string name) - { - Client = client; - Name = name; - } - - /// - /// Gets a collection from the database. - /// - /// The name of the collection. - /// A instance that exposes an API for CRUD operations on its contents. - public Collection GetCollection(string name) => GetCollection(name); - - /// - /// Gets a collection from the database. - /// - /// - /// The MongoDB Bson library is used - /// to decode the response. It will automatically handle most cases, but if you want to control the behavior - /// of the deserializer, you can use the attributes in the - /// MongoDB.Bson.Serialization.Attributes - /// namespace. - ///
- /// If you want to modify the global conventions used when deserializing the response, such as convert - /// camelCase properties to PascalCase, you can register a - /// ConventionPack. - ///
- /// The managed type that matches the shape of the documents in the collection. - /// The name of the collection. - /// A instance that exposes an API for CRUD operations on its contents. - public Collection GetCollection(string name) - where TDocument : class - { - Argument.Ensure(IsNameValid(name), "Collection names must be non-empty and not contain '.' or the null character.", nameof(name)); - - return new Collection(this, name); - } - } - - /// - /// An object representing a remote MongoDB collection. - /// - /// The managed type that matches the shape of the documents in the collection. - public class Collection - where TDocument : class - { - private MongoClient _client; - - /// - /// Gets the this collection belongs to. - /// - /// The collection's or null if the database was automatically - /// inferred with . - public Database? Database { get; } - - /// - /// Gets the name of the collection. - /// - /// The collection name. - public string Name { get; } - - internal Collection(Database database, string name) - { - _client = database.Client; - Database = database; - Name = name; - } - - internal Collection(MongoClient client, string name) - { - _client = client; - Name = name; - } - - /// - /// Inserts the provided document in the collection. - /// - /// The document to insert. - /// - /// An awaitable representing the remote insert operation. The result of the task - /// contains the _id of the inserted document. - /// - /// - public async Task InsertOneAsync(TDocument doc) - { - Argument.NotNull(doc, nameof(doc)); - - return await InvokeOperationAsync("insertOne", "document", doc); - } - - /// - /// Inserts one or more documents in the collection. - /// - /// The documents to insert. - /// - /// An awaitable representing the remote insert many operation. The result of the task - /// contains the _ids of the inserted documents. - /// - /// - public async Task InsertManyAsync(IEnumerable docs) - { - Argument.NotNull(docs, nameof(docs)); - Argument.Ensure(docs.All(d => d != null), "Collection must not contain null elements.", nameof(docs)); - - return await InvokeOperationAsync("insertMany", "documents", docs); - } - - /// - /// Updates a single document in the collection according to the specified arguments. - /// - /// - /// A document describing the selection criteria of the update. If not specified, the first document in the - /// collection will be updated. Can only contain - /// query selector expressions. - /// - /// - /// A document describing the update. Can only contain - /// update operator expressions. - /// - /// - /// A boolean controlling whether the update should insert a document if no documents match the . - /// Defaults to false. - /// - /// - /// An awaitable representing the remote update one operation. The result of the task - /// contains information about the number of matched and updated documents, as well as the _id of the - /// upserted document if was set to true and the operation resulted in an - /// upsert. - /// - /// - public async Task UpdateOneAsync(object? filter, object updateDocument, bool upsert = false) - { - Argument.NotNull(updateDocument, nameof(updateDocument)); - - return await InvokeOperationAsync("updateOne", "query", filter, "update", updateDocument, "upsert", upsert); - } - - /// - /// Updates one or more documents in the collection according to the specified arguments. - /// - /// - /// A document describing the selection criteria of the update. If not specified, all documents in the - /// collection will be updated. Can only contain - /// query selector expressions. - /// - /// - /// A document describing the update. Can only contain - /// update operator expressions. - /// - /// - /// A boolean controlling whether the update should insert a document if no documents match the . - /// Defaults to false. - /// - /// - /// An awaitable representing the remote update many operation. The result of the task - /// contains information about the number of matched and updated documents, as well as the _id of the - /// upserted document if was set to true and the operation resulted in an - /// upsert. - /// - /// - public async Task UpdateManyAsync(object? filter, object updateDocument, bool upsert = false) - { - Argument.NotNull(updateDocument, nameof(updateDocument)); - - return await InvokeOperationAsync("updateMany", "query", filter, "update", updateDocument, "upsert", upsert); - } - - /// - /// Removes a single document from a collection. If no documents match the , the collection is not modified. - /// - /// - /// A document describing the deletion criteria using query operators. - /// If not specified, the first document in the collection will be deleted. - /// - /// - /// An awaitable representing the remote delete one operation. The result of the task contains the number - /// of deleted documents. - /// - /// - public Task DeleteOneAsync(object? filter = null) => InvokeOperationAsync("deleteOne", "query", filter); - - /// - /// Removes one or more documents from a collection. If no documents match the , the collection is not modified. - /// - /// - /// A document describing the deletion criteria using query operators. - /// If not specified, all documents in the collection will be deleted. - /// - /// - /// An awaitable representing the remote delete many operation. The result of the task contains the number - /// of deleted documents. - /// - /// - public Task DeleteManyAsync(object? filter = null) => InvokeOperationAsync("deleteMany", "query", filter); - - /// - /// Finds the all documents in the collection up to . - /// - /// - /// A document describing the find criteria using query operators. - /// If not specified, all documents in the collection will be returned. - /// - /// A document describing the sort criteria. If not specified, the order of the returned documents is not guaranteed. - /// - /// A document describing the fields to return for all matching documents. If not specified, all fields are returned. - /// - /// The maximum number of documents to return. If not specified, all documents in the collection are returned. - /// - /// An awaitable representing the remote find operation. The result of the task is an array containing the documents that match the find criteria. - /// - /// - public Task FindAsync(object? filter = null, object? sort = null, object? projection = null, long? limit = null) - => InvokeOperationAsync("find", "query", filter, "project", projection, "sort", sort, "limit", limit); - - /// - /// Finds the first document in the collection that satisfies the query criteria. - /// - /// - /// A document describing the find criteria using query operators. - /// If not specified, all documents in the collection will match the request. - /// - /// A document describing the sort criteria. If not specified, the order of the returned documents is not guaranteed. - /// - /// A document describing the fields to return for all matching documents. If not specified, all fields are returned. - /// - /// - /// An awaitable representing the remote find one operation. The result of the task is the first document that matches the find criteria. - /// - /// - public Task FindOneAsync(object? filter = null, object? sort = null, object? projection = null) - => InvokeOperationAsync("findOne", "query", filter, "project", projection, "sort", sort); - - /// - /// Finds the first document in the collection that satisfies the query criteria. - /// - /// - /// A document describing the find criteria using query operators. - /// If not specified, all documents in the collection will match the request. - /// - /// - /// A document describing the update. Can only contain - /// update operator expressions. - /// - /// A document describing the sort criteria. If not specified, the order of the returned documents is not guaranteed. - /// - /// A document describing the fields to return for all matching documents. If not specified, all fields are returned. - /// - /// - /// A boolean controlling whether the update should insert a document if no documents match the . - /// Defaults to false. - /// - /// - /// A boolean controlling whether to return the new updated document. If set to false the original document - /// before the update is returned. Defaults to false. - /// - /// - /// An awaitable representing the remote find one operation. The result of the task is the first document that matches the find criteria. - /// - /// - public async Task FindOneAndUpdateAsync(object? filter, object updateDocument, object? sort = null, object? projection = null, bool upsert = false, bool returnNewDocument = false) - { - Argument.NotNull(updateDocument, nameof(updateDocument)); - - return await InvokeOperationAsync("findOneAndUpdate", "filter", filter, "update", updateDocument, "projection", projection, "sort", sort, "upsert", upsert, "returnNewDocument", returnNewDocument); - } - - /// - /// Finds the first document in the collection that satisfies the query criteria. - /// - /// - /// A document describing the find criteria using query operators. - /// If not specified, all documents in the collection will match the request. - /// - /// - /// The replacement document. Cannot contain update operator expressions. - /// - /// - /// A document describing the sort criteria. If not specified, the order of the returned documents is not guaranteed. - /// - /// - /// A document describing the fields to return for all matching documents. If not specified, all fields are returned. - /// - /// - /// A boolean controlling whether the replace should insert a document if no documents match the . - /// Defaults to false. - ///
- /// MongoDB will add the _id field to the replacement document if it is not specified in either the filter or - /// replacement documents. If _id is present in both, the values must be equal. - /// - /// - /// A boolean controlling whether to return the replacement document. If set to false the original document - /// before the update is returned. Defaults to false. - /// - /// - /// An awaitable representing the remote find one operation. The result of the task is the first document that matches the find criteria. - /// - /// - public async Task FindOneAndReplaceAsync(object? filter, TDocument replacementDoc, object? sort = null, object? projection = null, bool upsert = false, bool returnNewDocument = false) - { - Argument.NotNull(replacementDoc, nameof(replacementDoc)); - - return await InvokeOperationAsync("findOneAndReplace", "filter", filter, "update", replacementDoc, "projection", projection, "sort", sort, "upsert", upsert, "returnNewDocument", returnNewDocument); - } - - /// - /// Finds the first document in the collection that satisfies the query criteria. - /// - /// - /// A document describing the find criteria using query operators. - /// If not specified, all documents in the collection will match the request. - /// - /// A document describing the sort criteria. If not specified, the order of the returned documents is not guaranteed. - /// - /// A document describing the fields to return for all matching documents. If not specified, all fields are returned. - /// - /// - /// An awaitable representing the remote find one operation. The result of the task is the first document that matches the find criteria. - /// - /// - public Task FindOneAndDeleteAsync(object? filter = null, object? sort = null, object? projection = null) - => InvokeOperationAsync("findOneAndDelete", "filter", filter, "projection", projection, "sort", sort); - - /// - /// Executes an aggregation pipeline on the collection and returns the results as a array. - /// - /// The managed type that matches the shape of the result of the pipeline. - /// - /// Documents describing the different pipeline stages using pipeline expressions. - /// - /// - /// An awaitable representing the remote aggregate operation. The result of the task is an array containing the documents returned - /// by executing the aggregation . - /// - /// - public Task AggregateAsync(params object[] pipeline) => InvokeOperationAsync("aggregate", "pipeline", pipeline); - - /// - /// Executes an aggregation pipeline on the collection and returns the results as a array. - /// - /// - /// Documents describing the different pipeline stages using pipeline expressions. - /// - /// - /// An awaitable representing the remote aggregate operation. The result of the task is an array containing the documents returned - /// by executing the aggregation . - /// - /// - public Task AggregateAsync(params object[] pipeline) => AggregateAsync(pipeline); - - /// - /// Counts the number of documents in the collection that match the provided . - /// - /// - /// A document describing the find criteria using query operators. - /// If not specified, all documents in the collection will be counted. - /// - /// The maximum number of documents to count. If not specified, all documents in the collection are counted. - /// - /// An awaitable representing the remote count operation. The result of the task is the number of documents that match the - /// and criteria. - /// - public Task CountAsync(object? filter = null, long? limit = null) => InvokeOperationAsync("count", "query", filter, "limit", limit); - - private async Task InvokeOperationAsync(string functionName, params object?[] args) - { - var jsonBuilder = new StringBuilder(); - - jsonBuilder.Append("[{"); - - if (Database is not null) - { - jsonBuilder.Append($"\"database\":\"{Database.Name}\",\"collection\":\"{Name}\""); - } - else - { - // Database and Collection names are automatically inferred by server based on schema name - jsonBuilder.Append($"\"schema_name\":\"{Name}\""); - } - - Debug.Assert(args.Length % 2 == 0, "args should be provided as key-value pairs"); - - for (var i = 0; i < args.Length; i += 2) - { - if (args[i + 1] != null) - { - jsonBuilder.Append($",\"{args[i]}\":{args[i + 1].ToNativeJson()}"); - } - } - - jsonBuilder.Append("}]"); - - return await _client.User.Functions.CallSerializedAsync(functionName, jsonBuilder.ToString(), _client.ServiceName); - } - } - - /// - /// The result of or operation. - /// - public class UpdateResult - { - /// - /// Gets the number of documents matched by the filter. - /// - /// The number of matched documents. - [BsonElement("matchedCount")] - [Preserve] - public int MatchedCount { get; private set; } - - /// - /// Gets the number of documents modified by the operation. - /// - /// The number of modified documents. - [BsonElement("modifiedCount")] - [Preserve] - public int ModifiedCount { get; private set; } - - /// - /// Gets the _id of the inserted document if the operation resulted in an insertion. - /// - /// The _id of the inserted document or null if the operation didn't result in an insertion. - [BsonElement("upsertedId")] - [Preserve] - public object? UpsertedId { get; private set; } - } - - /// - /// The result of operation. - /// - public class InsertResult - { - /// - /// Gets the _id of the inserted document. - /// - /// The _id of the inserted document. - [BsonElement("insertedId")] - [Preserve] - public object InsertedId { get; private set; } = null!; - } - - /// - /// The result of operation. - /// - public class InsertManyResult - { - /// - /// Gets an array containing the _ids of the inserted documents. - /// - /// The _ids of the inserted documents. - [BsonElement("insertedIds")] - [Preserve] - public object[] InsertedIds { get; private set; } = Array.Empty(); - } - - /// - /// The result of or operation. - /// - public class DeleteResult - { - /// - /// Gets the number of deleted documents. - /// - /// The number of deleted documents. - [BsonElement("deletedCount")] - [Preserve] - public int DeletedCount { get; private set; } - } - } -} diff --git a/Realm/Realm/Sync/ProgressNotifications/ProgressDirection.cs b/Realm/Realm/Sync/ProgressNotifications/ProgressDirection.cs deleted file mode 100644 index b82c438c7c..0000000000 --- a/Realm/Realm/Sync/ProgressNotifications/ProgressDirection.cs +++ /dev/null @@ -1,36 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - /// - /// The transfer direction (upload or download) tracked by a given progress notification subscription. - /// - public enum ProgressDirection : byte - { - /// - /// Monitors upload progress. - /// - Upload = 0, - - /// - /// Monitors download progress. - /// - Download = 1 - } -} diff --git a/Realm/Realm/Sync/ProgressNotifications/ProgressMode.cs b/Realm/Realm/Sync/ProgressNotifications/ProgressMode.cs deleted file mode 100644 index 9fd170fa54..0000000000 --- a/Realm/Realm/Sync/ProgressNotifications/ProgressMode.cs +++ /dev/null @@ -1,40 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - /// - /// The desired behavior of a progress notification subscription. - /// - public enum ProgressMode - { - /// - /// The callback will be called forever, or until it is unregistered by disposing the subscription token. - /// - ReportIndefinitely, - - /// - /// The callback will be active: - /// - /// For uploads, until all the unsynced data available at the moment of the registration of the callback is sent to the server. - /// For downloads, until the client catches up to the current data (available at the moment of callback registration) or the next batch of data sent from the server. - /// - /// - ForCurrentlyOutstandingWork - } -} diff --git a/Realm/Realm/Sync/ProgressNotifications/ProgressNotificationToken.cs b/Realm/Realm/Sync/ProgressNotifications/ProgressNotificationToken.cs deleted file mode 100644 index 03a74eb692..0000000000 --- a/Realm/Realm/Sync/ProgressNotifications/ProgressNotificationToken.cs +++ /dev/null @@ -1,78 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Realms.Logging; - -namespace Realms.Sync -{ - internal class ProgressNotificationToken : IDisposable - { - private readonly ulong _nativeToken; - private readonly GCHandle _gcHandle; - private readonly Action _observer; - private readonly Action _unregister; - - private bool _isDisposed; - - public ProgressNotificationToken(Action observer, Func register, Action unregister) - { - _observer = observer; - _gcHandle = GCHandle.Alloc(this); - _unregister = unregister; - try - { - _nativeToken = register(_gcHandle); - } - catch - { - _gcHandle.Free(); - throw; - } - } - - public void Notify(double progressEstimate) - { - Task.Run(() => - { - try - { - _observer(new SyncProgress(progressEstimate)); - } - catch (Exception ex) - { - RealmLogger.Default.Log(LogLevel.Warn, $"An error occurred while reporting progress: {ex}"); - } - }); - } - - public void Dispose() - { - if (!_isDisposed) - { - GC.SuppressFinalize(this); - - _isDisposed = true; - _unregister(_nativeToken); - _gcHandle.Free(); - } - } - } -} diff --git a/Realm/Realm/Sync/ProgressNotifications/SyncProgress.cs b/Realm/Realm/Sync/ProgressNotifications/SyncProgress.cs deleted file mode 100644 index 20ddfa8379..0000000000 --- a/Realm/Realm/Sync/ProgressNotifications/SyncProgress.cs +++ /dev/null @@ -1,39 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - /// - /// A struct containing information about the progress state at a given instant. - /// - public readonly struct SyncProgress - { - /// - /// Gets the percentage estimate of the current progress, expressed as a double between 0.0 and 1.0. - /// - /// A percentage estimate of the progress. - public double ProgressEstimate { get; } - - internal SyncProgress(double progressEstimate) - { - ProgressEstimate = progressEstimate; - } - - internal bool IsComplete => ProgressEstimate >= 1.0; - } -} diff --git a/Realm/Realm/Sync/ProgressNotifications/SyncProgressObservable.cs b/Realm/Realm/Sync/ProgressNotifications/SyncProgressObservable.cs deleted file mode 100644 index c8d97a4a5d..0000000000 --- a/Realm/Realm/Sync/ProgressNotifications/SyncProgressObservable.cs +++ /dev/null @@ -1,48 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; - -namespace Realms.Sync -{ - internal class SyncProgressObservable : IObservable - { - private readonly SessionHandle _sessionHandle; - private readonly ProgressDirection _direction; - private readonly ProgressMode _mode; - - public SyncProgressObservable(SessionHandle sessionHandle, ProgressDirection direction, ProgressMode mode) - { - _sessionHandle = sessionHandle; - _direction = direction; - _mode = mode; - } - - public IDisposable Subscribe(IObserver observer) - { - return new ProgressNotificationToken(progress => - { - observer.OnNext(progress); - if (_mode == ProgressMode.ForCurrentlyOutstandingWork && progress.IsComplete) - { - observer.OnCompleted(); - } - }, handle => _sessionHandle.RegisterProgressNotifier(handle, _direction, _mode), _sessionHandle.UnregisterProgressNotifier); - } - } -} diff --git a/Realm/Realm/Sync/SchemaMode.cs b/Realm/Realm/Sync/SchemaMode.cs deleted file mode 100644 index bfa9cf9cb3..0000000000 --- a/Realm/Realm/Sync/SchemaMode.cs +++ /dev/null @@ -1,29 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - /// - /// To see details of each mode check its mirroring definition in core. - /// - internal enum SchemaMode : byte - { - AdditiveDiscovered = 5, - AdditiveExplicit = 6 - } -} diff --git a/Realm/Realm/Sync/Session.cs b/Realm/Realm/Sync/Session.cs deleted file mode 100644 index 51d9e2c96e..0000000000 --- a/Realm/Realm/Sync/Session.cs +++ /dev/null @@ -1,232 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Threading; -using System.Threading.Tasks; -using Realms.Exceptions.Sync; - -namespace Realms.Sync -{ - /// - /// An object encapsulating a synchronization session. Sessions represent the communication between the client (and a local Realm file on disk), - /// and MongoDB Atlas. Sessions are always created by the SDK and vended - /// out through various APIs. The lifespans of sessions associated with Realms are managed automatically. - /// - public class Session : INotifyPropertyChanged - { - private readonly SessionHandle _handle; - - internal readonly Action? _onSubscribed; - - internal readonly Action? _onUnsubscribed; - - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "This is the private event - the public is uppercased.")] - private event PropertyChangedEventHandler? _propertyChanged; - - private SessionHandle Handle - { - get - { - if (_handle.IsClosed) - { - throw new ObjectDisposedException( - nameof(Session), - "This Session instance is invalid. This typically means that Sync has closed or otherwise invalidated the native session. You can get a new valid instance by calling realm.GetSession()."); - } - - return _handle; - } - } - - internal bool IsClosed => _handle.IsClosed; - - /// - /// Occurs when a property value changes. - /// - public event PropertyChangedEventHandler? PropertyChanged - { - add - { - if (_propertyChanged == null) - { - Handle.SubscribeNotifications(this); - _onSubscribed?.Invoke(this); - } - - _propertyChanged += value; - } - - remove - { - _propertyChanged -= value; - - if (_propertyChanged == null) - { - _onUnsubscribed?.Invoke(); - Handle.UnsubscribeNotifications(); - } - } - } - - /// - /// Gets the session’s current state. - /// - /// An enum value indicating the state of the session. - public SessionState State => Handle.GetState(); - - /// - /// Gets the session’s current connection state. - /// - /// An enum value indicating the connection state of the session. - public ConnectionState ConnectionState => Handle.GetConnectionState(); - - /// - /// Gets the defined by the that is used to connect to MongoDB Atlas. - /// - /// The that was used to create the 's . - public User User => new(Handle.GetUser()); - - /// - /// Gets the on-disk path of the Realm file backing the this Session represents. - /// - /// The file path. - public string Path => Handle.GetPath(); - - /// - /// Gets an that can be used to track upload or download progress. - /// - /// - /// To start receiving notifications, you should call on the returned object. - /// The token returned from should be retained as long as progress - /// notifications are desired. To stop receiving notifications, call - /// on the token. - /// You don't need to keep a reference to the observable itself. - /// The progress callback will always be called once immediately upon subscribing in order to provide - /// the latest available status information. - /// - /// An observable that you can subscribe to and receive progress updates. - /// The transfer direction (upload or download) to track in the subscription callback. - /// The desired behavior of this progress notification block. - /// - /// - /// class ProgressNotifyingViewModel - /// { - /// private IDisposable notificationToken; - /// - /// public void ShowProgress() - /// { - /// var observable = session.GetProgressObservable(ProgressDirection.Upload, ProgressMode.ReportIndefinitely); - /// notificationToken = observable.Subscribe(progress => - /// { - /// // Update relevant properties by accessing progress.ProgressEstimate - /// }); - /// } - /// - /// public void HideProgress() - /// { - /// notificationToken?.Dispose(); - /// notificationToken = null; - /// } - /// } - /// - /// In this example we're using ObservableExtensions.Subscribe - /// found in the Reactive Extensions class library. - /// If you prefer not to take a dependency on it, you can create a class that implements - /// and use it to subscribe instead. - /// - public IObservable GetProgressObservable(ProgressDirection direction, ProgressMode mode) => new SyncProgressObservable(Handle, direction, mode); - - /// - /// Waits for the to finish all pending uploads. - /// - /// An optional cancellation token that can be used to cancel the wait operation. - /// An awaitable that will be completed when all pending uploads for this are completed. - /// Thrown when a faulted session is waited on. - public Task WaitForUploadAsync(CancellationToken? cancellationToken = null) => Handle.WaitAsync(ProgressDirection.Upload, cancellationToken); - - /// - /// Waits for the to finish all pending downloads. - /// - /// An optional cancellation token that can be used to cancel the wait operation. - /// An awaitable that will be completed when all pending downloads for this are completed. - /// Thrown when a faulted session is waited on. - public Task WaitForDownloadAsync(CancellationToken? cancellationToken = null) => Handle.WaitAsync(ProgressDirection.Download, cancellationToken); - - /// - /// Stops any synchronization with the server until the Realm is re-opened again - /// after fully closing it. - ///
- /// Synchronization can be re-enabled by calling again. - ///
- /// - /// If the session is already stopped, calling this method will do nothing. - /// - public void Stop() => Handle.Stop(); - - /// - /// Attempts to resume the session and enable synchronization with the server. - /// - /// - /// All sessions will be active by default and calling this method only makes sense if - /// was called before that. - /// - public void Start() => Handle.Start(); - - /// - public override bool Equals(object? obj) - => obj is Session other && - Handle.GetRawPointer() == other.Handle.GetRawPointer(); - - /// - public override int GetHashCode() => Handle.GetRawPointer().GetHashCode(); - - internal Session(SessionHandle handle, Action? onSubscribed = null, Action? onUnsubscribed = null) - { - _handle = handle; - _onSubscribed = onSubscribed; - _onUnsubscribed = onUnsubscribed; - } - - internal void CloseHandle(bool waitForShutdown = false) - { - GC.SuppressFinalize(this); - if (!IsClosed) - { - if (waitForShutdown) - { - _handle.ShutdownAndWait(); - } - - _propertyChanged = null; - _onUnsubscribed?.Invoke(); - _handle.Close(); - } - } - - internal void ReportErrorForTesting(int errorCode, string errorMessage, bool isFatal, ServerRequestsAction action) - => Handle.ReportErrorForTesting(errorCode, errorMessage, isFatal, action); - - internal void RaisePropertyChanged(string propertyName) - { - _propertyChanged?.Invoke(this, new(propertyName)); - } - } -} diff --git a/Realm/Realm/Sync/SessionState.cs b/Realm/Realm/Sync/SessionState.cs deleted file mode 100644 index 49b3b14524..0000000000 --- a/Realm/Realm/Sync/SessionState.cs +++ /dev/null @@ -1,36 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - /// - /// The current state of a sync session object. - /// - public enum SessionState : byte - { - /// - /// The session is connected to Atlas Devices and is actively transferring data. - /// - Active = 0, - - /// - /// The session is not currently communicating with the server. - /// - Inactive - } -} \ No newline at end of file diff --git a/Realm/Realm/Sync/SessionStopPolicy.cs b/Realm/Realm/Sync/SessionStopPolicy.cs deleted file mode 100644 index c943fccea4..0000000000 --- a/Realm/Realm/Sync/SessionStopPolicy.cs +++ /dev/null @@ -1,27 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - internal enum SessionStopPolicy - { - Immediately, // Immediately stop the session as soon as all Realms/Sessions go out of scope. - LiveIndefinitely, // Never stop the session. - AfterChangesUploaded, // Once all Realms/Sessions go out of scope, wait for uploads to complete and stop. - } -} \ No newline at end of file diff --git a/Realm/Realm/Sync/SyncTimeoutOptions.cs b/Realm/Realm/Sync/SyncTimeoutOptions.cs deleted file mode 100644 index 0cec1717b7..0000000000 --- a/Realm/Realm/Sync/SyncTimeoutOptions.cs +++ /dev/null @@ -1,108 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; - -namespace Realms.Sync -{ - /// - /// Options for configuring timeouts and intervals used by the sync client. - /// - public class SyncTimeoutOptions - { - /// - /// Gets or sets the maximum amount of time to allow for a connection to - /// become fully established. - /// - /// - /// This includes the time to resolve the - /// network address, the TCP connect operation, the SSL handshake, and - /// the WebSocket handshake. - ///
- /// Defaults to 2 minutes. - ///
- /// The connection timeout. - public TimeSpan ConnectTimeout { get; set; } = TimeSpan.FromMinutes(2); - - /// - /// Gets or sets the amount of time to keep a connection open after all - /// sessions have been abandoned. - /// - /// - /// After all synchronized Realms have been closed for a given server, the - /// connection is kept open until the linger time has expired to avoid the - /// overhead of reestablishing the connection when Realms are being closed and - /// reopened. - ///
- /// Defaults to 30 seconds. - ///
- /// The time to keep the connection open. - public TimeSpan ConnectionLingerTime { get; set; } = TimeSpan.FromSeconds(30); - - /// - /// Gets or sets how long to wait between each heartbeat ping message. - /// - /// - /// The client periodically sends ping messages to the server to check if the - /// connection is still alive. Shorter periods make connection state change - /// notifications more responsive at the cost of battery life (as the antenna - /// will have to wake up more often). - ///
- /// Defaults to 1 minute. - ///
- /// The ping interval. - public TimeSpan PingKeepAlivePeriod { get; set; } = TimeSpan.FromMinutes(1); - - /// - /// Gets or sets how long to wait for a response to a heartbeat ping before - /// concluding that the connection has dropped. - /// - /// - /// Shorter values will make connection state change notifications more - /// responsive as it will only change to `disconnected` after this much time has - /// elapsed, but overly short values may result in spurious disconnection - /// notifications when the server is simply taking a long time to respond. - ///
- /// Defaults to 2 minutes. - ///
- /// The pong timeout. - public TimeSpan PongKeepAliveTimeout { get; set; } = TimeSpan.FromMinutes(2); - - /// - /// Gets or sets the maximum amount of time since the loss of a - /// prior connection, for a new connection to be considered a "fast - /// reconnect". - /// - /// - /// When a client first connects to the server, it defers uploading any local - /// changes until it has downloaded all changesets from the server. This - /// typically reduces the total amount of merging that has to be done, and is - /// particularly beneficial the first time that a specific client ever connects - /// to the server. - ///
- /// When an existing client disconnects and then reconnects within the "fact - /// reconnect" time this is skipped and any local changes are uploaded - /// immediately without waiting for downloads, just as if the client was online - /// the whole time. - ///
- /// Defaults to 1 minute. - ///
- /// The window in which a drop in connectivity is considered transient. - public TimeSpan FastReconnectLimit { get; set; } = TimeSpan.FromMinutes(1); - } -} diff --git a/Realm/Realm/Sync/User.cs b/Realm/Realm/Sync/User.cs deleted file mode 100644 index 7f88a87ff2..0000000000 --- a/Realm/Realm/Sync/User.cs +++ /dev/null @@ -1,501 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using MongoDB.Bson; -using MongoDB.Bson.Serialization; -using Realms.Helpers; -using Realms.Sync.Exceptions; - -namespace Realms.Sync -{ - /// - /// This class represents a user in a Atlas App Services application. The credentials are provided by various 3rd party providers (Facebook, Google, etc.). - /// A user can log in to the server and, if access is granted, it is possible to synchronize the local and the remote Realm. Moreover, synchronization is halted when the user is logged out. - /// It is possible to persist a user. By retrieving a user, there is no need to log in to the 3rd party provider again. Persisting a user between sessions, the user's credentials are stored locally on the device, and should be treated as sensitive data. - /// - public class User : IEquatable - { - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "This is the private event - the public is uppercased.")] - private event EventHandler? _changed; - - /// - /// Occurs when a property value changes. - /// - public event EventHandler? Changed - { - add - { - if (_changed == null) - { - Handle.SubscribeNotifications(this); - } - - _changed += value; - } - - remove - { - _changed -= value; - - if (_changed == null) - { - Handle.UnsubscribeNotifications(); - } - } - } - - /// - /// Gets this user's refresh token. This is the user's credential for accessing MongoDB Atlas data and should be treated as sensitive information. - /// - /// A unique string that can be used for refreshing the user's credentials. - public string RefreshToken - { - get => Handle.GetRefreshToken(); - } - - /// - /// Gets this user's access token. This is the user's credential for accessing MongoDB Atlas data and should be treated as sensitive information. - /// - /// A unique string that can be used to represent this user before the server. - public string AccessToken - { - get => Handle.GetAccessToken(); - } - - /// - /// Gets a unique identifier for the device the user logged in to. - /// - /// A unique string that identifies the current device. - public string DeviceId - { - get => Handle.GetDeviceId(); - } - - /// - /// Gets the Id of this user in Atlas App Services. - /// - /// A string that uniquely identifies that user. - public string Id => Handle.GetUserId(); - - /// - /// Gets the current state of the user. - /// - /// A value indicating whether the user is active, logged out, or an error has occurred. - public UserState State => Handle.GetState(); - - /// - /// Gets a value indicating which this user logged in with. - /// - /// The used to login the user. - [Obsolete("User.Provider wasn't working consistently and will be removed in a future version. You can get the provider of the user identity instead.")] - public Credentials.AuthProvider Provider => Identities.FirstOrDefault()?.Provider ?? Credentials.AuthProvider.Unknown; - - /// - /// Gets the app with which this user is associated. - /// - /// An instance that owns this user. - public App App { get; } - - /// - /// Gets the profile information for that user. - /// - /// A object, containing information about the user's name, email, and so on. - public UserProfile Profile { get; } - - /// - /// Gets the custom user data associated with this user in the Realm app. - /// - /// - /// The data is only refreshed when the user's access token is refreshed or when explicitly calling . - /// - /// A document containing the user data. - /// Custom User Data Docs - public BsonDocument? GetCustomData() - { - var serialized = Handle.GetCustomData(); - if (string.IsNullOrEmpty(serialized) || !BsonDocument.TryParse(serialized, out var doc)) - { - return null; - } - - return doc; - } - - /// - /// Gets the custom user data associated with this user in the Realm app and parses it to the specified type. - /// - /// The managed type that matches the shape of the custom data documents. - /// - /// The data is only refreshed when the user's access token is refreshed or when explicitly calling . - /// - /// A document containing the user data. - /// Custom User Data Docs - public T? GetCustomData() - where T : class - { - var customData = GetCustomData(); - if (customData is null) - { - return null; - } - - return BsonSerializer.Deserialize(customData); - } - - /// - /// Gets a collection of all identities associated with this user. - /// - /// The user's identities across different s. - public UserIdentity[] Identities - { - get - { - var serialized = Handle.GetIdentities(); - return BsonSerializer.Deserialize(serialized); - } - } - - /// - /// Gets a instance that exposes functionality for managing user API keys. - /// - /// A instance scoped to this . - /// API Keys Authentication Docs - public ApiKeyClient ApiKeys { get; } - - /// - /// Gets a instance that exposes functionality for calling remote Atlas Functions. - /// - /// A instance scoped to this . - /// Functions Docs - public FunctionsClient Functions { get; } - - internal readonly SyncUserHandle Handle; - - internal User(SyncUserHandle handle, App? app = null) - { - if (app is null && handle.TryGetApp(out var appHandle)) - { - app = new App(appHandle); - } - - App = app!; - Handle = handle; - Profile = new UserProfile(this); - ApiKeys = new ApiKeyClient(this); - Functions = new FunctionsClient(this); - } - - /// - /// Removes the user's local credentials and attempts to invalidate their refresh token from the server. - /// - /// An awaitable that represents the remote logout operation. - public Task LogOutAsync() => App.RemoveUserAsync(this); - - /// - /// Re-fetch the user's custom data from the server. - /// - /// - /// An awaitable that represents the remote refresh operation. The result is a - /// containing the updated custom user data. The value returned by will also be updated with the new information. - /// - public async Task RefreshCustomDataAsync() - { - await Handle.RefreshCustomDataAsync(); - - return GetCustomData(); - } - - /// - /// Re-fetch the user's custom data from the server. - /// - /// The managed type that matches the shape of the custom data documents. - /// - /// An awaitable that represents the remote refresh operation. The result is an object - /// containing the updated custom user data. The value returned by will also be updated with the new information. - /// - public async Task RefreshCustomDataAsync() - where T : class - { - var result = await RefreshCustomDataAsync(); - if (result is null) - { - return null; - } - - return BsonSerializer.Deserialize(result); - } - - /// - /// Gets a instance for accessing documents in a MongoDB database. - /// - /// The name of the service as configured on the server. - /// A instance that can interact with the databases exposed in the remote service. - public MongoClient GetMongoClient(string serviceName) => new(this, serviceName); - - /// - /// Links the current user with a new user identity represented by the given credentials. - /// - /// - /// Linking a user with more credentials, mean the user can login either of these credentials. It also - /// makes it possible to "upgrade" an anonymous user by linking it with e.g. Email/Password credentials. - ///
- /// Note: It is not possible to link two existing users of Atlas App Services. The provided credentials must not have been used by another user. - ///
- /// Note for email/password auth: To link a user with a new set of credentials, you will need to first - /// register these credentials by calling . - ///
- /// - /// The following snippet shows how to associate an email and password with an anonymous user - /// allowing them to login on a different device. - /// - /// var app = App.Create("app-id") - /// var user = await app.LogInAsync(Credentials.Anonymous()); - /// - /// // This step is only needed for email password auth - a password record must exist - /// // before you can link a user to it. - /// await app.EmailPasswordAuth.RegisterUserAsync("email", "password"); - /// await user.LinkCredentialsAsync(Credentials.EmailPassword("email", "password")); - /// - /// - /// The credentials to link with the current user. - /// - /// An awaitable representing the remote link credentials operation. Upon successful completion, the task result - /// will contain the user to which the credentials were linked. - /// - public async Task LinkCredentialsAsync(Credentials credentials) - { - Argument.NotNull(credentials, nameof(credentials)); - - var handle = await Handle.LinkCredentialsAsync(App.Handle, credentials.ToNative()); - return new User(handle, App); - } - - /// - public override bool Equals(object? obj) => Equals(obj as User); - - /// - /// Determines whether this instance and another instance are equal by comparing their identities. - /// - /// The instance to compare with. - /// true if the two instances are equal; false otherwise. - public bool Equals(User? other) => Id.Equals(other?.Id); - - /// - public override int GetHashCode() => Id.GetHashCode(); - - /// - /// Determines whether two instances are equal. - /// - /// The first user to compare. - /// The second user to compare. - /// true if the two instances are equal; false otherwise. - public static bool operator ==(User? user1, User? user2) => user1?.Id == user2?.Id; - - /// - /// Determines whether two instances are different. - /// - /// The first user to compare. - /// The second user to compare. - /// true if the two instances are different; false otherwise. - public static bool operator !=(User? user1, User? user2) => !(user1 == user2); - - /// - /// Returns a string that represents the current object. - /// - /// A string that represents the current object. - public override string ToString() - { - return $"User {Id}, State: {State}"; - } - - internal void RaiseChanged() - { - _changed?.Invoke(this, EventArgs.Empty); - } - - /// - /// A class exposing functionality for users to manage API keys from the client. It is always scoped - /// to a particular and can only be accessed via . - /// - public class ApiKeyClient - { - private readonly User _user; - - internal ApiKeyClient(User user) - { - _user = user; - } - - /// - /// Creates an API key that can be used to authenticate as the user. - /// - /// - /// The value of the returned API key must be persisted at this time as this is the only - /// time it is visible. The key is enabled when created. It can be disabled by calling - /// . - /// - /// The friendly name of the key. - /// - /// An awaitable representing the asynchronous operation. Successful completion indicates - /// that the has been created on the server and its can - /// be used to create . - /// - public Task CreateAsync(string name) - { - Argument.NotNullOrEmpty(name, nameof(name)); - - return _user.Handle.CreateApiKeyAsync(_user.App.Handle, name); - } - - /// - /// Fetches a specific user API key by id. - /// - /// The id of the key to fetch. - /// - /// An awaitable representing the asynchronous lookup operation. - /// - public Task FetchAsync(ObjectId id) => Handle404(_user.Handle.FetchApiKeyAsync(_user.App.Handle, id)); - - /// - /// Fetches all API keys associated with the user. - /// - /// - /// An awaitable task representing the asynchronous lookup operation. Upon completion, the result contains - /// a collection of all API keys for that user. - /// - public async Task> FetchAllAsync() - { - return await _user.Handle.FetchAllApiKeysAsync(_user.App.Handle); - } - - /// - /// Deletes an API key by id. - /// - /// The id of the key to delete. - /// An awaitable representing the asynchronous delete operation. - public Task DeleteAsync(ObjectId id) => Handle404(_user.Handle.DeleteApiKeyAsync(_user.App.Handle, id)); - - /// - /// Disables an API key by id. - /// - /// The id of the key to disable. - /// An awaitable representing the asynchronous disable operation. - /// - public Task DisableAsync(ObjectId id) => Handle404(_user.Handle.DisableApiKeyAsync(_user.App.Handle, id), id); - - /// - /// Enables an API key by id. - /// - /// The id of the key to enable. - /// An awaitable representing the asynchronous enable operation. - /// - public Task EnableAsync(ObjectId id) => Handle404(_user.Handle.EnableApiKeyAsync(_user.App.Handle, id), id); - - private static async Task Handle404(Task task) - { - try - { - return await task; - } - catch (AppException ex) when (ex.StatusCode == HttpStatusCode.NotFound) - { - return default; - } - } - - private static async Task Handle404(Task task, ObjectId? id = null) - { - try - { - await task; - } - catch (AppException ex) when (ex.StatusCode == HttpStatusCode.NotFound) - { - if (id.HasValue) - { - throw new AppException($"Failed to execute operation because ApiKey with Id: {id} doesn't exist.", ex.HelpLink, 404); - } - } - } - } - - /// - /// A class exposing functionality for calling remote Atlas Functions. - /// - /// Functions Docs - public class FunctionsClient - { - private readonly User _user; - - internal FunctionsClient(User user) - { - _user = user; - } - - /// - /// Calls a remote function with the supplied arguments. - /// - /// Name of the Realm function to call. - /// Arguments that will be sent to the Realm function. They have to be json serializable values. - /// - /// An awaitable wrapping the asynchronous call function operation. The result of the task is - /// the value returned by the function. - /// - public Task CallAsync(string name, params object?[] args) => CallAsync(name, args); - - /// - /// Calls a remote function with the supplied arguments. - /// - /// - /// The MongoDB Bson library is used - /// to decode the response. It will automatically handle most cases, but if you want to control the behavior - /// of the deserializer, you can use the attributes in the - /// MongoDB.Bson.Serialization.Attributes - /// namespace. - ///
- /// If you want to modify the global conventions used when deserializing the response, such as convert - /// camelCase properties to PascalCase, you can register a - /// ConventionPack. - ///
- /// The type that the response will be decoded to. - /// Name of the Realm function to call. - /// Arguments that will be sent to the Realm function. They have to be json serializable values. - /// - /// An awaitable wrapping the asynchronous call function operation. The result of the task is - /// the value returned by the function decoded as . - /// - public Task CallAsync(string name, params object?[] args) => CallSerializedAsync(name, args.ToNativeJson()); - - internal async Task CallSerializedAsync(string name, string args, string? serviceName = null) - { - Argument.NotNullOrEmpty(name, nameof(name)); - - var response = await _user.Handle.CallFunctionAsync(_user.App.Handle, name, args, serviceName); - - return BsonSerializer.Deserialize(response); - } - } - } -} diff --git a/Realm/Realm/Sync/UserIdentity.cs b/Realm/Realm/Sync/UserIdentity.cs deleted file mode 100644 index 83951275cf..0000000000 --- a/Realm/Realm/Sync/UserIdentity.cs +++ /dev/null @@ -1,70 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using MongoDB.Bson.Serialization.Attributes; - -namespace Realms.Sync -{ - /// - /// A class containing information about an identity associated with a user. - /// - [BsonNoId] - public class UserIdentity - { - /// - /// Gets the unique identifier for this identity. - /// - /// The identity's Id. - [Preserve] - public string Id { get; private set; } = null!; - - /// - /// Gets the auth provider defining this identity. - /// - /// The identity's auth provider. - [Preserve] - public Credentials.AuthProvider Provider { get; private set; } - - /// - [Preserve] - public override bool Equals(object? obj) => (obj is UserIdentity id) && id.Id == Id && id.Provider == Provider; - - /// - /// Gets the hash code. - /// - /// The hash code. - [Preserve] - public override int GetHashCode() - { - unchecked - { - var hashCode = -1285871140; - hashCode = (hashCode * -1521134295) + (Id?.GetHashCode() ?? 0); - hashCode = (hashCode * -1521134295) + Provider.GetHashCode(); - return hashCode; - } - } - - /// - /// Returns a string representation of the value. - /// - /// A string representation of the value. - [Preserve] - public override string ToString() => $"UserIdentity: {Id} ({Provider})"; - } -} diff --git a/Realm/Realm/Sync/UserProfile.cs b/Realm/Realm/Sync/UserProfile.cs deleted file mode 100644 index ffb6b60757..0000000000 --- a/Realm/Realm/Sync/UserProfile.cs +++ /dev/null @@ -1,97 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using Realms.Native; - -namespace Realms.Sync -{ - /// - /// A class containing profile information about . - /// - public class UserProfile - { - private readonly User _user; - - /// - /// Gets the name of the user. - /// - /// A string representing the user's name or null if not available. - public string? Name => _user.Handle.GetProfileData(UserProfileField.Name); - - /// - /// Gets the email of the user. - /// - /// A string representing the user's email or null if not available. - public string? Email => _user.Handle.GetProfileData(UserProfileField.Email); - - /// - /// Gets the url for the user's profile picture. - /// - /// A string representing the user's profile picture url or null if not available. - public Uri? PictureUrl - { - get - { - var url = _user.Handle.GetProfileData(UserProfileField.PictureUrl); - return url != null ? new Uri(url) : null; - } - } - - /// - /// Gets the first name of the user. - /// - /// A string representing the user's first name or null if not available. - public string? FirstName => _user.Handle.GetProfileData(UserProfileField.FirstName); - - /// - /// Gets the last name of the user. - /// - /// A string representing the user's last name or null if not available. - public string? LastName => _user.Handle.GetProfileData(UserProfileField.LastName); - - /// - /// Gets the gender of the user. - /// - /// A string representing the user's gender or null if not available. - public string? Gender => _user.Handle.GetProfileData(UserProfileField.Gender); - - /// - /// Gets the birthday of the user. - /// - /// A string representing the user's birthday or null if not available. - public string? Birthday => _user.Handle.GetProfileData(UserProfileField.Birthday); - - /// - /// Gets the minimum age of the user. - /// - /// A string representing the user's minimum age or null if not available. - public string? MinAge => _user.Handle.GetProfileData(UserProfileField.MinAge); - - /// - /// Gets the maximum age of the user. - /// - /// A string representing the user's maximum age or null if not available. - public string? MaxAge => _user.Handle.GetProfileData(UserProfileField.MaxAge); - - internal UserProfile(User user) - { - _user = user; - } - } -} diff --git a/Realm/Realm/Sync/UserState.cs b/Realm/Realm/Sync/UserState.cs deleted file mode 100644 index 4ad62db2ae..0000000000 --- a/Realm/Realm/Sync/UserState.cs +++ /dev/null @@ -1,41 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - /// - /// The state of the user object. - /// - public enum UserState - { - /// - /// The user is logged out. Call with valid credentials to log the user back in. - /// - LoggedOut, - - /// - /// The user is logged in, and any Realms associated with it are synchronizing with Atlas Device Sync. - /// - LoggedIn, - - /// - /// The user has been logged out and their local data has been removed. - /// - Removed - } -} \ No newline at end of file diff --git a/Tests/Realm.Tests/Database/GuidRepresentationMigrationTests.cs b/Tests/Realm.Tests/Database/GuidRepresentationMigrationTests.cs index 187af873d7..add1944708 100644 --- a/Tests/Realm.Tests/Database/GuidRepresentationMigrationTests.cs +++ b/Tests/Realm.Tests/Database/GuidRepresentationMigrationTests.cs @@ -22,7 +22,6 @@ using MongoDB.Bson; using NUnit.Framework; using Realms.Logging; -using Realms.Tests.Sync; #if TEST_WEAVER using TestAsymmetricObject = Realms.AsymmetricObject; using TestEmbeddedObject = Realms.EmbeddedObject; @@ -35,7 +34,7 @@ namespace Realms.Tests.Database { [TestFixture, Preserve(AllMembers = true)] - public class GuidRepresentationMigrationTests : SyncTestBase + public class GuidRepresentationMigrationTests : RealmTest { private RealmConfiguration _configuration = null!; @@ -143,31 +142,6 @@ public void GuidRepresentationMatchesQuery([Values(true, false)] bool useLegacyR AssertQueryDescription(description, guidString, useLegacyRepresentation); } - [Test] - public void FlexibleSync_Subscriptions_MatchesGuid([Values(true, false)] bool useLegacyRepresentation) - { -#pragma warning disable CS0618 // Type or member is obsolete - Realm.UseLegacyGuidRepresentation = useLegacyRepresentation; -#pragma warning restore CS0618 // Type or member is obsolete - - var config = GetFakeFLXConfig(setFakeSyncRoute: true); - config.Schema = new[] { typeof(GuidType), typeof(EmbeddedGuidType) }; - using var realm = GetRealm(config); - - var guidString = "981b8fa2-c496-43b0-b401-48ce08b38e00"; - var guid = Guid.Parse(guidString); - - realm.Subscriptions.Update(() => - { - var query = (RealmResults)realm.All().Where(t => t.RegularProperty == guid); - realm.Subscriptions.Add(query); - }); - - var description = realm.Subscriptions.Single().Query; - - AssertQueryDescription(description, guidString, useLegacyRepresentation); - } - [Test] public void UnmigratedRealm_WhenOpenedAsReadonly_LogsAMessageAndDoesntChangeFile() { @@ -298,53 +272,6 @@ public void Migration_FromLittleEndian_WhenContainingBothGoodAndBadGuids_LogsWar Assert.That(logger.GetLog(), Does.Contain("found to contain Guid values in little-endian format and was automatically migrated")); } - [Test] - public void SynchronizedRealm_DoesntMigrate([Values(true, false)] bool useLegacyRepresentation) - { - var logger = new RealmLogger.InMemoryLogger(); - RealmLogger.Default = logger; - -#pragma warning disable CS0618 // Type or member is obsolete - Realm.UseLegacyGuidRepresentation = useLegacyRepresentation; -#pragma warning restore CS0618 // Type or member is obsolete - - var expected = GetGuidObjects().ToArray(); - - var config = GetFakeConfig(userId: "sync-guids-test-user", setFakeSyncRoute: true); - config.Schema = new[] { typeof(GuidType), typeof(EmbeddedGuidType) }; - - TestHelpers.CopyBundledFileToDocuments("sync-guids.realm", config.DatabasePath); - using var realm = GetRealm(config); - - var actual = realm.All().ToArray(); - - Assert.That(actual.Length, Is.EqualTo(expected.Length)); - - if (useLegacyRepresentation) - { - foreach (var expectedObj in expected) - { - var actualObj = actual.Single(o => o.Id == expectedObj.Id); - - AssertEqual(expectedObj, actualObj); - - var actualFound = realm.Find(expectedObj.Id); - Assert.That(actualObj, Is.EqualTo(actualFound)); - } - } - else - { - foreach (var expectedObj in expected) - { - var flipped = FlipGuid(expectedObj.Id); - var actualObj = actual.Single(o => o.Id == flipped); - Assert.That(actualObj.RegularProperty, Is.EqualTo(FlipGuid(expectedObj.RegularProperty))); - } - } - - Assert.That(logger.GetLog(), Does.Not.Contain("migrated")); - } - protected override void CustomSetUp() { _configuration = new RealmConfiguration(Guid.NewGuid().ToString()) diff --git a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/EmbeddedGuidType_generated.cs b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/EmbeddedGuidType_generated.cs index c96482794d..0c37456308 100644 --- a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/EmbeddedGuidType_generated.cs +++ b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/EmbeddedGuidType_generated.cs @@ -8,7 +8,6 @@ using Realms.Logging; using Realms.Schema; using Realms.Tests.Database; -using Realms.Tests.Sync; using Realms.Weaving; using System; using System.Collections.Generic; diff --git a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/GuidType_generated.cs b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/GuidType_generated.cs index 85a68ed3d6..8b9ea3b9d5 100644 --- a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/GuidType_generated.cs +++ b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/GuidType_generated.cs @@ -8,7 +8,6 @@ using Realms.Logging; using Realms.Schema; using Realms.Tests.Database; -using Realms.Tests.Sync; using Realms.Weaving; using System; using System.Collections.Generic; diff --git a/Tests/Realm.Tests/Program.cs b/Tests/Realm.Tests/Program.cs index ee7dfb8ed2..bfb7cd7bf5 100644 --- a/Tests/Realm.Tests/Program.cs +++ b/Tests/Realm.Tests/Program.cs @@ -32,8 +32,6 @@ public static int Main(string[] args) var autorun = new AutoRun(typeof(Program).GetTypeInfo().Assembly); IDisposable? logger = null; - (args, logger) = Sync.SyncTestHelpers.SetLoggerFromArgs(args); - args = Sync.SyncTestHelpers.ExtractBaasSettings(args); autorun.Execute(args); diff --git a/Tests/Realm.Tests/Realm.Tests.csproj b/Tests/Realm.Tests/Realm.Tests.csproj index dc7fa2dacc..e470ddd88c 100644 --- a/Tests/Realm.Tests/Realm.Tests.csproj +++ b/Tests/Realm.Tests/Realm.Tests.csproj @@ -151,12 +151,6 @@ Never - - - Sync\Baas\BaasClient.cs - - - diff --git a/Tests/Realm.Tests/Serialization/SerializationTests.cs b/Tests/Realm.Tests/Serialization/SerializationTests.cs index ea236cd9b3..63114c75b7 100644 --- a/Tests/Realm.Tests/Serialization/SerializationTests.cs +++ b/Tests/Realm.Tests/Serialization/SerializationTests.cs @@ -507,22 +507,6 @@ public void CollectionsObject_Serializes(TestCaseData testCase) AssertMatchesBsonDocument(deserializedBson, original); } - [Test, Ignore("Serializers are cached, so once we change the serializer we can't restore it, and this is a problem for the other tests.")] - public void LegacySerialization_CanBeSet() - { - var dateTime = new DateTimeOffset(2020, 10, 10, 10, 10, 10, TimeSpan.Zero); - -#pragma warning disable CS0618 // This is for a test - Realm.SetLegacySerialization(); -#pragma warning restore CS0618 // This is for a test - - var legacySerialized = SerializationHelper.ToNativeJson(dateTime); - - // The new serialization is serializing DateTimeOffset as a BSON DateTime, so - // we are veryfying that is being serialized as a string, as in the legacy serializer. - Assert.That(legacySerialized, Does.Contain("2020-10-10T10:10:10+00:00")); - } - private void AddIfNecessary(IRealmObject obj) { if (_managed) diff --git a/Tests/Realm.Tests/Sync/AppTests.cs b/Tests/Realm.Tests/Sync/AppTests.cs deleted file mode 100644 index 5a538b4d8e..0000000000 --- a/Tests/Realm.Tests/Sync/AppTests.cs +++ /dev/null @@ -1,442 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Reflection; -using System.Runtime.Versioning; -using System.Threading; -using System.Threading.Tasks; -using Baas; -using NUnit.Framework; -using Realms.Logging; -using Realms.PlatformHelpers; -using Realms.Sync; -using Realms.Sync.Exceptions; - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class AppTests : SyncTestBase - { - [Test] - public void DeviceInfo_OutputsMeaningfulInfo() - { - static void AssertBundleId(params string?[] expectedValues) - { - var localTestRunners = new[] { "ReSharperTestRunner", "testhost" }; - var values = expectedValues.Concat(localTestRunners).Select(Platform.Sha256).ToArray(); - Assert.That(values, Does.Contain(Platform.BundleId)); - } - - if (TestHelpers.IsUnity) - { - Assert.That(Platform.DeviceInfo.Name, Is.EqualTo(Platform.Unknown)); - Assert.That(Platform.DeviceInfo.Version, Is.Not.EqualTo(Platform.Unknown)); - Assert.That(Platform.BundleId, Is.EqualTo(Platform.Sha256("Tests.Unity"))); - return; - } - - var framework = Assembly.GetEntryAssembly()?.GetCustomAttribute()?.FrameworkName; - - var os = SharedRealmHandle.GetNativeLibraryOS(); - switch (os) - { - case "Windows": - case "Linux": - Assert.That(Platform.DeviceInfo.Name, Is.EqualTo(Platform.Unknown), "Name"); - Assert.That(Platform.DeviceInfo.Version, Is.EqualTo(Platform.Unknown), "Version"); - AssertBundleId("Realm.Tests"); - break; - case "macOS": - // We don't detect the device on .NET Core apps, only Xamarin or net6.0-maccatalyst. - if (framework?.Contains(".NETCoreApp") == true) - { - Assert.That(Platform.DeviceInfo.Name, Is.EqualTo(Platform.Unknown), "Name"); - Assert.That(Platform.DeviceInfo.Version, Is.EqualTo(Platform.Unknown), "Version"); - AssertBundleId("Realm.Tests"); - } - else - { - Assert.That(Platform.DeviceInfo.Name, Is.EqualTo("Apple"), "Name"); - Assert.That(Platform.DeviceInfo.Version, Is.Not.EqualTo(Platform.Unknown), "Version"); - AssertBundleId("Tests.XamarinMac"); - } - - break; - case "iOS": - Assert.That(Platform.DeviceInfo.Name, Is.EqualTo("iPhone"), "Name"); - Assert.That(Platform.DeviceInfo.Version, Does.Contain("iPhone").Or.EqualTo("x86_64").Or.EqualTo("arm64"), "Version"); - AssertBundleId("Tests.iOS", "Tests.Maui"); - break; - case "Android": - Assert.That(Platform.DeviceInfo.Name, Is.Not.EqualTo(Platform.Unknown), "Name"); - Assert.That(Platform.DeviceInfo.Version, Is.Not.EqualTo(Platform.Unknown), "Version"); - AssertBundleId("Tests.Android", "Tests.Maui", null); // On MAUI, we may not be able to detect the bundle id - break; - case "UWP": - if (TestHelpers.IsUWP) - { -#if DEBUG - // Extracting device info only works for local builds - in many cases we don't have registry access on CI - // so we can't make assumptions about what value we'll get for Name/Version. - Assert.That(Platform.DeviceInfo.Name, Is.Not.EqualTo(Platform.Unknown), "Name"); - Assert.That(Platform.DeviceInfo.Version, Is.Not.EqualTo(Platform.Unknown), "Version"); -#endif - } - else - { - Assert.That(Platform.DeviceInfo.Name, Is.EqualTo(Platform.Unknown), "Name"); - Assert.That(Platform.DeviceInfo.Version, Is.EqualTo(Platform.Unknown), "Version"); - } - - AssertBundleId("Tests.UWP"); - break; - case "tvOS": - Assert.That(Platform.DeviceInfo.Name, Is.EqualTo("Apple TV"), "Name"); - Assert.That(Platform.DeviceInfo.Version, Does.Contain("AppleTV").Or.EqualTo("x86_64"), "Version"); - AssertBundleId("Tests.XamarinTVOS"); - break; - case "Mac Catalyst": - Assert.That(Platform.DeviceInfo.Name, Is.EqualTo("iPad"), "Name"); - Assert.That(Platform.DeviceInfo.Version, Does.Contain("iPad").Or.EqualTo("x86_64").Or.EqualTo("arm64"), "Version"); - AssertBundleId("Tests.Maui"); - break; - default: - Assert.Fail($"Unknown OS: {os}"); - break; - } - } - - [Test] - public void AppCreate_CreatesApp() - { - var basePath = Path.Combine(InteropConfig.GetDefaultStorageFolder("No error expected here"), "foo-bar"); - Directory.CreateDirectory(basePath); - - // This is mostly a smoke test to ensure that nothing blows up when setting all properties. - var config = new AppConfiguration("abc-123") - { - BaseUri = new Uri("http://foo.bar"), - MetadataEncryptionKey = new byte[64], - MetadataPersistenceMode = MetadataPersistenceMode.Encrypted, - BaseFilePath = basePath, - DefaultRequestTimeout = TimeSpan.FromSeconds(123) - }; - - var app = CreateApp(config); - Assert.That(app.Sync, Is.Not.Null); - Assert.That(app.BaseUri, Is.EqualTo(config.BaseUri)); - Assert.That(app.BaseFilePath, Is.EqualTo(config.BaseFilePath)); - Assert.That(app.Id, Is.EqualTo(config.AppId)); - } - - [Test] - public void App_Login_Anonymous() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await DefaultApp.LogInAsync(Credentials.Anonymous()); - Assert.That(user, Is.Not.Null); - Assert.That(user.Id, Is.Not.Null); - }); - } - - [TestCase(LogLevel.Debug)] - [TestCase(LogLevel.Info)] - public void RealmConfiguration_WithCustomLogger_LogsSyncOperations(LogLevel logLevel) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - RealmLogger.SetLogLevel(logLevel); - var logger = new RealmLogger.InMemoryLogger(); - RealmLogger.Default = logger; - - var config = await GetIntegrationConfigAsync(Guid.NewGuid().ToString()); - using var realm = await GetRealmAsync(config); - realm.Write(() => - { - realm.Add(new PrimaryKeyStringObject { Id = Guid.NewGuid().ToString() }); - }); - - await WaitForUploadAsync(realm); - - var log = logger.GetLog(); - - Assert.That(log, Does.Contain($"{logLevel}:")); - Assert.That(log, Does.Not.Contain($"{logLevel - 1}:")); - }); - } - - [Test] - public void RealmConfiguration_WithCustomHttpClientHandler_CleanedUpAfterAppDestroyed() - { - TestHelpers.RunAsyncTest(async () => - { - App app = null!; - var gcTask = TestHelpers.EnsureObjectsAreCollected(() => - { - var handler = new HttpClientHandler(); - - app = CreateApp(new AppConfiguration("abc") - { - HttpClientHandler = handler - }); - - return new[] { handler }; - }); - - // Since apps are cached, we need to clear the cache to destroy the app - // and trigger the chain that will eventually free the HttpClient holding - // the HttpClientHandler - AppHandle.ForceCloseHandles(clearNativeCache: true); - - await gcTask; - }); - } - - [Test] - public void AppCreate_CacheTests([Values(true, false, null)] bool? useCache) - { - var config1 = GetConfig(); - var app1 = CreateApp(config1); - - var config2 = GetConfig(); - var app2 = CreateApp(config2); - - Assert.That(config1.BaseFilePath, Is.Not.EqualTo(config2.BaseFilePath)); - Assert.That(app1.Id, Is.EqualTo(app2.Id)); - Assert.That(app1.GetHashCode(), Is.EqualTo(app2.GetHashCode())); - - // null or true mean cache should be used - if (useCache != false) - { - // If we cached the app, the second base file path should have been ignored - Assert.That(app1.BaseFilePath, Is.EqualTo(app2.BaseFilePath)); - Assert.That(app1.Equals(app2), Is.True); - } - else - { - Assert.That(app1.BaseFilePath, Is.Not.EqualTo(app2.BaseFilePath)); - Assert.That(app1.Equals(app2), Is.False); - } - - AppConfiguration GetConfig() - { - var baseFilePath = Path.Combine(InteropConfig.GetDefaultStorageFolder("no error expected"), Guid.NewGuid().ToString()); - var config = new AppConfiguration("abc") - { - BaseFilePath = baseFilePath, - }; - - if (useCache.HasValue) - { - config.UseAppCache = useCache.Value; - } - - Directory.CreateDirectory(config.BaseFilePath); - return config; - } - } - - [Test] - public void App_Create_SameId_DifferentBaseUri_ReturnsDifferentApps() - { - var config1 = GetConfig("https://localhost:443"); - var config2 = GetConfig("http://localhost:80"); - - var app1 = CreateApp(config1); - var app2 = CreateApp(config2); - - Assert.That(app1.Id, Is.EqualTo(app2.Id)); - Assert.That(app1.GetHashCode(), Is.EqualTo(app2.GetHashCode())); - - Assert.That(app1.Equals(app2), Is.False); - Assert.That(app1 == app2, Is.False); - Assert.That(app1 != app2, Is.True); - Assert.That(app1.BaseUri, Is.Not.EqualTo(app2.BaseUri)); - Assert.That(app1.BaseFilePath, Is.Not.EqualTo(app2.BaseFilePath)); - - static AppConfiguration GetConfig(string uri) - { - var baseFilePath = Path.Combine(InteropConfig.GetDefaultStorageFolder("no error expected"), Guid.NewGuid().ToString()); - var config = new AppConfiguration("abc") - { - BaseFilePath = baseFilePath, - BaseUri = new Uri(uri) - }; - - Directory.CreateDirectory(config.BaseFilePath); - return config; - } - } - - [Test] - public void App_EqualsGetHashCodeTests() - { - var config1 = new AppConfiguration("abc"); - var config2 = new AppConfiguration("cde"); - var config3 = new AppConfiguration("abc"); - var config4 = new AppConfiguration("abc") - { - UseAppCache = false - }; - - var app1 = CreateApp(config1); - var app2 = CreateApp(config2); - var app3 = CreateApp(config3); - var app4 = CreateApp(config4); - - Assert.That(app1.GetHashCode(), Is.Not.EqualTo(app2.GetHashCode())); - Assert.That(app1.GetHashCode(), Is.EqualTo(app3.GetHashCode())); - Assert.That(app1.GetHashCode(), Is.EqualTo(app4.GetHashCode())); - - Assert.That(app1.Equals(app2), Is.False); - Assert.That(app1.Equals(app3), Is.True); - Assert.That(app1.Equals(app4), Is.False); // app4 is uncached, so a different instance is returned - - Assert.That(app1 == app2, Is.False); - Assert.That(app1 == app3, Is.True); - Assert.That(app1 == app4, Is.False); // app4 is uncached, so a different instance is returned - - Assert.That(app1 != app2, Is.True); - Assert.That(app1 != app3, Is.False); - Assert.That(app1 != app4, Is.True); // app4 is uncached, so a different instance is returned - - Assert.That(app1.Equals(app1.Id), Is.False); - Assert.That(app1.Equals(null), Is.False); - Assert.That(app1 == null, Is.False); - Assert.That(app1 != null, Is.True); - - App? app5 = null; - - Assert.That(app5 == null, Is.True); - Assert.That(app5 != null, Is.False); - } - - private class TestHttpClientHandler : DelegatingHandler - { - public readonly List<(HttpMethod Method, string Url)> Requests = new(); - - public TestHttpClientHandler() : base(TestHelpers.TestHttpHandlerFactory()) - { - } - - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - Requests.Add((request.Method, request.RequestUri!.AbsoluteUri)); - return base.SendAsync(request, cancellationToken); - } - } - - [Test] - public void RealmConfiguration_WithCustomHttpClientHandler_UsedWhenMakingCalls() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var handler = new TestHttpClientHandler(); - - var app = CreateApp(new AppConfiguration("abc") - { - BaseUri = SyncTestHelpers.BaasUri!, - HttpClientHandler = handler - }); - - var ex = await TestHelpers.AssertThrows(() => app.LogInAsync(Credentials.Anonymous())); - - // Http error - Assert.That(ex.Message, Does.Contain("cannot find app")); - - // The app doesn't exist, so we expect 404 - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - - Assert.That(handler.Requests.Count, Is.EqualTo(1)); - - // https://realm.mongodb.com/api/client/v2.0/app/abc/location - Assert.That(handler.Requests[0].Method, Is.EqualTo(HttpMethod.Get)); - Assert.That(handler.Requests[0].Url, Does.Contain("abc/location")); - }); - } - - [Test] - public void RealmConfiguration_HttpClientHandler_IsNotSet() - { - var config = new AppConfiguration("abc"); - Assert.That(config.HttpClientHandler, Is.Null); - } - - [Test] - public void RealmConfiguration_HttpClientHandler_MayBeNull() - { - var config = new AppConfiguration("abc"); - config.HttpClientHandler = null; - config.HttpClientHandler = new HttpClientHandler(); - config.HttpClientHandler = null; - } - - [Test] - public void RealmConfigurationBaseUrl_ReturnsExpectedValue() - { - var config = new AppConfiguration("abc"); - Assert.That(config.BaseUri, Is.EqualTo(new Uri("https://services.cloud.mongodb.com"))); - } - - [Test] - public void App_UpdateBaseUri_UpdatesBaseUri() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var appConfig = SyncTestHelpers.GetAppConfig(AppConfigType.FlexibleSync); - appConfig.BaseUri = new Uri("https://services.mongodb.com"); - var app = CreateApp(appConfig); - - Assert.That(app.BaseUri, Is.EqualTo(new Uri("https://services.mongodb.com"))); - -#pragma warning disable Rlm001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - await app.UpdateBaseUriAsync(SyncTestHelpers.BaasUri!); -#pragma warning restore Rlm001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - - Assert.That(app.BaseUri, Is.EqualTo(SyncTestHelpers.BaasUri)); - }); - } - - [Test] - public void App_UpdateBaseUri_WhenUnreachable_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var appConfig = SyncTestHelpers.GetAppConfig(AppConfigType.FlexibleSync); - appConfig.BaseUri = new Uri("https://services.mongodb.com"); - var app = CreateApp(appConfig); - - Assert.That(app.BaseUri, Is.EqualTo(new Uri("https://services.mongodb.com"))); - -#pragma warning disable Rlm001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - var ex = await TestHelpers.AssertThrows(() => app.UpdateBaseUriAsync(new Uri("https://google.com"))); -#pragma warning restore Rlm001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - - Assert.That(ex.Message, Does.Contain("404")); - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - }); - } - } -} diff --git a/Tests/Realm.Tests/Sync/AsymmetricObjectTests.cs b/Tests/Realm.Tests/Sync/AsymmetricObjectTests.cs deleted file mode 100644 index 21c83b068e..0000000000 --- a/Tests/Realm.Tests/Sync/AsymmetricObjectTests.cs +++ /dev/null @@ -1,792 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#if TEST_WEAVER -using TestAsymmetricObject = Realms.AsymmetricObject; -#else -using TestAsymmetricObject = Realms.IAsymmetricObject; -#endif -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Baas; -using MongoDB.Bson; -using NUnit.Framework; -using Realms.Dynamic; -using Realms.Exceptions; -using Realms.Sync; - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class AsymmetricObjectTests : SyncTestBase - { - public static object[] SetAndGetValueCases = - { - new object[] { "CharProperty", '0' }, - new object[] { "ByteProperty", (byte)100 }, - new object[] { "Int16Property", (short)100 }, - new object[] { "Int32Property", 100 }, - new object[] { "Int64Property", 100L }, - new object[] { "SingleProperty", 123.123f }, - new object[] { "DoubleProperty", 123.123 }, - new object[] { "BooleanProperty", true }, - new object[] { "ByteArrayProperty", new byte[] { 0xde, 0xad, 0xbe, 0xef } }, - new object[] { "ByteArrayProperty", Array.Empty() }, - new object[] { "StringProperty", "hello" }, - new object[] { "DecimalProperty", 123.456M }, - new object[] { "DecimalProperty", decimal.MinValue }, - new object[] { "DecimalProperty", decimal.MaxValue }, - new object[] { "DecimalProperty", decimal.One }, - new object[] { "DecimalProperty", decimal.MinusOne }, - new object[] { "DecimalProperty", decimal.Zero }, - new object[] { "Decimal128Property", new Decimal128(564.42343424323) }, - new object[] { "Decimal128Property", new Decimal128(decimal.MinValue) }, - new object[] { "Decimal128Property", new Decimal128(decimal.MaxValue) }, - new object[] { "Decimal128Property", Decimal128.MinValue }, - new object[] { "Decimal128Property", Decimal128.MaxValue }, - new object[] { "Decimal128Property", Decimal128.Zero }, - new object[] { "ObjectIdProperty", ObjectId.Empty }, - new object[] { "ObjectIdProperty", new ObjectId("5f63e882536de46d71877979") }, - new object[] { "GuidProperty", Guid.Empty }, - new object[] { "GuidProperty", Guid.Parse("{C4EC8CEF-D62A-405E-83BB-B0A3D8DABB36}") }, - }; - - public static object[] SetAndReplaceWithNullCases = - { - new object[] { "NullableCharProperty", '0' }, - new object[] { "NullableByteProperty", (byte)100 }, - new object[] { "NullableInt16Property", (short)100 }, - new object[] { "NullableInt32Property", 100 }, - new object[] { "NullableInt64Property", 100L }, - new object[] { "NullableSingleProperty", 123.123f }, - new object[] { "NullableDoubleProperty", 123.123 }, - new object[] { "NullableBooleanProperty", true }, - new object[] { "NullableDecimalProperty", 123.456M }, - new object[] { "NullableDecimal128Property", new Decimal128(123.456) }, - new object[] { "ByteArrayProperty", new byte[] { 0xde, 0xad, 0xbe, 0xef } }, - new object[] { "ByteArrayProperty", Array.Empty() }, - new object[] { "StringProperty", "hello" }, - new object[] { "StringProperty", string.Empty }, - new object[] { "NullableObjectIdProperty", new ObjectId("5f63e882536de46d71877979") }, - new object[] { "NullableGuidProperty", Guid.Parse("{C4EC8CEF-D62A-405E-83BB-B0A3D8DABB36}") } - }; - - [Test] - public void AddCollectionOfAsymmetricObjs() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var flxConfig = await GetFLXIntegrationConfigAsync(); - using var realm = await GetRealmAsync(flxConfig); - var partitionLike = Guid.NewGuid().ToString(); - - Assert.DoesNotThrow(() => - { - realm.Write(() => - { - realm.Add(new BasicAsymmetricObject[] - { - new() { PartitionLike = partitionLike }, - new() { PartitionLike = partitionLike }, - new() { PartitionLike = partitionLike }, - new() { PartitionLike = partitionLike }, - }); - }); - }); - - await WaitForUploadAsync(realm).Timeout(10_000, detail: "Wait for upload"); - - var documents = await GetRemoteObjects( - flxConfig.User, nameof(BasicAsymmetricObject.PartitionLike), partitionLike).Timeout(10_000, "Get remote objects"); - - Assert.That(documents.Length, Is.EqualTo(4)); - Assert.That(documents.Where(x => x.PartitionLike == partitionLike).Count, Is.EqualTo(4)); - }); - } - - [Test] - public void AddCollection_WithSomeObjectsAlreadyAdded_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var flxConfig = await GetFLXIntegrationConfigAsync(); - using var realm = await GetRealmAsync(flxConfig); - var partitionLike = Guid.NewGuid().ToString(); - - Assert.Throws(() => - { - realm.Write(() => - { - var doubleObj = new BasicAsymmetricObject { PartitionLike = partitionLike }; - realm.Add(new BasicAsymmetricObject[] - { - new BasicAsymmetricObject { PartitionLike = partitionLike }, - doubleObj, - }); - - realm.Add(new BasicAsymmetricObject[] - { - doubleObj, - new BasicAsymmetricObject { PartitionLike = partitionLike } - }); - }); - }); - }); - } - - [Test] - public void AddHugeAsymmetricObj() - { - const int ObjectSize = 1_000_000; - - SyncTestHelpers.RunBaasTestAsync(async () => - { - ObjectId id = default; - - var flxConfig = await GetFLXIntegrationConfigAsync(); - using var realm = await GetRealmAsync(flxConfig); - - realm.Write(() => - { - var hugeObj = AsymmetricObjectWithAllTypes.CreateWithData(ObjectSize); - id = hugeObj.Id; - realm.Add(hugeObj); - }); - - await WaitForUploadAsync(realm); - var documents = await GetRemoteObjects(flxConfig.User, "_id", BsonValue.Create(id)); - Assert.That(documents.Length, Is.EqualTo(1)); - Assert.That(documents[0].ByteArrayProperty!.Count, Is.EqualTo(ObjectSize)); - }); - } - - [Test] - public void AccessAsymmetricObjAfterAddedToRealm_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partitionLike = Guid.NewGuid().ToString(); - using var realm = await GetFLXIntegrationRealmAsync(); - - var asymmetribObj = new BasicAsymmetricObject - { - PartitionLike = partitionLike - }; - - realm.Write(() => - { - realm.Add(asymmetribObj); - }); - - Assert.That(asymmetribObj.IsManaged); - Assert.That(asymmetribObj.IsValid, Is.False); - - var ex = Assert.Throws(() => _ = asymmetribObj.PartitionLike)!; - Assert.That(ex.Message.Contains("Attempted to access detached row")); - }); - } - - [Test] - public void AddSameAsymmetricObjTwice_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var realm = await GetFLXIntegrationRealmAsync(); - var partitionLike = Guid.NewGuid().ToString(); - var asymmetricObj = new BasicAsymmetricObject - { - PartitionLike = partitionLike - }; - - realm.Write(() => - { - realm.Add(asymmetricObj); - Assert.Throws(() => - { - realm.Add(asymmetricObj); - }); - }); - }); - } - - [TestCaseSource(nameof(SetAndGetValueCases))] - [TestCaseSource(nameof(SetAndReplaceWithNullCases))] - public void SetAndRemotelyReadValue(string propertyName, object propertyValue) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - ObjectId id = default; - var flxConfig = await GetFLXIntegrationConfigAsync(); - using var realm = await GetRealmAsync(flxConfig); - - realm.Write(() => - { - var asymmetricObjAllTypes = new AsymmetricObjectWithAllTypes { RequiredStringProperty = string.Empty }; - id = asymmetricObjAllTypes.Id; - TestHelpers.SetPropertyValue(asymmetricObjAllTypes, propertyName, propertyValue); - realm.Add(asymmetricObjAllTypes); - }); - - await WaitForUploadAsync(realm); - var documents = await GetRemoteObjects( - flxConfig.User, "_id", BsonValue.Create(id)); - - Assert.That(documents.Length, Is.EqualTo(1)); - Assert.That(TestHelpers.GetPropertyValue(documents.Single(), propertyName), Is.EqualTo(propertyValue)); - }); - } - - [Test] - public void MixAddingObjectAsymmetricAndNot() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partitionLike = Guid.NewGuid().ToString(); - var id = new Random().Next(); - var flxConfig = await GetFLXIntegrationConfigAsync(); - - flxConfig.PopulateInitialSubscriptions = (realm) => - { - var query = realm.All().Where(n => n.Id == id); - realm.Subscriptions.Add(query); - }; - - using var realm = await GetRealmAsync(flxConfig); - - Assert.DoesNotThrow(() => - { - realm.Write(() => - { - realm.Add(new BasicAsymmetricObject - { - PartitionLike = partitionLike - }); - - realm.Add(new PrimaryKeyInt32Object - { - Id = id - }); - }); - }); - }); - } - - [Test] - public void AsymmetricObjectInPbs_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - config.Schema = new[] { typeof(BasicAsymmetricObject) }; - - var ex = Assert.Throws(() => GetRealm(config))!; - Assert.That(ex.Message, Does.Contain($"Asymmetric table '{nameof(BasicAsymmetricObject)}' not allowed in partition based sync")); - }); - } - - [Test] - public void AsymmetricObjectInLocalRealm_Throws() - { - var config = (RealmConfiguration)RealmConfiguration.DefaultConfiguration; - config.Schema = new[] { typeof(BasicAsymmetricObject) }; - - var ex = Assert.Throws(() => GetRealm(config))!; - Assert.That(ex.Message, Does.Contain($"Asymmetric table '{nameof(BasicAsymmetricObject)}' not allowed in a local Realm")); - } - - [Test] - public void EmbeddedObject_WhenParentAccessed_ReturnsParent() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var realm = await GetFLXIntegrationRealmAsync(); - - var parent = new AsymmetricObjectWithEmbeddedRecursiveObject - { - RecursiveObject = new EmbeddedLevel1 - { - Child = new EmbeddedLevel2 - { - Child = new EmbeddedLevel3() - } - } - }; - - realm.Write(() => - { - realm.Add(parent); - - Assert.That(parent, Is.EqualTo(parent.RecursiveObject.Parent)); - - var firstChild = parent.RecursiveObject; - Assert.That(firstChild, Is.EqualTo(firstChild.Child.Parent)); - - var secondChild = firstChild.Child; - Assert.That(secondChild, Is.EqualTo(secondChild.Child.Parent)); - }); - }); - } - - [Test] - public void EmbeddedObject_WhenParentAccessedInList_ReturnsParent() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var realm = await GetFLXIntegrationRealmAsync(); - - var parent = new AsymmetricObjectWithEmbeddedListObject(); - parent.EmbeddedListObject.Add(new EmbeddedIntPropertyObject()); - - realm.Write(() => - { - realm.Add(parent); - - Assert.That(parent, Is.EqualTo(parent.EmbeddedListObject.Single().Parent)); - }); - }); - } - - [Test] - public void EmbeddedObject_WhenParentAccessedInDictionary_ReturnsParent() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var realm = await GetFLXIntegrationRealmAsync(); - - var parent = new AsymmetricObjectWithEmbeddedDictionaryObject(); - parent.EmbeddedDictionaryObject.Add("child", new EmbeddedIntPropertyObject()); - - realm.Write(() => - { - realm.Add(parent); - - Assert.That(parent, Is.EqualTo(parent.EmbeddedDictionaryObject["child"]!.Parent)); - }); - }); - } - - [Test] - public void EmbeddedObjectUnmanaged_WhenParentAccessed_ReturnsNull() - { - var parent = new AsymmetricObjectWithEmbeddedRecursiveObject - { - RecursiveObject = new EmbeddedLevel1 - { - Child = new EmbeddedLevel2 - { - Child = new EmbeddedLevel3() - } - } - }; - - Assert.That(parent.RecursiveObject.Parent, Is.Null); - - var firstChild = parent.RecursiveObject; - Assert.That(firstChild.Child.Parent, Is.Null); - - var secondChild = firstChild.Child; - Assert.That(secondChild.Child.Parent, Is.Null); - } - - [Test] - public void NonEmbeddedObject_WhenParentAccessed_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var realm = await GetFLXIntegrationRealmAsync(); - - var topLevel = new BasicAsymmetricObject - { - PartitionLike = Guid.NewGuid().ToString() - }; - - realm.Write(() => - { - realm.Add(topLevel); - - // Objects not implementing IEmbeddedObject will not have the "Parent" field, - // but the "GetParent" method is still accessible on its accessor. It should - // throw as it should not be used for such objects. - Assert.Throws(() => ((IRealmObjectBase)topLevel).Accessor.GetParent()); - }); - }); - } - - [Test] - public void RealmValuePropertyWithAsymmetricObject_WhenAddedToRealm_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var realm = await GetRealmWithRealmValueSchemaAsync(); - - realm.Write(() => - { - var rvo = new RealmValueObject - { - RealmValueProperty = new BasicAsymmetricObject() - }; - - Assert.Throws(() => realm.Add(rvo)); - }); - }); - } - - [Test] - public void RealmValueListWithAsymmetricObject_WhenAddedToRealm_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var realm = await GetRealmWithRealmValueSchemaAsync(); - - realm.Write(() => - { - var rvo = new RealmValueObject - { - RealmValueList = { new BasicAsymmetricObject() } - }; - - Assert.Throws(() => realm.Add(rvo)); - }); - }); - } - - [Test] - public void RealmValueSetWithAsymmetricObject_WhenAddedToRealm_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var realm = await GetRealmWithRealmValueSchemaAsync(); - - realm.Write(() => - { - var rvo = new RealmValueObject - { - RealmValueSet = { new BasicAsymmetricObject() } - }; - - Assert.Throws(() => realm.Add(rvo)); - }); - }); - } - - [Test] - public void RealmValueDictionaryWithAsymmetricObject_WhenAddedToRealm_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var realm = await GetRealmWithRealmValueSchemaAsync(); - - realm.Write(() => - { - var rvo = new RealmValueObject - { - RealmValueDictionary = { { "embedded", new BasicAsymmetricObject() } } - }; - - Assert.Throws(() => realm.Add(rvo)); - }); - }); - } - - [Test] - public void RealmValuePropertyWithAsymmetricObject_WhenModifiedInRealm_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var realm = await GetRealmWithRealmValueSchemaAsync(); - - realm.Write(() => - { - var rvo = new RealmValueObject(); - realm.Add(rvo); - - Assert.Throws(() => rvo.RealmValueProperty = new BasicAsymmetricObject()); - }); - }); - } - - [Test] - public void RealmValueListWithAsymmetricObject_WhenModifiedInRealm_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var realm = await GetRealmWithRealmValueSchemaAsync(); - - realm.Write(() => - { - var rvo = new RealmValueObject(); - realm.Add(rvo); - - Assert.Throws(() => rvo.RealmValueList.Add(new BasicAsymmetricObject())); - Assert.Throws(() => rvo.RealmValueList.Insert(0, new BasicAsymmetricObject())); - Assert.Throws(() => rvo.RealmValueList[0] = new BasicAsymmetricObject()); - }); - }); - } - - [Test] - public void RealmValueSetWithAsymmetricObject_WhenModifiedInRealm_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var realm = await GetRealmWithRealmValueSchemaAsync(); - - realm.Write(() => - { - var rvo = new RealmValueObject(); - realm.Add(rvo); - - Assert.Throws(() => rvo.RealmValueSet.Add(new BasicAsymmetricObject())); - }); - }); - } - - [Test] - public void RealmValueDictionaryWithAsymmetricObject_WhenModifiedInRealm_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var realm = await GetRealmWithRealmValueSchemaAsync(); - - realm.Write(() => - { - var rvo = new RealmValueObject(); - realm.Add(rvo); - - Assert.Throws(() => rvo.RealmValueDictionary.Add("embedded", new BasicAsymmetricObject())); - Assert.Throws(() => rvo.RealmValueDictionary["embedded"] = new BasicAsymmetricObject()); - }); - }); - } - - [Test] - public void DynamicAccess([Values(true, false)] bool isDynamic) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.IsDynamic = isDynamic; - using var realm = await GetRealmAsync(flxConfig); - - realm.Write(() => - { - var asymmetricObj = (IAsymmetricObject)realm.DynamicApi.CreateObject(nameof(AsymmetricObjectWithAllTypes), ObjectId.GenerateNewId()); - - if (isDynamic) - { - Assert.That(asymmetricObj, Is.InstanceOf()); - } - else - { - Assert.That(asymmetricObj, Is.InstanceOf()); - } - - asymmetricObj.DynamicApi.Set(nameof(AsymmetricObjectWithAllTypes.CharProperty), 'F'); - asymmetricObj.DynamicApi.Set(nameof(AsymmetricObjectWithAllTypes.NullableCharProperty), 'o'); - asymmetricObj.DynamicApi.Set(nameof(AsymmetricObjectWithAllTypes.StringProperty), "o"); - - Assert.That(asymmetricObj.DynamicApi.Get(nameof(AllTypesObject.CharProperty)), Is.EqualTo('F')); - Assert.That(asymmetricObj.DynamicApi.Get(nameof(AllTypesObject.NullableCharProperty)), Is.EqualTo('o')); - Assert.That(asymmetricObj.DynamicApi.Get(nameof(AllTypesObject.StringProperty)), Is.EqualTo("o")); - }); - -#if !UNITY - realm.Write(() => - { - dynamic asymmetricObj = realm.DynamicApi.CreateObject(nameof(AsymmetricObjectWithAllTypes), ObjectId.GenerateNewId()); - if (isDynamic) - { - Assert.That(asymmetricObj, Is.InstanceOf()); - } - else - { - Assert.That(asymmetricObj, Is.InstanceOf()); - } - - asymmetricObj.CharProperty = 'F'; - asymmetricObj.NullableCharProperty = 'o'; - asymmetricObj.StringProperty = "o"; - - Assert.That((char)asymmetricObj.CharProperty, Is.EqualTo('F')); - Assert.That((char)asymmetricObj.NullableCharProperty, Is.EqualTo('o')); - Assert.That(asymmetricObj.StringProperty, Is.EqualTo("o")); - }); -#endif - }); - } - - private static Task GetRemoteObjects(User user, string remoteFieldName, BsonValue fieldValue) - where T : class - { - var mongoClient = user.GetMongoClient("BackingDB"); - var db = mongoClient.GetDatabase(SyncTestHelpers.SyncMongoDBName(AppConfigType.FlexibleSync)); - var collection = db.GetCollection(typeof(T).Name); - var filter = new BsonDocument - { - { - remoteFieldName, new BsonDocument - { - { "$eq", fieldValue } - } - } - }; - return collection.FindAsync(filter); - } - - private async Task GetRealmWithRealmValueSchemaAsync() - { - var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.PopulateInitialSubscriptions = (realm) => - { - var query = realm.All(); - realm.Subscriptions.Add(query); - }; - - return await GetRealmAsync(flxConfig); - } - } - - [Explicit] - public partial class BasicAsymmetricObject : TestAsymmetricObject - { - [PrimaryKey, MapTo("_id")] - public ObjectId Id { get; private set; } = ObjectId.GenerateNewId(); - - public string? PartitionLike { get; set; } - } - - [Explicit] - public partial class AsymmetricObjectWithAllTypes : TestAsymmetricObject - { - [PrimaryKey, MapTo("_id")] - public ObjectId Id { get; private set; } = ObjectId.GenerateNewId(); - - public char CharProperty { get; set; } - - public byte ByteProperty { get; set; } - - public short Int16Property { get; set; } - - public int Int32Property { get; set; } - - public long Int64Property { get; set; } - - public float SingleProperty { get; set; } - - public double DoubleProperty { get; set; } - - public bool BooleanProperty { get; set; } - - public decimal DecimalProperty { get; set; } - - public Decimal128 Decimal128Property { get; set; } - - public ObjectId ObjectIdProperty { get; set; } - - public Guid GuidProperty { get; set; } - -#if TEST_WEAVER - [Required] -#endif - public string RequiredStringProperty { get; set; } = string.Empty; - - public string? StringProperty { get; set; } - - public byte[]? ByteArrayProperty { get; set; } - - public char? NullableCharProperty { get; set; } - - public byte? NullableByteProperty { get; set; } - - public short? NullableInt16Property { get; set; } - - public int? NullableInt32Property { get; set; } - - public long? NullableInt64Property { get; set; } - - public float? NullableSingleProperty { get; set; } - - public double? NullableDoubleProperty { get; set; } - - public bool? NullableBooleanProperty { get; set; } - - public DateTimeOffset? NullableDateTimeOffsetProperty { get; set; } - - public decimal? NullableDecimalProperty { get; set; } - - public Decimal128? NullableDecimal128Property { get; set; } - - public ObjectId? NullableObjectIdProperty { get; set; } - - public Guid? NullableGuidProperty { get; set; } - - public static AsymmetricObjectWithAllTypes CreateWithData(int dataSize) - { - var data = new byte[dataSize]; - TestHelpers.Random.NextBytes(data); - return new AsymmetricObjectWithAllTypes - { - ByteArrayProperty = data, - RequiredStringProperty = string.Empty, - }; - } - - // We can't test against the following types as they are not Bson deserializable - - // public DateTimeOffset DateTimeOffsetProperty { get; set; } - - // public RealmInteger ByteCounterProperty { get; set; } - - // public RealmInteger Int16CounterProperty { get; set; } - - // public RealmInteger Int32CounterProperty { get; set; } - - // public RealmInteger Int64CounterProperty { get; set; } - - // public RealmValue RealmValueProperty { get; set; } - } - - [Explicit] - public partial class AsymmetricObjectWithEmbeddedListObject : TestAsymmetricObject - { - [PrimaryKey, MapTo("_id")] - public ObjectId Id { get; private set; } = ObjectId.GenerateNewId(); - - public IList EmbeddedListObject { get; } = null!; - } - - [Explicit] - public partial class AsymmetricObjectWithEmbeddedRecursiveObject : TestAsymmetricObject - { - [PrimaryKey, MapTo("_id")] - public ObjectId Id { get; private set; } = ObjectId.GenerateNewId(); - - public EmbeddedLevel1? RecursiveObject { get; set; } - } - - [Explicit] - public partial class AsymmetricObjectWithEmbeddedDictionaryObject : TestAsymmetricObject - { - [PrimaryKey, MapTo("_id")] - public ObjectId Id { get; private set; } = ObjectId.GenerateNewId(); - - public IDictionary EmbeddedDictionaryObject { get; } = null!; - } -} diff --git a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs deleted file mode 100644 index 310af261d8..0000000000 --- a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs +++ /dev/null @@ -1,1112 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using MongoDB.Bson; -using NUnit.Framework; -using Realms.Extensions; -using Realms.Helpers; -using Realms.Tests.Database; - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class DataTypeSynchronizationTests : SyncTestBase - { - private readonly RealmValueWithCollections.RealmValueComparer _rvComparer = new(); - - #region Boolean - - [Test] - public void List_Boolean() => TestListCore(o => o.BooleanList, true, false); - - [Test] - public void Set_Boolean() => TestSetCore(o => o.BooleanSet, true, false); - - [Test] - public void Dict_Boolean() => TestDictionaryCore(o => o.BooleanDict, true, false); - - [Test] - public void Property_Boolean() => TestPropertyCore(o => o.BooleanProperty, (o, rv) => o.BooleanProperty = rv, true, false); - - #endregion - - #region Byte - - [Test] - public void List_Byte() => TestListCore(o => o.ByteList, (byte)9, (byte)255); - - [Test] - public void Set_Byte() => TestSetCore(o => o.ByteSet, (byte)9, (byte)255); - - [Test] - public void Dict_Byte() => TestDictionaryCore(o => o.ByteDict, (byte)9, (byte)255); - - [Test] - public void Property_Byte() => TestPropertyCore(o => o.ByteProperty, (o, rv) => o.ByteProperty = rv, (byte)9, (byte)255); - - #endregion - - #region Int16 - - [Test] - public void List_Int16() => TestListCore(o => o.Int16List, (short)55, (short)987); - - [Test] - public void Set_Int16() => TestSetCore(o => o.Int16Set, (short)55, (short)987); - - [Test] - public void Dict_Int16() => TestDictionaryCore(o => o.Int16Dict, (short)55, (short)987); - - [Test] - public void Property_Int16() => TestPropertyCore(o => o.Int16Property, (o, rv) => o.Int16Property = rv, (short)55, (short)987); - - #endregion - - #region Int32 - - [Test] - public void List_Int32() => TestListCore(o => o.Int32List, 987, 123); - - [Test] - public void Set_Int32() => TestSetCore(o => o.Int32Set, 987, 123); - - [Test] - public void Dict_Int32() => TestDictionaryCore(o => o.Int32Dict, 555, 666); - - [Test] - public void Property_Int32() => TestPropertyCore(o => o.Int32Property, (o, rv) => o.Int32Property = rv, 987, 123); - - #endregion - - #region Int64 - - [Test] - public void List_Int64() => TestListCore(o => o.Int64List, 12345678910111213, 987654321); - - [Test] - public void Set_Int64() => TestSetCore(o => o.Int64Set, 12345678910111213, 987654321); - - [Test] - public void Dict_Int64() => TestDictionaryCore(o => o.Int64Dict, 9999999999999L, 1111111111111111111L); - - [Test] - public void Property_Int64() => TestPropertyCore(o => o.Int64Property, (o, rv) => o.Int64Property = rv, 12345678910111213, 987654321); - - #endregion - - #region Byte - - [Test] - public void List_Double() => TestListCore(o => o.DoubleList, 123.456, 789.123); - - [Test] - public void Set_Double() => TestSetCore(o => o.DoubleSet, 123.456, 789.123); - - [Test] - public void Dict_Double() => TestDictionaryCore(o => o.DoubleDict, 99999.555555, 8777778.12312456); - - [Test] - public void Property_Double() => TestPropertyCore(o => o.DoubleProperty, (o, rv) => o.DoubleProperty = rv, 99999.555555, 8777778.12312456); - - #endregion - - #region Float - - [Test] - public void List_Float() => TestListCore(o => o.FloatList, 43.24f, 0.4f); - - [Test] - public void Set_Float() => TestSetCore(o => o.FloatSet, 43.24f, 0.4f); - - [Test] - public void Dict_Float() => TestDictionaryCore(o => o.FloatDict, 43.24f, 0.4f); - - [Test] - public void Property_Float() => TestPropertyCore(o => o.FloatProperty, (o, rv) => o.FloatProperty = rv, 43.24f, 0.4f); - - #endregion - - #region Decimal - - [Test] - public void List_Decimal() => TestListCore(o => o.DecimalList, 123.7777772342322347777777m, 999.99222222999999999m); - - [Test] - public void Set_Decimal() => TestSetCore(o => o.DecimalSet, 123.7777774444447777777m, 999.000099999999999m); - - [Test] - public void Dict_Decimal() => TestDictionaryCore(o => o.DecimalDict, 987654321.7777777m, 999.99999999999999999999m); - - [Test] - public void Property_Decimal() => TestPropertyCore(o => o.DecimalProperty, (o, rv) => o.DecimalProperty = rv, 987654321.7777777999999999999999999m, 999.99999999999m); - - #endregion - - #region Decimal128 - - [Test] - public void List_Decimal128() => TestListCore(o => o.Decimal128List, 123.7777771111111117777777m, 999.99999333333333999999m); - - [Test] - public void Set_Decimal128() => TestSetCore(o => o.Decimal128Set, 123.777444447777777777m, 999.99999999999m); - - [Test] - public void Dict_Decimal128() => TestDictionaryCore(o => o.Decimal128Dict, 1.123456789m, 987654321.77777777777777777777m); - - [Test] - public void Property_Decimal128() => TestPropertyCore(o => o.Decimal128Property, (o, rv) => o.Decimal128Property = rv, 1.123456789m, 987654321.7777m); - - #endregion - - #region ObjectId - - [Test] - public void List_ObjectId() => TestListCore(o => o.ObjectIdList, ObjectId.GenerateNewId(), ObjectId.GenerateNewId()); - - [Test] - public void Set_ObjectId() => TestSetCore(o => o.ObjectIdSet, ObjectId.GenerateNewId(), ObjectId.GenerateNewId()); - - [Test] - public void Dict_ObjectId() => TestDictionaryCore(o => o.ObjectIdDict, ObjectId.GenerateNewId(), ObjectId.GenerateNewId()); - - [Test] - public void Property_ObjectId() => TestPropertyCore(o => o.ObjectIdProperty, (o, rv) => o.ObjectIdProperty = rv, ObjectId.GenerateNewId(), ObjectId.GenerateNewId()); - - #endregion - - #region DateTimeOffset - - [Test] - public void List_DateTimeOffset() => TestListCore(o => o.DateTimeOffsetList, DateTimeOffset.MinValue, DateTimeOffset.MaxValue); - - [Test] - public void Set_DateTimeOffset() => TestSetCore(o => o.DateTimeOffsetSet, DateTimeOffset.MinValue, DateTimeOffset.MaxValue); - - [Test] - public void Dict_DateTimeOffset() => TestDictionaryCore(o => o.DateTimeOffsetDict, DateTimeOffset.MinValue, DateTimeOffset.MaxValue); - - [Test] - public void Property_DateTimeOffset() => TestPropertyCore(o => o.DateTimeOffsetProperty, (o, rv) => o.DateTimeOffsetProperty = rv, DateTimeOffset.MinValue, DateTimeOffset.MaxValue); - - #endregion - - #region String - - [Test] - public void List_String() => TestListCore(o => o.StringList, "abc", "cde"); - - [Test] - public void Set_String() => TestSetCore(o => o.StringSet, "abc", "cde"); - - [Test] - public void Dict_String() => TestDictionaryCore(o => o.StringDict, "hohoho", string.Empty); - - [Test] - public void Property_String() => TestPropertyCore(o => o.StringProperty, (o, rv) => o.StringProperty = rv, "abc", "cde"); - - #endregion - - #region Binary - - [Test] - public void List_Binary() => TestListCore(o => o.ByteArrayList, TestHelpers.GetBytes(5), TestHelpers.GetBytes(6), (a, b) => a.SequenceEqual(b)); - - [Test] - public void Set_Binary() => TestSetCore(o => o.ByteArraySet, TestHelpers.GetBytes(5), TestHelpers.GetBytes(6), (a, b) => a.SequenceEqual(b)); - - [Test] - public void Dict_Binary() => TestDictionaryCore(o => o.ByteArrayDict, TestHelpers.GetBytes(10), TestHelpers.GetBytes(15), (a, b) => a.SequenceEqual(b)); - - [Test] - public void Property_Binary() => TestPropertyCore(o => o.ByteArrayProperty, (o, rv) => o.ByteArrayProperty = rv, TestHelpers.GetBytes(5), TestHelpers.GetBytes(10)); - - #endregion - - #region Object - - [Test] - public void List_Object() => TestListCore(o => o.ObjectList, new IntPropertyObject { Int = 5 }, new IntPropertyObject { Int = 456 }, (a, b) => a.Int == b.Int); - - [Test] - public void Set_Object() => TestSetCore(o => o.ObjectSet, new IntPropertyObject { Int = 5 }, new IntPropertyObject { Int = 456 }, (a, b) => a.Int == b.Int); - - [Test] - public void Dict_Object() => TestDictionaryCore(o => o.ObjectDict, new IntPropertyObject { Int = 5 }, new IntPropertyObject { Int = 456 }, (a, b) => a?.Int == b?.Int); - - #endregion - - #region EmbeddedObject - - [Test] - public void List_EmbeddedObject() => TestListCore(o => o.EmbeddedObjectList, new EmbeddedIntPropertyObject { Int = 5 }, new EmbeddedIntPropertyObject { Int = 456 }, (a, b) => a.Int == b.Int); - - [Test] - public void Dict_EmbeddedObject() => TestDictionaryCore(o => o.EmbeddedObjectDict, new EmbeddedIntPropertyObject { Int = 5 }, new EmbeddedIntPropertyObject { Int = 456 }, (a, b) => a?.Int == b?.Int); - - #endregion - - #region RealmValue - - public static readonly object[] RealmTestPrimitiveValues = - { - new object[] { (RealmValue)"abc", (RealmValue)10 }, - new object[] { (RealmValue)new ObjectId("5f63e882536de46d71877979"), (RealmValue)new Guid("{F2952191-A847-41C3-8362-497F92CB7D24}") }, - new object[] { (RealmValue)new byte[] { 0, 1, 2 }, (RealmValue)DateTimeOffset.FromUnixTimeSeconds(1616137641) }, - new object[] { (RealmValue)true, RealmValue.Object(new IntPropertyObject { Int = 10 }) }, - new object[] { RealmValue.Null, (RealmValue)5m }, - new object[] { (RealmValue)12.5f, (RealmValue)15d }, - }; - - public static readonly object[] RealmTestValuesWithCollections = RealmTestPrimitiveValues.Concat(new[] - { - new object[] { (RealmValue)12.5f, (RealmValue)15d }, - new object[] - { - (RealmValue)new List - { - RealmValue.Null, - 1, - true, - "string", - new byte[] { 0, 1, 2 }, - new DateTimeOffset(1234, 5, 6, 7, 8, 9, TimeSpan.Zero), - 1f, - 2d, - 3m, - new ObjectId("5f63e882536de46d71877979"), - Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31"), - new List { 1, true, "string" }, - new Dictionary - { - { "key1", RealmValue.Null }, - { "key2", 1 }, - { "key3", true }, - { "key4", "string" }, - } - }, - (RealmValue)15d - }, - new object[] - { - (RealmValue)new Dictionary - { - { "key1", RealmValue.Null }, - { "key2", 1 }, - { "key3", true }, - { "key4", "string" }, - { "key5", new byte[] { 0, 1, 2, 3 } }, - { "key6", new DateTimeOffset(1234, 5, 6, 7, 8, 9, TimeSpan.Zero) }, - { "key7", 1f }, - { "key8", 2d }, - { "key9", 3m }, - { "key10", new ObjectId("5f63e882536de46d71877979") }, - { "key11", Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31") }, - { "key12", new List { 1, true, "string" } }, - { - "key13", new Dictionary - { - { "key1", RealmValue.Null }, - { "key2", 1 }, - { "key3", true }, - { "key4", "string" }, - } - }, - }, - (RealmValue)15d - }, - }).ToArray(); - - [TestCaseSource(nameof(RealmTestValuesWithCollections))] - public void List_RealmValue(RealmValue first, RealmValue second) => TestListCore(o => o.RealmValueList, - Clone(first), Clone(second), equalsOverride: RealmValueEquals); - - [TestCaseSource(nameof(RealmTestPrimitiveValues))] - public void Set_RealmValue(RealmValue first, RealmValue second) => TestSetCore(o => o.RealmValueSet, - Clone(first), Clone(second), equalsOverride: RealmValueEquals); - - [TestCaseSource(nameof(RealmTestValuesWithCollections))] - public void Dict_RealmValue(RealmValue first, RealmValue second) => TestDictionaryCore(o => o.RealmValueDict, - Clone(first), Clone(second), equalsOverride: RealmValueEquals); - - [TestCaseSource(nameof(RealmTestValuesWithCollections))] - public void Property_RealmValue(RealmValue first, RealmValue second) => TestPropertyCore( - o => o.RealmValueProperty, (o, rv) => o.RealmValueProperty = rv, Clone(first), Clone(second)); - - #endregion - - private void TestListCore(Func> getter, T item1, T item2, - Func? equalsOverride = null) - { - equalsOverride ??= (a, b) => a?.Equals(b) == true; - - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partition = Guid.NewGuid().ToString(); - var realm1 = await GetIntegrationRealmAsync(partition); - var realm2 = await GetIntegrationRealmAsync(partition); - - var obj1 = realm1.Write(() => realm1.Add(new SyncCollectionsObject())); - - var obj2 = await WaitForObjectAsync(obj1, realm2); - - var list1 = getter(obj1); - var list2 = getter(obj2); - - // Assert Add works from both sides - realm1.Write(() => - { - list1.Add(item1); - }); - - await WaitForCollectionAsync(list2, list1, equalsOverride, "add from 1 shows up in 2"); - - realm2.Write(() => - { - list2.Add(item2); - }); - - await WaitForCollectionAsync(list1, list2, equalsOverride, "add from 2 shows up in 1"); - - // Assert Remove works - realm2.Write(() => - { - list2.Remove(list2.First()); - }); - - await WaitForCollectionAsync(list1, list2, equalsOverride, "remove from 2 shows up in 1"); - - // Assert Clear works - realm1.Write(() => - { - list1.Clear(); - }); - - await TestHelpers.WaitForConditionAsync(() => !list2.Any(), errorMessage: "clear from 1 shows up in 2"); - - Assert.That(list1, Is.Empty); - Assert.That(list2, Is.Empty); - }); - } - - private void TestSetCore(Func> getter, T item1, T item2, Func? equalsOverride = null) - { - equalsOverride ??= (a, b) => a?.Equals(b) == true; - - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partition = Guid.NewGuid().ToString(); - var realm1 = await GetIntegrationRealmAsync(partition); - var realm2 = await GetIntegrationRealmAsync(partition); - - var obj1 = realm1.Write(() => realm1.Add(new SyncCollectionsObject())); - - var obj2 = await WaitForObjectAsync(obj1, realm2); - - var set1 = getter(obj1); - var set2 = getter(obj2); - - // Assert Add works from both sides - realm1.Write(() => - { - set1.Add(item1); - }); - - await WaitForCollectionAsync(set2, set1, equalsOverride, "add from 1 shows up in 2"); - - realm2.Write(() => - { - set2.Add(item2); - }); - - await WaitForCollectionAsync(set1, set2, equalsOverride, "add from 2 shows up in 1"); - - // Assert Remove works - realm2.Write(() => - { - set2.Remove(set2.First()); - }); - - await WaitForCollectionAsync(set1, set2, equalsOverride, "remove from 2 shows up in 1"); - - // Assert Clear works - realm1.Write(() => - { - set1.Clear(); - }); - - await TestHelpers.WaitForConditionAsync(() => !set2.Any(), errorMessage: "clear from 1 shows up in 2"); - - Assert.That(set1, Is.Empty); - Assert.That(set2, Is.Empty); - }); - } - - private void TestDictionaryCore(Func> getter, T item1, T item2, Func? equalsOverride = null) - { - Func, KeyValuePair, bool> comparer = - (a, b) => a.Key == b.Key && (equalsOverride?.Invoke(a.Value, b.Value) ?? a.Value?.Equals(b.Value) == true); - - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partition = Guid.NewGuid().ToString(); - var realm1 = await GetIntegrationRealmAsync(partition); - var realm2 = await GetIntegrationRealmAsync(partition); - - var obj1 = realm1.Write(() => realm1.Add(new SyncCollectionsObject())); - - var obj2 = await WaitForObjectAsync(obj1, realm2, "initial obj from 1 shows up in 2"); - - var dict1 = getter(obj1); - var dict2 = getter(obj2); - - var key1 = "a"; - var key2 = "b"; - - // Assert Add works from both sides - realm1.Write(() => - { - dict1.Add(key1, item1); - }); - - await WaitForCollectionAsync(dict2, dict1, comparer, "add from 1 shows up in 2"); - - realm2.Write(() => - { - dict2[key2] = item2; - }); - - await WaitForCollectionAsync(dict1, dict2, comparer, "add from 2 shows up in 1"); - - // Assert Update works - // item2 might belong to realm2, so let's find the equivalent in realm1 - item2 = CloneOrLookup(item2, realm1); - - realm1.Write(() => - { - dict1[key1] = item2; - }); - - await WaitForCollectionAsync(dict2, dict1, comparer, "set from 1 shows up in 2"); - - // Assert Remove works - realm2.Write(() => - { - dict2.Remove(key1); - }); - - await WaitForCollectionAsync(dict1, dict2, comparer, "remove from 2 shows up in 1"); - - // Assert Clear works - realm1.Write(() => - { - dict1.Clear(); - }); - - await TestHelpers.WaitForConditionAsync(() => !dict2.Any(), errorMessage: "clear from 1 shows up in 2"); - - Assert.That(dict1, Is.Empty); - Assert.That(dict2, Is.Empty); - }, timeout: 60_000); - } - - private void TestPropertyCore(Func getter, Action setter, - T item1, T item2) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var realm1 = await GetFLXIntegrationRealmAsync(); - - realm1.Subscriptions.Update(() => - { - realm1.Subscriptions.Add(realm1.All()); - realm1.Subscriptions.Add(realm1.All()); - }); - - var realm2 = await GetFLXIntegrationRealmAsync(); - realm2.Subscriptions.Update(() => - { - realm2.Subscriptions.Add(realm2.All()); - realm2.Subscriptions.Add(realm2.All()); - }); - - var obj1 = realm1.Write(() => realm1.Add(new SyncAllTypesObject())); - - var obj2 = await WaitForObjectAsync(obj1, realm2); - - realm1.Write(() => - { - setter(obj1, item1); - }); - - await WaitForPropertyChangedAsync(obj2); - - var prop1 = getter(obj1); - var prop2 = getter(obj2); - - Assert.That(item1, Is.EqualTo(prop1).Using(_rvComparer)); - Assert.That(prop1, Is.EqualTo(prop2).Using(_rvComparer)); - - realm2.Write(() => - { - setter(obj2, item2); - }); - - await WaitForPropertyChangedAsync(obj1); - - prop1 = getter(obj1); - prop2 = getter(obj2); - - Assert.That(item2, Is.EqualTo(prop2).Using(_rvComparer)); - Assert.That(prop2, Is.EqualTo(prop1).Using(_rvComparer)); - }); - } - - [Test] - public void NestedCollections_Bootstrap() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var realm1 = await GetFLXIntegrationRealmAsync(); - realm1.Subscriptions.Update(() => - { - realm1.Subscriptions.Add(realm1.All()); - realm1.Subscriptions.Add(realm1.All()); - }); - - var parent = new SyncAllTypesObject(); - var child = new IntPropertyObject(); - - var valuesValue = new List - { - 1, - "Realm", - child, - new List { 1, "Realm", child }, - new Dictionary - { - { "key1", 1 }, { "key2", "Realm" }, { "key3", child }, - } - }; - - realm1.Write(() => - { - realm1.Add(parent); - parent.StringProperty = "PARENT"; - parent.ObjectProperty = child; - parent.RealmValueProperty = valuesValue; - }); - - var parentId = parent.Id; - var childId = child.Id; - - await realm1.SyncSession.WaitForUploadAsync(); - realm1.Dispose(); - - var realm2 = await GetFLXIntegrationRealmAsync(); - realm2.Subscriptions.Update(() => - { - realm2.Subscriptions.Add(realm2.All()); - realm2.Subscriptions.Add(realm2.All()); - }); - await realm2.SyncSession.WaitForDownloadAsync(); - - var syncedParent = - await TestHelpers.WaitForConditionAsync(() => realm2.FindCore(parentId), - o => o != null); - var syncedChild = - await TestHelpers.WaitForConditionAsync(() => realm2.FindCore(childId), - o => o != null); - var syncedValues = syncedParent!.RealmValueProperty.AsList(); - Assert.AreEqual(valuesValue[0], syncedValues[0]); - Assert.AreEqual(valuesValue[1], syncedValues[1]); - Assert.AreEqual(childId, syncedValues[2].AsRealmObject().Id); - var nestedExpectedList = valuesValue[3].AsList(); - var nestedSyncedList = syncedValues[3].AsList(); - Assert.AreEqual(nestedExpectedList[0], nestedSyncedList[0]); - Assert.AreEqual(nestedExpectedList[1], nestedSyncedList[1]); - Assert.AreEqual(childId, nestedSyncedList[2].AsRealmObject().Id); - - var nestedExpectedDictionary = valuesValue[4].AsDictionary(); - var nestedSyncedDictionary = syncedValues[4].AsDictionary(); - Assert.AreEqual(nestedExpectedDictionary["key1"], nestedSyncedDictionary["key1"]); - Assert.AreEqual(nestedExpectedDictionary["key2"], nestedSyncedDictionary["key2"]); - Assert.AreEqual(childId, nestedSyncedDictionary["key3"].AsRealmObject().Id); - }); - } - - private static readonly RealmValue[] RealmValueCollectionTestValues = - { - "abc", - new ObjectId("5f63e882536de46d71877979"), - new byte[] { 0, 1, 2 }, - DateTimeOffset.FromUnixTimeSeconds(1616137641), - true, - RealmValue.Null, - 5m, - 12.5f, - 15d, - new List - { - RealmValue.Null, - 1, - true, - "string", - new byte[] { 0, 1, 2 }, - new DateTimeOffset(1234, 5, 6, 7, 8, 9, TimeSpan.Zero), - 1f, - 2d, - 3m, - new ObjectId("5f63e882536de46d71877979"), - Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31"), - new List { 1, true, "string" }, - new Dictionary - { - { "key1", RealmValue.Null }, - { "key2", 1 }, - { "key3", true }, - { "key4", "string" }, - } - }, - new Dictionary - { - { "key1", RealmValue.Null }, - { "key2", 1 }, - { "key3", true }, - { "key4", "string" }, - { "key5", new byte[] { 0, 1, 2, 3 } }, - { "key6", new DateTimeOffset(1234, 5, 6, 7, 8, 9, TimeSpan.Zero) }, - { "key7", 1f }, - { "key8", 2d }, - { "key9", 3m }, - { "key10", new ObjectId("5f63e882536de46d71877979") }, - { "key11", Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31") }, - { "key12", new List { 1, true, "string" } }, - { - "key13", new Dictionary - { - { "key1", RealmValue.Null }, - { "key2", 1 }, - { "key3", true }, - { "key4", "string" }, - } - }, - }, - }; - - [Test] - public void NestedCollections_ListManipulations() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var realm1 = await GetFLXIntegrationRealmAsync(); - var realm2 = await GetFLXIntegrationRealmAsync(); - - realm1.Subscriptions.Update(() => { realm1.Subscriptions.Add(realm1.All()); }); - realm2.Subscriptions.Update(() => { realm2.Subscriptions.Add(realm2.All()); }); - - var obj1 = realm1.Write(() => - { - var o = realm1.Add(new SyncAllTypesObject()); - o.RealmValueProperty = new List(); - return o; - }); - - var obj2 = await WaitForObjectAsync(obj1, realm2); - - // Append elements one by one and verify that they are synced - foreach (var realmTestValue in RealmValueCollectionTestValues) - { - realm1.Write(() => - { - obj1.RealmValueProperty.AsList().Add(realmTestValue); - }); - await realm1.SyncSession.WaitForUploadAsync(); - - await realm2.SyncSession.WaitForDownloadAsync(); - var expectedValues = obj1.RealmValueProperty.AsList(); - var actualValues = obj2.RealmValueProperty.AsList(); - Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); - } - - // Remove elements one by one and verify that changes are synced - for (var i = 0; i < RealmValueCollectionTestValues.Length; i++) - { - realm1.Write(() => - { - obj1.RealmValueProperty.AsList().RemoveAt(0); - }); - await realm1.SyncSession.WaitForUploadAsync(); - - await realm2.SyncSession.WaitForDownloadAsync(); - var expectedValues = obj1.RealmValueProperty.AsList(); - var actualValues = obj2.RealmValueProperty.AsList(); - Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); - } - - // Insert/override elements at index 0 and verify that changes are synced - foreach (var realmTestValue in RealmValueCollectionTestValues) - { - realm1.Write(() => - { - if (obj1.RealmValueProperty.AsList().Count == 0) - { - obj1.RealmValueProperty.AsList().Insert(0, realmTestValue); - } - else - { - obj1.RealmValueProperty.AsList()[0] = realmTestValue; - } - }); - await realm1.SyncSession.WaitForUploadAsync(); - - await realm2.SyncSession.WaitForDownloadAsync(); - var expectedValues = obj1.RealmValueProperty.AsList(); - var actualValues = obj2.RealmValueProperty.AsList(); - Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); - } - }); - } - - [Test] - public void NestedCollections_DictionaryManipulations() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var realm1 = await GetFLXIntegrationRealmAsync(); - realm1.Subscriptions.Update(() => - { - realm1.Subscriptions.Add(realm1.All()); - }); - - var realm2 = await GetFLXIntegrationRealmAsync(); - realm2.Subscriptions.Update(() => - { - realm2.Subscriptions.Add(realm2.All()); - }); - - var obj1 = realm1.Write(() => - { - var o = realm1.Add(new SyncAllTypesObject()); - o.RealmValueProperty = new Dictionary(); - return o; - }); - - var obj2 = await WaitForObjectAsync(obj1, realm2); - - for (int index = 0; index < RealmTestValuesWithCollections.Length; index++) - { - var realmTestValue = RealmValueCollectionTestValues[index]; - realm1.Write(() => { obj1.RealmValueProperty.AsDictionary()[$"{index}"] = realmTestValue; }); - - await realm1.SyncSession.WaitForUploadAsync(); - await realm2.SyncSession.WaitForDownloadAsync(); - var expectedValues = obj1.RealmValueProperty.AsDictionary(); - var actualValues = obj2.RealmValueProperty.AsDictionary(); - Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); - } - - for (int index = 0; index < RealmTestValuesWithCollections.Length; index++) - { - realm1.Write(() => { obj1.RealmValueProperty.AsDictionary().Remove($"{index}"); }); - await realm1.SyncSession.WaitForUploadAsync(); - await realm2.SyncSession.WaitForDownloadAsync(); - var expectedValues = obj1.RealmValueProperty.AsDictionary(); - var actualValues = obj2.RealmValueProperty.AsDictionary(); - Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); - } - }); - } - - [Test] - public void NestedCollections_Merge() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var realm1 = await GetFLXIntegrationRealmAsync(); - realm1.Subscriptions.Update(() => - { - realm1.Subscriptions.Add(realm1.All()); - }); - var realm2 = await GetFLXIntegrationRealmAsync(); - realm2.Subscriptions.Update(() => - { - realm2.Subscriptions.Add(realm2.All()); - }); - - var obj1 = realm1.Write(() => - { - var o = realm1.Add(new SyncAllTypesObject()); - o.RealmValueProperty = new Dictionary - { - { "list", new List { 1, 2, 3 } }, - { "dictionary", new Dictionary { { "key1", 1 } } }, - }; - return o; - }); - - var obj2 = await WaitForObjectAsync(obj1, realm2); - - realm1.SyncSession.Stop(); - realm2.SyncSession.Stop(); - - realm1.Write(() => - { - var list = obj1.RealmValueProperty.AsDictionary()["list"].AsList(); - list.RemoveAt(0); - list.Add(4); - var dictionary = obj1.RealmValueProperty.AsDictionary()["dictionary"].AsDictionary(); - dictionary.Remove("key1"); - dictionary["key2"] = 2; - }); - realm2.Write(() => - { - var list = obj2.RealmValueProperty.AsDictionary()["list"].AsList(); - list.RemoveAt(0); - list.Add(5); - var dictionary = obj2.RealmValueProperty.AsDictionary()["dictionary"].AsDictionary(); - dictionary.Remove("key1"); - dictionary["key3"] = 3; - }); - - realm1.SyncSession.Start(); - realm2.SyncSession.Start(); - - await realm1.SyncSession.WaitForUploadAsync(); - await realm2.SyncSession.WaitForUploadAsync(); - await realm1.SyncSession.WaitForDownloadAsync(); - await realm2.SyncSession.WaitForDownloadAsync(); - - var list1 = obj1.RealmValueProperty.AsDictionary()["list"].AsList(); - var dictionary1 = obj1.RealmValueProperty.AsDictionary()["dictionary"].AsDictionary(); - var list2 = obj1.RealmValueProperty.AsDictionary()["list"].AsList(); - var dictionary2 = obj1.RealmValueProperty.AsDictionary()["dictionary"].AsDictionary(); - - Assert.That(list1, Does.Not.Contain((RealmValue)1)); - Assert.That(list1, Contains.Item((RealmValue)2)); - Assert.That(list1, Contains.Item((RealmValue)3)); - Assert.That(list1, Contains.Item((RealmValue)4)); - Assert.That(list1, Contains.Item((RealmValue)5)); - Assert.That(list1, Is.EqualTo(list2).Using(_rvComparer)); - - Assert.That(dictionary1, Does.Not.ContainKey("key1")); - Assert.That(dictionary1, Contains.Key("key2")); - Assert.That(dictionary1, Contains.Key("key3")); - Assert.That(dictionary1, Is.EqualTo(dictionary2).Using(_rvComparer)); - }); - } - - [Test] - public void NestedCollections_MergeNewObjects() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partition = ObjectId.GenerateNewId(); - - var realm1 = await GetFLXIntegrationRealmAsync(); - await realm1.All().Where(o => o.ObjectIdProperty == partition).SubscribeAsync(); - realm1.SyncSession.Stop(); - - var realm2 = await GetFLXIntegrationRealmAsync(); - await realm2.All().Where(o => o.ObjectIdProperty == partition).SubscribeAsync(); - realm2.SyncSession.Stop(); - - var id = ObjectId.GenerateNewId(); - realm1.Write(() => - { - realm1.Add(new SyncAllTypesObject - { - Id = id, - RealmValueProperty = new RealmValue[] { 5, "abc" }, - StringProperty = "client 1", - ObjectIdProperty = partition - }); - }); - - realm2.Write(() => - { - realm2.Add(new SyncAllTypesObject - { - Id = id, - RealmValueProperty = new RealmValue[] { 100, "def" }, - StringProperty = "client 2", - ObjectIdProperty = partition - }); - }); - - realm1.SyncSession.Start(); - realm2.SyncSession.Start(); - - await WaitForUploadAsync(realm1); - await WaitForUploadAsync(realm2); - await WaitForDownloadAsync(realm1); - await WaitForDownloadAsync(realm2); - - var objs1 = realm1.All(); - var objs2 = realm2.All(); - - Assert.That(objs1.Count(), Is.EqualTo(1)); - Assert.That(objs2.Count(), Is.EqualTo(1)); - - var obj1 = objs1.Single(); - var obj2 = objs2.Single(); - - Assert.That(obj1.RealmValueProperty.AsList().Count, Is.EqualTo(2)); - Assert.That(obj2.RealmValueProperty.AsList().Count, Is.EqualTo(2)); - - Assert.That(obj1.StringProperty, Is.EqualTo(obj2.StringProperty)); - - var expected = obj1.StringProperty == "client 1" - ? new RealmValue[] { 5, "abc" } - : new RealmValue[] { 100, "def" }; - - Assert.That(obj1.RealmValueProperty.AsList(), Is.EqualTo(expected)); - Assert.That(obj2.RealmValueProperty.AsList(), Is.EqualTo(expected)); - }); - } - - private static RealmValue Clone(RealmValue original) - { - if (original.Type != RealmValueType.Object) - { - return original; - } - - var robj = original.AsIRealmObject(); - var clone = (IRealmObjectBase)Activator.CreateInstance(robj.GetType())!; - var properties = robj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(p => p is { CanWrite: true, CanRead: true } && !p.HasCustomAttribute()); - - foreach (var prop in properties) - { - prop.SetValue(clone, prop.GetValue(robj)); - } - - return RealmValue.Object(clone); - } - - private static T CloneOrLookup(T value, Realm targetRealm) - { - // If embedded - we need to clone as it might already be assigned to a different property - if (value is IEmbeddedObject eobj) - { - return Operator.Convert(Clone(RealmValue.Object(eobj)).AsIRealmObject()); - } - - // If IRealmObject - we need to look up the existing equivalent in the correct realm - if (value is IRealmObject robj) - { - // item2 belongs to realm2 - we want to look up the equivalent in realm1 to add it to dict1 - Assert.That(robj.GetObjectMetadata()!.Helper.TryGetPrimaryKeyValue(robj, out var pk), Is.True); - var item2InRealm1 = targetRealm.DynamicApi.FindCore(robj.ObjectSchema!.Name, Operator.Convert(pk))!; - return Operator.Convert(item2InRealm1); - } - - // If RealmValue that is holding an object, call CloneOrLookup - if (value is RealmValue { Type: RealmValueType.Object } rvalue) - { - var cloned = CloneOrLookup(rvalue.AsIRealmObject(), targetRealm); - return Operator.Convert(cloned); - } - - return value; - } - - private static bool RealmValueEquals(RealmValue a, RealmValue b) - { - if (a.Equals(b)) - { - return true; - } - - // Special handling the object case as they might be "equivalent" but belonging to different realms - if (a.Type != RealmValueType.Object || b.Type != RealmValueType.Object) - { - return false; - } - - var objA = a.AsIRealmObject(); - var objB = b.AsIRealmObject(); - - if (objA.GetType() != objB.GetType()) - { - return false; - } - - return objA.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(o => o is { CanWrite: true, CanRead: true }) - .All(prop => prop.GetValue(objA)?.Equals(prop.GetValue(objB)) == true); - } - - private static async Task WaitForPropertyChangedAsync(IRealmObject realmObject, int timeout = 10 * 1000) - { - var tcs = new TaskCompletionSource(); - (realmObject as INotifyPropertyChanged)!.PropertyChanged += RealmObject_PropertyChanged; - - void RealmObject_PropertyChanged(object? sender, PropertyChangedEventArgs? e) - { - if (e != null) - { - tcs.TrySetResult(); - } - } - - await tcs.Task.Timeout(timeout); - (realmObject as INotifyPropertyChanged)!.PropertyChanged -= RealmObject_PropertyChanged; - } - - private static async Task WaitForCollectionAsync(IEnumerable first, IEnumerable second, Func comparer, string message) - { - await TestHelpers.WaitForConditionAsync(() => IsEquivalent(first, second, comparer), errorMessage: message); - Assert.That(first, Is.EquivalentTo(second).Using(comparer)); - } - - private static bool IsEquivalent(IEnumerable first, IEnumerable second, Func comparer) - { - var copy1 = first.ToList(); - var copy2 = second.ToList(); - - while (copy1.Count > 0) - { - var item = copy1[0]; - copy1.RemoveAt(0); - var success = false; - for (var j = 0; j < copy2.Count; j++) - { - if (comparer(copy2[j], item)) - { - success = true; - copy2.RemoveAt(j); - } - } - - if (!success) - { - return false; - } - } - - return copy2.Count == 0; - } - } -} diff --git a/Tests/Realm.Tests/Sync/FlexibleSyncTests.cs b/Tests/Realm.Tests/Sync/FlexibleSyncTests.cs deleted file mode 100644 index 2b95b9e603..0000000000 --- a/Tests/Realm.Tests/Sync/FlexibleSyncTests.cs +++ /dev/null @@ -1,2456 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using MongoDB.Bson; -using NUnit.Framework; -using Realms.Exceptions; -using Realms.Sync; -using Realms.Sync.Exceptions; - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class FlexibleSyncTests : SyncTestBase - { - [Test] - public void Realm_Subscriptions_WhenLocalRealm_Throws() - { - var realm = GetRealm(); - - Assert.That(() => realm.Subscriptions, Throws.TypeOf()); - } - - [Test] - public void Realm_Subscriptions_WhenPBS_Throws() - { - var config = GetFakeConfig(); - var realm = GetRealm(config); - - Assert.That(() => realm.Subscriptions, Throws.TypeOf()); - } - - [Test] - public void Realm_Subscriptions_WhenFLX_ReturnsSubscriptions() - { - var realm = GetFakeFLXRealm(); - - Assert.That(realm.Subscriptions, Is.Not.Null); - Assert.That(realm.Subscriptions.Version, Is.Zero); - Assert.That(realm.Subscriptions.Count, Is.Zero); - Assert.That(realm.Subscriptions.Error, Is.Null); - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Pending)); - } - - [Test] - public void SubscriptionSet_GetsGarbageCollected() - { - TestHelpers.RunAsyncTest(async () => - { - var realm = GetFakeFLXRealm(); - - await TestHelpers.EnsureObjectsAreCollected(() => - { - var subs = realm.Subscriptions; - return new object[] { subs }; - }); - - // This tests things via reflection because we don't want to expose private members, even internally. - var subsRefs = typeof(Realm).GetField("_subscriptionRef", BindingFlags.NonPublic | BindingFlags.Instance)!; - var weakSubs = (WeakReference)subsRefs.GetValue(realm)!; - - Assert.That(weakSubs, Is.Not.Null); - Assert.That(weakSubs.TryGetTarget(out _), Is.False); - }); - } - - [Test] - public void Realm_Subscriptions_WhenSameVersion_ReturnsExistingReference() - { - var realm = GetFakeFLXRealm(); - - var subs1 = realm.Subscriptions; - var subs2 = realm.Subscriptions; - - Assert.That(subs1, Is.EqualTo(subs2)); - Assert.That(ReferenceEquals(subs1, subs2)); - } - - [Test] - public void Realm_Subscriptions_WhenVersionIsGCed_CreatesANewOne() - { - TestHelpers.RunAsyncTest(async () => - { - var realm = GetFakeFLXRealm(); - - await TestHelpers.EnsureObjectsAreCollected(() => - { - var subs = realm.Subscriptions; - return new object[] { subs }; - }); - - // This tests things via reflection because we don't want to expose private members, even internally. - var subsRef = typeof(Realm).GetField("_subscriptionRef", BindingFlags.NonPublic | BindingFlags.Instance)!; - var weakSubs = (WeakReference)subsRef.GetValue(realm)!; - - Assert.That(weakSubs, Is.Not.Null); - Assert.That(weakSubs.TryGetTarget(out _), Is.False); - - // The old one was gc-ed, so we should get a new one here - var subsAgain = realm.Subscriptions; - - weakSubs = (WeakReference)subsRef.GetValue(realm)!; - - Assert.That(weakSubs, Is.Not.Null); - Assert.That(weakSubs.TryGetTarget(out var subsFromList), Is.True); - - Assert.That(ReferenceEquals(subsAgain, subsFromList)); - }); - } - - [Test] - public void SubscriptionSet_Add_WithoutUpdate_Throws() - { - var realm = GetFakeFLXRealm(); - - var query = realm.All(); - - Assert.Throws(() => realm.Subscriptions.Add(query)); - } - - [Test] - public void SubscriptionSet_Update_WhenEmpty_Succeeds() - { - var realm = GetFakeFLXRealm(); - - realm.Subscriptions.Update(() => - { - // An empty update - }); - - Assert.That(realm.Subscriptions.Version, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.Count, Is.Zero); - Assert.That(realm.Subscriptions.Error, Is.Null); - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Pending)); - } - - [Test] - public void SubscriptionSet_Update_UpdatesItself() - { - var realm = GetFakeFLXRealm(); - var query = realm.All(); - - var subs = realm.Subscriptions; - subs.Update(() => - { - subs.Add(query); - }); - - Assert.That(subs.Version, Is.EqualTo(1)); - Assert.That(subs.Count, Is.EqualTo(1)); - Assert.That(subs.Error, Is.Null); - Assert.That(subs.State, Is.EqualTo(SubscriptionSetState.Pending)); - AssertSubscriptionDetails(subs[0], nameof(SyncAllTypesObject)); - - var foundSub = subs.Find(query); - Assert.That(foundSub, Is.Not.Null); - } - - [Test] - public void SubscriptionSet_Add_AddsSubscription() - { - var realm = GetFakeFLXRealm(); - var query = realm.All(); - - realm.Subscriptions.Update(() => - { - var sub = realm.Subscriptions.Add(query); - - AssertSubscriptionDetails(sub, nameof(SyncAllTypesObject)); - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - }); - - Assert.That(realm.Subscriptions.Version, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.Error, Is.Null); - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Pending), "State should be 'Pending' after change"); - - AssertSubscriptionDetails(realm.Subscriptions[0], nameof(SyncAllTypesObject)); - } - - [Test] - public void SubscriptionSet_Add_ComplexQuery_AddsSubscription() - { - var realm = GetFakeFLXRealm(); - var query = realm.All().Where(o => o.StringProperty!.StartsWith("foo") && (o.BooleanProperty || o.DoubleProperty > 0.5)); - var expectedQueryString = "StringProperty BEGINSWITH \"foo\" and (BooleanProperty == true or DoubleProperty > 0.5)"; - - realm.Subscriptions.Update(() => - { - var sub = realm.Subscriptions.Add(query); - - AssertSubscriptionDetails(sub, nameof(SyncAllTypesObject), expectedQueryString); - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.Error, Is.Null); - - AssertSubscriptionDetails(realm.Subscriptions[0], nameof(SyncAllTypesObject), expectedQueryString); - } - - [Test] - public void SubscriptionSet_AddTwice_Deduplicates() - { - var realm = GetFakeFLXRealm(); - var query = realm.All(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query); - realm.Subscriptions.Add(query); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.Error, Is.Null); - } - - [Test] - public void SubscriptionSet_AddTwice_DifferentNames_Duplicates() - { - var realm = GetFakeFLXRealm(); - var query = realm.All(); - - realm.Subscriptions.Update(() => - { - var sub1 = realm.Subscriptions.Add(query, new SubscriptionOptions { Name = "a" }); - var sub2 = realm.Subscriptions.Add(query, new SubscriptionOptions { Name = "b" }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(2)); - - Assert.That(sub1.Name, Is.EqualTo("a")); - Assert.That(sub2.Name, Is.EqualTo("b")); - Assert.That(sub1.Query, Is.EqualTo(sub2.Query)); - Assert.That(sub1.ObjectType, Is.EqualTo(sub2.ObjectType)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(2)); - Assert.That(realm.Subscriptions.Error, Is.Null); - } - - [Test] - public void SubscriptionSet_AddTwice_NamedAndUnnamed_Duplicates() - { - var realm = GetFakeFLXRealm(); - var query = realm.All(); - - realm.Subscriptions.Update(() => - { - var sub1 = realm.Subscriptions.Add(query); - var sub2 = realm.Subscriptions.Add(query, new SubscriptionOptions { Name = "b" }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(2)); - - Assert.That(sub1.Name, Is.Null); - Assert.That(sub2.Name, Is.EqualTo("b")); - Assert.That(sub1.Query, Is.EqualTo(sub2.Query)); - Assert.That(sub1.ObjectType, Is.EqualTo(sub2.ObjectType)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(2)); - Assert.That(realm.Subscriptions.Error, Is.Null); - } - - [Test] - public void SubscriptionSet_AddSameName_NoUpdate_WhenIdentical_DoesntThrow() - { - var realm = GetFakeFLXRealm(); - var query = realm.All(); - - realm.Subscriptions.Update(() => - { - var sub1 = realm.Subscriptions.Add(query, new SubscriptionOptions { Name = "a" }); - var sub2 = realm.Subscriptions.Add(query, new SubscriptionOptions { Name = "a", UpdateExisting = false }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - - AssertSubscriptionDetails(sub1, nameof(SyncAllTypesObject), name: "a"); - AssertSubscriptionDetails(sub2, nameof(SyncAllTypesObject), name: "a", expectUpdateOnly: true); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.Error, Is.Null); - } - - [Test] - public void SubscriptionSet_AddSameName_NoUpdate_WhenDifferentQuery_Throws() - { - var realm = GetFakeFLXRealm(); - var query = realm.All(); - - realm.Subscriptions.Update(() => - { - var sub1 = realm.Subscriptions.Add(query, new SubscriptionOptions { Name = "a" }); - Assert.Throws(() => realm.Subscriptions.Add(query.Where(a => a.BooleanProperty), new SubscriptionOptions { Name = "a", UpdateExisting = false })); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - - AssertSubscriptionDetails(sub1, nameof(SyncAllTypesObject), name: "a"); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.Error, Is.Null); - } - - [Test] - public void SubscriptionSet_AddSameName_NoUpdate_WhenDifferentType_Throws() - { - var realm = GetFakeFLXRealm(); - var query1 = realm.All(); - var query2 = realm.All(); - - realm.Subscriptions.Update(() => - { - var sub1 = realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - Assert.Throws(() => realm.Subscriptions.Add(query2, new SubscriptionOptions { Name = "a", UpdateExisting = false })); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - - AssertSubscriptionDetails(sub1, nameof(SyncAllTypesObject), name: "a"); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.Error, Is.Null); - } - - [Test] - public void SubscriptionSet_AddSameName_UpdateExisting_Updates() - { - var realm = GetFakeFLXRealm(); - var query1 = realm.All(); - var query2 = query1.Where(a => a.StringProperty!.StartsWith("foo")); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query2, new SubscriptionOptions { Name = "a" }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - - AssertSubscriptionDetails(realm.Subscriptions[0], nameof(SyncAllTypesObject), "StringProperty BEGINSWITH \"foo\"", "a", expectUpdateOnly: true); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.Error, Is.Null); - } - - [Test] - public void SubscriptionSet_AddSameName_DifferentType_UpdateExisting_Updates() - { - var realm = GetFakeFLXRealm(); - var query1 = realm.All(); - var query2 = realm.All(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query2, new SubscriptionOptions { Name = "a" }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - - AssertSubscriptionDetails(realm.Subscriptions[0], nameof(SyncCollectionsObject), name: "a", expectUpdateOnly: true); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.Error, Is.Null); - } - - [Test] - public void SubscriptionSet_AddSameQuery_DifferentClasses_AddsBoth() - { - var realm = GetFakeFLXRealm(); - var query1 = realm.All(); - var query2 = realm.All(); - - realm.Subscriptions.Update(() => - { - var sub1 = realm.Subscriptions.Add(query1); - var sub2 = realm.Subscriptions.Add(query2); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(2)); - - AssertSubscriptionDetails(sub1, nameof(SyncAllTypesObject)); - AssertSubscriptionDetails(sub2, nameof(SyncCollectionsObject)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(2)); - Assert.That(realm.Subscriptions.Error, Is.Null); - } - - [Test] - public void SubscriptionSet_Iteration() - { - var realm = GetFakeFLXRealm(); - var query1 = realm.All(); - var query2 = realm.All(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query2); - }); - - var index = 0; - foreach (var sub in realm.Subscriptions) - { - switch (index++) - { - case 0: - AssertSubscriptionDetails(sub, nameof(SyncAllTypesObject), name: "a"); - break; - case 1: - AssertSubscriptionDetails(sub, nameof(SyncCollectionsObject)); - break; - default: - throw new Exception("Expected only 2 items"); - } - } - } - - [Test] - public void SubscriptionSet_Indexer() - { - var realm = GetFakeFLXRealm(); - var query1 = realm.All(); - var query2 = realm.All(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query2); - }); - - AssertSubscriptionDetails(realm.Subscriptions[0], nameof(SyncAllTypesObject), name: "a"); - AssertSubscriptionDetails(realm.Subscriptions[1], nameof(SyncCollectionsObject)); - - Assert.Throws(() => _ = realm.Subscriptions[-1]); - Assert.Throws(() => _ = realm.Subscriptions[2]); - } - - [Test] - public void SubscriptionSet_Update_IncrementsVersion() - { - var realm = GetFakeFLXRealm(); - - for (var i = 0; i < 10; i++) - { - Assert.That(realm.Subscriptions.Version, Is.EqualTo(i)); - realm.Subscriptions.Update(() => { }); - } - } - - [Test] - public void SubscriptionSet_FindByName_Finds() - { - var realm = GetFakeFLXRealm(); - - var query1 = realm.All(); - var query2 = realm.All(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query2, new SubscriptionOptions { Name = "b" }); - }); - - var sub1 = realm.Subscriptions.Find("a"); - AssertSubscriptionDetails(sub1, nameof(SyncAllTypesObject), name: "a"); - - var sub2 = realm.Subscriptions.Find("b"); - AssertSubscriptionDetails(sub2, nameof(SyncCollectionsObject), name: "b"); - - Assert.That(sub1.CreatedAt, Is.LessThan(sub2.CreatedAt)); - } - - [Test] - public void SubscriptionSet_FindByName_ReturnsNullWhenMissing() - { - var realm = GetFakeFLXRealm(); - - Assert.That(realm.Subscriptions.Find("nonexistent"), Is.Null); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(realm.All(), new SubscriptionOptions { Name = "a" }); - }); - - Assert.That(realm.Subscriptions.Find("a"), Is.Not.Null); - Assert.That(realm.Subscriptions.Find("A"), Is.Null); - } - - [Test] - public void SubscriptionSet_FindByQuery_Finds() - { - var realm = GetFakeFLXRealm(); - - var query1 = realm.All(); - var query2 = realm.All(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query2); - }); - - var sub1 = realm.Subscriptions.Find(query1); - AssertSubscriptionDetails(sub1, nameof(SyncAllTypesObject), name: "a"); - - var sub2 = realm.Subscriptions.Find(query2); - AssertSubscriptionDetails(sub2, nameof(SyncCollectionsObject)); - - Assert.That(sub1.CreatedAt, Is.LessThan(sub2.CreatedAt)); - } - - [Test] - public void SubscriptionSet_FindByQuery_ReturnsNullWhenMissing() - { - var realm = GetFakeFLXRealm(); - - var existingQuery = realm.All(); - var nonExistingQuery = realm.All(); - - Assert.That(realm.Subscriptions.Find(existingQuery), Is.Null); - Assert.That(realm.Subscriptions.Find(nonExistingQuery), Is.Null); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(existingQuery); - }); - - Assert.That(realm.Subscriptions.Find(existingQuery), Is.Not.Null); - Assert.That(realm.Subscriptions.Find(nonExistingQuery), Is.Null); - } - - [Test] - public void SubscriptionSet_Remove_ByName() - { - var realm = GetFakeFLXRealm(); - - var query1 = realm.All(); - var query2 = realm.All(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query2, new SubscriptionOptions { Name = "b" }); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(2)); - - realm.Subscriptions.Update(() => - { - Assert.That(realm.Subscriptions.Remove("a"), Is.True); - Assert.That(realm.Subscriptions.Remove("a"), Is.False); - Assert.That(realm.Subscriptions.Remove("c"), Is.False); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions[0].Name, Is.EqualTo("b")); - - realm.Subscriptions.Update(() => - { - Assert.That(realm.Subscriptions.Remove("a"), Is.False); - Assert.That(realm.Subscriptions.Remove("b"), Is.True); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(0)); - } - - [Test] - public void SubscriptionSet_Remove_ByName_OutsideUpdate_Throws() - { - var realm = GetFakeFLXRealm(); - - Assert.Throws(() => realm.Subscriptions.Remove("foo")); - } - - [Test] - public void SubscriptionSet_Remove_Subscription() - { - var realm = GetFakeFLXRealm(); - - var query1 = realm.All(); - var query2 = realm.All(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query2, new SubscriptionOptions { Name = "b" }); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(2)); - var sub = realm.Subscriptions[0]; - var nonExistent = new Subscription(ObjectId.GenerateNewId(), "a", nameof(SyncAllTypesObject), "TRUEPREDICATE", DateTimeOffset.UtcNow, DateTimeOffset.UtcNow); - - realm.Subscriptions.Update(() => - { - Assert.That(realm.Subscriptions.Remove(sub), Is.True); - Assert.That(realm.Subscriptions.Remove(sub), Is.False); - Assert.That(realm.Subscriptions.Remove(nonExistent), Is.False); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions[0].Id != sub.Id); - - realm.Subscriptions.Update(() => - { - var sub2 = realm.Subscriptions[0]; - Assert.That(realm.Subscriptions.Remove(sub), Is.False); - Assert.That(realm.Subscriptions.Remove(sub2), Is.True); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(0)); - } - - [Test] - public void SubscriptionSet_Remove_Subscription_OutsideUpdate_Throws() - { - var realm = GetFakeFLXRealm(); - var sub = new Subscription(ObjectId.GenerateNewId(), "a", "b", "c", DateTimeOffset.UtcNow, DateTimeOffset.UtcNow); - - Assert.Throws(() => realm.Subscriptions.Remove(sub)); - } - - [Test] - public void SubscriptionSet_Remove_ByQuery_RemoveNamed() - { - var realm = GetFakeFLXRealm(); - - var query1 = realm.All(); - var query2 = realm.All().Where(s => s.BooleanProperty); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "b" }); - - realm.Subscriptions.Add(query2); - realm.Subscriptions.Add(query2, new SubscriptionOptions { Name = "c" }); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(5)); - - realm.Subscriptions.Update(() => - { - var removed = realm.Subscriptions.Remove(query1, removeNamed: true); - Assert.That(removed, Is.EqualTo(3)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(2)); - Assert.That(realm.Subscriptions.Select(o => o.Name), Is.EquivalentTo(new[] { null, "c" })); - - realm.Subscriptions.Update(() => - { - var removed1 = realm.Subscriptions.Remove(query1, removeNamed: true); - Assert.That(removed1, Is.EqualTo(0)); - - var removed2 = realm.Subscriptions.Remove(query2, removeNamed: true); - Assert.That(removed2, Is.EqualTo(2)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(0)); - } - - [Test] - public void SubscriptionSet_Remove_ByQuery_RemoveNamed_False() - { - var realm = GetFakeFLXRealm(); - - var query1 = realm.All(); - var query2 = realm.All().Where(s => s.BooleanProperty); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "b" }); - - realm.Subscriptions.Add(query2); - realm.Subscriptions.Add(query2, new SubscriptionOptions { Name = "c" }); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(5)); - - realm.Subscriptions.Update(() => - { - var removed = realm.Subscriptions.Remove(query1, removeNamed: false); - Assert.That(removed, Is.EqualTo(1)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(4)); - Assert.That(realm.Subscriptions.Select(o => o.Name), Is.EquivalentTo(new[] { "a", "b", null, "c" })); - - realm.Subscriptions.Update(() => - { - var removed1 = realm.Subscriptions.Remove(query1, removeNamed: false); - Assert.That(removed1, Is.EqualTo(0)); - - var removed2 = realm.Subscriptions.Remove(query2, removeNamed: false); - Assert.That(removed2, Is.EqualTo(1)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(3)); - Assert.That(realm.Subscriptions.Select(o => o.Name), Is.EquivalentTo(new[] { "a", "b", "c" })); - } - - [Test] - public void SubscriptionSet_Remove_ByQuery_OutsideUpdate_Throws([Values(true, false)] bool removeNamed) - { - var realm = GetFakeFLXRealm(); - var query = realm.All(); - - Assert.Throws(() => realm.Subscriptions.Remove(query, removeNamed: removeNamed)); - } - - [Test] - public void SubscriptionSet_RemoveByType_RemoveNamed() - { - var realm = GetFakeFLXRealm(); - - var query1 = realm.All(); - var query2 = realm.All(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "b" }); - - realm.Subscriptions.Add(query2); - realm.Subscriptions.Add(query2, new SubscriptionOptions { Name = "c" }); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(5)); - - realm.Subscriptions.Update(() => - { - var removed = realm.Subscriptions.RemoveAll(nameof(SyncAllTypesObject), removeNamed: true); - Assert.That(removed, Is.EqualTo(3)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(2)); - } - - [Test] - public void SubscriptionSet_RemoveByType_RemoveNamed_False() - { - var realm = GetFakeFLXRealm(); - - var query1 = realm.All(); - var query2 = realm.All(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "b" }); - - realm.Subscriptions.Add(query2); - realm.Subscriptions.Add(query2, new SubscriptionOptions { Name = "c" }); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(5)); - - realm.Subscriptions.Update(() => - { - var removed = realm.Subscriptions.RemoveAll(nameof(SyncAllTypesObject), removeNamed: false); - Assert.That(removed, Is.EqualTo(1)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(4)); - } - - [Test] - public void SubscriptionSet_RemoveByType_Generic_RemoveNamed() - { - var realm = GetFakeFLXRealm(); - - var query1 = realm.All(); - var query2 = realm.All(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "b" }); - - realm.Subscriptions.Add(query2); - realm.Subscriptions.Add(query2, new SubscriptionOptions { Name = "c" }); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(5)); - - realm.Subscriptions.Update(() => - { - var removed = realm.Subscriptions.RemoveAll(removeNamed: true); - Assert.That(removed, Is.EqualTo(3)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(2)); - } - - [Test] - public void SubscriptionSet_RemoveByType_Generic_RemoveNamed_False() - { - var realm = GetFakeFLXRealm(); - - var query1 = realm.All(); - var query2 = realm.All(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "b" }); - - realm.Subscriptions.Add(query2); - realm.Subscriptions.Add(query2, new SubscriptionOptions { Name = "c" }); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(5)); - - realm.Subscriptions.Update(() => - { - var removed = realm.Subscriptions.RemoveAll(removeNamed: false); - Assert.That(removed, Is.EqualTo(1)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(4)); - } - - [Test] - public void SubscriptionSet_RemoveAll_RemoveNamed() - { - var realm = GetFakeFLXRealm(); - - var query1 = realm.All(); - var query2 = realm.All().Where(s => s.BooleanProperty); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "b" }); - - realm.Subscriptions.Add(query2); - realm.Subscriptions.Add(query2, new SubscriptionOptions { Name = "c" }); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(5)); - - realm.Subscriptions.Update(() => - { - var removed = realm.Subscriptions.RemoveAll(removeNamed: true); - Assert.That(removed, Is.EqualTo(5)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(0)); - } - - [Test] - public void SubscriptionSet_RemoveAll_RemoveNamed_False() - { - var realm = GetFakeFLXRealm(); - - var query1 = realm.All(); - var query2 = realm.All().Where(s => s.BooleanProperty); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query1); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query1, new SubscriptionOptions { Name = "b" }); - - realm.Subscriptions.Add(query2); - realm.Subscriptions.Add(query2, new SubscriptionOptions { Name = "c" }); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(5)); - - realm.Subscriptions.Update(() => - { - var removed = realm.Subscriptions.RemoveAll(removeNamed: false); - Assert.That(removed, Is.EqualTo(2)); - }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(3)); - Assert.That(realm.Subscriptions.Select(o => o.Name), Is.EquivalentTo(new[] { "a", "b", "c" })); - } - - [Test] - public void SubscriptionSet_RemoveAll_OutsideUpdate_Throws([Values(true, false)] bool removeNamed) - { - var realm = GetFakeFLXRealm(); - - Assert.Throws(() => realm.Subscriptions.RemoveAll(removeNamed: removeNamed)); - } - - [Test] - public void SubscriptionSet_WhenParentRealmIsClosed_GetsClosed() - { - var realm = GetFakeFLXRealm(); - var subs = realm.Subscriptions; - - realm.Dispose(); - - Assert.That(DeleteRealmWithRetries(realm.Config), Is.True); - - Assert.Throws(() => _ = subs.Count); - } - - [Test] - public void SubscriptionSet_WhenSupersededParentRealmIsClosed_GetsClosed() - { - TestHelpers.RunAsyncTest(async () => - { - var realm = GetFakeFLXRealm(); - var subs = realm.Subscriptions; - - await Task.Run(() => - { - using var bgRealm = GetRealm(realm.Config); - bgRealm.Subscriptions.Update(() => - { - bgRealm.Subscriptions.Add(bgRealm.All()); - }); - }); - - var updatedSubs = realm.Subscriptions; - Assert.That(subs, Is.Not.SameAs(updatedSubs)); - - realm.Dispose(); - - var handleField = typeof(SubscriptionSet).GetField("_handle", BindingFlags.NonPublic | BindingFlags.Instance)!; - var subsHandle = (SubscriptionSetHandle)handleField.GetValue(subs)!; - var updatedSubsHandle = (SubscriptionSetHandle)handleField.GetValue(updatedSubs)!; - Assert.That(subsHandle.IsClosed); - Assert.That(updatedSubsHandle.IsClosed); - - Assert.That(DeleteRealmWithRetries(realm.Config), Is.True); - - Assert.Throws(() => _ = subs.Count); - Assert.Throws(() => _ = updatedSubs.Count); - }); - } - - [Test] - public void SubscriptionSet_Update_WhenActionThrows_RollsbackTransaction() - { - var realm = GetFakeFLXRealm(); - var query = realm.All(); - - var subs = realm.Subscriptions; - subs.Update(() => - { - subs.Add(query); - }); - - Assert.That(subs.Count, Is.EqualTo(1)); - - var ex = Assert.Throws(() => subs.Update(() => - { - subs.Add(realm.All()); - Assert.That(subs.Count, Is.EqualTo(2)); - - // Now we do something that throws an error. - throw new Exception("Oh no!"); - }))!; - - Assert.That(ex.Message, Is.EqualTo("Oh no!")); - Assert.That(subs.Count, Is.EqualTo(1)); - - Assert.That(subs.Find(query), Is.Not.Null); - } - - [Test] - public void SubscriptionSet_Update_WhenTransactionIsInProgress_Throws() - { - var realm = GetFakeFLXRealm(); - - var subs = realm.Subscriptions; - var ex = Assert.Throws(() => subs.Update(() => - { - subs.Update(() => { }); - }))!; - - Assert.That(ex.Message, Does.Contain("already being updated")); - } - - [Test] - public void Realm_Subscriptions_WhenDisposed_Throws() - { - var realm = GetFakeFLXRealm(); - realm.Dispose(); - - Assert.Throws(() => _ = realm.Subscriptions); - } - - [Test] - public void SubscriptionSet_Enumerator() - { - var realm = GetFakeFLXRealm(); - - var query = realm.All(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query); - realm.Subscriptions.Add(query, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query, new SubscriptionOptions { Name = "b" }); - }); - - var index = 0; - - IEnumerable enumerableSubs = realm.Subscriptions; - foreach (Subscription? sub in enumerableSubs) - { - Assert.That(sub, Is.Not.Null); - Assert.That(sub!.Id, Is.EqualTo(realm.Subscriptions[index++].Id)); - } - } - - [Test] - public void SubscriptionSet_Enumerator_DoubleDispose_Throws() - { - var realm = GetFakeFLXRealm(); - var enumerator = realm.Subscriptions.GetEnumerator(); - - enumerator.Dispose(); - - Assert.Throws(() => enumerator.Dispose()); - } - - [Test] - public void SubscriptionSet_Enumerator_Reset() - { - var realm = GetFakeFLXRealm(); - - var query = realm.All(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(query); - realm.Subscriptions.Add(query, new SubscriptionOptions { Name = "a" }); - realm.Subscriptions.Add(query, new SubscriptionOptions { Name = "b" }); - }); - - using var enumerator = realm.Subscriptions.GetEnumerator(); - - var index = 0; - while (enumerator.MoveNext()) - { - Assert.That(enumerator.Current!.Id, Is.EqualTo(realm.Subscriptions[index++].Id)); - } - - // Ensure we can reset the enumerator and iterate over it again. - enumerator.Reset(); - - index = 0; - while (enumerator.MoveNext()) - { - Assert.That(enumerator.Current!.Id, Is.EqualTo(realm.Subscriptions[index++].Id)); - } - } - - [Test] - public void Integration_WaitForSynchronization_EmptyUpdate() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var realm = await GetFLXIntegrationRealmAsync(); - - realm.Subscriptions.Update(() => - { - }); - - await realm.Subscriptions.WaitForSynchronizationAsync(); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete), "State should be 'Complete' after synchronization"); - }); - } - - [Test] - public void Integration_CloseRealmBeforeWaitCompletes() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - await AddSomeData(testGuid); - - Task waitTask; - using (var realm = await GetFLXIntegrationRealmAsync()) - { - realm.Subscriptions.Update(() => - { - var query = realm.All().Where(o => o.GuidProperty == testGuid); - - realm.Subscriptions.Add(query); - }); - - waitTask = WaitForSubscriptionsAsync(realm); - } - - var ex = await TestHelpers.AssertThrows(() => waitTask); - Assert.That(ex.Message, Is.EqualTo("The SubscriptionSet was closed before the wait could complete. This is likely because the Realm it belongs to was disposed.")); - }); - } - - [Test] - public void Integration_SubscriptionSet_AddRemove() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete), "State should be 'Complete' after GetInstanceAsync"); - - var query = realm.All().Where(o => o.DoubleProperty > 2 && o.GuidProperty == testGuid); - - await UpdateAndWaitForSubscription(query); - - Assert.That(query.Count(), Is.EqualTo(1)); - Assert.That(query.Single().DoubleProperty, Is.EqualTo(2.5)); - - var query2 = realm.All().Where(o => o.DoubleProperty < 2 && o.GuidProperty == testGuid); - await UpdateAndWaitForSubscription(query2); - - Assert.That(realm.All().Count(), Is.EqualTo(2)); - - await UpdateAndWaitForSubscription(query, shouldAdd: false); - - Assert.That(realm.All().Count(), Is.EqualTo(1)); - Assert.That(query.Count(), Is.EqualTo(0)); - Assert.That(query2.Count(), Is.EqualTo(1)); - - await UpdateAndWaitForSubscription(query2, shouldAdd: false); - - Assert.That(realm.All().Count(), Is.EqualTo(0)); - }); - } - - [Test] - public void Integration_SubscriptionSet_MoveObjectOutsideView() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete), "State should be 'Complete' after GetInstanceAsync"); - - var query = realm.All().Where(o => o.DoubleProperty > 2 && o.GuidProperty == testGuid); - - await UpdateAndWaitForSubscription(query); - - Assert.That(query.Count(), Is.EqualTo(1)); - Assert.That(query.Single().DoubleProperty, Is.EqualTo(2.5)); - - realm.Write(() => - { - query.Single().DoubleProperty = 1.99; - }); - - await TestHelpers.WaitForConditionAsync(() => !query.Any()); - }); - } - - [Test] - public void Integration_SubscriptionSet_MoveObjectInsideView() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - var writerRealm = await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete), "State should be 'Complete' after GetInstanceAsync"); - - var query = realm.All().Where(o => o.DoubleProperty > 2 && o.GuidProperty == testGuid); - - await UpdateAndWaitForSubscription(query); - - Assert.That(query.Count(), Is.EqualTo(1)); - Assert.That(query.Single().DoubleProperty, Is.EqualTo(2.5)); - - // Add a new object - writerRealm.Write(() => - { - writerRealm.Add(new SyncAllTypesObject - { - DoubleProperty = 9.9, - GuidProperty = testGuid - }); - }); - - await TestHelpers.WaitForConditionAsync(() => query.Count() == 2); - - Assert.That(query.AsEnumerable().Select(o => o.DoubleProperty), Is.EquivalentTo(new[] { 2.5, 9.9 })); - - // Update an existing object - writerRealm.Write(() => - { - writerRealm.All().Single(o => o.DoubleProperty == 1.5).DoubleProperty = 11; - }); - - await TestHelpers.WaitForConditionAsync(() => query.Count() == 3); - - Assert.That(query.AsEnumerable().Select(o => o.DoubleProperty), Is.EquivalentTo(new[] { 2.5, 9.9, 11 })); - - writerRealm.Write(() => - { - writerRealm.All().Single(o => o.DoubleProperty == 11).DoubleProperty = 0; - }); - - await TestHelpers.WaitForConditionAsync(() => query.Count() == 2); - - Assert.That(query.AsEnumerable().Select(o => o.DoubleProperty), Is.EquivalentTo(new[] { 2.5, 9.9 })); - }); - } - - [Test] - public void Integration_SubscriptionWithLinks() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - var realm1 = await GetFLXIntegrationRealmAsync(); - realm1.Subscriptions.Update(() => - { - realm1.Subscriptions.Add(realm1.All().Where(o => o.GuidProperty == testGuid)); - realm1.Subscriptions.Add(realm1.All().Where(o => o.GuidProperty == testGuid)); - }); - - realm1.Write(() => - { - realm1.Add(GetAtoWithLink(1)); - realm1.Add(GetAtoWithLink(2)); - realm1.Add(GetAtoWithLink(3)); - }); - - await WaitForUploadAsync(realm1); - - var realm2 = await GetFLXIntegrationRealmAsync(); - realm2.Subscriptions.Update(() => - { - realm2.Subscriptions.Add(realm2.All().Where(o => o.GuidProperty == testGuid && o.Int64Property >= 2)); - }); - - await WaitForSubscriptionsAsync(realm2); - - var atos = realm2.All().OrderBy(o => o.Int64Property); - CollectionAssert.AreEqual(new[] { 2, 3 }, atos.AsEnumerable().Select(o => o.Int64Property)); - - // We didn't subscribe to IntPropertyObject, so even though SyncAllTypesObject links to those, we should not see any. - Assert.That(realm2.All().Count(), Is.EqualTo(0)); - Assert.That(atos.AsEnumerable().All(o => o.ObjectProperty == null)); - - realm2.Subscriptions.Update(() => - { - realm2.Subscriptions.Add(realm2.All().Where(o => o.GuidProperty == testGuid && o.Int <= 2)); - }); - - await WaitForSubscriptionsAsync(realm2); - - CollectionAssert.AreEqual(new[] { 2, 3 }, atos.AsEnumerable().Select(o => o.Int64Property)); - - // Now we should have subscribed and should see 2 objects - var intObjects = realm2.All().OrderBy(o => o.Int); - CollectionAssert.AreEqual(new[] { 1, 2 }, intObjects.AsEnumerable().Select(o => o.Int)); - - // We subscribed to ato 2 and 3, but intObject 1 and 2. So 2 should point to 2, 3 to null - Assert.That(atos.ElementAt(0).ObjectProperty!.Int, Is.EqualTo(2)); - Assert.That(atos.ElementAt(1).ObjectProperty, Is.Null); - - realm1.Write(() => - { - // ato 3 points to intObject 3, but that's outside of the view. We'll update intObject - // to -1 to bring it into view. - realm1.All().Single(o => o.Int == 3).Int = -1; - }); - - // intObject3 should sync down so we'll end up with 3 intObjects - await TestHelpers.WaitForConditionAsync(() => intObjects.Count() == 3); - - Assert.That(atos.ElementAt(1).ObjectProperty!.Int, Is.EqualTo(-1)); - - SyncAllTypesObject GetAtoWithLink(int value) => new() - { - Int64Property = value, - GuidProperty = testGuid, - ObjectProperty = GetIntPropertyObject(value, testGuid) - }; - }); - } - - [Test] - public void Integration_SubscriptionWithEmbeddedObjects() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - var realm1 = await GetFLXIntegrationRealmAsync(); - realm1.Subscriptions.Update(() => - { - realm1.Subscriptions.Add(realm1.All().Where(o => o.GuidProperty == testGuid)); - realm1.Subscriptions.Add(realm1.All().Where(o => o.GuidProperty == testGuid)); - }); - - realm1.Write(() => - { - realm1.Add(GetAtoWithEmbedded(1)); - realm1.Add(GetAtoWithEmbedded(2)); - realm1.Add(GetAtoWithEmbedded(3)); - }); - - await WaitForUploadAsync(realm1); - - var realm2 = await GetFLXIntegrationRealmAsync(); - realm2.Subscriptions.Update(() => - { - realm2.Subscriptions.Add(realm2.All().Where(o => o.GuidProperty == testGuid && o.Int64Property >= 2)); - }); - - await WaitForSubscriptionsAsync(realm2); - - var atos = realm2.All().OrderBy(o => o.Int64Property); - - Assert.That(atos.Count(), Is.EqualTo(2)); - Assert.That(atos.ElementAt(0).Int64Property, Is.EqualTo(2)); - Assert.That(atos.ElementAt(0).EmbeddedObjectProperty!.Int, Is.EqualTo(2)); - - Assert.That(atos.ElementAt(1).Int64Property, Is.EqualTo(3)); - Assert.That(atos.ElementAt(1).EmbeddedObjectProperty!.Int, Is.EqualTo(3)); - - realm1.Write(() => - { - realm1.All().Single(o => o.Int64Property == 3).Int64Property = 1; - }); - - await TestHelpers.WaitForConditionAsync(() => atos.Count() == 1); - - Assert.That(atos.Single().Int64Property, Is.EqualTo(2)); - Assert.That(atos.Single().EmbeddedObjectProperty!.Int, Is.EqualTo(2)); - - SyncAllTypesObject GetAtoWithEmbedded(int value) => new() - { - Int64Property = value, - GuidProperty = testGuid, - EmbeddedObjectProperty = new EmbeddedIntPropertyObject - { - Int = value - } - }; - }); - } - - [Test] - public void Integration_SubscriptionWithCollections() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - var realm1 = await GetFLXIntegrationRealmAsync(); - realm1.Subscriptions.Update(() => - { - realm1.Subscriptions.Add(realm1.All().Where(o => o.GuidProperty == testGuid)); - realm1.Subscriptions.Add(realm1.All().Where(o => o.GuidProperty == testGuid)); - }); - - var ito1 = GetIntPropertyObject(1, testGuid); - var ito2 = GetIntPropertyObject(2, testGuid); - var ito3 = GetIntPropertyObject(3, testGuid); - - var colObj1 = realm1.Write(() => - { - var collection = realm1.Add(new SyncCollectionsObject - { - GuidProperty = testGuid, - }); - - collection.ObjectList.Add(ito1); - collection.ObjectList.Add(ito2); - collection.ObjectList.Add(ito3); - - // Add 1 and 2 again - collection.ObjectList.Add(ito1); - collection.ObjectList.Add(ito2); - - collection.ObjectSet.Add(ito1); - collection.ObjectSet.Add(ito2); - collection.ObjectSet.Add(ito3); - - collection.ObjectDict.Add("1", ito1); - collection.ObjectDict.Add("2", ito2); - collection.ObjectDict.Add("3", ito3); - collection.ObjectDict.Add("1_again", ito1); - collection.ObjectDict.Add("3_again", ito3); - - return collection; - }); - - await WaitForUploadAsync(realm1); - - var realm2 = await GetFLXIntegrationRealmAsync(); - - realm2.Subscriptions.Update(() => - { - realm2.Subscriptions.Add(realm2.All().Where(o => o.GuidProperty == testGuid)); - realm2.Subscriptions.Add(realm2.All().Where(o => o.GuidProperty == testGuid && o.Int >= 2)); - }); - - await WaitForSubscriptionsAsync(realm2); - - Assert.That(realm2.All().Count(), Is.EqualTo(1)); - Assert.That(realm2.All().Count(), Is.EqualTo(2)); - - var colObj2 = realm2.All().Single(); - - // We should have 2,3,2 because the 1s should be filtered - CollectionAssert.AreEqual(new[] { 2, 3, 2 }, colObj2.ObjectList.Select(o => o.Int)); - Assert.That(colObj2.ObjectList[0], Is.EqualTo(colObj2.ObjectList[2])); - - Assert.That(colObj2.ObjectSet.Select(o => o.Int), Is.EquivalentTo(new[] { 2, 3 })); - - Assert.That(colObj2.ObjectDict.Count, Is.EqualTo(5)); - Assert.That(colObj2.ObjectDict["1"], Is.Null); - Assert.That(colObj2.ObjectDict["2"]!.Int, Is.EqualTo(2)); - Assert.That(colObj2.ObjectDict["3"]!.Int, Is.EqualTo(3)); - Assert.That(colObj2.ObjectDict["1_again"], Is.Null); - Assert.That(colObj2.ObjectDict["3_again"]!.Int, Is.EqualTo(3)); - Assert.That(colObj2.ObjectDict["3_again"], Is.EqualTo(colObj2.ObjectDict["3"])); - - // Add 4th element from the second client - realm2.Write(() => - { - var ito4 = realm2.Add(GetIntPropertyObject(4, testGuid)); - colObj2.ObjectList.Add(ito4); - colObj2.ObjectSet.Add(ito4); - colObj2.ObjectDict.Add("4", ito4); - }); - - await TestHelpers.WaitForConditionAsync(() => realm1.All().Count() == 4); - - Assert.That(colObj1.ObjectList.Count, Is.EqualTo(6)); - CollectionAssert.AreEqual(new[] { 1, 2, 3, 1, 2, 4 }, colObj1.ObjectList.Select(o => o.Int)); - - Assert.That(colObj1.ObjectSet.Count, Is.EqualTo(4)); - Assert.That(colObj1.ObjectSet.Select(o => o.Int), Is.EquivalentTo(new[] { 1, 2, 3, 4 })); - - Assert.That(colObj1.ObjectDict.Count, Is.EqualTo(6)); - Assert.That(colObj1.ObjectDict["4"]!.Int, Is.EqualTo(4)); - - // Move an element out of view from client1 - realm1.Write(() => - { - ito3.Int = -1; - }); - - await TestHelpers.WaitForConditionAsync(() => realm2.All().Count() == 2); - - // We should have 2, 2, 4 because 1 and 3 should be filtered - CollectionAssert.AreEqual(new[] { 2, 2, 4 }, colObj2.ObjectList.Select(o => o.Int)); - Assert.That(colObj2.ObjectList[0], Is.EqualTo(colObj2.ObjectList[1])); - - Assert.That(colObj2.ObjectSet.Count, Is.EqualTo(2)); - Assert.That(colObj2.ObjectSet.Select(o => o.Int), Is.EquivalentTo(new[] { 2, 4 })); - - Assert.That(colObj2.ObjectDict.Count, Is.EqualTo(6)); - Assert.That(colObj2.ObjectDict["1"], Is.Null); - Assert.That(colObj2.ObjectDict["2"]!.Int, Is.EqualTo(2)); - Assert.That(colObj2.ObjectDict["3"], Is.Null); - Assert.That(colObj2.ObjectDict["1_again"], Is.Null); - Assert.That(colObj2.ObjectDict["3_again"], Is.Null); - Assert.That(colObj2.ObjectDict["4"]!.Int, Is.EqualTo(4)); - - // Move an element into view from client1 - realm1.Write(() => - { - ito1.Int = 100; - }); - - await TestHelpers.WaitForConditionAsync(() => realm2.All().Count() == 3); - - // We should have 100, 2, 100, 2, 4 because 3 should be filtered - CollectionAssert.AreEqual(new[] { 100, 2, 100, 2, 4 }, colObj2.ObjectList.Select(o => o.Int)); - Assert.That(colObj2.ObjectList[0], Is.EqualTo(colObj2.ObjectList[2])); - Assert.That(colObj2.ObjectList[1], Is.EqualTo(colObj2.ObjectList[3])); - - Assert.That(colObj2.ObjectSet.Count, Is.EqualTo(3)); - Assert.That(colObj2.ObjectSet.Select(o => o.Int), Is.EquivalentTo(new[] { 100, 2, 4 })); - - Assert.That(colObj2.ObjectDict.Count, Is.EqualTo(6)); - Assert.That(colObj2.ObjectDict["1"]!.Int, Is.EqualTo(100)); - Assert.That(colObj2.ObjectDict["2"]!.Int, Is.EqualTo(2)); - Assert.That(colObj2.ObjectDict["3"], Is.Null); - Assert.That(colObj2.ObjectDict["1_again"]!.Int, Is.EqualTo(100)); - Assert.That(colObj2.ObjectDict["3_again"], Is.Null); - Assert.That(colObj2.ObjectDict["4"]!.Int, Is.EqualTo(4)); - Assert.That(colObj2.ObjectDict["1_again"], Is.EqualTo(colObj2.ObjectDict["1"])); - }); - } - - [Test] - public void Integration_RealmRemoveAllWithSubscriptions() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - var realm1 = await GetFLXIntegrationRealmAsync(); - realm1.Subscriptions.Update(() => - { - realm1.Subscriptions.Add(realm1.All().Where(o => o.GuidProperty == testGuid)); - realm1.Subscriptions.Add(realm1.All().Where(o => o.GuidProperty == testGuid)); - }); - - // Get int property objects in random order just to make sure removal is value-related rather than chronological. - var ito2 = GetIntPropertyObject(2, testGuid); - var ito1 = GetIntPropertyObject(1, testGuid); - var ito3 = GetIntPropertyObject(3, testGuid); - - var colObj1 = realm1.Write(() => - { - var collection = realm1.Add(new SyncCollectionsObject - { - GuidProperty = testGuid, - }); - - collection.ObjectList.Add(ito1); - collection.ObjectList.Add(ito2); - collection.ObjectList.Add(ito3); - - // Add 1 and 2 again - collection.ObjectList.Add(ito1); - collection.ObjectList.Add(ito2); - - collection.ObjectSet.Add(ito1); - collection.ObjectSet.Add(ito2); - collection.ObjectSet.Add(ito3); - - collection.ObjectDict.Add("1", ito1); - collection.ObjectDict.Add("2", ito2); - collection.ObjectDict.Add("3", ito3); - collection.ObjectDict.Add("1_again", ito1); - collection.ObjectDict.Add("3_again", ito3); - return collection; - }); - - Assert.That(colObj1.ObjectList.Count, Is.EqualTo(5)); - Assert.That(colObj1.ObjectSet.Count, Is.EqualTo(3)); - Assert.That(colObj1.ObjectDict.Count, Is.EqualTo(5)); - Assert.That(realm1.All().Count(), Is.EqualTo(3)); - - await WaitForUploadAsync(realm1); - - var realm2 = await GetFLXIntegrationRealmAsync(); - - realm2.Subscriptions.Update(() => - { - realm2.Subscriptions.Add(realm2.All().Where(o => o.GuidProperty == testGuid && o.Int >= 2)); - }); - - await WaitForSubscriptionsAsync(realm2); - - // No collections synced - Assert.That(realm2.All().Count(), Is.EqualTo(0)); - - // Only objects >= 2 synced - Assert.That(realm2.All().Count(), Is.EqualTo(2)); - - realm2.Write(() => - { - realm2.RemoveAll(); - }); - - await TestHelpers.WaitForConditionAsync(() => realm1.All().Count() == 1); - - Assert.That(colObj1.ObjectList.Count, Is.EqualTo(2)); - Assert.That(colObj1.ObjectSet.Count, Is.EqualTo(1)); - Assert.That(colObj1.ObjectSet.Select(o => o.Int), Is.EquivalentTo(new[] { 1 })); - - Assert.That(colObj1.ObjectDict.Count, Is.EqualTo(5)); - Assert.That(colObj1.ObjectDict["1"]!.Int, Is.EqualTo(1)); - Assert.That(colObj1.ObjectDict["1_again"]!.Int, Is.EqualTo(1)); - Assert.That(colObj1.ObjectDict["2"], Is.Null); - Assert.That(colObj1.ObjectDict["3"], Is.Null); - Assert.That(colObj1.ObjectDict["3_again"], Is.Null); - }); - } - - [Test] - public void Integration_SubscriptionSet_WaitForSynchronization_CanBeCalledMultipleTimes() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - var realm = await GetFLXIntegrationRealmAsync(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(realm.All().Where(o => o.GuidProperty == testGuid)); - }); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Pending), "State should be 'Pending' after change"); - await realm.Subscriptions.WaitForSynchronizationAsync(); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete), "State should be 'Complete' after synchronization"); - - // Call WaitForSynchronizationAsync again - await realm.Subscriptions.WaitForSynchronizationAsync(); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete), "State should remain 'Complete'"); - }); - } - - [Test] - public void Integration_SubscriptionSet_WaitForSynchronization_CanBeCancelled() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(realm.All().Where(o => o.GuidProperty == testGuid)); - }); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Pending), "State should be 'Pending' after change"); - var cts = new CancellationTokenSource(1); - - await TestHelpers.AssertThrows(() => realm.Subscriptions.WaitForSynchronizationAsync(cts.Token)); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Pending), "State should still be 'Pending' after cancellation"); - - await realm.Subscriptions.WaitForSynchronizationAsync(); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete), "State should be 'Complete' after synchronization"); - }); - } - - [Test] - public void Integration_SubscriptionSet_WaitForSynchronization_CanBeCalledWithCancelledToken() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(realm.All().Where(o => o.GuidProperty == testGuid)); - }); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Pending), "State should be 'Pending' after change"); - var cts = new CancellationTokenSource(); - cts.Cancel(); - - await TestHelpers.AssertThrows(() => realm.Subscriptions.WaitForSynchronizationAsync(cts.Token)); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Pending), "State should still be 'Pending' after cancellation"); - - await realm.Subscriptions.WaitForSynchronizationAsync(); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete), "State should be 'Complete' after synchronization"); - }); - } - - [Test] - public void Integration_CreateObjectNotMatchingSubscriptions_ShouldError() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var errorTcs = new TaskCompletionSource(); - var testGuid = Guid.NewGuid(); - var config = await GetFLXIntegrationConfigAsync(); - - config.OnSessionError = (_, error) => - { - errorTcs.TrySetResult(error); - }; - - var realm = await GetRealmAsync(config); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(realm.All().Where(o => o.GuidProperty == testGuid)); - }); - - realm.Write(() => - { - realm.Add(new SyncAllTypesObject()); - }); - - var sessionError = await errorTcs.Task; - Assert.That(sessionError.ErrorCode, Is.EqualTo(ErrorCode.CompensatingWrite)); - }); - } - - [Test] - public void Integration_UpdateObjectNotMatchingSubscriptions_ShouldError() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var errorTcs = new TaskCompletionSource(); - var testGuid = Guid.NewGuid(); - var config = await GetFLXIntegrationConfigAsync(); - config.OnSessionError = (_, error) => - { - errorTcs.TrySetResult(error); - }; - - var realm = await GetRealmAsync(config); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(realm.All().Where(o => o.GuidProperty == testGuid)); - }); - - var ato = realm.Write(() => realm.Add(new SyncAllTypesObject - { - GuidProperty = testGuid, - })); - - await WaitForUploadAsync(realm); - - realm.Write(() => - { - ato.GuidProperty = Guid.NewGuid(); - }); - - realm.Write(() => - { - ato.Int32Property = 15; - }); - - var sessionError = await errorTcs.Task; - Assert.That(sessionError.ErrorCode, Is.EqualTo(ErrorCode.CompensatingWrite)); - }); - } - - [Test] - public void Integration_SubscriptionOnUnqueryableField_ShouldAddThemAutomatically() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var realm = await GetFLXIntegrationRealmAsync(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(realm.All().Where(o => o.StringProperty == "foo")); - }); - - await WaitForSubscriptionsAsync(realm); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete)); - }); - } - - [Test] - public void Integration_AfterAnError_CanRecover() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var realm = await GetFLXIntegrationRealmAsync(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(realm.All().Filter("SUBQUERY(ObjectList, $obj, $obj.Int > 10).@count > 0")); - }); - - try - { - await realm.Subscriptions.WaitForSynchronizationAsync(); - Assert.Fail("Expected an error to be thrown."); - } - catch (SubscriptionException ex) - { - Assert.That(ex.Message, Does.Contain("SUBQUERY").And.Contains(nameof(SyncCollectionsObject))); - } - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Error), "State should be 'Error' when querying unqueryable field"); - Assert.That(realm.Subscriptions.Error!.Message, Does.Contain("SUBQUERY").And.Contains(nameof(SyncCollectionsObject))); - - var testGuid = Guid.NewGuid(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.RemoveAll(removeNamed: true); - realm.Subscriptions.Add(realm.All().Where(o => o.GuidProperty == testGuid)); - }); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Pending), "State should be 'Pending' after change"); - - await realm.Subscriptions.WaitForSynchronizationAsync(); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete), "State should be 'Complete' after synchronization"); - Assert.That(realm.Subscriptions.Error, Is.Null); - }); - } - - [Test] - public void Integration_UpdatingSubscription_SupersedesPreviousOnes() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var realm = await GetFLXIntegrationRealmAsync(); - - var testGuid = Guid.NewGuid(); - - realm.Subscriptions.Update(() => - { - realm.Subscriptions.Add(realm.All().Where(o => o.GuidProperty == testGuid)); - }); - - var version = realm.Subscriptions.Version; - - // Capture subs here as we expect the collection to be superseded by the background update - var subs = realm.Subscriptions; - - await Task.Run(() => - { - using var bgRealm = GetRealm(realm.Config); - - Assert.That(bgRealm.Subscriptions.Version, Is.EqualTo(version)); - - bgRealm.Subscriptions.Update(() => - { - bgRealm.Subscriptions.Add(bgRealm.All().Where(o => o.GuidProperty == testGuid)); - }); - - Assert.That(bgRealm.Subscriptions.Version, Is.EqualTo(version + 1)); - }); - - Assert.That(subs.Version, Is.EqualTo(version)); - Assert.That(realm.Subscriptions.Version, Is.EqualTo(version + 1)); - Assert.That(ReferenceEquals(subs, realm.Subscriptions), Is.False); - - await realm.Subscriptions.WaitForSynchronizationAsync(); - await subs.WaitForSynchronizationAsync(); - - Assert.That(subs.State, Is.EqualTo(SubscriptionSetState.Superseded)); - Assert.That(subs.Version, Is.EqualTo(version)); - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete), "State should be 'Complete' after synchronization"); - }); - } - - [Test] - public void Integration_InitialSubscriptions_Unnamed([Values(true, false)] bool openAsync) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - await AddSomeData(testGuid); - - var config = await GetFLXIntegrationConfigAsync(); - config.PopulateInitialSubscriptions = (r) => - { - var query = r.All().Where(o => o.GuidProperty == testGuid); - - r.Subscriptions.Add(query); - }; - - using var realm = openAsync ? await GetRealmAsync(config) : GetRealm(config); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - - if (openAsync) - { - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete), "State should be 'Complete' after GetInstanceAsync"); - } - else - { - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Pending), "State should be 'Pending' before synchronization"); - await WaitForSubscriptionsAsync(realm).Timeout(20_000); - } - - var query = realm.All().ToArray().Select(o => o.DoubleProperty).ToArray(); - Assert.That(query, Is.EquivalentTo(new[] { 1.5, 2.5 })); - }, timeout: 60_000); - } - - [Test] - public void Integration_InitialSubscriptions_Named([Values(true, false)] bool openAsync) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - await AddSomeData(testGuid); - - var config = await GetFLXIntegrationConfigAsync(); - config.PopulateInitialSubscriptions = (r) => - { - var query = r.All().Where(o => o.GuidProperty == testGuid && o.DoubleProperty > 2); - - r.Subscriptions.Add(query, new() { Name = "initial" }); - }; - - using var realm = openAsync ? await GetRealmAsync(config) : GetRealm(config); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions[0].Name, Is.EqualTo("initial")); - - if (openAsync) - { - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete), "State should be 'Complete' after GetInstanceAsync"); - } - else - { - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Pending), "State should be 'Pending' before synchronization"); - await WaitForSubscriptionsAsync(realm); - } - - var query = realm.All().ToArray().Select(o => o.DoubleProperty).ToArray(); - Assert.That(query, Is.EquivalentTo(new[] { 2.5 })); - }); - } - - [Test] - public void Integration_InitialSubscriptions_RunsOnlyOnce([Values(true, false)] bool openAsync) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var invocations = 0; - var config = await GetFLXIntegrationConfigAsync(); - config.PopulateInitialSubscriptions = _ => - { - invocations++; - }; - - for (var i = 0; i < 2; i++) - { - using var realm = openAsync ? await GetRealmAsync(config) : GetRealm(config); - Assert.That(invocations, Is.EqualTo(1)); - } - }); - } - - [Test] - public void Integration_InitialSubscriptions_PropagatesErrors([Values(true, false)] bool openAsync) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - var dummyException = new Exception("Very careless"); - var config = await GetFLXIntegrationConfigAsync(); - config.PopulateInitialSubscriptions = (r) => - { - var query = r.All().Where(o => o.GuidProperty == testGuid); - - r.Subscriptions.Add(query); - - throw dummyException; - }; - - try - { - using var shouldFailRealm = openAsync ? await GetRealmAsync(config) : GetRealm(config); - Assert.Fail("Expected an exception to occur"); - } - catch (AggregateException ex) - { - Assert.That(ex.Flatten().InnerException, Is.SameAs(dummyException)); - } - - // We failed the first time, we should not see PopulateInitialSubscriptions get invoked the second time - using var realm = openAsync ? await GetRealmAsync(config) : GetRealm(config); - Assert.That(realm.Subscriptions.Count, Is.Zero); - }); - } - - [Test] - public void Integration_WriteData_WhenOutsideOfSubscriptions_GetsRevertedByServer() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var errorTcs = new TaskCompletionSource(); - - var testGuid = Guid.NewGuid(); - var config = await GetFLXIntegrationConfigAsync(); - config.PopulateInitialSubscriptions = (r) => - { - r.Subscriptions.Add(r.All().Where(o => o.GuidProperty == testGuid)); - }; - - config.OnSessionError = (_, e) => errorTcs.TrySetResult(e); - using var realm = await GetRealmAsync(config); - - var obj = realm.Write(() => realm.Add(new SyncAllTypesObject())); - var id = obj.Id; - - var error = await errorTcs.Task; - - Assert.That(error.ErrorCode, Is.EqualTo(ErrorCode.CompensatingWrite)); - Assert.That(error.HelpLink, Does.Contain("logs?co_id=")); - - var compensatingError = error as CompensatingWriteException; - - Assert.That(compensatingError, Is.Not.Null); - Assert.That(compensatingError!.CompensatingWrites.Count(), Is.EqualTo(1)); - - var errorInfo = compensatingError.CompensatingWrites.Single(); - Assert.That(errorInfo.ObjectType, Is.EqualTo(nameof(SyncAllTypesObject))); - Assert.That(errorInfo.PrimaryKey.AsObjectId(), Is.EqualTo(id)); - Assert.That(errorInfo.Reason, Does.Contain("object is outside of the current query view")); - }); - } - - [Test] - public void Results_SubscribeUnnamed_WithValidQuery() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - var query = await realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty > 2) - .SubscribeAsync(); - - Assert.That(query.Count(), Is.EqualTo(1)); - Assert.That(query.Single().DoubleProperty, Is.EqualTo(2.5)); - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.Single().Name, Is.Null); - }); - } - - [Test] - public void Results_SubscribeNamed_WithValidQuery() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - var query = await realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty > 2) - .SubscribeAsync(new() { Name = "abc" }); - - Assert.That(query.Count(), Is.EqualTo(1)); - Assert.That(query.Single().DoubleProperty, Is.EqualTo(2.5)); - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.Single().Name, Is.EqualTo("abc")); - }); - } - - [Test] - public void Results_SubscribeNamed_UpdatesQuery() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - var query = await realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty > 2) - .SubscribeAsync(new() { Name = "abc" }); - - Assert.That(query.Count(), Is.EqualTo(1)); - Assert.That(query.Single().DoubleProperty, Is.EqualTo(2.5)); - - var query2 = await realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty < 5) - .SubscribeAsync(new() { Name = "abc" }); - - Assert.That(query2.Count(), Is.EqualTo(2)); - - // Even though the subscription with the name `abc` changed, the original query hasn't, so we should - // see results that reflect it. - Assert.That(query.Count(), Is.EqualTo(1)); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.Single().Name, Is.EqualTo("abc")); - }); - } - - [Test] - public void Results_SubscribeNamed_UpdateExistingFalse_WithSameQuery() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - var query = await realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty > 2) - .SubscribeAsync(new() { Name = "abc" }); - - Assert.That(query.Count(), Is.EqualTo(1)); - Assert.That(query.Single().DoubleProperty, Is.EqualTo(2.5)); - - // Should not throw - await realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty > 2) - .SubscribeAsync(new() { Name = "abc", UpdateExisting = false }); - - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.Single().Name, Is.EqualTo("abc")); - }); - } - - [Test] - public void Results_SubscribeNamed_UpdateExistingFalse_WithDifferentQuery_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - var query = await realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty > 2) - .SubscribeAsync(new() { Name = "abc" }); - - Assert.That(query.Count(), Is.EqualTo(1)); - Assert.That(query.Single().DoubleProperty, Is.EqualTo(2.5)); - - var ex = await TestHelpers.AssertThrows(() => realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty < 5) - .SubscribeAsync(new() { Name = "abc", UpdateExisting = false })); - - Assert.That(ex.Message, Does.Contain("subscription with the name 'abc' already exists")); - }); - } - - [Test] - public void Results_Subscribe_FirstTimeOnly_DoesntWaitForChanges([Values("abc", null)] string? name) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - var writerRealm = await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - var query = await realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty > 2) - .SubscribeAsync(new() { Name = name }); - Assert.That(query.Count(), Is.EqualTo(1)); - - writerRealm.Write(() => - { - for (var i = 0; i < 10; i++) - { - writerRealm.Add(new SyncAllTypesObject - { - ByteArrayProperty = TestHelpers.GetBytes(100_000), - DoubleProperty = 3.5, - GuidProperty = testGuid - }); - } - }); - - await WaitForUploadAsync(writerRealm); - - // Resubscribe to the same query with waitForSync.FirstTime. Since - // we already have this subscription, SubscribeAsync should return - // immediately without waiting for the download to happen. - await query.SubscribeAsync(new() { Name = name }); - Assert.That(query.Count(), Is.EqualTo(1)); - - // If we manually wait for the download, we should see the second object show up. - await WaitForDownloadAsync(realm); - Assert.That(query.Count(), Is.EqualTo(11)); - }); - } - - [Test] - public void Results_SubscribeNamed_FirstTimeOnly_DifferentQuery_WaitsForChanges() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - var writerRealm = await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - var query = await realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty > 2) - .SubscribeAsync(new() { Name = "abc" }); - Assert.That(query.Count(), Is.EqualTo(1)); - - writerRealm.Write(() => - { - for (var i = 0; i < 10; i++) - { - writerRealm.Add(new SyncAllTypesObject - { - ByteArrayProperty = TestHelpers.GetBytes(100_000), - DoubleProperty = 3.5, - GuidProperty = testGuid - }); - } - }); - - await WaitForUploadAsync(writerRealm); - - // Resubscribe to a different query with same name with waitForSync.FirstTime. Even though - // we have a subscription with the same name, SubscribeAsync should wait for the new subscription. - await realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty > 2.0001) - .SubscribeAsync(new() { Name = "abc" }); - Assert.That(query.Count(), Is.EqualTo(11)); - }); - } - - [Test] - public void Results_SubscribeNamed_FirstTimeOnly_DifferentObjectType_WaitsForChanges() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - var query = await realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty > 2) - .SubscribeAsync(new() { Name = "abc" }); - Assert.That(query.Count(), Is.EqualTo(1)); - - var testString = testGuid.ToString(); - - // Even though we're subscribing with the same name and waitForSync = FirstTime, we should - // still wait for the change since the subscription is now on a different type. - await realm.All() - .Where(o => o.Value == testString) - .SubscribeAsync(new() { Name = "abc" }); - Assert.That(query.Count(), Is.EqualTo(0)); - }); - } - - [Test] - public void Results_Subscribe_Never_DoesntWaitChanges([Values("abc", null)] string? name) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - var query = await realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty > 2) - .SubscribeAsync(new() { Name = name }, WaitForSyncMode.Never); - Assert.That(query.Count(), Is.EqualTo(0)); - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Pending)); - - await realm.Subscriptions.WaitForSynchronizationAsync(); - - Assert.That(query.Count(), Is.EqualTo(1)); - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete)); - }); - } - - [Test] - public void Results_Subscribe_Always_WaitsForChanges([Values("abc", null)] string? name) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - var writerRealm = await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - var query = await realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty > 2) - .SubscribeAsync(new() { Name = name }); - Assert.That(query.Count(), Is.EqualTo(1)); - - writerRealm.Write(() => - { - for (var i = 0; i < 10; i++) - { - writerRealm.Add(new SyncAllTypesObject - { - ByteArrayProperty = TestHelpers.GetBytes(100_000), - DoubleProperty = 3.5, - GuidProperty = testGuid - }); - } - }); - - await WaitForUploadAsync(writerRealm); - - await query.SubscribeAsync(new() { Name = name }, WaitForSyncMode.Always); - Assert.That(query.Count(), Is.EqualTo(11)); - }); - } - - [Test] - public void Results_Subscribe_WithCancellation_CancelsTheWait() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var testGuid = Guid.NewGuid(); - - await AddSomeData(testGuid); - - var realm = await GetFLXIntegrationRealmAsync(); - - var cts = new CancellationTokenSource(1); - var query = realm.All() - .Where(o => o.GuidProperty == testGuid && o.DoubleProperty > 2); - await TestHelpers.AssertThrows(() => query.SubscribeAsync(cancellationToken: cts.Token)); - - Assert.That(query.Count(), Is.EqualTo(0)); - Assert.That(realm.Subscriptions.Count, Is.EqualTo(1)); - - await realm.Subscriptions.WaitForSynchronizationAsync(); - - Assert.That(query.Count(), Is.EqualTo(1)); - }); - } - - private async Task AddSomeData(Guid testGuid) - { - var writerRealm = await GetFLXIntegrationRealmAsync(); - writerRealm.Subscriptions.Update(() => - { - writerRealm.Subscriptions.Add(writerRealm.All().Where(o => o.GuidProperty == testGuid)); - }); - - writerRealm.Write(() => - { - writerRealm.Add(new SyncAllTypesObject - { - DoubleProperty = 1.5, - GuidProperty = testGuid, - }); - - writerRealm.Add(new SyncAllTypesObject - { - DoubleProperty = 2.5, - GuidProperty = testGuid, - }); - }); - - await WaitForUploadAsync(writerRealm); - - return writerRealm; - } - - private Realm GetFakeFLXRealm() => GetRealm(GetFakeFLXConfig()); - - private static void AssertSubscriptionDetails([NotNull] Subscription? sub, string type, string query = "TRUEPREDICATE", string? name = null, bool expectUpdateOnly = false) - { - Assert.That(sub, Is.Not.Null); - Assert.That(sub!.Id, Is.Not.EqualTo(ObjectId.Empty)); - Assert.That(sub.Name, Is.EqualTo(name).IgnoreCase); - Assert.That(sub.Query, Is.EqualTo(query).IgnoreCase); - Assert.That(sub.ObjectType, Is.EqualTo(type)); - Assert.That(sub.UpdatedAt, Is.EqualTo(DateTimeOffset.UtcNow).Within(TimeSpan.FromSeconds(1))); - - if (expectUpdateOnly) - { - Assert.That(sub.CreatedAt, Is.LessThan(sub.UpdatedAt)); - } - else - { - Assert.That(sub.CreatedAt, Is.EqualTo(sub.UpdatedAt)); - } - } - - private static async Task UpdateAndWaitForSubscription(IQueryable query, bool shouldAdd = true) - where T : IRealmObject - { - var realm = ((RealmResults)query).Realm; - - realm.Subscriptions.Update(() => - { - if (shouldAdd) - { - realm.Subscriptions.Add(query); - } - else - { - realm.Subscriptions.Remove(query); - } - }); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Pending), "State should be 'Pending' after change"); - - await WaitForSubscriptionsAsync(realm); - - Assert.That(realm.Subscriptions.State, Is.EqualTo(SubscriptionSetState.Complete), "State should be 'Complete' after synchronization"); - } - - private static IntPropertyObject GetIntPropertyObject(int value, Guid guid) => new() - { - Int = value, - GuidProperty = guid - }; - } -} diff --git a/Tests/Realm.Tests/Sync/FunctionsTests.cs b/Tests/Realm.Tests/Sync/FunctionsTests.cs deleted file mode 100644 index e53eb7d57b..0000000000 --- a/Tests/Realm.Tests/Sync/FunctionsTests.cs +++ /dev/null @@ -1,669 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Concurrent; -using System.Globalization; -using System.Linq; -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Conventions; -using NUnit.Framework; -using static Realms.Tests.TestHelpers; - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class FunctionsTests : SyncTestBase - { - private readonly ConcurrentQueue> _conventionsToRemove = new(); - - [Test] - public void CallFunction_ReturnsResult() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - var result = await user.Functions.CallAsync("sumFunc", 1L, 2L, 3L); - - Assert.That(result.AsInt64, Is.EqualTo(6)); - }); - } - - [Test] - public void CallFunctionGeneric_ReturnsResult() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - var result = await user.Functions.CallAsync("sumFunc", 1, 2, 3); - - Assert.That(result, Is.EqualTo(6)); - }); - } - - [Test] - public void CallFunction_NoArguments() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - var result = await user.Functions.CallAsync("sumFunc"); - - Assert.That(result, Is.EqualTo(0)); - }); - } - - [Test] - public void CallFunction_WrongGeneric() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - var ex = await TestHelpers.AssertThrows(() => user.Functions.CallAsync("sumFunc", 1, 2)); - - Assert.That(ex.Message, Does.Contain("Cannot deserialize")); - }); - } - - [Test] - public void CallFunction_WithAnonymousParams_ReturnsBsonResult() - { - TestHelpers.IgnoreOnUnity("Anonymous objects need lambda compilation, which doesn't work on IL2CPP"); - - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - var first = new - { - intValue = 1, - floatValue = 1.2, - stringValue = "Hello ", - objectId = ObjectId.GenerateNewId(), - arr = new[] { 1, 2, 3 }, - date = DateTime.UtcNow, - child = new - { - intValue = 2L - } - }; - - var second = new - { - intValue = 2, - floatValue = 2.3, - stringValue = "world", - objectId = ObjectId.GenerateNewId(), - date = DateTime.UtcNow.AddHours(5), - arr = new[] { 4, 5, 6 }, - child = new - { - intValue = 3L - } - }; - - var result = await user.Functions.CallAsync("documentFunc", first, second); - - Assert.That(result["intValue"].AsInt32, Is.EqualTo(3)); - Assert.That(result["floatValue"].AsDouble, Is.EqualTo(3.5)); - Assert.That(result["stringValue"].AsString, Is.EqualTo("Hello world")); - Assert.That(result["objectId"].AsObjectId, Is.EqualTo(first.objectId)); - AssertDateTimeEquals(result["date"].ToUniversalTime(), second.date); - Assert.That(result["arr"].AsBsonArray.Select(a => a.AsInt32), Is.EquivalentTo(new[] { 1, 4 })); - Assert.That(result["child"]["intValue"].AsInt64, Is.EqualTo(5)); - }); - } - - [Test] - public void CallFunction_WithBsonDocument_ReturnsBsonResult() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - var first = new BsonDocument - { - { "intValue", 1 }, - { "floatValue", 1.2 }, - { "stringValue", "Hello " }, - { "objectId", ObjectId.GenerateNewId() }, - { "arr", new BsonArray { 1, 2, 3 } }, - { "date", DateTime.UtcNow }, - { "child", new BsonDocument { { "intValue", 2L } } } - }; - - var second = new BsonDocument - { - { "intValue", 2 }, - { "floatValue", 2.3 }, - { "stringValue", "world" }, - { "objectId", ObjectId.GenerateNewId() }, - { "date", DateTime.UtcNow.AddHours(5) }, - { "arr", new BsonArray { 4, 5, 6 } }, - { "child", new BsonDocument { { "intValue", 3L } } } - }; - - var result = await user.Functions.CallAsync("documentFunc", first, second); - - Assert.That(result["intValue"].AsInt32, Is.EqualTo(3)); - Assert.That(result["floatValue"].AsDouble, Is.EqualTo(3.5)); - Assert.That(result["stringValue"].AsString, Is.EqualTo("Hello world")); - Assert.That(result["objectId"].AsObjectId, Is.EqualTo(first["objectId"].AsObjectId)); - AssertDateTimeEquals(result["date"].ToUniversalTime(), second["date"].ToUniversalTime()); - Assert.That(result["arr"].AsBsonArray.Select(a => a.AsInt32), Is.EquivalentTo(new[] { 1, 4 })); - Assert.That(result["child"]["intValue"].AsInt64, Is.EqualTo(5)); - }); - } - - [Test] - public void CallFunction_WithTypedParams_ReturnsTypedResult() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - AddCamelCaseConvention(); - - var user = await GetUserAsync(); - var first = new FunctionArgument - { - IntValue = 1, - FloatValue = 1.2, - StringValue = "Hello ", - ObjectId = ObjectId.GenerateNewId(), - Arr = new[] { 1, 2, 3 }, - Date = DateTime.UtcNow, - Child = new Child - { - IntValue = 2 - } - }; - - var second = new FunctionArgument - { - IntValue = 2, - FloatValue = 2.3, - StringValue = "world", - ObjectId = ObjectId.GenerateNewId(), - Date = DateTime.UtcNow.AddHours(5), - Arr = new[] { 4, 5, 6 }, - Child = new Child - { - IntValue = 3 - } - }; - - var result = await user.Functions.CallAsync("documentFunc", first, second); - - Assert.That(result.IntValue, Is.EqualTo(3)); - Assert.That(result.FloatValue, Is.EqualTo(3.5)); - Assert.That(result.StringValue, Is.EqualTo("Hello world")); - AssertDateTimeEquals(result.Date, second.Date); - Assert.That(result.ObjectId, Is.EqualTo(first.ObjectId)); - Assert.That(result.Arr, Is.EquivalentTo(new[] { 1, 4 })); - Assert.That(result.Child!.IntValue, Is.EqualTo(5)); - }); - } - - #region TestDeserialization - - [Test] - public void CallFunction_AndTestDeserialization_Short([ValueSource(nameof(ShortTestCases))] short arg) - { - TestDeserialization(arg); - } - - [Test] - public void CallFunction_AndTestDeserialization_Int([ValueSource(nameof(IntTestCases))] int arg) - { - TestDeserialization(arg); - } - - [Test] - public void CallFunction_AndTestDeserialization_Long([ValueSource(nameof(LongTestCases))] long arg) - { - TestDeserialization(arg); - } - - [Test] - public void CallFunction_AndTestDeserialization_Float([ValueSource(nameof(FloatTestCases))] float arg) - { - TestDeserialization(arg); - } - - [Test] - public void CallFunction_AndTestDeserialization_Double([ValueSource(nameof(DoubleTestCases))] double arg) - { - TestDeserialization(arg); - } - - [Test] - public void CallFunction_AndTestDeserialization_Decimal([ValueSource(nameof(DecimalTestCases))] decimal arg) - { - TestDeserialization(arg); - } - - [Test] - public void CallFunction_AndTestDeserialization_Decimal128([ValueSource(nameof(Decimal128TestCases))] Decimal128 arg) - { - TestDeserialization(arg); - } - - [Test] - public void CallFunction_AndTestDeserialization_String([ValueSource(nameof(StringTestCases))] string arg) - { - TestDeserialization(arg); - } - - [Test] - public void CallFunction_AndTestDeserialization_DateTime([ValueSource(nameof(DateTimeTestCases))] DateTime arg) - { - TestDeserialization(arg); - } - - [Test] - public void CallFunction_AndTestDeserialization_DateTimeOffset([ValueSource(nameof(DateTimeOffsetTestCases))] DateTimeOffset arg) - { - TestDeserialization(arg); - } - - [Test] - public void CallFunction_AndTestDeserialization_ObjectId([ValueSource(nameof(ObjectIdTestCases))] ObjectId arg) - { - TestDeserialization(arg); - } - - [Test] - public void CallFunction_AndTestDeserialization_Guid([ValueSource(nameof(GuidTestCases))] Guid arg) - { - TestDeserialization(arg); - } - - [Test] - public void CallFunction_AndTestDeserialization_Boolean([ValueSource(nameof(BooleanTestCases))] bool arg) - { - TestDeserialization(arg); - } - - [Test] - public void CallFunction_AndTestDeserialization_Null() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - string? str = null; - var result = await user.Functions.CallAsync("mirror", str); - - Assert.That(result, Is.Null); - }); - } - - private void TestDeserialization(T val) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var result = await user.Functions.CallAsync("mirror", val); - - if (val is DateTime date && result is DateTime dateResult) - { - AssertDateTimeEquals(date, dateResult); - } - else if (val is DateTimeOffset dto && result is DateTimeOffset dtoResult) - { - AssertDateTimeOffsetEquals(dto, dtoResult); - } - else - { - Assert.That(result, Is.EqualTo(val)); - } - - var arr = new[] { val }; - var arrResult = await user.Functions.CallAsync("mirror", arr); - - if (arr is DateTime[] dateArr && arrResult is DateTime[] dateArrResult) - { - Assert.That(dateArr.Length, Is.EqualTo(dateArrResult.Length)); - for (var i = 0; i < dateArr.Length; i++) - { - AssertDateTimeEquals(dateArr[i], dateArrResult[i]); - } - } - else if (arr is DateTimeOffset[] dtoArr && arrResult is DateTimeOffset[] dtoArrResult) - { - Assert.That(dtoArr.Length, Is.EqualTo(dtoArrResult.Length)); - for (var i = 0; i < dtoArr.Length; i++) - { - AssertDateTimeOffsetEquals(dtoArr[i], dtoArrResult[i]); - } - } - else - { - Assert.That(arrResult, Is.EquivalentTo(arr)); - } - }); - } - - #endregion - - #region TestBsonValue - - [Test] - public void CallFunction_AndTestBsonValue_Short([ValueSource(nameof(ShortTestCases))] short arg) - { - TestBsonValue(arg); - } - - [Test] - public void CallFunction_AndTestBsonValue_Int([ValueSource(nameof(IntTestCases))] int arg) - { - TestBsonValue(arg); - } - - [Test] - public void CallFunction_AndTestBsonValue_Long([ValueSource(nameof(LongTestCases))] long arg) - { - TestBsonValue(arg); - } - - [Test] - [Ignore("BsonValue can't represent float.")] - public void CallFunction_AndTestBsonValue_Float([ValueSource(nameof(FloatTestCases))] float arg) - { - TestBsonValue(arg); - } - - [Test] - public void CallFunction_AndTestBsonValue_Double([ValueSource(nameof(DoubleTestCases))] double arg) - { - TestBsonValue(arg); - } - - [Test] - public void CallFunction_AndTestBsonValue_Decimal([ValueSource(nameof(DecimalTestCases))] decimal arg) - { - if (arg is decimal.MinValue or decimal.MaxValue) - { - // MongoDB.Bson serializes MinValue/MaxValue as Decimal128.MinValue/MaxValue: - // https://github.com/mongodb/mongo-csharp-driver/blob/b2668fb80c8d45be58a8009e336006c9545c1581/src/MongoDB.Bson/Serialization/Options/RepresentationConverter.cs#L153-L160 - Assert.Ignore("MongoDB.Bson representation for decimal.MinValue/MaxValue is incorrect."); - } - - TestBsonValue(arg); - } - - [Test] - public void CallFunction_AndTestBsonValue_Decimal128([ValueSource(nameof(Decimal128TestCases))] Decimal128 arg) - { - TestBsonValue(arg); - } - - [Test] - public void CallFunction_AndTestBsonValue_String([ValueSource(nameof(StringTestCases))] string arg) - { - TestBsonValue(arg); - } - - [Test] - public void CallFunction_AndTestBsonValue_DateTime([ValueSource(nameof(DateTimeTestCases))] DateTime arg) - { - TestBsonValue(arg); - } - - [Test] - public void CallFunction_AndTestBsonValue_DateTimeOffset([ValueSource(nameof(DateTimeOffsetTestCases))] DateTimeOffset arg) - { - TestBsonValue(arg); - } - - [Test] - public void CallFunction_AndTestBsonValue_ObjectId([ValueSource(nameof(ObjectIdTestCases))] ObjectId arg) - { - TestBsonValue(arg); - } - - [Test] - public void CallFunction_AndTestBsonValue_Guid([ValueSource(nameof(ObjectIdTestCases))] ObjectId arg) - { - TestBsonValue(arg); - } - - [Test] - public void CallFunction_AndTestBsonValue_Boolean([ValueSource(nameof(BooleanTestCases))] bool arg) - { - TestBsonValue(arg); - } - - private void TestBsonValue(T val) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; - - var user = await GetUserAsync(); - - var result = await user.Functions.CallAsync("mirror", val); - - if (val is DateTime date) - { - AssertDateTimeEquals(result.ToUniversalTime(), date); - } - else if (val is DateTimeOffset dto) - { - AssertDateTimeOffsetEquals(result, dto); - } - else - { - Assert.That(result.ToString()!.ToLower(), Is.EqualTo(val!.ToString()!.ToLower())); - } - - var arr = new[] { val }; - var arrResult = await user.Functions.CallAsync("mirror", arr); - - if (arr is DateTime[] dateArr) - { - var dateArrResult = arrResult.AsBsonArray.Select(a => a.ToUniversalTime()).ToArray(); - Assert.That(dateArrResult.Length, Is.EqualTo(dateArr.Length)); - for (var i = 0; i < dateArr.Length; i++) - { - AssertDateTimeEquals(dateArrResult[i], dateArr[i]); - } - } - else if (arr is DateTimeOffset[] dtoArr) - { - var dtoArrResult = arrResult.AsBsonArray; - Assert.That(dtoArrResult.Count, Is.EqualTo(dtoArr.Length)); - for (var i = 0; i < dtoArr.Length; i++) - { - AssertDateTimeOffsetEquals(dtoArrResult[i], dtoArr[i]); - } - } - else - { - Assert.That( - arrResult.AsBsonArray.Select(a => a.ToString()!.ToLower()), - Is.EquivalentTo(arr.Select(a => a!.ToString()!.ToLower()))); - } - }); - } - - #endregion - - protected override void CustomTearDown() - { - base.CustomTearDown(); - - _conventionsToRemove.DrainQueue(ConventionRegistry.Remove); - } - - private static void AssertDateTimeEquals(DateTime first, DateTime second) - { - var diff = (first - second).TotalMilliseconds; - Assert.That(Math.Abs(diff), Is.LessThan(1), $"Expected {first} to equal {second} with millisecond precision, but it didn't."); - } - - private static void AssertDateTimeOffsetEquals(BsonValue first, DateTimeOffset second) - { - Assert.That(first.IsValidDateTime); - DateTimeOffset firstDto = first.ToUniversalTime(); - AssertDateTimeOffsetEquals(firstDto, second); - } - - private static void AssertDateTimeOffsetEquals(DateTimeOffset first, DateTimeOffset second) - { - Assert.That(first.ToUnixTimeMilliseconds(), Is.EqualTo(second.ToUnixTimeMilliseconds())); - } - - private void AddCamelCaseConvention() - { - const string name = "camel case"; - var pack = new ConventionPack(); - pack.Add(new CamelCaseElementNameConvention()); - ConventionRegistry.Register(name, pack, _ => true); - - _conventionsToRemove.Enqueue(name); - } - - private class FunctionArgument - { - public int IntValue { get; set; } - - public double FloatValue { get; set; } - - public string? StringValue { get; set; } - - public ObjectId ObjectId { get; set; } - - public int[]? Arr { get; set; } - - public Child? Child { get; set; } - - public DateTime Date { get; set; } - } - - private class Child - { - public int IntValue { get; set; } - } - - public static readonly int[] IntTestCases = new[] - { - 1, - 0, - -1, - int.MinValue, - int.MaxValue, - }; - - public static readonly long[] LongTestCases = new[] - { - 1L, - -1L, - 0L, - long.MinValue, - long.MaxValue, - }; - - public static readonly double[] DoubleTestCases = new[] - { - 1.54, - 0.00, - -1.224, - - // These don't roundtrip correctly: https://github.com/realm/realm-object-store/issues/1106 - // double.MinValue, - // double.MaxValue, - }; - - public static readonly float[] FloatTestCases = new[] - { - 1.54f, - 0.00f, - -1.224f, - float.MinValue, - float.MaxValue, - }; - - public static readonly Decimal128[] Decimal128TestCases = new[] - { - Decimal128.MinValue, - Decimal128.MaxValue, - new Decimal128(1.2M), - new Decimal128(0), - new Decimal128(-1.53464399239324M), - }; - - public static readonly decimal[] DecimalTestCases = new[] - { - decimal.MinValue, - decimal.MaxValue, - 1.2M, - 0M, - -1.53464399239324M, - }; - - public static readonly string[] StringTestCases = new[] - { - "fooo", - string.Empty, - }; - - public static readonly ObjectId[] ObjectIdTestCases = new[] - { - ObjectId.Parse("5f766ae78d273beeab5b0e6b"), - ObjectId.Empty, - }; - - public static readonly Guid[] GuidTestCases = new[] - { - Guid.Parse("86CA0BAD-F069-4713-AD42-F9175579B83D"), - Guid.Empty, - }; - - public static readonly DateTime[] DateTimeTestCases = new[] - { - new DateTime(202065954, DateTimeKind.Utc), - new DateTime(3333444, DateTimeKind.Utc), - new DateTime(0, DateTimeKind.Utc), - DateTime.MinValue, - DateTime.MaxValue, - }; - - public static readonly DateTimeOffset[] DateTimeOffsetTestCases = new[] - { - new DateTimeOffset(202065955, TimeSpan.Zero), - new DateTimeOffset(3333444, TimeSpan.Zero), - new DateTimeOffset(0, TimeSpan.Zero), - DateTimeOffset.MinValue, - DateTimeOffset.MaxValue, - }; - - public static readonly bool[] BooleanTestCases = new[] - { - true, - false, - }; - - public static readonly short[] ShortTestCases = new[] - { - (short)1, - (short)0, - (short)-1, - short.MinValue, - short.MaxValue, - }; - } -} diff --git a/Tests/Realm.Tests/Sync/MongoClientTests.cs b/Tests/Realm.Tests/Sync/MongoClientTests.cs deleted file mode 100644 index 6dc426b85a..0000000000 --- a/Tests/Realm.Tests/Sync/MongoClientTests.cs +++ /dev/null @@ -1,2336 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using Baas; -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; -using NUnit.Framework; -using Realms.Sync; -using Realms.Sync.Exceptions; - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class MongoClientTests : SyncTestBase - { - private const string ServiceName = "BackingDB"; - private const string FoosCollectionName = "foos"; - private const string SalesCollectionName = "sales"; - - [Test] - public void MongoClient_ServiceName_ReturnsOriginalName() - { - var user = GetFakeUser(); - var client = user.GetMongoClient("foo-bar"); - - Assert.That(client.ServiceName, Is.EqualTo("foo-bar")); - } - - [Test] - public void MongoDatabase_Name_ReturnsOriginalName() - { - var user = GetFakeUser(); - var client = user.GetMongoClient("foo-bar"); - var db = client.GetDatabase(SyncTestHelpers.RemoteMongoDBName()); - - Assert.That(db.Name, Is.EqualTo(SyncTestHelpers.RemoteMongoDBName())); - Assert.That(db.Client.ServiceName, Is.EqualTo("foo-bar")); - } - - [Test] - public void MongoCollection_Name_ReturnsOriginalName() - { - var user = GetFakeUser(); - var client = user.GetMongoClient("foo-bar"); - var db = client.GetDatabase(SyncTestHelpers.RemoteMongoDBName()); - var collection = db.GetCollection("foos"); - - Assert.That(collection.Name, Is.EqualTo("foos")); - Assert.That(collection.Database?.Name, Is.EqualTo(SyncTestHelpers.RemoteMongoDBName())); - Assert.That(collection.Database?.Client.ServiceName, Is.EqualTo("foo-bar")); - } - - [Test] - public void MongoCollection_InsertOne() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var foo = new Foo("a", 5); - var result = await collection.InsertOneAsync(foo); - - Assert.That(result.InsertedId, Is.EqualTo(foo.Id)); - - var foos = await collection.FindAsync(); - Assert.That(foos.Length, Is.EqualTo(1)); - Assert.That(foos[0], Is.EqualTo(foo)); - }); - } - - [Test] - public void MongoCollection_InsertOne_WithNullDoc() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - await TestHelpers.AssertThrows(() => collection.InsertOneAsync(null!)); - }); - } - - [Test] - public void MongoCollection_InsertOne_WithDocWithInvalidSchema() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetBsonCollection(); - - var doc = new BsonDocument - { - { "_id", ObjectId.GenerateNewId() }, - { "longValue", "this is a string!" } - }; - - var ex = await TestHelpers.AssertThrows(() => collection.InsertOneAsync(doc)); - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - Assert.That(ex.Message, Does.Contain("insert not permitted")); - Assert.That(ex.HelpLink, Does.Contain("/logs?co_id=")); - }); - } - - [Test] - public void MongoCollection_InsertOne_WithBsonDoc() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetBsonCollection(); - - var doc = new BsonDocument - { - { "_id", ObjectId.GenerateNewId() }, - { "longValue", 5L }, - { "stringValue", "bla bla" }, - }; - var result = await collection.InsertOneAsync(doc); - - Assert.That(result.InsertedId, Is.EqualTo(doc["_id"].AsObjectId)); - - var foos = await collection.FindAsync(); - Assert.That(foos.Length, Is.EqualTo(1)); - Assert.That(foos[0]["stringValue"].AsString, Is.EqualTo("bla bla")); - Assert.That(foos[0]["longValue"].AsInt64, Is.EqualTo(5)); - }); - } - - [Test] - public void MongoCollection_InsertMany() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var foos = new[] - { - new Foo("first", 123), - new Foo("second", 456) - }; - - var result = await collection.InsertManyAsync(foos); - - Assert.That(result.InsertedIds.Length, Is.EqualTo(2)); - Assert.That(result.InsertedIds, Is.EquivalentTo(foos.Select(f => f.Id))); - - var foundFoos = await collection.FindAsync(); - Assert.That(foundFoos.Length, Is.EqualTo(2)); - Assert.That(foundFoos, Is.EquivalentTo(foos)); - }); - } - - [Test] - public void MongoCollection_InsertMany_WithNullCollection() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - await TestHelpers.AssertThrows(() => collection.InsertManyAsync(null!)); - }); - } - - [Test] - public void MongoCollection_InsertMany_WithNullDocuments() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var foos = new[] - { - new Foo("first", 123), - null - }; - - var ex = await TestHelpers.AssertThrows(() => collection.InsertManyAsync(foos!)); - Assert.That(ex.ParamName, Is.EqualTo("docs")); - Assert.That(ex.Message, Does.Contain("null elements")); - }); - } - - [Test] - public void MongoCollection_InsertMany_WithDocWithInvalidSchema() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetBsonCollection(); - - var doc = new BsonDocument - { - { "_id", ObjectId.GenerateNewId() }, - { "longValue", "this is a string!" } - }; - - var ex = await TestHelpers.AssertThrows(() => collection.InsertManyAsync(new[] { doc })); - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - Assert.That(ex.Message, Does.Contain("insert not permitted")); - Assert.That(ex.HelpLink, Does.Contain("/logs?co_id=")); - }); - } - - [Test] - public void MongoCollection_InsertMany_WithBsonDoc() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetBsonCollection(); - - var docs = new[] - { - new BsonDocument - { - { "_id", ObjectId.GenerateNewId() }, - { "longValue", 5L }, - { "stringValue", "first" }, - }, - new BsonDocument - { - { "_id", ObjectId.GenerateNewId() }, - { "longValue", 999L }, - { "stringValue", "second" }, - }, - }; - - var result = await collection.InsertManyAsync(docs); - - Assert.That(result.InsertedIds, Is.EquivalentTo(docs.Select(d => d["_id"].AsObjectId))); - - var foos = await collection.FindAsync(); - Assert.That(foos.Length, Is.EqualTo(2)); - Assert.That(foos[0]["stringValue"].AsString, Is.EqualTo("first")); - Assert.That(foos[0]["longValue"].AsInt64, Is.EqualTo(5)); - Assert.That(foos[1]["stringValue"].AsString, Is.EqualTo("second")); - Assert.That(foos[1]["longValue"].AsInt64, Is.EqualTo(999)); - }); - } - - [Test] - public void MongoCollection_UpdateOne_WithoutFilter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var update = BsonDocument.Parse(@"{ - $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999""} - } - }"); - - var result = await collection.UpdateOneAsync(filter: null, update); - - Assert.That(result.UpsertedId, Is.Null); - Assert.That(result.MatchedCount, Is.EqualTo(1)); - Assert.That(result.ModifiedCount, Is.EqualTo(1)); - - // Update inserted with expected values after the update - inserted[0].StringValue = "this is update!"; - inserted[0].LongValue = 999; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_UpdateOne_WithFilter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var update = BsonDocument.Parse(@"{ - $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - } - }"); - - var filter = BsonDocument.Parse(@"{ - LongValue: { $gte: 1 } - }"); - - var result = await collection.UpdateOneAsync(filter, update); - - Assert.That(result.UpsertedId, Is.Null); - Assert.That(result.MatchedCount, Is.EqualTo(1)); - Assert.That(result.ModifiedCount, Is.EqualTo(1)); - - // Update inserted with expected values after the update - should have matched - // the second element - inserted[1].StringValue = "this is update!"; - inserted[1].LongValue = 999; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_UpdateOne_NoMatches_Upsert() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var upsertId = ObjectId.GenerateNewId(); - - var update = BsonDocument.Parse(@"{ - $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - }, - $setOnInsert: { - _id: ObjectId(""" + upsertId + @""") - } - }"); - - var filter = BsonDocument.Parse(@"{ - LongValue: { $gte: 5 } - }"); - - var result = await collection.UpdateOneAsync(filter, update, upsert: true); - - Assert.That(result.UpsertedId, Is.EqualTo(upsertId)); - Assert.That(result.MatchedCount, Is.EqualTo(0)); - Assert.That(result.ModifiedCount, Is.EqualTo(0)); - - // Update inserted with expected values after the update - should have matched - // no docs, so we expect an upsert - inserted = inserted.Concat(new[] - { - new Foo("this is update!", 999) - { - Id = upsertId - } - }).ToArray(); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_UpdateOne_NoMatches_Noupsert() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var upsertId = ObjectId.GenerateNewId(); - var update = BsonDocument.Parse(@"{ - $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - }, - $setOnInsert: { - _id: ObjectId(""" + upsertId + @""") - } - }"); - - var filter = BsonDocument.Parse(@"{ - LongValue: { $gte: 5 } - }"); - - var result = await collection.UpdateOneAsync(filter, update, upsert: false); - - Assert.That(result.UpsertedId, Is.Null); - Assert.That(result.MatchedCount, Is.EqualTo(0)); - Assert.That(result.ModifiedCount, Is.EqualTo(0)); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_UpdateOne_NullUpdate_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var ex = await TestHelpers.AssertThrows(() => collection.UpdateOneAsync(filter: null, updateDocument: null!)); - Assert.That(ex.ParamName, Is.EqualTo("updateDocument")); - }); - } - - [Test] - public void MongoCollection_UpdateMany_WithoutFilter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var update = BsonDocument.Parse(@"{ $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - } }"); - - var result = await collection.UpdateManyAsync(filter: null, update); - - Assert.That(result.UpsertedId, Is.Null); - Assert.That(result.MatchedCount, Is.EqualTo(3)); - Assert.That(result.ModifiedCount, Is.EqualTo(3)); - - // Update inserted with expected values after the update - foreach (var foo in inserted) - { - foo.StringValue = "this is update!"; - foo.LongValue = 999; - } - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_UpdateMany_WithFilter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var update = BsonDocument.Parse(@"{ $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - } }"); - - var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); - - var result = await collection.UpdateManyAsync(filter, update); - - Assert.That(result.UpsertedId, Is.Null); - Assert.That(result.MatchedCount, Is.EqualTo(2)); - Assert.That(result.ModifiedCount, Is.EqualTo(2)); - - // Update inserted with expected values after the update - should have matched - // the second element - for (var i = 1; i < 3; i++) - { - inserted[i].StringValue = "this is update!"; - inserted[i].LongValue = 999; - } - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_UpdateMany_NoMatches_Upsert() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var upsertId = ObjectId.GenerateNewId(); - - var update = BsonDocument.Parse(@"{ - $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - }, - $setOnInsert: { - _id: ObjectId(""" + upsertId + @""") - } - }"); - - var filter = BsonDocument.Parse("{ LongValue: { $gte: 5 } }"); - - var result = await collection.UpdateManyAsync(filter, update, upsert: true); - - Assert.That(result.UpsertedId, Is.EqualTo(upsertId)); - Assert.That(result.MatchedCount, Is.EqualTo(0)); - Assert.That(result.ModifiedCount, Is.EqualTo(0)); - - // Update inserted with expected values after the update - should have matched - // no docs, so we expect an upsert - inserted = inserted.Concat(new[] - { - new Foo("this is update!", 999) - { - Id = upsertId - } - }).ToArray(); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_UpdateMany_NoMatches_Noupsert() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var upsertId = ObjectId.GenerateNewId(); - - var update = BsonDocument.Parse(@"{ - $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - }, - $setOnInsert: { - _id: ObjectId(""" + upsertId + @""") - } - }"); - - var filter = BsonDocument.Parse("{ LongValue: { $gte: 5 } }"); - - var result = await collection.UpdateManyAsync(filter, update, upsert: false); - - Assert.That(result.UpsertedId, Is.Null); - Assert.That(result.MatchedCount, Is.EqualTo(0)); - Assert.That(result.ModifiedCount, Is.EqualTo(0)); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_UpdateMany_NullUpdate_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var ex = await TestHelpers.AssertThrows(() => collection.UpdateManyAsync(filter: null, updateDocument: null!)); - Assert.That(ex.ParamName, Is.EqualTo("updateDocument")); - }); - } - - [Test] - public void MongoCollection_DeleteOne_WithoutFilter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var result = await collection.DeleteOneAsync(); - - Assert.That(result.DeletedCount, Is.EqualTo(1)); - - // The first element is removed - inserted = inserted.Where((_, i) => i > 0).ToArray(); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_DeleteOne_WithFilter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - - var result = await collection.DeleteOneAsync(filter); - - Assert.That(result.DeletedCount, Is.EqualTo(1)); - - // The second element is removed - inserted = inserted.Where((_, i) => i != 1).ToArray(); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_DeleteOne_WithFilter_NoMatches() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 5 } - } - } - }; - - var result = await collection.DeleteOneAsync(filter); - - Assert.That(result.DeletedCount, Is.EqualTo(0)); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_DeleteMany_WithoutFilter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - await InsertSomeData(collection, 3); - - var result = await collection.DeleteManyAsync(); - - Assert.That(result.DeletedCount, Is.EqualTo(3)); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.Empty); - }); - } - - [Test] - public void MongoCollection_DeleteMany_WithFilter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - - var result = await collection.DeleteManyAsync(filter); - - Assert.That(result.DeletedCount, Is.EqualTo(2)); - - // The second and third elements are removed - inserted = inserted.Where((_, i) => i == 0).ToArray(); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_DeleteMany_WithFilter_NoMatches() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 5 } - } - } - }; - - var result = await collection.DeleteManyAsync(filter); - - Assert.That(result.DeletedCount, Is.EqualTo(0)); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_Count_WithoutFilter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - await InsertSomeData(collection, 3); - - var result = await collection.CountAsync(); - Assert.That(result, Is.EqualTo(3)); - }); - } - - [Test] - public void MongoCollection_Count_WithFilter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - - var result = await collection.CountAsync(filter); - - Assert.That(result, Is.EqualTo(2)); - }); - } - - [Test] - public void MongoCollection_Count_WithFilter_NoMatches() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 5 } - } - } - }; - - var result = await collection.CountAsync(filter); - - Assert.That(result, Is.EqualTo(0)); - }); - } - - [Test] - public void MongoCollection_FindOne() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var nonexistent = await collection.FindOneAsync(); - Assert.That(nonexistent, Is.Null); - - var inserted = await InsertSomeData(collection, 3); - - var result = await collection.FindOneAsync(); - Assert.That(result, Is.EqualTo(inserted[0])); - }); - } - - [Test] - public void MongoCollection_FindOne_Sort() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var sort = new BsonDocument { { "LongValue", -1 } }; - var result = await collection.FindOneAsync(sort: sort); - Assert.That(result, Is.EqualTo(inserted[2])); - }); - } - - [Test] - public void MongoCollection_FindOne_Filter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - - var result = await collection.FindOneAsync(filter); - Assert.That(result, Is.EqualTo(inserted[1])); - }); - } - - [Test] - public void MongoCollection_FindOne_FilterSort() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - - var sort = new BsonDocument { { "StringValue", -1 } }; - - var result = await collection.FindOneAsync(filter, sort: sort); - Assert.That(result, Is.EqualTo(inserted[2])); - }); - } - - [Test] - public void MongoCollection_FindOne_Projection() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var projection = new BsonDocument - { - { "_id", 0 }, - { "StringValue", 1 } - }; - - foreach (var foo in inserted) - { - foo.Id = default; - foo.LongValue = default; - } - - var result = await collection.FindOneAsync(projection: projection); - Assert.That(result, Is.EqualTo(inserted[0])); - }); - } - - [Test] - public void MongoCollection_FindOne_FilterProjection() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - - var projection = new BsonDocument - { - { "_id", 0 }, - { "LongValue", 1 } - }; - - foreach (var foo in inserted) - { - foo.Id = default; - foo.StringValue = default; - } - - var result = await collection.FindOneAsync(filter, projection: projection); - Assert.That(result, Is.EqualTo(inserted[1])); - }); - } - - [Test] - public void MongoCollection_FindOne_ProjectionSort() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var projection = new BsonDocument - { - { "_id", 1 }, - { "LongValue", 1 } - }; - - var sort = new BsonDocument { { "LongValue", -1 } }; - - foreach (var foo in inserted) - { - foo.StringValue = default; - } - - var result = await collection.FindOneAsync(sort: sort, projection: projection); - Assert.That(result, Is.EqualTo(inserted[2])); - }); - } - - [Test] - public void MongoCollection_FindOne_FilterProjectionSort() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var projection = new BsonDocument - { - { "_id", 0 }, - { "LongValue", 1 }, - { "StringValue", 1 } - }; - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - - var sort = new BsonDocument { { "LongValue", -1 } }; - - foreach (var foo in inserted) - { - foo.Id = default; - } - - var result = await collection.FindOneAsync(filter, sort, projection); - Assert.That(result, Is.EqualTo(inserted[2])); - }); - } - - [Test] - public void MongoCollection_Find() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var result = await collection.FindAsync(); - Assert.That(result, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_Find_Sort() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var sort = new BsonDocument { { "LongValue", -1 } }; - var result = await collection.FindAsync(sort: sort); - Assert.That(result, Is.EquivalentTo(inserted.Reverse())); - }); - } - - [Test] - public void MongoCollection_Find_Filter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - - var result = await collection.FindAsync(filter); - Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1))); - }); - } - - [Test] - public void MongoCollection_Find_FilterSort() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - - var sort = new BsonDocument { { "StringValue", -1 } }; - - var result = await collection.FindAsync(filter, sort: sort); - Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1).Reverse())); - }); - } - - [Test] - public void MongoCollection_Find_Projection() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var projection = new BsonDocument - { - { "_id", 0 }, - { "StringValue", 1 } - }; - - foreach (var foo in inserted) - { - foo.Id = default; - foo.LongValue = default; - } - - var result = await collection.FindAsync(projection: projection); - Assert.That(result, Is.EquivalentTo(inserted.Reverse())); - }); - } - - [Test] - public void MongoCollection_Find_FilterProjection() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - -#if UNITY - var projection = new BsonDocument - { - { "_id", 0 }, - { "LongValue", 1 } - }; -#else - var projection = new - { - _id = 0, - LongValue = 1 - }; -#endif - - foreach (var foo in inserted) - { - foo.Id = default; - foo.StringValue = default; - } - - var result = await collection.FindAsync(filter, projection: projection); - Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1))); - }); - } - - [Test] - public void MongoCollection_Find_ProjectionSort() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - -#if UNITY - var sort = new BsonDocument { { "LongValue", -1 } }; - var projection = new BsonDocument - { - { "_id", 1 }, - { "LongValue", 1 } - }; -#else - var sort = new { LongValue = -1 }; - var projection = new - { - _id = 1, - LongValue = 1 - }; -#endif - - foreach (var foo in inserted) - { - foo.StringValue = default; - } - - var result = await collection.FindAsync(sort: sort, projection: projection); - Assert.That(result, Is.EquivalentTo(inserted.Reverse())); - }); - } - - [Test] - public void MongoCollection_Find_FilterProjectionSort() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - -#if UNITY - var sort = new BsonDocument { { "LongValue", -1 } }; - var projection = new BsonDocument - { - { "_id", 0 }, - { "LongValue", 1 }, - { "StringValue", 1 }, - }; -#else - var sort = new { LongValue = -1 }; - var projection = new - { - _id = 0, - LongValue = 1, - StringValue = 1 - }; -#endif - - foreach (var foo in inserted) - { - foo.Id = default; - } - - var result = await collection.FindAsync(filter, sort, projection); - Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1).Reverse())); - }); - } - - [Test] - public void MongoCollection_Find_Limit() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var result = await collection.FindAsync(limit: 2); - Assert.That(result, Is.EquivalentTo(inserted.Take(2))); - }); - } - - [Test] - public void MongoCollection_Find_SortLimit() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var sort = new BsonDocument { { "LongValue", -1 } }; - var result = await collection.FindAsync(sort: sort, limit: 2); - Assert.That(result, Is.EquivalentTo(inserted.Reverse().Take(2))); - }); - } - - [Test] - public void MongoCollection_Find_Filter_Limit() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - - var result = await collection.FindAsync(filter, limit: 1); - Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1).Take(1))); - }); - } - - [Test] - public void MongoCollection_Find_FilterSortLimit() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - - var sort = new BsonDocument { { "StringValue", -1 } }; - - var result = await collection.FindAsync(filter, sort: sort, limit: 1); - Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1).Reverse().Take(1))); - }); - } - - [Test] - public void MongoCollection_Find_ProjectionLimit() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var projection = new BsonDocument - { - { "_id", 0 }, - { "StringValue", 1 } - }; - - foreach (var foo in inserted) - { - foo.Id = default; - foo.LongValue = default; - } - - var result = await collection.FindAsync(projection: projection, limit: 100); - Assert.That(result, Is.EquivalentTo(inserted.Reverse().Take(100))); - }); - } - - [Test] - public void MongoCollection_Find_FilterProjectionLimit() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - - var projection = new BsonDocument - { - { "_id", 0 }, - { "LongValue", 1 } - }; - - foreach (var foo in inserted) - { - foo.Id = default; - foo.StringValue = default; - } - - var result = await collection.FindAsync(filter, projection: projection, limit: 2); - Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1).Take(2))); - }); - } - - [Test] - public void MongoCollection_Find_ProjectionSortLimit() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var projection = new BsonDocument - { - { "_id", 1 }, - { "LongValue", 1 } - }; - - var sort = new BsonDocument { { "LongValue", -1 } }; - - foreach (var foo in inserted) - { - foo.StringValue = default; - } - - var result = await collection.FindAsync(sort: sort, projection: projection, limit: 2); - Assert.That(result, Is.EquivalentTo(inserted.Reverse().Take(2))); - }); - } - - [Test] - public void MongoCollection_Find_FilterProjectionSortLimit() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - - var inserted = await InsertSomeData(collection, 3); - - var projection = new BsonDocument - { - { "_id", 0 }, - { "LongValue", 1 }, - { "StringValue", 1 } - }; - - var filter = new BsonDocument - { - { - "LongValue", new BsonDocument - { - { "$gte", 1 } - } - } - }; - - var sort = new BsonDocument { { "LongValue", -1 } }; - - foreach (var foo in inserted) - { - foo.Id = default; - } - - var result = await collection.FindAsync(filter, sort, projection, limit: 1); - Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1).Reverse().Take(1))); - }); - } - - [Test] - public void MongoCollection_Aggregate() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetSalesCollection(); - - await InsertSalesData(collection); - - var aggregation = GetSalesAggregation(); - - var result = await collection.AggregateAsync(aggregation); - - // Expected results: - // { "_id" : { "day" : 46, "year" : 2014 }, "totalAmount" : 150, "count" : 2 } - // { "_id" : { "day" : 34, "year" : 2014 }, "totalAmount" : 45, "count" : 2 } - // { "_id" : { "day" : 1, "year" : 2014 }, "totalAmount" : 20, "count" : 1 } - Assert.That(result.Length, Is.EqualTo(3)); - - // Fix the ordering to make it easier to assert the expected values - result = result.OrderByDescending(r => r["_id"]["Day"].AsInt32).ToArray(); - - Assert.That(result[0]["_id"]["Day"].AsInt32, Is.EqualTo(46)); - Assert.That(result[0]["_id"]["Year"].AsInt32, Is.EqualTo(2014)); - Assert.That(result[0]["TotalAmount"].AsDecimal, Is.EqualTo(150)); - Assert.That(result[0]["Count"].AsInt32, Is.EqualTo(2)); - - Assert.That(result[1]["_id"]["Day"].AsInt32, Is.EqualTo(34)); - Assert.That(result[1]["_id"]["Year"].AsInt32, Is.EqualTo(2014)); - Assert.That(result[1]["TotalAmount"].AsDecimal, Is.EqualTo(45)); - Assert.That(result[1]["Count"].AsInt32, Is.EqualTo(2)); - - Assert.That(result[2]["_id"]["Day"].AsInt32, Is.EqualTo(1)); - Assert.That(result[2]["_id"]["Year"].AsInt32, Is.EqualTo(2014)); - Assert.That(result[2]["TotalAmount"].AsDecimal, Is.EqualTo(20)); - Assert.That(result[2]["Count"].AsInt32, Is.EqualTo(1)); - }); - } - - [Test] - public void MongoCollection_Aggregate_GenericResult() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetSalesCollection(); - - await InsertSalesData(collection); - - var aggregation = GetSalesAggregation(); - - var result = await collection.AggregateAsync(aggregation); - - // Expected results: - // { "_id" : { "day" : 46, "year" : 2014 }, "totalAmount" : 150, "count" : 2 } - // { "_id" : { "day" : 34, "year" : 2014 }, "totalAmount" : 45, "count" : 2 } - // { "_id" : { "day" : 1, "year" : 2014 }, "totalAmount" : 20, "count" : 1 } - Assert.That(result.Length, Is.EqualTo(3)); - - // Fix the ordering to make it easier to assert the expected values - result = result.OrderByDescending(r => r.Id.Day).ToArray(); - - Assert.That(result[0].Id.Day, Is.EqualTo(46)); - Assert.That(result[0].Id.Year, Is.EqualTo(2014)); - Assert.That(result[0].TotalAmount, Is.EqualTo(150)); - Assert.That(result[0].Count, Is.EqualTo(2)); - - Assert.That(result[1].Id.Day, Is.EqualTo(34)); - Assert.That(result[1].Id.Year, Is.EqualTo(2014)); - Assert.That(result[1].TotalAmount, Is.EqualTo(45)); - Assert.That(result[1].Count, Is.EqualTo(2)); - - Assert.That(result[2].Id.Day, Is.EqualTo(1)); - Assert.That(result[2].Id.Year, Is.EqualTo(2014)); - Assert.That(result[2].TotalAmount, Is.EqualTo(20)); - Assert.That(result[2].Count, Is.EqualTo(1)); - }); - } - - [Test] - public void MongoCollection_FindOneAndUpdate() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var update = BsonDocument.Parse(@"{ $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - } }"); - - var result = await collection.FindOneAndUpdateAsync(filter: null, update); - - Assert.That(result, Is.EqualTo(inserted[0])); - - // Update inserted with expected values after the update - inserted[0].StringValue = "this is update!"; - inserted[0].LongValue = 999; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndUpdate_ReturnNewDocument() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var update = BsonDocument.Parse(@"{ $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - } }"); - - var result = await collection.FindOneAndUpdateAsync(filter: null, update, returnNewDocument: true); - - Assert.That(result.StringValue, Is.EqualTo("this is update!")); - Assert.That(result.LongValue, Is.EqualTo(999)); - - // Update inserted with expected values after the update - inserted[0] = result; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndUpdate_Filter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var update = BsonDocument.Parse(@"{ $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - } }"); - - var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); - - var result = await collection.FindOneAndUpdateAsync(filter, update); - - Assert.That(result, Is.EqualTo(inserted[1])); - - // Update inserted with expected values after the update - inserted[1].StringValue = "this is update!"; - inserted[1].LongValue = 999; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndUpdate_Sort() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var update = BsonDocument.Parse(@"{ $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - } }"); - - var sort = BsonDocument.Parse("{ LongValue: -1 }"); - - var result = await collection.FindOneAndUpdateAsync(filter: null, update, sort: sort); - - Assert.That(result, Is.EqualTo(inserted[2])); - - // Update inserted with expected values after the update - inserted[2].StringValue = "this is update!"; - inserted[2].LongValue = 999; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndUpdate_Projection() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var update = BsonDocument.Parse(@"{ $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - } }"); - - var projection = BsonDocument.Parse("{ LongValue: 1, _id: 0 }"); - - var result = await collection.FindOneAndUpdateAsync(filter: null, update, projection: projection); - - Assert.That(result.StringValue, Is.Null); - Assert.That(result.LongValue, Is.EqualTo(inserted[0].LongValue)); - Assert.That(result.Id, Is.EqualTo(default(ObjectId))); - - // Update inserted with expected values after the update - inserted[0].StringValue = "this is update!"; - inserted[0].LongValue = 999; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndUpdate_FilterUpsert_Matches() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var update = BsonDocument.Parse(@"{ $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - } }"); - - var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); - - var result = await collection.FindOneAndUpdateAsync(filter, update, upsert: true); - - Assert.That(result, Is.EqualTo(inserted[1])); - - // Update inserted with expected values after the update - inserted[1].StringValue = "this is update!"; - inserted[1].LongValue = 999; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndUpdate_FilterUpsert_NoMatches() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var update = BsonDocument.Parse(@"{ $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - } }"); - - var filter = BsonDocument.Parse("{ LongValue: { $gte: 5} }"); - - var result = await collection.FindOneAndUpdateAsync(filter, update, upsert: true); - Assert.That(result, Is.Null); - - // Update inserted with expected values after the update - inserted = inserted.Concat(new[] { new Foo("this is update!", 999) }).ToArray(); - - var docs = await collection.FindAsync(); - inserted[3].Id = docs[3].Id; - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndUpdate_FilterUpsertReturnNewDocument_NoMatches() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var update = BsonDocument.Parse(@"{ $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - } }"); - - var filter = BsonDocument.Parse("{ LongValue: { $gte: 5} }"); - - var result = await collection.FindOneAndUpdateAsync(filter, update, upsert: true, returnNewDocument: true); - Assert.That(result.StringValue, Is.EqualTo("this is update!")); - Assert.That(result.LongValue, Is.EqualTo(999)); - - // Update inserted with expected values after the update - inserted = inserted.Concat(new[] { result }).ToArray(); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndUpdate_FilterSortProjectionUpsertReturnNewDocument() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var update = BsonDocument.Parse(@"{ $set: { - StringValue: ""this is update!"", - LongValue: { $numberLong: ""999"" } - } }"); - - var sort = BsonDocument.Parse("{ LongValue: -1 }"); - var projection = BsonDocument.Parse("{ StringValue: 1, _id: 0 }"); - var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); - - var result = await collection.FindOneAndUpdateAsync(filter, update, sort, projection, upsert: true, returnNewDocument: true); - Assert.That(result.StringValue, Is.EqualTo("this is update!")); - Assert.That(result.LongValue, Is.EqualTo(default(long))); - Assert.That(result.Id, Is.EqualTo(default(ObjectId))); - - inserted[2].StringValue = "this is update!"; - inserted[2].LongValue = 999; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndReplace() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var replacement = Foo.WithoutId("this is update!", 999); - - var result = await collection.FindOneAndReplaceAsync(filter: null, replacement); - - Assert.That(result, Is.EqualTo(inserted[0])); - - // Update inserted with expected values after the update - inserted[0].StringValue = replacement.StringValue; - inserted[0].LongValue = replacement.LongValue; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndReplace_ReturnNewDocument() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var replacement = Foo.WithoutId("this is update!", 999); - - var result = await collection.FindOneAndReplaceAsync(filter: null, replacement, returnNewDocument: true); - - replacement.Id = inserted[0].Id; - Assert.That(result, Is.EqualTo(replacement)); - - // Update inserted with expected values after the update - inserted[0].StringValue = replacement.StringValue; - inserted[0].LongValue = replacement.LongValue; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndReplace_UpsertNoMatches_GeneratesId() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var replacement = Foo.WithoutId("this is update!", 999); - var filter = BsonDocument.Parse("{ LongValue: { $gte: 5} }"); - - var result = await collection.FindOneAndReplaceAsync(filter, replacement, upsert: true, returnNewDocument: true); - - Assert.That(result.Id, Is.Not.EqualTo(default(ObjectId))); - replacement.Id = result.Id; - Assert.That(result, Is.EqualTo(replacement)); - - // Update inserted with expected values after the update - inserted = inserted.Concat(new[] { result }).ToArray(); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndReplace_Filter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var replacement = Foo.WithoutId("this is update!", 999); - - var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); - - var result = await collection.FindOneAndReplaceAsync(filter, replacement); - - Assert.That(result, Is.EqualTo(inserted[1])); - - // Update inserted with expected values after the update - inserted[1].StringValue = replacement.StringValue; - inserted[1].LongValue = replacement.LongValue; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndReplace_Sort() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var replacement = Foo.WithoutId("this is update!", 999); - var sort = BsonDocument.Parse("{ LongValue: -1 }"); - - var result = await collection.FindOneAndReplaceAsync(filter: null, replacement, sort: sort); - - Assert.That(result, Is.EqualTo(inserted[2])); - - // Update inserted with expected values after the update - inserted[2].StringValue = replacement.StringValue; - inserted[2].LongValue = replacement.LongValue; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndReplace_Projection() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var replacement = Foo.WithoutId("this is update!", 999); - var projection = BsonDocument.Parse("{ LongValue: 1, _id: 0 }"); - - var result = await collection.FindOneAndReplaceAsync(filter: null, replacement, projection: projection); - - Assert.That(result.StringValue, Is.Null); - Assert.That(result.LongValue, Is.EqualTo(inserted[0].LongValue)); - Assert.That(result.Id, Is.EqualTo(default(ObjectId))); - - // Update inserted with expected values after the update - inserted[0].StringValue = replacement.StringValue; - inserted[0].LongValue = replacement.LongValue; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndReplace_FilterUpsert_Matches() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var replacement = Foo.WithoutId("this is update!", 999); - var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); - - var result = await collection.FindOneAndReplaceAsync(filter, replacement, upsert: true); - - Assert.That(result, Is.EqualTo(inserted[1])); - - // Update inserted with expected values after the update - inserted[1].StringValue = replacement.StringValue; - inserted[1].LongValue = replacement.LongValue; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndReplace_FilterUpsert_NoMatches() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var replacement = new Foo("this is update!", 999); - var filter = BsonDocument.Parse("{ LongValue: { $gte: 5} }"); - - var result = await collection.FindOneAndReplaceAsync(filter, replacement, upsert: true); - Assert.That(result, Is.Null); - - // Update inserted with expected values after the update - inserted = inserted.Concat(new[] { replacement }).ToArray(); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndReplace_FilterUpsertReturnNewDocument_NoMatches() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var replacement = new Foo("this is update!", 999); - - var filter = BsonDocument.Parse("{ LongValue: { $gte: 5} }"); - - var result = await collection.FindOneAndReplaceAsync(filter, replacement, upsert: true, returnNewDocument: true); - Assert.That(result, Is.EqualTo(replacement)); - - // Update inserted with expected values after the update - inserted = inserted.Concat(new[] { replacement }).ToArray(); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndReplace_FilterSortProjectionUpsertReturnNewDocument() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var replacement = Foo.WithoutId("this is update!", 999); - var sort = BsonDocument.Parse("{ LongValue: -1 }"); - var projection = BsonDocument.Parse("{ StringValue: 1, _id: 0 }"); - var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); - - var result = await collection.FindOneAndReplaceAsync(filter, replacement, sort, projection, upsert: true, returnNewDocument: true); - Assert.That(result.StringValue, Is.EqualTo(replacement.StringValue)); - Assert.That(result.LongValue, Is.EqualTo(default(long))); - Assert.That(result.Id, Is.EqualTo(default(ObjectId))); - - inserted[2].StringValue = replacement.StringValue; - inserted[2].LongValue = replacement.LongValue; - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndDelete() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var result = await collection.FindOneAndDeleteAsync(); - Assert.That(result, Is.EqualTo(inserted[0])); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted.Where((_, i) => i != 0))); - }); - } - - [Test] - public void MongoCollection_FindOneAndDelete_Filter() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); - - var result = await collection.FindOneAndDeleteAsync(filter); - Assert.That(result, Is.EqualTo(inserted[1])); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted.Where((_, i) => i != 1))); - }); - } - - [Test] - public void MongoCollection_FindOneAndDelete_Filter_NoMatches() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var filter = BsonDocument.Parse("{ LongValue: { $gte: 5} }"); - - var result = await collection.FindOneAndDeleteAsync(filter); - Assert.That(result, Is.Null); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted)); - }); - } - - [Test] - public void MongoCollection_FindOneAndDelete_Sort() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var sort = BsonDocument.Parse("{ LongValue: -1 }"); - - var result = await collection.FindOneAndDeleteAsync(sort: sort); - Assert.That(result, Is.EqualTo(inserted[2])); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted.Where((_, i) => i != 2))); - }); - } - - [Test] - public void MongoCollection_FindOneAndDelete_FilterSort() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var sort = BsonDocument.Parse("{ LongValue: -1 }"); - var filter = BsonDocument.Parse("{ LongValue: { $lt: 2 } }"); - - var result = await collection.FindOneAndDeleteAsync(filter, sort: sort); - Assert.That(result, Is.EqualTo(inserted[1])); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted.Where((_, i) => i != 1))); - }); - } - - [Test] - public void MongoCollection_FindOneAndDelete_FilterSortProjection() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertSomeData(collection, 3); - - var sort = BsonDocument.Parse("{ LongValue: -1 }"); - var filter = BsonDocument.Parse("{ LongValue: { $lt: 2 } }"); - var projection = BsonDocument.Parse("{ LongValue: 1 }"); - - var result = await collection.FindOneAndDeleteAsync(filter, sort, projection); - Assert.That(result, Is.Not.Null); - Assert.That(result!.Id, Is.EqualTo(inserted[1].Id)); - Assert.That(result.StringValue, Is.Null); - Assert.That(result.LongValue, Is.EqualTo(inserted[1].LongValue)); - - var docs = await collection.FindAsync(); - Assert.That(docs, Is.EquivalentTo(inserted.Where((_, i) => i != 1))); - }); - } - - [Test] - public void MongoCollection_FindOne_Remapped() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(); - var inserted = await InsertRemappedData(collection); - - var result = await collection.FindOneAsync(); - Assert.That(result, Is.Not.Null); - Assert.That(result!.Id, Is.EqualTo(inserted[0].Id)); - Assert.That(result.StringValue, Is.EqualTo(inserted[0].StringValue)); - Assert.That(result.MappedLink!.Id, Is.EqualTo(inserted[1].Id)); - Assert.That(result.MappedList[0].Id, Is.EqualTo(inserted[2].Id)); - }); - } - - private static async Task InsertRemappedData(MongoClient.Collection collection) - { - const int documentCount = 3; - - var docs = Enumerable.Range(0, documentCount) - .Select(i => new RemappedTypeObject - { - Id = ObjectId.GenerateNewId().GetHashCode(), - StringValue = $"Doc #{i}", - }) - .ToArray(); - - docs[0].MappedLink = docs[1]; - docs[0].MappedList.Add(docs[2]); - - await collection.InsertManyAsync(docs); - - var remoteCount = await collection.CountAsync(); - Assert.That(remoteCount, Is.EqualTo(documentCount)); - - return docs; - } - - private static async Task InsertSomeData(MongoClient.Collection collection, int documentCount) - { - var docs = Enumerable.Range(0, documentCount) - .Select(i => new Foo("Document #" + i, i)) - .ToArray(); - - await collection.InsertManyAsync(docs); - - var remoteCount = await collection.CountAsync(); - Assert.That(remoteCount, Is.EqualTo(documentCount)); - - return docs; - } - - private static async Task InsertSalesData(MongoClient.Collection collection) - { - // Example from https://docs.mongodb.com/manual/reference/operator/aggregation/sum/ - var sales = new[] - { - new Sale(1, "abc", 10, 2, new DateTime(2014, 1, 1, 8, 0, 0, DateTimeKind.Utc)), - new Sale(2, "jkl", 20, 1, new DateTime(2014, 2, 3, 9, 0, 0, DateTimeKind.Utc)), - new Sale(3, "xyz", 5, 5, new DateTime(2014, 2, 3, 9, 5, 0, DateTimeKind.Utc)), - new Sale(4, "abc", 10, 10, new DateTime(2014, 2, 15, 8, 0, 0, DateTimeKind.Utc)), - new Sale(5, "xyz", 5, 10, new DateTime(2014, 2, 15, 9, 5, 0, DateTimeKind.Utc)), - }; - - await collection.InsertManyAsync(sales); - } - - private static BsonDocument GetSalesAggregation() - { - return BsonDocument.Parse(@"{ $group: { - _id: { Day: { $dayOfYear: ""$Date""}, Year: { $year: ""$Date"" } }, - TotalAmount: { $sum: { $multiply:[ ""$Price"", ""$Quantity"" ] } }, - Count: { $sum: 1 } - }}"); - } - - private async Task> GetCollection() - { - var user = await GetUserAsync(); - var client = user.GetMongoClient(ServiceName); - var db = client.GetDatabase(SyncTestHelpers.RemoteMongoDBName()); - var collection = db.GetCollection(FoosCollectionName); - - await collection.DeleteManyAsync(); - - return collection; - } - - private async Task> GetBsonCollection() - { - var user = await GetUserAsync(); - var client = user.GetMongoClient(ServiceName); - var db = client.GetDatabase(SyncTestHelpers.RemoteMongoDBName()); - var collection = db.GetCollection(FoosCollectionName); - - await collection.DeleteManyAsync(); - - return collection; - } - - private async Task> GetSalesCollection() - { - var user = await GetUserAsync(); - var client = user.GetMongoClient(ServiceName); - var db = client.GetDatabase(SyncTestHelpers.RemoteMongoDBName()); - var collection = db.GetCollection(SalesCollectionName); - - await collection.DeleteManyAsync(); - - return collection; - } - - private async Task> GetCollection() - where T : class, IRealmObjectBase - { - var appConfigType = AppConfigType.Default; - var app = App.Create(SyncTestHelpers.GetAppConfig(appConfigType)); - var user = await GetUserAsync(app); - - SyncConfigurationBase config = GetIntegrationConfig(user); - - using var realm = await GetRealmAsync(config); - var client = user.GetMongoClient(ServiceName); - var collection = client.GetCollection(); - await collection.DeleteManyAsync(new object()); - - return collection; - } - - private class Foo - { - [BsonElement("_id")] - [BsonIgnoreIfDefault] - [Preserve] - public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); - - public string? StringValue { get; set; } - - public long LongValue { get; set; } - - public Foo(string? stringValue, long longValue) - { - StringValue = stringValue; - LongValue = longValue; - } - - public static Foo WithoutId(string? stringValue, long longValue) => new(stringValue, longValue) - { - Id = default - }; - - public override bool Equals(object? obj) => - (obj is Foo foo) && - foo.Id == Id && - foo.StringValue == StringValue && - foo.LongValue == LongValue; - - public override string ToString() - { - return $"Id: {Id}, StringValue: {StringValue}, LongValue: {LongValue}"; - } - } - - private class Sale - { - [Preserve] - [BsonElement("_id")] - public int Id { get; set; } - - [Preserve] - public string Item { get; set; } - - [Preserve] - public decimal Price { get; set; } - - [Preserve] - public decimal Quantity { get; set; } - - [Preserve] - public DateTime Date { get; set; } - - public Sale(int id, string item, decimal price, decimal quantity, DateTime date) - { - Id = id; - Item = item; - Price = price; - Quantity = quantity; - Date = date; - } - } - - private class AggregationResult - { - [Preserve] - [BsonElement("_id")] - public IdResult Id { get; set; } = null!; - - [Preserve] - public decimal TotalAmount { get; set; } - - [Preserve] - public int Count { get; set; } - - public class IdResult - { - [Preserve] - public int Day { get; set; } - - [Preserve] - public int Year { get; set; } - } - } - } -} diff --git a/Tests/Realm.Tests/Sync/PartitionKeyTests.cs b/Tests/Realm.Tests/Sync/PartitionKeyTests.cs deleted file mode 100644 index 115efeabc8..0000000000 --- a/Tests/Realm.Tests/Sync/PartitionKeyTests.cs +++ /dev/null @@ -1,148 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Linq; -using System.Threading.Tasks; -using MongoDB.Bson; -using NUnit.Framework; -using Realms.Sync; - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class PartitionKeyTests : SyncTestBase - { - [Test] - public void OpenRealm_StringPK_Works() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partitionValue = Guid.NewGuid().ToString(); - var config1 = await GetIntegrationConfigAsync(partitionValue); - var config2 = await GetIntegrationConfigAsync(partitionValue); - - await RunPartitionKeyTestsCore(config1, config2); - }, timeout: 120_000); - } - - [Test] - public void OpenRealm_Int64PK_Works() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partitionValue = TestHelpers.Random.Next(int.MinValue, int.MaxValue); - var config1 = await GetIntegrationConfigAsync(partitionValue); - var config2 = await GetIntegrationConfigAsync(partitionValue); - - await RunPartitionKeyTestsCore(config1, config2); - }, timeout: 120_000); - } - - [Test] - public void OpenRealm_ObjectIdPK_Works() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partitionValue = ObjectId.GenerateNewId(); - var config1 = await GetIntegrationConfigAsync(partitionValue); - var config2 = await GetIntegrationConfigAsync(partitionValue); - - await RunPartitionKeyTestsCore(config1, config2); - }, timeout: 120_000); - } - - [Test] - public void OpenRealm_GuidPK_Works() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partitionValue = Guid.NewGuid(); - var config1 = await GetIntegrationConfigAsync(partitionValue); - var config2 = await GetIntegrationConfigAsync(partitionValue); - - await RunPartitionKeyTestsCore(config1, config2); - }, timeout: 120_000); - } - - private async Task RunPartitionKeyTestsCore(PartitionSyncConfiguration config1, PartitionSyncConfiguration config2) - { - var schema = new Type[] - { - typeof(PrimaryKeyInt64Object), - typeof(PrimaryKeyObjectIdObject), - typeof(PrimaryKeyGuidObject), - typeof(RequiredPrimaryKeyStringObject), - typeof(PrimaryKeyNullableInt64Object), - typeof(PrimaryKeyNullableObjectIdObject), - typeof(PrimaryKeyNullableGuidObject), - typeof(PrimaryKeyStringObject), - }; - - config1.Schema = schema; - config2.Schema = schema; - - using var realm1 = await GetRealmAsync(config1, 30_000); - using var realm2 = await GetRealmAsync(config2, 30_000); - - await AssertChangePropagation(realm1, realm2).Timeout(30000, detail: "Assert changes 1"); - await AssertChangePropagation(realm2, realm1).Timeout(30000, detail: "Assert changes 2"); - - async Task AssertChangePropagation(Realm first, Realm second) - { - var ids = new - { - Long = TestHelpers.Random.Next(), - ObjectId = ObjectId.GenerateNewId(), - Guid = Guid.NewGuid(), - String = Guid.NewGuid().ToString(), - }; - - var intObjectToAdd = new PrimaryKeyInt64Object { Id = ids.Long }; - first.Write(() => - { - first.Add(intObjectToAdd); - first.Add(new PrimaryKeyObjectIdObject { Id = ids.ObjectId }); - first.Add(new PrimaryKeyGuidObject { Id = ids.Guid }); - first.Add(new RequiredPrimaryKeyStringObject { Id = ids.String }); - first.Add(new PrimaryKeyNullableInt64Object { Id = ids.Long }); - first.Add(new PrimaryKeyNullableObjectIdObject { Id = ids.ObjectId }); - first.Add(new PrimaryKeyNullableGuidObject { Id = ids.Guid }); - first.Add(new PrimaryKeyStringObject { Id = ids.String }); - }); - - await WaitForObjectAsync(intObjectToAdd, second); - - AssertFind(second, ids.Long); - AssertFind(second, ids.ObjectId); - AssertFind(second, ids.Guid); - AssertFind(second, ids.String); - AssertFind(second, ids.Long); - AssertFind(second, ids.ObjectId); - AssertFind(second, ids.Guid); - AssertFind(second, ids.String); - } - - void AssertFind(Realm realm, RealmValue id) - where T : IRealmObject - { - Assert.That(realm.FindCore(id)?.IsValid, Is.True, $"Failed to find {typeof(T).Name} with id {id}. Objects in Realm: {realm.All().ToArray().Select(o => o.DynamicApi.Get("_id").ToString()).Join()}"); - } - } - } -} diff --git a/Tests/Realm.Tests/Sync/SessionTests.cs b/Tests/Realm.Tests/Sync/SessionTests.cs deleted file mode 100644 index d97bcc93d0..0000000000 --- a/Tests/Realm.Tests/Sync/SessionTests.cs +++ /dev/null @@ -1,1603 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Baas; -using NUnit.Framework; -using Realms.Exceptions.Sync; -using Realms.Sync; -using Realms.Sync.ErrorHandling; -using Realms.Sync.Exceptions; -using Realms.Sync.Native; -using Realms.Sync.Testing; -using static Realms.Sync.ErrorHandling.ClientResetHandlerBase; -using static Realms.Tests.TestHelpers; -#if TEST_WEAVER -using TestRealmObject = Realms.RealmObject; -#else -using TestRealmObject = Realms.IRealmObject; -#endif - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class SessionTests : SyncTestBase - { - public static readonly string[] AppTypes = new[] - { - AppConfigType.Default, - AppConfigType.FlexibleSync - }; - - public static readonly object[] AllClientResetHandlers = new object[] - { - typeof(DiscardUnsyncedChangesHandler), - typeof(RecoverUnsyncedChangesHandler), - typeof(RecoverOrDiscardUnsyncedChangesHandler), - }; - - public static readonly ProgressMode[] ProgressModeTypes = new ProgressMode[] - { - ProgressMode.ForCurrentlyOutstandingWork, - ProgressMode.ReportIndefinitely, - }; - - [Preserve] - static SessionTests() - { - _ = new RecoverUnsyncedChangesHandler - { - OnBeforeReset = (beforeFrozen) => { }, - OnAfterReset = (beforeFrozen, after) => { }, - ManualResetFallback = (clientResetException) => { }, - }; - - _ = new RecoverOrDiscardUnsyncedChangesHandler - { - OnBeforeReset = (beforeFrozen) => { }, - OnAfterRecovery = (beforeFrozen, after) => { }, - OnAfterDiscard = (beforeFrozen, after) => { }, - ManualResetFallback = (clientResetException) => { }, - }; - - _ = new DiscardUnsyncedChangesHandler - { - OnBeforeReset = (beforeFrozen) => { }, - OnAfterReset = (beforeFrozen, after) => { }, - ManualResetFallback = (clientResetException) => { }, - }; - } - - [Test] - public void Realm_SyncSession_WhenSyncedRealm() - { - var config = GetFakeConfig(); - - using var realm = GetRealm(config); - var session = GetSession(realm); - - Assert.That(session.User, Is.EqualTo(config.User)); - } - - [Test] - public void Realm_SyncSession_WhenLocalRealm_ShouldThrow() - { - using var realm = GetRealm(); - Assert.That(() => realm.SyncSession, Throws.TypeOf()); - } - - [Test] - public void Realm_GetSession_ShouldReturnSameObject() - { - var config = GetFakeConfig(); - using var realm = GetRealm(config); - var session1 = GetSession(realm); - var session2 = GetSession(realm); - - Assert.That(session1, Is.EqualTo(session2)); - Assert.That(session1.GetHashCode(), Is.EqualTo(session2.GetHashCode())); - } - - [TestCaseSource(nameof(AppTypes))] - public void Session_ClientReset_ManualRecovery_InitiateClientReset(string appType) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var errorTcs = new TaskCompletionSource(); - var (config, _) = await GetConfigForApp(appType); - config.ClientResetHandler = new ManualRecoveryHandler(e => - { - errorTcs.TrySetResult(e); - }); - - using var realm = await GetRealmAsync(config, waitForSync: true); - - await TriggerClientReset(realm); - - var clientEx = await errorTcs.Task; - - Assert.That(clientEx.Message, Does.Contain("Bad client file identifier")); - Assert.That(clientEx.InnerException, Is.Null); - - await TryInitiateClientReset(realm, clientEx, ErrorCode.ClientReset); - }); - } - - [Test] - public void Session_ClientResetHandlers_ManualResetFallback_InitiateClientReset( - [ValueSource(nameof(AppTypes))] string appType, - [ValueSource(nameof(AllClientResetHandlers))] Type resetHandlerType) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var errorTcs = new TaskCompletionSource(); - - var (config, _) = await GetConfigForApp(appType); - - config.ClientResetHandler = GetClientResetHandler(resetHandlerType, beforeCb: BeforeCb, manualCb: ManualCb); - - using var realm = await GetRealmAsync(config, waitForSync: true, timeout: 20_000); - - await TriggerClientReset(realm); - - var clientEx = await errorTcs.Task.Timeout(20_000, "Expected client reset"); - - await TryInitiateClientReset(realm, clientEx, ErrorCode.AutoClientResetFailed); - - void ManualCb(ClientResetException err) - { - errorTcs.TrySetResult(err); - } - - void BeforeCb(Realm _) - { - throw new Exception("This fails!"); - } - }); - } - - [Test] - public void Session_ClientResetHandlers_OnBefore_And_OnAfter( - [ValueSource(nameof(AppTypes))] string appType, - [ValueSource(nameof(AllClientResetHandlers))] Type resetHandlerType) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var onBeforeTriggered = false; - var onAfterTriggered = false; - var tcs = new TaskCompletionSource(); - - var (config, _) = await GetConfigForApp(appType); - - var beforeCb = GetOnBeforeHandler(tcs, beforeFrozen => - { - Assert.That(onBeforeTriggered, Is.False); - Assert.That(onAfterTriggered, Is.False); - onBeforeTriggered = true; - }); - - var afterCb = GetOnAfterHandler(tcs, (beforeFrozen, after) => - { - Assert.That(onBeforeTriggered, Is.True); - Assert.That(onAfterTriggered, Is.False); - onAfterTriggered = true; - }); - config.ClientResetHandler = GetClientResetHandler(resetHandlerType, beforeCb, afterCb); - using var realm = await GetRealmAsync(config, waitForSync: true, timeout: 20000); - - await TriggerClientReset(realm); - - await tcs.Task.Timeout(30_000, detail: "Wait for client reset"); - - Assert.That(onBeforeTriggered, Is.True); - Assert.That(onAfterTriggered, Is.True); - }, timeout: 120_000); - } - - [TestCaseSource(nameof(AppTypes))] - public void Session_AutomaticRecoveryFallsbackToDiscardLocal(string appType) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - await DisableClientResetRecoveryOnServer(appType); - - var automaticResetCalled = false; - var discardLocalResetCalled = false; - - var (config, guid) = await GetConfigForApp(appType); - - var tcsAfterClientReset = new TaskCompletionSource(); - - var afterAutomaticResetCb = GetOnAfterHandler(tcsAfterClientReset, (before, after) => - { - automaticResetCalled = true; - }); - - var afterDiscardLocalResetCb = GetOnAfterHandler(tcsAfterClientReset, (before, after) => - { - discardLocalResetCalled = true; - Assert.That(after.All().Count, Is.EqualTo(0)); - }); - - config.ClientResetHandler = new RecoverOrDiscardUnsyncedChangesHandler - { - OnAfterRecovery = afterAutomaticResetCb, - OnAfterDiscard = afterDiscardLocalResetCb, - ManualResetFallback = ex => - { - tcsAfterClientReset.TrySetException(ex); - } - }; - - var realm = await GetRealmAsync(config, waitForSync: true); - - var session = GetSession(realm); - session.Stop(); - - realm.Write(() => - { - realm.Add(new ObjectWithPartitionValue(guid)); - }); - - await TriggerClientReset(realm); - - await tcsAfterClientReset.Task.Timeout(20_000, detail: "Expected client reset"); - Assert.That(automaticResetCalled, Is.False); - Assert.That(discardLocalResetCalled, Is.True); - }); - } - - /* Any ArrayInsert to an index beyond the fresh list size is changed to insert to the end of the list. - * - * 1. clientA adds objectA with array innerObj[0,1,2] and syncs it, then disconnects - * 2. clientB starts and syncs the same objectA, then disconnects - * 3. While offline, clientA deletes innerObj[2] while clientB inserts innerObj[3] - * 4. A client reset is triggered on the server - * 5. clientA goes online and uploads the changes - * 6. only now clientB goes online, downloads and merges the changes. clientB will have innerObj[0,1,3] - * 7. clientA will also have innerObj[0,1,3] - */ - [Test, NUnit.Framework.Explicit("This is an integration test testing the client reset behavior and should probably be in Core")] - public void SessionIntegrationTest_ClientResetHandlers_OutOfBoundArrayInsert_AddedToTail() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partition = Guid.NewGuid().ToString(); - - // ===== clientA ===== - var tcsAfterClientResetA = new TaskCompletionSource(); - var configA = await GetIntegrationConfigAsync(partition); - configA.Schema = new[] { typeof(SyncObjectWithRequiredStringList) }; - var afterCbA = GetOnAfterHandler(tcsAfterClientResetA, (before, after) => - { - var list = after.All().First().Strings; - - // We deleted an object, so that should have been merged - Assert.That(list, Is.EqualTo(new[] { "0", "1" })); - }); - - configA.ClientResetHandler = new RecoverUnsyncedChangesHandler() - { - OnAfterReset = afterCbA - }; - using var realmA = await GetRealmAsync(configA, waitForSync: true); - - var originalObj = realmA.Write(() => - { - var toAdd = new SyncObjectWithRequiredStringList - { - Id = Guid.NewGuid().ToString() - }; - toAdd.Strings.Add("0"); - toAdd.Strings.Add("1"); - toAdd.Strings.Add("2"); - return realmA.Add(toAdd); - }); - await WaitForUploadAsync(realmA).Timeout(10_000, detail: "Wait for upload realm A"); - - var sessionA = GetSession(realmA); - sessionA.Stop(); - - realmA.Write(() => - { - originalObj.Strings.RemoveAt(2); - }); - - // ===== clientB ===== - var configB = await GetIntegrationConfigAsync(partition); - configB.Schema = new[] { typeof(SyncObjectWithRequiredStringList) }; - var tcsAfterClientResetB = new TaskCompletionSource(); - var afterCbB = GetOnAfterHandler(tcsAfterClientResetB, (before, after) => - { - var list = after.All().Single().Strings.ToArray(); - - // We added an object in the tail, that should be merged - Assert.That(list, Is.EqualTo(new[] { "0", "1", "3" })); - }); - configB.ClientResetHandler = new RecoverUnsyncedChangesHandler() - { - OnAfterReset = afterCbB - }; - - using var realmB = await GetRealmAsync(configB, waitForSync: true); - await WaitForDownloadAsync(realmB).Timeout(10_000, detail: "Wait for download realm B"); - - var originalObjStr = realmB.All().Single().Strings; - Assert.That(originalObjStr.ToArray(), Is.EqualTo(new[] { "0", "1", "2" })); - - var sessionB = GetSession(realmB); - sessionB.Stop(); - - realmB.Write(() => - { - originalObjStr.Add("3"); - }); - - await TriggerClientReset(realmA); - - // We want the client reset for A to go through first. - await TriggerClientReset(realmB, restartSession: false); - - // ===== clientA ===== - await tcsAfterClientResetA.Task.Timeout(10_000, detail: "Client Reset A"); - - var tcsAfterRemoteUpdateA = new TaskCompletionSource(); - - var stringsA = realmA.All().First().Strings; - - Assert.That(stringsA.ToArray(), Is.EquivalentTo(new[] { "0", "1" })); - - using var token = stringsA.SubscribeForNotifications((sender, changes) => - { - if (sender.Count != 3) - { - return; - } - - // After clientB merges and uploads the changes, - // clientA should receive the updated status - Assert.That(sender.ToArray(), Is.EqualTo(new[] { "0", "1", "3" })); - - tcsAfterRemoteUpdateA.TrySetResult(); - }); - - // ===== clientB ===== - sessionB.Start(); - - await tcsAfterClientResetB.Task.Timeout(10_000, detail: "Client Reset B"); - await tcsAfterRemoteUpdateA.Task.Timeout(10_000, detail: "After remote update A"); - Assert.That(stringsA.ToArray(), Is.EquivalentTo(new[] { "0", "1", "3" })); - }, timeout: 120_000); - } - - private async Task<(SyncConfigurationBase Config, Guid Guid)> GetConfigForApp(string appType) - { - var appConfig = SyncTestHelpers.GetAppConfig(appType); - var app = App.Create(appConfig); - var user = await GetUserAsync(app); - - var guid = Guid.NewGuid(); - SyncConfigurationBase config; - if (appType == AppConfigType.FlexibleSync) - { - var flxConfig = GetFLXIntegrationConfig(user); - flxConfig.PopulateInitialSubscriptions = (realm) => - { - var query = realm.All().Where(o => o.Guid == guid); - realm.Subscriptions.Add(query); - }; - - config = flxConfig; - } - else - { - config = GetIntegrationConfig(user); - } - - return (config, guid); - } - - [Test] - public void Session_ClientResetHandlers_AccessRealm_OnBeforeReset( - [ValueSource(nameof(AppTypes))] string appType, - [ValueSource(nameof(AllClientResetHandlers))] Type resetHandlerType) - { - const string alwaysSynced = "always synced"; - const string maybeSynced = "deleted only on discardLocal"; - - SyncTestHelpers.RunBaasTestAsync(async () => - { - var tcs = new TaskCompletionSource(); - var onBeforeTriggered = false; - - var (config, guid) = await GetConfigForApp(appType); - - var beforeCb = GetOnBeforeHandler(tcs, beforeFrozen => - { - Assert.That(onBeforeTriggered, Is.False); - - AssertOnObjectPair(beforeFrozen); - onBeforeTriggered = true; - tcs.TrySetResult(); - }); - config.ClientResetHandler = GetClientResetHandler(resetHandlerType, beforeCb); - - using var realm = await GetRealmAsync(config, waitForSync: true); - - realm.Write(() => - { - realm.Add(new ObjectWithPartitionValue(guid) - { - Value = alwaysSynced, - }); - }); - - await WaitForUploadAsync(realm).Timeout(15_000, detail: "Wait for upload"); - var session = GetSession(realm); - session.Stop(); - - realm.Write(() => - { - realm.Add(new ObjectWithPartitionValue(guid) - { - Value = maybeSynced, - }); - }); - - AssertOnObjectPair(realm); - - await TriggerClientReset(realm); - - await tcs.Task.Timeout(20_000, detail: "Expected client reset"); - Assert.That(onBeforeTriggered, Is.True); - - var objs = realm.All(); - var isDiscardLocal = config.ClientResetHandler.ClientResetMode == ClientResyncMode.Discard; - var objectsCount = isDiscardLocal ? 1 : 2; - - await WaitForConditionAsync(() => objs.Count() == objectsCount); - - if (isDiscardLocal) - { - Assert.That(objs.Single().Value, Is.EqualTo(alwaysSynced)); - } - else - { - AssertOnObjectPair(realm); - } - - static void AssertOnObjectPair(Realm realm) - { - Assert.That(realm.All().ToArray().Select(o => o.Value), - Is.EquivalentTo(new[] { alwaysSynced, maybeSynced })); - } - }, timeout: 120_000); - } - - [Test] - public void Session_ClientResetHandlers_AccessRealms_OnAfterReset( - [ValueSource(nameof(AppTypes))] string appType, - [ValueSource(nameof(AllClientResetHandlers))] Type resetHandlerType) - { - const string alwaysSynced = "always synced"; - const string maybeSynced = "deleted only on discardLocal"; - - SyncTestHelpers.RunBaasTestAsync(async () => - { - var tcs = new TaskCompletionSource(); - var onAfterTriggered = false; - - var (config, guid) = await GetConfigForApp(appType); - - var afterCb = GetOnAfterHandler(tcs, (beforeFrozen, after) => - { - Assert.That(onAfterTriggered, Is.False); - Assert.That(beforeFrozen.All().ToArray().Select(o => o.Value), - Is.EquivalentTo(new[] { alwaysSynced, maybeSynced })); - onAfterTriggered = true; - }); - config.ClientResetHandler = GetClientResetHandler(resetHandlerType, afterCb: afterCb); - - using var realm = await GetRealmAsync(config, waitForSync: true); - - realm.Write(() => - { - realm.Add(new ObjectWithPartitionValue(guid) - { - Value = alwaysSynced, - }); - }); - - await WaitForUploadAsync(realm).Timeout(20_000, detail: "Wait for upload"); - - var session = GetSession(realm); - session.Stop(); - - realm.Write(() => - { - realm.Add(new ObjectWithPartitionValue(guid) - { - Value = maybeSynced, - }); - }); - - await TriggerClientReset(realm); - - await tcs.Task.Timeout(30_000, detail: "Expected client reset"); - Assert.That(onAfterTriggered, Is.True); - - var expected = config.ClientResetHandler.ClientResetMode == ClientResyncMode.Discard ? - new[] { alwaysSynced } : new[] { alwaysSynced, maybeSynced }; - - await WaitForConditionAsync(() => realm.All().Count() == expected.Length, attempts: 300); - - Assert.That(realm.All().ToArray().Select(o => o.Value), Is.EquivalentTo(expected)); - }, timeout: 120_000); - } - - [Test] - public void Session_ClientResetDiscard_TriggersNotifications() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - // We'll add an object with the wrong partition - var config = await GetIntegrationConfigAsync(); - config.Schema = new[] { typeof(ObjectWithPartitionValue) }; - config.ClientResetHandler = new DiscardUnsyncedChangesHandler(); - - using var realm = await GetRealmAsync(config, waitForSync: true); - - realm.Write(() => - { - realm.Add(new ObjectWithPartitionValue(Guid.NewGuid()) - { - Value = "this will sync" - }); - }); - - await WaitForUploadAsync(realm).Timeout(10_000, detail: "Wait for upload"); - var session = GetSession(realm); - session.Stop(); - - realm.Write(() => - { - realm.Add(new ObjectWithPartitionValue(Guid.NewGuid()) - { - Value = "this will be merged at client reset" - }); - }); - - var objects = realm.All().AsRealmCollection(); - Assert.That(objects.Count, Is.EqualTo(2)); - var tcs = new TaskCompletionSource(); - using var token = objects.SubscribeForNotifications((sender, changes) => - { - if (changes != null) - { - tcs.TrySetResult(changes); - } - }); - - await TriggerClientReset(realm); - - var args = await tcs.Task.Timeout(15_000, "Wait for notifications"); - - Assert.That(args.DeletedIndices.Length, Is.EqualTo(1)); - Assert.That(objects.Count, Is.EqualTo(1)); - }, timeout: 120_000); - } - - [Test] - public void Session_ClientResetHandlers_ManualResetFallback_Exception_OnBefore( - [ValueSource(nameof(AppTypes))] string appType, - [ValueSource(nameof(AllClientResetHandlers))] Type resetHandlerType) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var tcs = new TaskCompletionSource(); - var onBeforeTriggered = false; - var manualFallbackTriggered = false; - var onAfterResetTriggered = false; - - var (config, _) = await GetConfigForApp(appType); - - void beforeCb(Realm beforeFrozen) - { - try - { - Assert.That(onBeforeTriggered, Is.False); - Assert.That(onAfterResetTriggered, Is.False); - Assert.That(manualFallbackTriggered, Is.False); - onBeforeTriggered = true; - } - catch (Exception ex) - { - tcs.TrySetException(ex); - } - - throw new Exception("Exception thrown in OnBeforeReset"); - } - - var afterCb = GetOnAfterHandler(tcs, (beforeFrozen, after) => - { - onAfterResetTriggered = true; - }); - - var manualCb = GetManualResetHandler(tcs, (ex) => - { - Assert.That(ex, Is.InstanceOf()); - Assert.That(onBeforeTriggered, Is.True); - Assert.That(onAfterResetTriggered, Is.False); - Assert.That(manualFallbackTriggered, Is.False); - manualFallbackTriggered = true; - }); - - config.ClientResetHandler = GetClientResetHandler(resetHandlerType, beforeCb, afterCb, manualCb); - - using var realm = await GetRealmAsync(config, waitForSync: true); - - await TriggerClientReset(realm); - - await tcs.Task; - - Assert.That(manualFallbackTriggered, Is.True); - Assert.That(onBeforeTriggered, Is.True); - Assert.That(onAfterResetTriggered, Is.False); - }); - } - - [Test] - public void Session_ClientResetHandlers_ManualResetFallback_Exception_OnAfter( - [ValueSource(nameof(AppTypes))] string appType, - [ValueSource(nameof(AllClientResetHandlers))] Type resetHandlerType) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var tcs = new TaskCompletionSource(); - var onBeforeTriggered = false; - var manualFallbackTriggered = false; - var onAfterResetTriggered = false; - - var (config, _) = await GetConfigForApp(appType); - - var beforeCb = GetOnBeforeHandler(tcs, beforeFrozen => - { - Assert.That(onBeforeTriggered, Is.False); - Assert.That(onAfterResetTriggered, Is.False); - Assert.That(manualFallbackTriggered, Is.False); - onBeforeTriggered = true; - }); - - void afterCb(Realm beforeFrozen, Realm after) - { - Assert.That(onBeforeTriggered, Is.True); - Assert.That(onAfterResetTriggered, Is.False); - Assert.That(manualFallbackTriggered, Is.False); - onAfterResetTriggered = true; - throw new Exception("Exception thrown in OnAfterReset"); - } - - var manualCb = GetManualResetHandler(tcs, (ex) => - { - Assert.That(ex, Is.InstanceOf()); - Assert.That(onBeforeTriggered, Is.True); - Assert.That(onAfterResetTriggered, Is.True); - Assert.That(manualFallbackTriggered, Is.False); - manualFallbackTriggered = true; - }); - - config.ClientResetHandler = GetClientResetHandler(resetHandlerType, beforeCb, afterCb, manualCb); - - using var realm = await GetRealmAsync(config, waitForSync: true); - - await TriggerClientReset(realm); - - await tcs.Task.Timeout(20_000, detail: "Expect client reset"); - - Assert.That(manualFallbackTriggered, Is.True); - Assert.That(onBeforeTriggered, Is.True); - Assert.That(onAfterResetTriggered, Is.True); - }, timeout: 120_000); - } - - [Test] - public void Session_OnSessionError() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var sessionErrorTriggered = false; - var tcs = new TaskCompletionSource(); - var config = await GetIntegrationConfigAsync(); - var errorMsg = "simulated sync issue"; - config.OnSessionError = (sender, e) => - { - Assert.That(sender, Is.InstanceOf()); - Assert.That(e, Is.InstanceOf()); - Assert.That(e.ErrorCode, Is.EqualTo(ErrorCode.WriteNotAllowed)); - Assert.That(e.Message, Is.EqualTo(errorMsg)); - Assert.That(e.InnerException, Is.Null); - Assert.That(sessionErrorTriggered, Is.False); - sessionErrorTriggered = true; - tcs.TrySetResult(true); - }; - - using var realm = await GetRealmAsync(config, waitForSync: true); - var session = GetSession(realm); - - var protocolError = 230; // ProtocolError::write_not_allowed - session.SimulateError((ErrorCode)protocolError, errorMsg); - - await tcs.Task; - - Assert.That(sessionErrorTriggered, Is.True); - }); - } - - [Test] - public void SessionIntegrationTest_ProgressObservable( - [ValueSource(nameof(AppTypes))] string appType, - [ValueSource(nameof(ProgressModeTypes))] ProgressMode mode) - { - const int objectSize = 1_000_000; - const int objectsToRecord = 2; - var partitionString = Guid.NewGuid().ToString(); - - SyncTestHelpers.RunBaasTestAsync(async () => - { - var uploadRealm = await GetTestRealm(appType, partitionString); - var uploadSession = GetSession(uploadRealm); - var uploadObservable = uploadSession.GetProgressObservable(ProgressDirection.Upload, mode); - - for (var i = 0; i < objectsToRecord; i++) - { - uploadRealm.Write(() => - { - uploadRealm.Add(new HugeSyncObject(objectSize)); - }); - } - - await TestObservable(uploadObservable); - - var downloadRealm = await GetTestRealm(appType, partitionString); - var downloadSession = GetSession(downloadRealm); - var downloadObservable = downloadSession.GetProgressObservable(ProgressDirection.Download, mode); - - await TestObservable(downloadObservable); - }, timeout: 120_000); - return; - - async Task GetTestRealm(string type, string partition) - { - if (appType == AppConfigType.Default) - { - var config = await GetIntegrationConfigAsync(partition); - return GetRealm(config); - } - else - { - var config = await GetFLXIntegrationConfigAsync(); - config.PopulateInitialSubscriptions = (r) => - { - r.Subscriptions.Add(r.All()); - }; - return GetRealm(config); - } - } - - async Task TestObservable(IObservable observable) - { - var completionTcs = new TaskCompletionSource(); - var lastReportedProgress = 0.0d; - var callbacksInvoked = 0; - - using var token = observable.Subscribe(p => - { - try - { - callbacksInvoked++; - - if (p.ProgressEstimate is < 0.0 or > 1.0) - { - throw new Exception($"Expected progress estimate to be between 0.0 and 1.0, but was {p.ProgressEstimate}"); - } - - if (p.ProgressEstimate < lastReportedProgress) - { - throw new Exception($"Expected progress estimate is expected to be monotonically increasing, but it wasn't."); - } - - if (p.IsComplete) - { - if (p.ProgressEstimate != 1.0) - { - throw new Exception($"Expected progress estimate to be complete if and only if ProgressEstimate == 1.0"); - } - - completionTcs.TrySetResult(); - } - - lastReportedProgress = p.ProgressEstimate; - } - catch (Exception e) - { - completionTcs.TrySetException(e); - } - }); - - await completionTcs.Task; - Assert.That(callbacksInvoked, Is.GreaterThanOrEqualTo(1)); - } - } - - [Test] - public void Session_Stop_StopsSession() - { - // OpenRealmAndStopSession will call Stop and assert the state changed - OpenRealmAndStopSession(); - } - - [Test] - public void Session_Start_ResumesSession() - { - var session = OpenRealmAndStopSession(); - - session.Start(); - Assert.That(session.State, Is.EqualTo(SessionState.Active)); - } - - [Test] - public void Session_Stop_IsIdempotent() - { - var session = OpenRealmAndStopSession(); - - // Stop it again - session.Stop(); - Assert.That(session.State, Is.EqualTo(SessionState.Inactive)); - } - - [Test] - public void Session_Start_IsIdempotent() - { - var session = OpenRealmAndStopSession(); - - session.Start(); - Assert.That(session.State, Is.EqualTo(SessionState.Active)); - - // Start it again - session.Start(); - Assert.That(session.State, Is.EqualTo(SessionState.Active)); - } - - [Test] - public void Session_ConnectionState_FullFlow() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - using var realm = GetRealm(config); - - var session = realm.SyncSession; - session.Stop(); - - var expectedState = ConnectionState.Connected; - var notificationTcs = new TaskCompletionSource(); - - session.PropertyChanged += NotificationChanged; - - session.Start(); - - var actualState = await notificationTcs.Task.Timeout(10_000, detail: "Expected to transition to Connected"); - Assert.That(actualState, Is.EqualTo(ConnectionState.Connected)); - - expectedState = ConnectionState.Disconnected; - notificationTcs = new TaskCompletionSource(); - - session.Stop(); - - actualState = await notificationTcs.Task.Timeout(10_000, detail: "Expected to transition to Disconnected"); - Assert.That(actualState, Is.EqualTo(ConnectionState.Disconnected)); - - session.PropertyChanged -= NotificationChanged; - - void NotificationChanged(object? sender, PropertyChangedEventArgs? e) - { - try - { - Assert.That(sender is Session, Is.True); - Assert.That(e!.PropertyName, Is.EqualTo(nameof(Session.ConnectionState))); - - if (session.ConnectionState == expectedState) - { - notificationTcs.TrySetResult(session.ConnectionState); - } - } - catch (Exception ex) - { - notificationTcs.TrySetException(ex); - } - } - }); - } - - [Test] - public void Session_PropertyChanged_FreedNotificationToken() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - using var realm = GetRealm(config); - var session = realm.SyncSession; - - var internalNotificationToken = GetNotificationToken(session); - Assert.That(internalNotificationToken, Is.Null); - - session.PropertyChanged += NotificationChanged; - internalNotificationToken = GetNotificationToken(session); - Assert.That(internalNotificationToken, Is.Not.Null); - - session.PropertyChanged -= NotificationChanged; - internalNotificationToken = GetNotificationToken(session); - Assert.That(internalNotificationToken, Is.Null); - - // repeated to make sure that re-subscribing is fine - session.PropertyChanged += NotificationChanged; - internalNotificationToken = GetNotificationToken(session); - Assert.That(internalNotificationToken, Is.Not.Null); - - session.PropertyChanged -= NotificationChanged; - internalNotificationToken = GetNotificationToken(session); - Assert.That(internalNotificationToken, Is.Null); - - static void NotificationChanged(object? sender, PropertyChangedEventArgs? e) - { - } - }); - } - - [Test] - public void Session_PropertyChanged_MultipleSubscribers() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - using var realm = GetRealm(config); - var session = realm.SyncSession; - var subscriberATriggered = false; - var subscriberBTriggered = false; - var completionTcsA = new TaskCompletionSource(); - var completionTcsB = new TaskCompletionSource(); - - // wait for connecting and connected to be done - await Task.Delay(500); - - session.PropertyChanged += NotificationChangedA; - session.PropertyChanged += NotificationChangedB; - - session.Stop(); - - await completionTcsA.Task; - await completionTcsB.Task; - Assert.That(subscriberATriggered, Is.True); - Assert.That(subscriberBTriggered, Is.True); - - session.PropertyChanged -= NotificationChangedA; - session.PropertyChanged -= NotificationChangedB; - - void NotificationChangedA(object? sender, PropertyChangedEventArgs? e) - { - Assert.That(subscriberATriggered, Is.False); - subscriberATriggered = true; - completionTcsA.TrySetResult(); - } - - void NotificationChangedB(object? sender, PropertyChangedEventArgs? e) - { - Assert.That(subscriberBTriggered, Is.False); - subscriberBTriggered = true; - completionTcsB.TrySetResult(); - } - }); - } - - [Test] - public void Session_Should_Not_Free_Instance_With_PropertyChanged_Subscribers() - { - WeakReference weakSessionRef = null!; - - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - using var realm = GetRealm(config); - var session = realm.SyncSession; - weakSessionRef = new WeakReference(session); - Assert.That(weakSessionRef.IsAlive, Is.True); - session.PropertyChanged += HandlePropertyChanged; - }); - - static void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e) - { - } - - GC.Collect(); - Assert.That(weakSessionRef.IsAlive, Is.True); - } - - [Test] - public void Session_Should_Free_Instance_With_No_PropertyChanged_Subscribers() - { - WeakReference weakSessionRef = null!; - - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - using var realm = GetRealm(config); - var session = realm.SyncSession; - weakSessionRef = new WeakReference(session); - Assert.That(weakSessionRef.IsAlive, Is.True); - session.PropertyChanged += HandlePropertyChanged; - session.PropertyChanged -= HandlePropertyChanged; - }); - - static void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e) - { - } - - GC.Collect(); - Assert.That(weakSessionRef.IsAlive, Is.False); - } - - [Test] - public void Session_Should_Keep_Instance_Until_There_Are_Subscribers() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - using var realm = GetRealm(config); - - var weakSessionRef = new Func(() => new WeakReference(realm.SyncSession))(); - - Assert.That(weakSessionRef.IsAlive, Is.True); - - realm.SyncSession.PropertyChanged += HandlePropertyChanged; - - // We want to have more assurance that the reference is not being collected at a later time - await WaitUntilReferencesAreCollected(500, weakSessionRef); - Assert.That(weakSessionRef.IsAlive, Is.True); - - realm.SyncSession.PropertyChanged -= HandlePropertyChanged; - - await WaitUntilReferencesAreCollected(500, weakSessionRef); - Assert.That(weakSessionRef.IsAlive, Is.False); - }); - - static void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e) - { - } - } - - [Test] - public void Session_NotificationToken_Freed_When_Close_Realm() - { - WeakReference weakNotificationTokenRef = null!; - - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - var realm = GetRealm(config); - var session = realm.SyncSession; - - session.PropertyChanged += NotificationChanged; - var internalNotificationToken = GetNotificationToken(session); - Assert.That(internalNotificationToken, Is.Not.Null); - weakNotificationTokenRef = new WeakReference(internalNotificationToken); - Assert.That(weakNotificationTokenRef.IsAlive, Is.True); - - static void NotificationChanged(object? sender, PropertyChangedEventArgs? e) - { - } - }); - - GC.Collect(); - Assert.That(weakNotificationTokenRef.IsAlive, Is.False); - } - - [Test] - public void Session_ConnectionState_Propagated_Within_Multiple_Sessions() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - using var realmA = GetRealm(config); - using var realmB = GetRealm(config); - - var sessionA = realmA.SyncSession; - var sessionB = realmB.SyncSession; - sessionA.Stop(); - - var expectedState = ConnectionState.Connected; - var notificationTcs = new TaskCompletionSource(); - - sessionB.PropertyChanged += NotificationChanged; - - sessionA.Start(); - - var sessionBState = await notificationTcs.Task.Timeout(10_000, detail: "Expected to transition to connected"); - Assert.That(sessionBState, Is.EqualTo(ConnectionState.Connected)); - - expectedState = ConnectionState.Disconnected; - notificationTcs = new TaskCompletionSource(); - - sessionA.Stop(); - - sessionBState = await notificationTcs.Task.Timeout(10_000, detail: "Expected to transition to disconnected"); - Assert.That(sessionBState, Is.EqualTo(ConnectionState.Disconnected)); - - Assert.That(sessionB.ConnectionState, Is.EqualTo(ConnectionState.Disconnected)); - Assert.That(sessionA.ConnectionState, Is.EqualTo(ConnectionState.Disconnected)); - - sessionB.PropertyChanged -= NotificationChanged; - - void NotificationChanged(object? sender, PropertyChangedEventArgs? e) - { - try - { - Assert.That(sender is Session, Is.True); - Assert.That(e!.PropertyName, Is.EqualTo(nameof(Session.ConnectionState))); - - if (sessionB.ConnectionState == expectedState) - { - notificationTcs.TrySetResult(sessionB.ConnectionState); - } - } - catch (Exception ex) - { - notificationTcs.TrySetException(ex); - } - } - }); - } - - [Test] - public void Session_WhenDisposed_MethodsThrow() - { - var session = OpenRealmAndStopSession(); - - session.CloseHandle(); - - Assert.Throws(() => session.Start()); - Assert.Throws(() => session.Stop()); - Assert.Throws(() => _ = session.State); - Assert.Throws(() => _ = session.User); - Assert.Throws(() => _ = session.Path); - Assert.Throws(() => _ = session.GetHashCode()); - Assert.Throws(() => _ = session.GetProgressObservable(ProgressDirection.Upload, ProgressMode.ForCurrentlyOutstandingWork)); - Assert.Throws(() => _ = session.Equals(session)); - Assert.Throws(() => _ = session.WaitForDownloadAsync()); - Assert.Throws(() => _ = session.WaitForUploadAsync()); - Assert.Throws(() => session.ReportErrorForTesting(1, "test", false, ServerRequestsAction.ApplicationBug)); - - // Calling CloseHandle multiple times should be fine - session.CloseHandle(); - session.CloseHandle(waitForShutdown: true); - } - - [Test] - public void Session_Equals_WhenSameRealm_ReturnsTrue() - { - var config = GetFakeConfig(); - var realm = GetRealm(config); - var first = GetSession(realm); - var second = GetSession(realm); - - Assert.That(ReferenceEquals(first, second)); - Assert.That(first.Equals(second)); - Assert.That(second.Equals(first)); - } - - [Test] - public void Session_GetHashCode_WhenSameRealm_ReturnsSameValue() - { - var config = GetFakeConfig(); - var realm = GetRealm(config); - var first = GetSession(realm); - var second = GetSession(realm); - - Assert.That(first.GetHashCode(), Is.EqualTo(second.GetHashCode())); - } - - [Test] - public void Session_Equals_WhenDifferentRealm_ReturnsFalse() - { - var realm1 = GetRealm(GetFakeConfig()); - var first = GetSession(realm1); - - var realm2 = GetRealm(GetFakeConfig()); - var second = GetSession(realm2); - - Assert.That(ReferenceEquals(first, second), Is.False); - Assert.That(first.Equals(second), Is.False); - Assert.That(second.Equals(first), Is.False); - } - - [Test] - public void Session_GetHashCode_WhenDifferentRealm_ReturnsDifferentValue() - { - var realm1 = GetRealm(GetFakeConfig()); - var first = GetSession(realm1); - - var realm2 = GetRealm(GetFakeConfig()); - var second = GetSession(realm2); - - Assert.That(first.GetHashCode(), Is.Not.EqualTo(second.GetHashCode())); - } - - [Test] - public void Session_Equals_WhenOtherIsNotASession_ReturnsFalse() - { - var session = OpenRealmAndStopSession(); - - Assert.That(session.Equals(1), Is.False); - Assert.That(session.Equals(new object()), Is.False); - } - - [Test] - public void Session_PermissionDenied_DoesntCrash() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var tcs = new TaskCompletionSource(); - var config = await GetIntegrationConfigAsync("read-only"); - - config.OnSessionError = (_, error) => - { - Assert.That(error.ErrorCode == ErrorCode.PermissionDenied); - Assert.That(error.InnerException == null); - - tcs.TrySetResult(true); - }; - - using var realm = GetRealm(config); - - realm.Write(() => - { - realm.Add(new PrimaryKeyStringObject - { - Id = Guid.NewGuid().ToString(), - Value = "abc" - }); - }); - - await tcs.Task; - }, timeout: 1000000); - } - - [Test] - public void Session_GetUser_GetApp_ReturnsMeaningfulValue() - { - // Session.User.App doesn't have a reference to the managed app, which means we need to - // recreate it from the native one. This test verifies that the properties on the native - // app match the ones on the managed one. - var fakeConfig = new Realms.Sync.AppConfiguration("foo-bar") - { - BaseUri = new Uri("http://localhost:12345"), - BaseFilePath = Path.Combine(InteropConfig.GetDefaultStorageFolder("no error expected"), Guid.NewGuid().ToString()) - }; - Directory.CreateDirectory(fakeConfig.BaseFilePath); - - var fakeApp = CreateApp(fakeConfig); - - var realm = GetRealm(GetFakeConfig(fakeApp)); - var session = GetSession(realm); - - Assert.That(session.User.App.BaseFilePath, Is.EqualTo(fakeConfig.BaseFilePath)); - Assert.That(session.User.App.BaseUri, Is.EqualTo(fakeConfig.BaseUri)); - } - - private static void AddSomeObjects(Realm realm, int count = 10) - { - realm.Write(() => - { - for (var i = 0; i < count; i++) - { - realm.Add(new SyncAllTypesObject - { - Int32Property = i, - ByteArrayProperty = GetBytes(10_000) - }); - } - }); - } - - [Test] - public void Session_WaitForUpload_CanBeCancelled() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var realm = await GetIntegrationRealmAsync(); - AddSomeObjects(realm); - - var cts = new CancellationTokenSource(1); - - var sw = new Stopwatch(); - sw.Start(); - - await AssertThrows(() => GetSession(realm).WaitForUploadAsync(cts.Token)); - - sw.Stop(); - - Assert.That(sw.ElapsedMilliseconds, Is.LessThan(30)); - }); - } - - [Test] - public void Session_WaitForUpload_WithCancelledToken() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var realm = await GetIntegrationRealmAsync(); - AddSomeObjects(realm); - - var cts = new CancellationTokenSource(); - cts.Cancel(); - - var sw = new Stopwatch(); - sw.Start(); - - await AssertThrows(() => GetSession(realm).WaitForUploadAsync(cts.Token)); - - sw.Stop(); - - Assert.That(sw.ElapsedMilliseconds, Is.LessThan(2)); - }); - } - - [Test] - public void Session_WaitForDownload_CanBeCancelled() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partition = Guid.NewGuid().ToString(); - var writerRealm = await GetIntegrationRealmAsync(partition: partition); - AddSomeObjects(writerRealm); - - await WaitForUploadAsync(writerRealm); - - var config = await GetIntegrationConfigAsync(partition: partition); - var readerRealm = GetRealm(config); - - var cts = new CancellationTokenSource(1); - - var sw = new Stopwatch(); - sw.Start(); - - await AssertThrows(() => GetSession(readerRealm).WaitForDownloadAsync(cts.Token)); - - sw.Stop(); - - Assert.That(sw.ElapsedMilliseconds, Is.LessThan(30)); - Assert.That(readerRealm.All().Count(), Is.Zero); - - await GetSession(readerRealm).WaitForDownloadAsync(); - Assert.That(readerRealm.All().Count(), Is.EqualTo(10)); - }); - } - - [Test] - public void Session_WaitForDownload_WithCancelledToken() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partition = Guid.NewGuid().ToString(); - var writerRealm = await GetIntegrationRealmAsync(partition: partition); - AddSomeObjects(writerRealm); - - await WaitForUploadAsync(writerRealm); - - var config = await GetIntegrationConfigAsync(partition: partition); - var readerRealm = GetRealm(config); - - var cts = new CancellationTokenSource(1); - cts.Cancel(); - - var sw = new Stopwatch(); - sw.Start(); - - await AssertThrows(() => GetSession(readerRealm).WaitForDownloadAsync(cts.Token)); - - sw.Stop(); - - Assert.That(sw.ElapsedMilliseconds, Is.LessThan(2)); - Assert.That(readerRealm.All().Count(), Is.Zero); - - await GetSession(readerRealm).WaitForDownloadAsync(); - Assert.That(readerRealm.All().Count(), Is.EqualTo(10)); - }); - } - - private static ClientResetHandlerBase GetClientResetHandler( - Type type, - BeforeResetCallback? beforeCb = null, - AfterResetCallback? afterCb = null, - ClientResetCallback? manualCb = null) - { - var handler = (ClientResetHandlerBase)Activator.CreateInstance(type)!; - - if (beforeCb != null) - { - type.GetProperty(nameof(DiscardUnsyncedChangesHandler.OnBeforeReset))!.SetValue(handler, beforeCb); - } - - if (afterCb != null) - { - var prop = type.GetProperty(nameof(DiscardUnsyncedChangesHandler.OnAfterReset)) - ?? type.GetProperty(nameof(RecoverOrDiscardUnsyncedChangesHandler.OnAfterRecovery)); - - prop!.SetValue(handler, afterCb); - } - - if (manualCb != null) - { - type.GetProperty(nameof(DiscardUnsyncedChangesHandler.ManualResetFallback))!.SetValue(handler, manualCb); - } - - return handler; - } - - private static async Task TryInitiateClientReset(Realm realm, ClientResetException ex, ErrorCode expectedError) - { - if (!realm.IsClosed) - { - realm.Dispose(); - } - - Assert.That(ex.ErrorCode, Is.EqualTo(expectedError)); - Assert.That(File.Exists(realm.Config.DatabasePath), Is.True); - - var didReset = false; - for (var i = 0; i < 100 && !didReset; i++) - { - await Task.Delay(50); - didReset = ex.InitiateClientReset(); - } - - Assert.That(didReset, Is.True, "Failed to complete manual reset after 100 attempts."); - - Assert.That(File.Exists(realm.Config.DatabasePath), Is.False); - } - - private static AfterResetCallback GetOnAfterHandler(TaskCompletionSource tcs, Action assertions) - { - return (frozen, live) => - { - try - { - assertions(frozen, live); - tcs.TrySetResult(); - } - catch (Exception ex) - { - tcs.TrySetException(ex); - } - }; - } - - private static BeforeResetCallback GetOnBeforeHandler(TaskCompletionSource tcs, Action assertions) - { - return frozen => - { - try - { - assertions(frozen); - } - catch (Exception ex) - { - tcs.TrySetException(ex); - } - }; - } - - private static ClientResetCallback GetManualResetHandler(TaskCompletionSource tcs, Action assertions) - { - return clientResetException => - { - try - { - assertions(clientResetException); - tcs.TrySetResult(); - } - catch (Exception ex) - { - tcs.TrySetException(ex); - } - }; - } - - /// - /// Opens a random realm and calls session.Stop(). It will assert state changes - /// to Inactive. - /// - /// The stopped session. - private Session OpenRealmAndStopSession() - { - var config = GetFakeConfig(); - var realm = GetRealm(config); - var session = GetSession(realm); - - Assert.That(session.State, Is.EqualTo(SessionState.Active)); - - session.Stop(); - Assert.That(session.State, Is.EqualTo(SessionState.Inactive)); - - return session; - } - - private static SessionNotificationToken? GetNotificationToken(Session session) - { - var sessionHandle = (SessionHandle?)typeof(Session).GetField("_handle", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(session); - return sessionHandle != null ? - (SessionNotificationToken?)typeof(SessionHandle).GetField("_notificationToken", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(sessionHandle)! : - null; - } - } - - [Explicit] - public partial class ObjectWithPartitionValue : TestRealmObject - { - [PrimaryKey] - [MapTo("_id")] - public string? Id { get; private set; } = Guid.NewGuid().ToString(); - - public string? Value { get; set; } - - [MapTo("realm_id")] - public string? Partition { get; set; } - - public Guid Guid { get; private set; } - - public ObjectWithPartitionValue(Guid guid) - { - Guid = guid; - } - -#if TEST_WEAVER - private ObjectWithPartitionValue() - { - } -#endif - } - - public partial class SyncObjectWithRequiredStringList : TestRealmObject - { - [PrimaryKey] - [MapTo("_id")] - public string? Id { get; set; } - -#if TEST_WEAVER - [Required] -#endif - public IList Strings { get; } = null!; - } -} diff --git a/Tests/Realm.Tests/Sync/StaticQueriesTests.cs b/Tests/Realm.Tests/Sync/StaticQueriesTests.cs deleted file mode 100644 index f4864edec7..0000000000 --- a/Tests/Realm.Tests/Sync/StaticQueriesTests.cs +++ /dev/null @@ -1,1348 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Threading.Tasks; -using Baas; -using MongoDB.Bson; -using NUnit.Framework; -using Realms.Schema; -using Realms.Sync; -using Realms.Sync.Exceptions; -using static Realms.Tests.TestHelpers; - -namespace Realms.Tests.Sync -{ -#if !TEST_WEAVER - [TestFixture, Preserve(AllMembers = true)] - public class StaticQueriesTests : SyncTestBase - { - private const string ServiceName = "BackingDB"; - - [Test] - public void RealmObjectAPI_Collections() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var props = SyncCollectionsObject.RealmSchema - .Where(p => p.Type.IsCollection(out _) && !p.Type.HasFlag(PropertyType.Object)) - .ToArray(); - - var collection = await GetCollection(AppConfigType.FlexibleSync); - var obj1 = new SyncCollectionsObject(); - FillCollectionProps(obj1); - - var syncObj2 = new SyncCollectionsObject(); - FillCollectionProps(syncObj2); - - await collection.InsertOneAsync(obj1); - - using var realm = await GetFLXIntegrationRealmAsync(); - var syncObjects = await realm.All().Where(o => o.Id == obj1.Id || o.Id == syncObj2.Id).SubscribeAsync(); - await syncObjects.WaitForEventAsync((sender, _) => sender.Count > 0); - - var syncObj1 = syncObjects.Single(); - - AssertCollectionProps(obj1, syncObj1); - - realm.Write(() => realm.Add(syncObj2)); - - var filter = new { _id = syncObj2.Id }; - - var obj2 = await WaitForNonNullObjectAsync(() => collection.FindOneAsync(filter)); - - AssertCollectionProps(syncObj2, obj2); - - void FillCollectionProps(SyncCollectionsObject obj) - { - foreach (var prop in props) - { - DataGenerator.FillCollection(obj.GetProperty(prop), 5); - } - } - - void AssertCollectionProps(SyncCollectionsObject expected, SyncCollectionsObject actual) - { - foreach (var prop in props) - { - var expectedProp = expected.GetProperty(prop); - var actualProp = actual.GetProperty(prop); - - Assert.That(actualProp, Is.EquivalentTo(expectedProp).Using((object a, object e) => AreValuesEqual(a, e)), $"Expected collections to match for {prop.ManagedName}"); - } - } - }, timeout: 120000); - } - - public static readonly object[] PrimitiveTestCases = - { - new object[] { CreateTestCase("Empty object", new SyncAllTypesObject()) }, - new object[] - { - CreateTestCase("All values", new SyncAllTypesObject - { - BooleanProperty = true, - ByteArrayProperty = GetBytes(5), - ByteProperty = 255, - CharProperty = 'C', - DateTimeOffsetProperty = new DateTimeOffset(638380790696454240, TimeSpan.Zero), - Decimal128Property = 4932.539258328M, - DecimalProperty = 4884884883.99999999999M, - DoubleProperty = 34934.123456, - GuidProperty = Guid.NewGuid(), - Int16Property = 999, - Int32Property = 49394939, - Int64Property = 889898965342443, - ObjectIdProperty = ObjectId.GenerateNewId(), - RealmValueProperty = "this is a string", - StringProperty = "foo bar" - }) - }, - new object[] { CreateTestCase("Bool RealmValue", new SyncAllTypesObject { RealmValueProperty = true }) }, - new object[] { CreateTestCase("Int RealmValue", new SyncAllTypesObject { RealmValueProperty = 123 }) }, - new object[] { CreateTestCase("Long RealmValue", new SyncAllTypesObject { RealmValueProperty = 9999999999 }) }, - new object[] { CreateTestCase("Null RealmValue", new SyncAllTypesObject { RealmValueProperty = RealmValue.Null }) }, - new object[] { CreateTestCase("String RealmValue", new SyncAllTypesObject { RealmValueProperty = "abc" }) }, - new object[] { CreateTestCase("Data RealmValue", new SyncAllTypesObject { RealmValueProperty = GetBytes(10) }) }, - new object[] { CreateTestCase("Float RealmValue", new SyncAllTypesObject { RealmValueProperty = 15.2f }) }, - new object[] { CreateTestCase("Double RealmValue", new SyncAllTypesObject { RealmValueProperty = -123.45678909876 }) }, - new object[] { CreateTestCase("Decimal RealmValue", new SyncAllTypesObject { RealmValueProperty = 1.1111111111111111111M }) }, - new object[] { CreateTestCase("Decimal RealmValue", new SyncAllTypesObject { RealmValueProperty = 1.1111111111111111111M }) }, - new object[] { CreateTestCase("Decimal128 RealmValue", new SyncAllTypesObject { RealmValueProperty = new Decimal128(2.1111111111111111111M) }) }, - new object[] { CreateTestCase("ObjectId RealmValue", new SyncAllTypesObject { RealmValueProperty = ObjectId.GenerateNewId() }) }, - new object[] { CreateTestCase("Guid RealmValue", new SyncAllTypesObject { RealmValueProperty = Guid.NewGuid() }) }, - }; - - [TestCaseSource(nameof(PrimitiveTestCases))] - public void RealmObjectAPI_Primitive_AtlasToRealm(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var props = SyncAllTypesObject.RealmSchema - .Where(p => !p.Type.HasFlag(PropertyType.Object)) - .ToArray(); - - var collection = await GetCollection(AppConfigType.FlexibleSync); - var obj = testCase.Value; - - await collection.InsertOneAsync(obj); - - using var realm = await GetFLXIntegrationRealmAsync(); - var syncObjects = await realm.All().Where(o => o.Id == obj.Id).SubscribeAsync(); - await syncObjects.WaitForEventAsync((sender, _) => sender.Count > 0); - - var syncObj = syncObjects.Single(); - - AssertProps(props, obj, syncObj); - }, timeout: 120000); - } - - [TestCaseSource(nameof(PrimitiveTestCases))] - public void RealmObjectAPI_Primitive_RealmToAtlas(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var props = SyncAllTypesObject.RealmSchema - .Where(p => !p.Type.HasFlag(PropertyType.Object)) - .ToArray(); - - var collection = await GetCollection(AppConfigType.FlexibleSync); - var obj = testCase.Value; - - using var realm = await GetFLXIntegrationRealmAsync(); - await realm.All().Where(o => o.Id == obj.Id).SubscribeAsync(); - realm.Write(() => realm.Add(obj)); - - var filter = new { _id = obj.Id }; - - var syncObj = await WaitForNonNullObjectAsync(() => collection.FindOneAsync(filter)); - - AssertProps(props, obj, syncObj); - }, timeout: 120000); - } - - /* We need specific tests regarding datetime because of the different behaviour of the server and - * DateTimeOffset.ToUnixUniversalTime(): - * - DateTimeOffset.ToUnixTimeMilliseconds always rounds to the “past”. So for dates after Unix epoch it’s the same as a truncation, - * for dates before this means that they subtract 1 millisecond if there’s any sub-millisecond digit (https://github.com/microsoft/referencesource/blob/51cf7850defa8a17d815b4700b67116e3fa283c2/mscorlib/system/datetimeoffset.cs#L666C11-L666C11) - * - The server truncates sub-milliseconds precision all times - * This means that there is an issue when going from Realm to Atlas and then deserializing from Atlas, as there will be a difference - * of 1 millisecond when the original DateTimeOffset has sub-millisecond precision. - */ - public static readonly object[] DateTimeTestCasesAtlasToRealm = - { - new object[] { CreateTestCase("PostEpoch", new DateTimeOffset(638404890727190000, TimeSpan.Zero)) }, - new object[] { CreateTestCase("PostEpoch-subprecision", new DateTimeOffset(638404890727196472, TimeSpan.Zero)) }, - new object[] { CreateTestCase("PreEpoch", new DateTimeOffset(606033288264220000, TimeSpan.Zero)) }, - new object[] { CreateTestCase("PreEpoch-subprecision", new DateTimeOffset(606033288264226494, TimeSpan.Zero)) }, // Problematic case - }; - - [TestCaseSource(nameof(DateTimeTestCasesAtlasToRealm))] - public void RealmObjectAPI_DateTime_AtlasToRealm(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(AppConfigType.FlexibleSync); - var obj = new SyncAllTypesObject { DateTimeOffsetProperty = testCase.Value }; - - await collection.InsertOneAsync(obj); - - using var realm = await GetFLXIntegrationRealmAsync(); - var syncObjects = await realm.All().Where(o => o.Id == obj.Id).SubscribeAsync(); - await syncObjects.WaitForEventAsync((sender, _) => sender.Count > 0); - - var syncObj = syncObjects.Single(); - - AssertAreEqual(syncObj.DateTimeOffsetProperty, obj.DateTimeOffsetProperty); - }, timeout: 120000); - } - - public static readonly object[] DateTimeTestCasesRealmToAtlas = - { - new object[] { CreateTestCase("PostEpoch", new DateTimeOffset(638404890727190000, TimeSpan.Zero)) }, - new object[] { CreateTestCase("PostEpoch-subprecision", new DateTimeOffset(638404890727196472, TimeSpan.Zero)) }, - new object[] { CreateTestCase("PreEpoch", new DateTimeOffset(606033288264220000, TimeSpan.Zero)) }, - }; - - [TestCaseSource(nameof(DateTimeTestCasesRealmToAtlas))] - public void RealmObjectAPI_DateTime_RealmToAtlas(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(AppConfigType.FlexibleSync); - var obj = new SyncAllTypesObject { DateTimeOffsetProperty = testCase.Value }; - - using var realm = await GetFLXIntegrationRealmAsync(); - await realm.All().Where(o => o.Id == obj.Id).SubscribeAsync(); - realm.Write(() => realm.Add(obj)); - - var filter = new { _id = obj.Id }; - - var syncObj = await WaitForNonNullObjectAsync(() => collection.FindOneAsync(filter)); - - AssertAreEqual(syncObj.DateTimeOffsetProperty, obj.DateTimeOffsetProperty); - }, timeout: 120000); - } - - [TestCase] - public void RealmObjectAPI_DateTime_RealmToAtlas_SpecialCase() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - // This is problematic because it's before UnixEpoch, and has sub-millisecond precision - var problematicDateTime = new DateTimeOffset(606033288264226494, TimeSpan.Zero); - - var collection = await GetCollection(AppConfigType.FlexibleSync); - var obj = new SyncAllTypesObject { DateTimeOffsetProperty = problematicDateTime }; - - using var realm = await GetFLXIntegrationRealmAsync(); - await realm.All().Where(o => o.Id == obj.Id).SubscribeAsync(); - realm.Write(() => realm.Add(obj)); - - var filter = new { _id = obj.Id }; - - var syncObj = await WaitForNonNullObjectAsync(() => collection.FindOneAsync(filter)); - - var originalUnixMs = obj.DateTimeOffsetProperty.ToUnixTimeMilliseconds(); - var expectedUnixMs = syncObj.DateTimeOffsetProperty.ToUnixTimeMilliseconds(); - - Assert.That(expectedUnixMs - originalUnixMs, Is.EqualTo(1)); - }, timeout: 120000); - } - - public static readonly object[] CounterTestCases = - { - new object[] - { - CreateTestCase("All values", new CounterObject - { - Id = 1, - ByteProperty = 255, - Int16Property = 999, - Int32Property = 49394939, - Int64Property = 889898965342443, - NullableByteProperty = 255, - NullableInt16Property = 999, - NullableInt32Property = 49394939, - NullableInt64Property = 889898965342443 - }) - }, - new object[] - { - CreateTestCase("Nullable values", new CounterObject - { - Id = 2, - ByteProperty = 255, - Int16Property = 999, - Int32Property = 49394939, - Int64Property = 889898965342443, - NullableByteProperty = null, - NullableInt16Property = null, - NullableInt32Property = null, - NullableInt64Property = null, - }) - }, - }; - - [TestCaseSource(nameof(CounterTestCases))] - public void RealmObjectAPI_Counter_AtlasToRealm(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var props = CounterObject.RealmSchema.ToArray(); - - var collection = await GetCollection(AppConfigType.FlexibleSync); - var obj = testCase.Value; - - await collection.InsertOneAsync(obj); - - using var realm = await GetFLXIntegrationRealmAsync(); - var syncObjects = await realm.All().Where(o => o.Id == obj.Id).SubscribeAsync(); - await syncObjects.WaitForEventAsync((sender, _) => sender.Count > 0); - - var syncObj = syncObjects.Single(); - - AssertProps(props, obj, syncObj); - }, timeout: 120000); - } - - [TestCaseSource(nameof(CounterTestCases))] - public void RealmObjectAPI_Counter_RealmToAtlas(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var props = CounterObject.RealmSchema.ToArray(); - - var collection = await GetCollection(AppConfigType.FlexibleSync); - var obj = testCase.Value; - - using var realm = await GetFLXIntegrationRealmAsync(); - await realm.All().Where(o => o.Id == obj.Id).SubscribeAsync(); - realm.Write(() => realm.Add(obj)); - - var filter = new { _id = obj.Id }; - - var syncObj = await WaitForNonNullObjectAsync(() => collection.FindOneAsync(filter)); - - AssertProps(props, obj, syncObj); - }, timeout: 120000); - } - - public static readonly object[] AsymmetricTestCases = - { - new object[] - { - CreateTestCase("Base", new BasicAsymmetricObject { PartitionLike = "testString" }) - }, - }; - - [TestCaseSource(nameof(AsymmetricTestCases))] - public void RealmObjectAPI_Asymmetric_RealmToAtlas(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(AppConfigType.FlexibleSync); - var obj = testCase.Value; - var stringProperty = obj.PartitionLike; - - var filter = new { _id = obj.Id }; - - using var realm = await GetFLXIntegrationRealmAsync(); - realm.Write(() => realm.Add(obj)); - - var syncObj = await WaitForNonNullObjectAsync(() => collection.FindOneAsync(filter)); - - Assert.That(stringProperty, Is.EqualTo(syncObj.PartitionLike)); - }, timeout: 120000); - } - - public static readonly object[] ObjectTestCases = - { - new object[] - { - CreateTestCase("All values", new SyncAllTypesObject - { - ObjectProperty = new IntPropertyObject { Int = 23 }, - }) - }, - }; - - [TestCaseSource(nameof(ObjectTestCases))] - public void RealmObjectAPI_Object_AtlasToRealm(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var syncAllTypesCollection = await GetCollection(AppConfigType.FlexibleSync); - var intPropertyCollection = await GetCollection(AppConfigType.FlexibleSync); - - var obj = testCase.Value; - - await syncAllTypesCollection.InsertOneAsync(obj); - - using var realm = await GetFLXIntegrationRealmAsync(); - var syncAllTypesObjects = await realm.All().Where(o => o.Id == obj.Id).SubscribeAsync(); - var intPropertyObjects = await realm.All().Where(o => o.Id == obj.ObjectProperty!.Id).SubscribeAsync(); - - await syncAllTypesObjects.WaitForEventAsync((sender, _) => sender.Count > 0); - - var syncObj = syncAllTypesObjects.Single(); - - // The object property is null, because we didn't add the object yet to Atlas - Assert.That(syncObj.ObjectProperty, Is.Null); - - await intPropertyCollection.InsertOneAsync(obj.ObjectProperty!); - await intPropertyObjects.WaitForEventAsync((sender, _) => sender.Count > 0); - - Assert.That(syncObj.ObjectProperty!.Id, Is.EqualTo(obj.ObjectProperty!.Id)); - Assert.That(syncObj.ObjectProperty!.Int, Is.EqualTo(obj.ObjectProperty!.Int)); - }, timeout: 120000); - } - - [TestCaseSource(nameof(ObjectTestCases))] - public void RealmObjectAPI_Object_RealmToAtlas(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var syncAllTypesCollection = await GetCollection(AppConfigType.FlexibleSync); - var intPropertyCollection = await GetCollection(AppConfigType.FlexibleSync); - - var obj = testCase.Value; - - using var realm = await GetFLXIntegrationRealmAsync(); - await realm.All().Where(o => o.Id == obj.Id).SubscribeAsync(); - await realm.All().Where(o => o.Id == obj.ObjectProperty!.Id).SubscribeAsync(); - realm.Write(() => realm.Add(obj)); - - var syncAllTypeObj = await WaitForNonNullObjectAsync(() => syncAllTypesCollection.FindOneAsync(new { _id = obj.Id })); - var intPropertyObj = await WaitForNonNullObjectAsync(() => intPropertyCollection.FindOneAsync(new { _id = obj.ObjectProperty!.Id })); - - Assert.That(syncAllTypeObj.ObjectProperty!.Id, Is.EqualTo(obj.ObjectProperty!.Id)); - Assert.That(syncAllTypeObj.ObjectProperty!.Int, Is.Not.EqualTo(obj.ObjectProperty!.Int)); - - Assert.That(intPropertyObj.Id, Is.EqualTo(obj.ObjectProperty.Id)); - Assert.That(intPropertyObj.Int, Is.EqualTo(obj.ObjectProperty.Int)); - }, timeout: 120000); - } - - public static readonly object[] LinksTestCases = - { - new object[] - { - CreateTestCase("Single link", new LinksObject("singleLink") - { - Link = new("second") { Value = 2 }, - Value = 1, - }), - }, - new object[] - { - CreateTestCase("List", new LinksObject("listLink") - { - List = - { - new("list.1") { Value = 100 }, - new("list.2") { Value = 200 }, - }, - Value = 987 - }), - }, - new object[] - { - CreateTestCase("Dictionary", new LinksObject("dictLink") - { - Dictionary = - { - ["key_1"] = new("dict.1") { Value = 100 }, - ["key_null"] = null, - ["key_2"] = new("dict.2") { Value = 200 }, - }, - Value = 999 - }) - }, - new object[] - { - CreateTestCase("Set", new LinksObject("setLink") - { - Set = - { - new("set.1") { Value = 100 }, - new("set.2") { Value = 200 }, - }, - Value = 123 - }), - }, - new object[] - { - CreateTestCase("All types", new LinksObject("parent") - { - Value = 1, - Link = new("link") { Value = 2 }, - List = - { - new("list.1") { Value = 3 }, - new("list.2") { Value = 4 }, - }, - Set = - { - new("set.1") { Value = 5 }, - new("set.2") { Value = 6 }, - }, - Dictionary = - { - ["dict_1"] = new("dict.1") { Value = 7 }, - ["dict_2"] = new("dict.2") { Value = 8 }, - ["dict_null"] = null - } - }), - } - }; - - [TestCaseSource(nameof(LinksTestCases))] - public void RealmObjectAPI_Links_AtlasToRealm(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(AppConfigType.FlexibleSync); - - var obj = testCase.Value; - - var elementsToInsert = obj.List.Concat(obj.Set).Concat(obj.Dictionary.Values.Where(d => d is not null)).Concat(new[] { obj }); - - if (obj.Link is not null) - { - elementsToInsert = elementsToInsert.Concat(new[] { obj.Link }); - } - - await collection.InsertManyAsync(elementsToInsert!); - - // How many objects we expect - var totalCount = obj.List.Count + obj.Set.Count + obj.Dictionary.Count(d => d.Value != null) + 1 + (obj.Link is null ? 0 : 1); - - using var realm = await GetFLXIntegrationRealmAsync(); - var linkObjs = await realm.All().SubscribeAsync(); - - await realm.SyncSession.WaitForDownloadAsync(); - await linkObjs.WaitForEventAsync((sender, _) => sender.Count >= totalCount && realm.Find(obj.Id) != null); - - var linkObj = realm.Find(obj.Id); - - AssertEqual(linkObj!.Link, obj.Link); - - Assert.That(linkObj.List.Count, Is.EqualTo(obj.List.Count)); - - for (int i = 0; i < linkObj.List.Count; i++) - { - AssertEqual(linkObj.List[i], obj.List[i]); - } - - Assert.That(linkObj.Dictionary.Count, Is.EqualTo(obj.Dictionary.Count)); - - foreach (var key in obj.Dictionary.Keys) - { - Assert.That(linkObj.Dictionary.ContainsKey(key)); - AssertEqual(linkObj.Dictionary[key], obj.Dictionary[key]); - } - - Assert.That(linkObj.Set.Count, Is.EqualTo(obj.Set.Count)); - - var orderedOriginalSet = obj.Set.OrderBy(a => a.Id).ToList(); - var orderedRetrievedSet = linkObj.Set.OrderBy(a => a.Id).ToList(); - - for (int i = 0; i < orderedOriginalSet.Count; i++) - { - AssertEqual(orderedRetrievedSet[i], orderedOriginalSet[i]); - } - - static void AssertEqual(LinksObject? retrieved, LinksObject? original) - { - if (original is null) - { - Assert.That(retrieved, Is.Null); - } - else - { - Assert.That(retrieved, Is.Not.Null); - Assert.That(retrieved!.Id, Is.EqualTo(original.Id)); - Assert.That(retrieved.Value, Is.EqualTo(original.Value)); - } - } - }, timeout: 120000); - } - - [TestCaseSource(nameof(LinksTestCases))] - public void RealmObjectAPI_Links_RealmToAtlas(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(AppConfigType.FlexibleSync); - - var obj = testCase.Value; - - using var realm = await GetFLXIntegrationRealmAsync(); - await realm.All().SubscribeAsync(); - - await WaitForConditionAsync(() => !realm.All().Any()); - - realm.Write(() => realm.Add(obj)); - await WaitForUploadAsync(realm); - - var linkObj = await WaitForNonNullObjectAsync(() => collection.FindOneAsync(new { _id = obj.Id })); - - await AssertEqual(collection, linkObj.Link, obj.Link); - - for (int i = 0; i < linkObj.List.Count; i++) - { - await AssertEqual(collection, linkObj.List[i], obj.List[i]); - } - - Assert.That(linkObj.Dictionary.Count, Is.EqualTo(obj.Dictionary.Count)); - - foreach (var key in obj.Dictionary.Keys) - { - Assert.That(linkObj.Dictionary.ContainsKey(key)); - await AssertEqual(collection, linkObj.Dictionary[key], obj.Dictionary[key]); - } - - Assert.That(linkObj.Set.Count, Is.EqualTo(obj.Set.Count)); - - var orderedOriginalSet = obj.Set.OrderBy(a => a.Id).ToList(); - var orderedRetrievedSet = linkObj.Set.OrderBy(a => a.Id).ToList(); - - for (int i = 0; i < orderedOriginalSet.Count; i++) - { - await AssertEqual(collection, orderedRetrievedSet[i], orderedOriginalSet[i]); - } - - static async Task AssertEqual(MongoClient.Collection collection, LinksObject? partiallyRetrieved, LinksObject? original) - { - if (original is null) - { - Assert.That(partiallyRetrieved, Is.Null); - return; - } - - // The partiallyRetrieved object should contain only the id, and not other fields - Assert.That(partiallyRetrieved, Is.Not.Null); - Assert.That(partiallyRetrieved!.Id, Is.EqualTo(original.Id)); - Assert.That(partiallyRetrieved.Value, Is.Not.EqualTo(original.Value)); - - var fullyRetrieved = await WaitForNonNullObjectAsync(() => collection.FindOneAsync(new { _id = original.Id })); - - Assert.That(fullyRetrieved.Id, Is.EqualTo(original.Id)); - Assert.That(fullyRetrieved.Value, Is.EqualTo(original.Value)); - } - }, timeout: 120000); - } - - public static readonly object[] RealmValueLinkTestCases = - { - new object[] - { - CreateTestCase("Single link", new RealmValueObject - { - RealmValueProperty = new IntPropertyObject { Int = 2 }, - }), - }, - new object[] - { - CreateTestCase("List", new RealmValueObject - { - RealmValueList = - { - new IntPropertyObject { Int = 100 }, - new IntPropertyObject { Int = 200 }, - }, - }), - }, - new object[] - { - CreateTestCase("Dictionary", new RealmValueObject - { - RealmValueDictionary = - { - ["key_1"] = new IntPropertyObject { Int = 100 }, - ["key_null"] = RealmValue.Null, - ["key_2"] = new IntPropertyObject { Int = 200 }, - }, - }) - }, - new object[] - { - CreateTestCase("Set", new RealmValueObject - { - RealmValueSet = - { - new IntPropertyObject { Int = 100 }, - new IntPropertyObject { Int = 200 }, - }, - }), - }, - new object[] - { - CreateTestCase("All types", new RealmValueObject - { - RealmValueProperty = new IntPropertyObject { Int = 2 }, - RealmValueList = - { - new IntPropertyObject { Int = 3 }, - new IntPropertyObject { Int = 4 }, - }, - RealmValueSet = - { - new IntPropertyObject { Int = 5 }, - new IntPropertyObject { Int = 6 }, - }, - RealmValueDictionary = - { - ["dict_1"] = new IntPropertyObject { Int = 7 }, - ["dict_2"] = new IntPropertyObject { Int = 8 }, - ["dict_null"] = RealmValue.Null - } - }), - } - }; - - [TestCaseSource(nameof(RealmValueLinkTestCases))] - public void RealmObjectAPI_RealmValueLinks_AtlasToRealm(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var realmValCollection = await GetCollection(AppConfigType.FlexibleSync); - var intCollection = await GetCollection(AppConfigType.FlexibleSync); - - var obj = testCase.Value; - await realmValCollection.InsertOneAsync(obj); - - var elementsToInsert = obj.RealmValueList.Select(o => o.As()) - .Concat(obj.RealmValueSet.Select(o => o.As()) - .Concat(obj.RealmValueDictionary.Values.Select(o => o.As()).Where(v => v is not null))); - - if (obj.RealmValueProperty != RealmValue.Null) - { - elementsToInsert = elementsToInsert.Concat(new[] { obj.RealmValueProperty.As() }); - } - - await intCollection.InsertManyAsync(elementsToInsert); - - // How many objects we expect - var totalCount = obj.RealmValueList.Count + obj.RealmValueSet.Count + obj.RealmValueDictionary.Count(d => d.Value != RealmValue.Null); - - using var realm = await GetFLXIntegrationRealmAsync(); - var intObjs = await realm.All().SubscribeAsync(); - var realmObjs = await realm.All().SubscribeAsync(); - - await intObjs.WaitForEventAsync((sender, _) => sender.Count >= totalCount); - await realmObjs.WaitForEventAsync((sender, _) => sender.Count == 1 && realm.Find(obj.Id) != null); - - var realmValObj = realm.Find(obj.Id); - - AssertEqual(realmValObj!.RealmValueProperty, obj.RealmValueProperty); - - Assert.That(realmValObj.RealmValueList.Count, Is.EqualTo(obj.RealmValueList.Count)); - - for (int i = 0; i < realmValObj.RealmValueList.Count; i++) - { - AssertEqual(realmValObj.RealmValueList[i], obj.RealmValueList[i]); - } - - Assert.That(realmValObj.RealmValueDictionary.Count, Is.EqualTo(obj.RealmValueDictionary.Count)); - - foreach (var key in obj.RealmValueDictionary.Keys) - { - Assert.That(realmValObj.RealmValueDictionary.ContainsKey(key)); - AssertEqual(realmValObj.RealmValueDictionary[key], obj.RealmValueDictionary[key]); - } - - Assert.That(realmValObj.RealmValueSet.Count, Is.EqualTo(obj.RealmValueSet.Count)); - - var orderedOriginalSet = obj.RealmValueSet.OrderBy(a => a.As().Id).ToList(); - var orderedRetrievedSet = realmValObj.RealmValueSet.OrderBy(a => a.As().Id).ToList(); - - for (int i = 0; i < orderedOriginalSet.Count; i++) - { - AssertEqual(orderedRetrievedSet[i], orderedOriginalSet[i]); - } - - static void AssertEqual(RealmValue retrieved, RealmValue original) - { - if (original == RealmValue.Null) - { - Assert.That(retrieved, Is.EqualTo(RealmValue.Null)); - return; - } - - Assert.That(retrieved.Type, Is.EqualTo(RealmValueType.Object)); - Assert.That(original.Type, Is.EqualTo(RealmValueType.Object)); - - var retrievedAsObj = retrieved.As(); - var originalAsObj = original.As(); - - Assert.That(retrievedAsObj.Id, Is.EqualTo(originalAsObj.Id)); - Assert.That(retrievedAsObj.Int, Is.EqualTo(originalAsObj.Int)); - } - }, timeout: 120000); - } - - [TestCaseSource(nameof(RealmValueLinkTestCases))] - public void RealmObjectAPI_RealmValueLinks_RealmToAtlas(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var realmValCollection = await GetCollection(AppConfigType.FlexibleSync); - var intCollection = await GetCollection(AppConfigType.FlexibleSync); - - var obj = testCase.Value; - - using var realm = await GetFLXIntegrationRealmAsync(); - await realm.All().SubscribeAsync(); - await realm.All().SubscribeAsync(); - - realm.Write(() => realm.Add(obj)); - await WaitForUploadAsync(realm); - - var realmValObj = await WaitForNonNullObjectAsync(() => realmValCollection.FindOneAsync(new { _id = obj.Id })); - - await AssertEqual(intCollection, realmValObj.RealmValueProperty, obj.RealmValueProperty); - - for (int i = 0; i < realmValObj.RealmValueList.Count; i++) - { - await AssertEqual(intCollection, realmValObj.RealmValueList[i], obj.RealmValueList[i]); - } - - Assert.That(realmValObj.RealmValueDictionary.Count, Is.EqualTo(obj.RealmValueDictionary.Count)); - - foreach (var key in obj.RealmValueDictionary.Keys) - { - Assert.That(realmValObj.RealmValueDictionary.ContainsKey(key)); - await AssertEqual(intCollection, realmValObj.RealmValueDictionary[key], obj.RealmValueDictionary[key]); - } - - Assert.That(realmValObj.RealmValueSet.Count, Is.EqualTo(obj.RealmValueSet.Count)); - - var orderedOriginalSet = obj.RealmValueSet.OrderBy(a => a.As().Id).ToList(); - var orderedRetrievedSet = realmValObj.RealmValueSet.OrderBy(a => a.As().Id).ToList(); - - for (int i = 0; i < orderedOriginalSet.Count; i++) - { - await AssertEqual(intCollection, orderedRetrievedSet[i], orderedOriginalSet[i]); - } - - static async Task AssertEqual(MongoClient.Collection collection, RealmValue retrieved, RealmValue original) - { - if (original == RealmValue.Null) - { - Assert.That(retrieved, Is.EqualTo(RealmValue.Null)); - return; - } - - Assert.That(retrieved.Type, Is.EqualTo(RealmValueType.Object)); - Assert.That(original.Type, Is.EqualTo(RealmValueType.Object)); - - var retrievedAsObj = retrieved.As(); - var originalAsObj = original.As(); - - Assert.That(retrievedAsObj.Id, Is.EqualTo(originalAsObj.Id)); - Assert.That(retrievedAsObj.Int, Is.Not.EqualTo(originalAsObj.Int)); - - var fullyRetrieved = await WaitForNonNullObjectAsync(() => collection.FindOneAsync(new { _id = originalAsObj.Id })); - - Assert.That(fullyRetrieved.Id, Is.EqualTo(originalAsObj.Id)); - Assert.That(fullyRetrieved.Int, Is.EqualTo(originalAsObj.Int)); - } - }, timeout: 120000); - } - - public static readonly object[] EmbeddedTestCases = - { - new object[] - { - CreateTestCase("Single", new ObjectWithEmbeddedProperties - { - AllTypesObject = new() - { - BooleanProperty = true, - ByteArrayProperty = new byte[] { 1, 2, 3 }, - DoubleProperty = 3.14, - Int32Property = 4, - StringProperty = "bla bla" - } - }) - }, - new object[] - { - CreateTestCase("Recursive", new ObjectWithEmbeddedProperties - { - RecursiveObject = new() - { - String = "Top", - Child = new() - { - String = "Middle", - Child = new() - { - String = "Bottom" - } - } - } - }) - }, - - new object[] - { - CreateTestCase("List", new ObjectWithEmbeddedProperties - { - ListOfAllTypesObjects = - { - new() - { - BooleanProperty = true, - ByteArrayProperty = new byte[] { 1, 2, 3 }, - DoubleProperty = 3.14, - Int32Property = 4, - StringProperty = "bla bla" - }, - new() - { - BooleanProperty = false, - ByteArrayProperty = new byte[] { 4, 1, 2, 3 }, - DoubleProperty = 6.14, - Int32Property = 6, - StringProperty = "oh oh" - } - }, - }) - }, - new object[] - { - CreateTestCase("Dictionary", new ObjectWithEmbeddedProperties - { - DictionaryOfAllTypesObjects = - { - ["key1"] = new() - { - BooleanProperty = true, - ByteArrayProperty = new byte[] { 1, 2, 3 }, - DoubleProperty = 3.14, - Int32Property = 4, - StringProperty = "bla bla" - }, - ["key2"] = new() - { - BooleanProperty = false, - ByteArrayProperty = new byte[] { 4, 1, 2, 3 }, - DoubleProperty = 6.14, - Int32Property = 6, - StringProperty = "oh oh" - }, - ["key3"] = null, - }, - }) - }, - new object[] - { - CreateTestCase("All types", new ObjectWithEmbeddedProperties - { - AllTypesObject = new() - { - BooleanProperty = true, - ByteArrayProperty = new byte[] { 1, 2, 3 }, - DoubleProperty = 3.14, - Int32Property = 4, - StringProperty = "bla bla" - }, - RecursiveObject = new() - { - String = "Top", - Child = new() - { - String = "Middle", - Child = new() - { - String = "Bottom" - } - } - }, - ListOfAllTypesObjects = - { - new() - { - BooleanProperty = true, - ByteArrayProperty = new byte[] { 5, 1, 2, 3 }, - DoubleProperty = 6.78, - Int32Property = 2, - StringProperty = "blas blas" - }, - new() - { - BooleanProperty = false, - ByteArrayProperty = new byte[] { 4, 1, 2, 3 }, - DoubleProperty = 6.14, - Int32Property = 6, - StringProperty = "oh oh" - } - }, - DictionaryOfAllTypesObjects = - { - ["key1"] = new() - { - BooleanProperty = true, - ByteArrayProperty = new byte[] { 1, 6, 3 }, - DoubleProperty = 3.14, - Int32Property = 4, - StringProperty = "hej hej" - }, - ["key2"] = new() - { - BooleanProperty = false, - ByteArrayProperty = new byte[] { 4, 1, 6, 3 }, - DoubleProperty = 16.14, - Int32Property = 62, - StringProperty = "oha oha" - }, - ["key3"] = null, - }, - }) - } - }; - - [TestCaseSource(nameof(EmbeddedTestCases))] - public void RealmObjectAPI_Embedded_AtlasToRealm(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(AppConfigType.FlexibleSync); - var obj = testCase.Value; - - await collection.InsertOneAsync(obj); - - using var realm = await GetFLXIntegrationRealmAsync(); - var syncObjects = await realm.All().Where(o => o.PrimaryKey == obj.PrimaryKey).SubscribeAsync(); - await syncObjects.WaitForEventAsync((sender, _) => sender.Count > 0); - - var syncObj = syncObjects.Single(); - - AssertEmbedded(syncObj, obj); - }, timeout: 120000); - } - - [TestCaseSource(nameof(EmbeddedTestCases))] - public void RealmObjectAPI_Embedded_RealmToAtlas(TestCaseData testCase) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollection(AppConfigType.FlexibleSync); - var obj = testCase.Value; - - using var realm = await GetFLXIntegrationRealmAsync(); - await realm.All().SubscribeAsync(); - - realm.Write(() => realm.Add(obj)); - await WaitForUploadAsync(realm); - - var syncObj = await WaitForNonNullObjectAsync(() => collection.FindOneAsync(new { _id = obj.PrimaryKey })); - - AssertEmbedded(syncObj, obj); - }, timeout: 120000); - } - - [Test] - public void RealmObjectAPI_ExtraFields_AtlasToRealm() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollectionAsBson(nameof(PrimaryKeyStringObject), AppConfigType.FlexibleSync); - - const string primaryKey = "primaryKeyVal"; - - var doc = new BsonDocument - { - { "_id", primaryKey }, - { "Value", "myVal" }, - { "ExtraField", "extraFieldVal" }, - { "ExtraField2", new BsonDocument { { "inner", 22 } } }, - { "ExtraField3", new BsonArray(new[] { 1, 2, 3, 4 }) }, - }; - - await collection.InsertOneAsync(doc); - - using var realm = await GetFLXIntegrationRealmAsync(); - var syncObjects = await realm.All().Where(o => o.Id == primaryKey).SubscribeAsync(); - await syncObjects.WaitForEventAsync((sender, _) => sender.Count > 0); - - var syncObj = syncObjects.Single(); - - Assert.That(syncObj, Is.Not.Null); - Assert.That(syncObj.Value, Is.EqualTo(doc["Value"].AsString)); - }, timeout: 120000); - } - - [Test] - public void RealmObjectAPI_ExtraFields_IgnoredWhenUsingTypedCollection() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var app = App.Create(SyncTestHelpers.GetAppConfig(AppConfigType.FlexibleSync)); - var user = await GetUserAsync(app); - - var config = GetFLXIntegrationConfig(user); - - using var realm = await GetRealmAsync(config); - var client = user.GetMongoClient(ServiceName); - var db = client.GetDatabase(SyncTestHelpers.SyncMongoDBName(AppConfigType.FlexibleSync)); - - const string collectionName = "test"; - var bsonCollection = db.GetCollection(collectionName); - await bsonCollection.DeleteManyAsync(new object()); - - const string primaryKey = "primaryKeyVal"; - - var doc = new BsonDocument - { - { "_id", primaryKey }, - { "Value", "myVal" }, - { "ExtraField", "extraFieldVal" }, - { "ExtraField2", new BsonDocument { { "inner", 22 } } }, - { "ExtraField3", new BsonArray(new[] { 1, 2, 3, 4 }) }, - }; - - await bsonCollection.InsertOneAsync(doc); - - var typedCollection = db.GetCollection(collectionName); - - var retrieved = await typedCollection.FindOneAsync(new { _id = primaryKey }); - - Assert.That(retrieved, Is.Not.Null); - Assert.That(retrieved!.Value, Is.EqualTo(doc["Value"].AsString)); - }, timeout: 120000); - } - - [Test] - public void RealmObjectAPI_MismatchedType_ThrowsOnInsertWhenCollectionInSchema() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollectionAsBson(nameof(PrimaryKeyStringObject), AppConfigType.FlexibleSync); - - const string primaryKey = "primaryKeyVal"; - - var doc = new BsonDocument - { - { "_id", primaryKey }, - { "Value", ObjectId.GenerateNewId() }, // Wrong type - }; - - var ex = await AssertThrows(() => collection.InsertOneAsync(doc)); - Assert.That(ex.Message, Does.Contain("insert not permitted")); - }, timeout: 120000); - } - - [Test] - public void RealmObjectAPI_MismatchedType_ThrowsWhenDeserialized() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var app = App.Create(SyncTestHelpers.GetAppConfig(AppConfigType.FlexibleSync)); - var user = await GetUserAsync(app); - - var config = GetFLXIntegrationConfig(user); - - using var realm = await GetRealmAsync(config); - var client = user.GetMongoClient(ServiceName); - var db = client.GetDatabase(SyncTestHelpers.SyncMongoDBName(AppConfigType.FlexibleSync)); - - const string collectionName = "test"; - var bsonCollection = db.GetCollection(collectionName); - await bsonCollection.DeleteManyAsync(new object()); - - const string primaryKey = "primaryKeyVal"; - - var doc = new BsonDocument - { - { "_id", primaryKey }, - { "Value", ObjectId.GenerateNewId() }, // Wrong type - }; - - await bsonCollection.InsertOneAsync(doc); - - var typedCollection = db.GetCollection(collectionName); - - var ex = await TestHelpers.AssertThrows(() => typedCollection.FindOneAsync(new { _id = primaryKey })); - Assert.That(ex.Message, Does.Contain("Error while deserializing property Value: Cannot deserialize a 'String' from BsonType 'ObjectId'")); - }, timeout: 120000); - } - - [Test] - public void RealmObjectAPI_MissingField_ThrowsOnInsertWhenCollectionInSchema() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var collection = await GetCollectionAsBson(nameof(IntPropertyObject), AppConfigType.FlexibleSync); - - var primaryKey = ObjectId.GenerateNewId(); - - var doc = new BsonDocument - { - { "_id", primaryKey }, - { "Int", 23 }, // Missing the GuidProperty field - }; - - var ex = await AssertThrows(() => collection.InsertOneAsync(doc)); - Assert.That(ex.Message, Does.Contain("insert not permitted")); - }, timeout: 120000); - } - - [Test] - public void RealmObjectAPI_MissingField_GetsDefaultValueWhenDeserialized() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var app = App.Create(SyncTestHelpers.GetAppConfig(AppConfigType.FlexibleSync)); - var user = await GetUserAsync(app); - - var config = GetFLXIntegrationConfig(user); - - using var realm = await GetRealmAsync(config); - var client = user.GetMongoClient(ServiceName); - var db = client.GetDatabase(SyncTestHelpers.SyncMongoDBName(AppConfigType.FlexibleSync)); - - const string collectionName = "test"; - var bsonCollection = db.GetCollection(collectionName); - await bsonCollection.DeleteManyAsync(new object()); - - var primaryKey = ObjectId.GenerateNewId(); - - var doc = new BsonDocument - { - { "_id", primaryKey }, - { "Int", 23 }, // Missing the GuidProperty field - }; - - await bsonCollection.InsertOneAsync(doc); - - var typedCollection = db.GetCollection(collectionName); - var retrieved = await typedCollection.FindOneAsync(new { _id = primaryKey }); - - Assert.That(retrieved, Is.Not.Null); - Assert.That(retrieved!.Id, Is.EqualTo(primaryKey)); - Assert.That(retrieved.Int, Is.EqualTo(doc["Int"].AsInt32)); - Assert.That(retrieved.GuidProperty, Is.EqualTo(default(Guid))); - }, timeout: 120000); - } - - // Retrieves the MongoClient.Collection for a specific object type and removes everything that's eventually there already - private async Task> GetCollection(string appConfigType = AppConfigType.Default) - where T : class, IRealmObjectBase - { - var app = App.Create(SyncTestHelpers.GetAppConfig(appConfigType)); - var user = await GetUserAsync(app); - - SyncConfigurationBase config = appConfigType == AppConfigType.FlexibleSync ? GetFLXIntegrationConfig(user) : GetIntegrationConfig(user); - - using var realm = await GetRealmAsync(config, true); - var client = user.GetMongoClient(ServiceName); - var collection = client.GetCollection(); - await collection.DeleteManyAsync(new object()); - - return collection; - } - - private async Task> GetCollectionAsBson(string collectionName, string appConfigType = AppConfigType.Default) - { - var app = App.Create(SyncTestHelpers.GetAppConfig(appConfigType)); - var user = await GetUserAsync(app); - - SyncConfigurationBase config = appConfigType == AppConfigType.FlexibleSync ? GetFLXIntegrationConfig(user) : GetIntegrationConfig(user); - - using var realm = await GetRealmAsync(config, true); - var client = user.GetMongoClient(ServiceName); - var db = client.GetDatabase(SyncTestHelpers.SyncMongoDBName(appConfigType)); - - var collection = db.GetCollection(collectionName); - await collection.DeleteManyAsync(new object()); - - return collection; - } - - private static void AssertEmbedded(ObjectWithEmbeddedProperties syncObj, ObjectWithEmbeddedProperties obj) - { - var props = EmbeddedAllTypesObject.RealmSchema - .Where(p => !p.Type.HasFlag(PropertyType.Object) && !p.Type.IsComputed()) - .ToArray(); - - AssertEquals(syncObj.AllTypesObject, obj.AllTypesObject); - - Assert.That(syncObj.ListOfAllTypesObjects.Count, Is.EqualTo(obj.ListOfAllTypesObjects.Count)); - - for (int i = 0; i < obj.ListOfAllTypesObjects.Count; i++) - { - AssertEquals(syncObj.ListOfAllTypesObjects[i], obj.ListOfAllTypesObjects[i]); - } - - Assert.That(syncObj.DictionaryOfAllTypesObjects.Count, Is.EqualTo(obj.DictionaryOfAllTypesObjects.Count)); - - foreach (var key in obj.DictionaryOfAllTypesObjects.Keys) - { - Assert.That(syncObj.DictionaryOfAllTypesObjects.ContainsKey(key)); - AssertEquals(syncObj.DictionaryOfAllTypesObjects[key], obj.DictionaryOfAllTypesObjects[key]); - } - - if (obj.RecursiveObject is null) - { - Assert.That(syncObj.RecursiveObject, Is.Null); - } - else - { - // For simplicity, if the top level is not null, then we assume the lower levels are not null either. - Assert.That(syncObj.RecursiveObject, Is.Not.Null); - - Assert.That(syncObj.RecursiveObject!.String, Is.EqualTo(obj.RecursiveObject.String)); - Assert.That(syncObj.RecursiveObject.Child!.String, Is.EqualTo(obj.RecursiveObject.Child!.String)); - Assert.That(syncObj.RecursiveObject.Child.Child!.String, Is.EqualTo(obj.RecursiveObject.Child.Child!.String)); - } - - void AssertEquals(EmbeddedAllTypesObject? retrieved, EmbeddedAllTypesObject? original) - { - if (original is null) - { - Assert.That(retrieved, Is.Null); - } - else - { - Assert.That(retrieved, Is.Not.Null); - AssertProps(props, original, retrieved!); - } - } - } - - private static void AssertProps(IEnumerable props, IRealmObjectBase expected, IRealmObjectBase actual) - { - foreach (var prop in props) - { - var expectedProp = expected.GetProperty(prop); - var actualProp = actual.GetProperty(prop); - - AssertAreEqual(actualProp, expectedProp, $"property: {prop.Name}"); - } - } - } -#endif -} diff --git a/Tests/Realm.Tests/Sync/SyncConfigurationTests.cs b/Tests/Realm.Tests/Sync/SyncConfigurationTests.cs deleted file mode 100644 index 640da71377..0000000000 --- a/Tests/Realm.Tests/Sync/SyncConfigurationTests.cs +++ /dev/null @@ -1,146 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using NUnit.Framework; -using Realms.Sync; - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class SyncConfigurationTests : SyncTestBase - { - [Test] - public void SyncConfiguration_WithoutPath() - { - var config = GetFakeConfig(); - - var file = new FileInfo(config.DatabasePath); - Assert.That(file.Exists, Is.False); - - using (var realm = GetRealm(config)) - { - } - - file = new FileInfo(config.DatabasePath); - Assert.That(file.Exists); - } - - [Test] - public void SyncConfiguration_WithRelativePath() - { - var config = GetFakeConfig(optionalPath: "myrealm.realm"); - - var file = new FileInfo(config.DatabasePath); - Assert.That(file.Exists, Is.False); - - using (var realm = GetRealm(config)) - { - } - - file = new FileInfo(config.DatabasePath); - Assert.That(file.Exists); - Assert.That(config.DatabasePath.EndsWith("myrealm.realm")); - } - - [Test] - public void SyncConfiguration_WithAbsolutePath() - { - var path = Path.Combine(InteropConfig.GetDefaultStorageFolder("No error expected here"), Guid.NewGuid().ToString()); - var config = GetFakeConfig(optionalPath: path); - - Realm.DeleteRealm(config); - var file = new FileInfo(config.DatabasePath); - Assert.That(file.Exists, Is.False); - - using (var realm = GetRealm(config)) - { - } - - file = new FileInfo(config.DatabasePath); - Assert.That(file.Exists); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && TestHelpers.IsUnity) - { - // Unity on Windows has a peculiar behavior where Path.Combine will generate paths with - // forward slashes rather than backslashes. - path = path.Replace('/', '\\'); - } - - Assert.That(config.DatabasePath, Is.EqualTo(path)); - } - - [Test] - public void Test_SyncConfigRelease() - { - WeakReference weakConfigRef = null!; - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - weakConfigRef = new WeakReference(config); - using var realm = await Realm.GetInstanceAsync(config); - var session = realm.SyncSession; - Assert.That(weakConfigRef.Target, Is.Not.Null); - }); - - TearDown(); - - for (var i = 0; i < 50; i++) - { - GC.Collect(); - if (!weakConfigRef.IsAlive) - { - break; - } - - Task.Delay(100).Wait(); - } - - Assert.That(weakConfigRef.Target, Is.Null); - } - - [Test] - public void SyncConfiguration_WithEncryptionKey_DoesntThrow() - { - var key = Enumerable.Range(0, 63).Select(i => (byte)i).ToArray(); - - var config = GetFakeConfig(); - config.EncryptionKey = TestHelpers.GetEncryptionKey(key); - - Assert.That(() => GetRealm(config), Throws.Nothing); - } - - [Test] - public void SyncConfiguration_CanBeSetAsRealmConfigurationDefault() - { - var config = GetFakeConfig(); - RealmConfiguration.DefaultConfiguration = config; - - var realm = GetRealm(); - - Assert.That(realm.Config, Is.TypeOf()); - var syncConfig = (PartitionSyncConfiguration)realm.Config; - Assert.That(syncConfig.User.Id, Is.EqualTo(config.User.Id)); - Assert.That(syncConfig.Partition, Is.EqualTo(config.Partition)); - } - } -} diff --git a/Tests/Realm.Tests/Sync/SyncMigrationTests.cs b/Tests/Realm.Tests/Sync/SyncMigrationTests.cs deleted file mode 100644 index cd1bc86343..0000000000 --- a/Tests/Realm.Tests/Sync/SyncMigrationTests.cs +++ /dev/null @@ -1,365 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2024 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Linq; -using System.Threading.Tasks; -using Baas; -using MongoDB.Bson; -using NUnit.Framework; -using Realms.Sync; -using Realms.Sync.Exceptions; - -namespace Realms.Tests.Sync -{ - // The model must match BaasClient.Schemas.Nullables - [MapTo("Nullables"), Explicit] - public partial class NullablesV0 : IRealmObject - { - [PrimaryKey, MapTo("_id")] - public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); - - public ObjectId Differentiator { get; set; } - - public bool? BoolValue { get; set; } - - public int? IntValue { get; set; } - - public double? DoubleValue { get; set; } - - public Decimal128? DecimalValue { get; set; } - - public DateTimeOffset? DateValue { get; set; } - - public string? StringValue { get; set; } - - public ObjectId? ObjectIdValue { get; set; } - - public Guid? UuidValue { get; set; } - - public byte[]? BinaryValue { get; set; } - } - - [MapTo("Nullables"), Explicit] - public partial class NullablesV1 : IRealmObject - { - [PrimaryKey, MapTo("_id")] - public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); - - public ObjectId Differentiator { get; set; } - - public bool BoolValue { get; set; } - - public int IntValue { get; set; } - - public double DoubleValue { get; set; } - - public Decimal128 DecimalValue { get; set; } - - public DateTimeOffset DateValue { get; set; } - - public string StringValue { get; set; } = string.Empty; - - public ObjectId ObjectIdValue { get; set; } - - public Guid UuidValue { get; set; } - - public byte[] BinaryValue { get; set; } = Array.Empty(); - - public string WillBeRemoved { get; set; } = string.Empty; - } - - [TestFixture, Preserve(AllMembers = true)] - public class SyncMigrationTests : SyncTestBase - { - private async Task OpenRealm(ObjectId differentiator, Type schema, ulong schemaVersion) - { - var app = App.Create(SyncTestHelpers.GetAppConfig(AppConfigType.StaticSchema)); - var config = await GetFLXIntegrationConfigAsync(app); - config.SchemaVersion = schemaVersion; - config.Schema = new[] - { - schema - }; - - config.PopulateInitialSubscriptions = (r) => - { - r.Subscriptions.Add(r.DynamicApi.All("Nullables").Filter("Differentiator == $0", differentiator)); - }; - - var realm = GetRealm(config); - - await WaitForSubscriptionsAsync(realm); - - return realm; - } - - [Test, Ignore("TODO: restore once https://mongodb.slack.com/archives/C04NACGT7J7/p1716383151806129 is resolved")] - public void Model_CanMigratePropertyOptionality() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var differentiator = ObjectId.GenerateNewId(); - var date = new DateTimeOffset(2015, 12, 13, 11, 24, 59, TimeSpan.Zero); - var oid = ObjectId.GenerateNewId(); - var uuid = Guid.NewGuid(); - var binary = new byte[] - { - 1, 2, 3 - }; - - var realmv0 = await OpenRealm(differentiator, typeof(NullablesV0), schemaVersion: 0); - var objv0 = realmv0.Write(() => realmv0.Add(new NullablesV0 - { - BoolValue = true, - DateValue = date, - DecimalValue = 324.987m, - DoubleValue = -999.87654321, - IntValue = 42, - ObjectIdValue = oid, - StringValue = "bla bla", - UuidValue = uuid, - BinaryValue = binary, - Differentiator = differentiator, - })); - - await WaitForUploadAsync(realmv0); - - var realmv1 = await OpenRealm(differentiator, typeof(NullablesV1), schemaVersion: 1); - var objv1 = realmv1.All().Single(); - - Assert.That(objv1.BoolValue, Is.EqualTo(true)); - Assert.That(objv1.DateValue, Is.EqualTo(date)); - Assert.That(objv1.DecimalValue, Is.EqualTo(new Decimal128(324.987m))); - Assert.That(objv1.DoubleValue, Is.EqualTo(-999.87654321)); - Assert.That(objv1.IntValue, Is.EqualTo(42)); - Assert.That(objv1.ObjectIdValue, Is.EqualTo(oid)); - Assert.That(objv1.StringValue, Is.EqualTo("bla bla")); - Assert.That(objv1.UuidValue, Is.EqualTo(uuid)); - Assert.That(objv1.BinaryValue, Is.EqualTo(binary)); - Assert.That(objv1.WillBeRemoved, Is.EqualTo(string.Empty)); - - var realmv2 = await OpenRealm(differentiator, typeof(NullablesV0), schemaVersion: 2); - var objv2 = realmv2.All().Single(); - - Assert.That(objv2.BoolValue, Is.EqualTo(true)); - Assert.That(objv2.DateValue, Is.EqualTo(date)); - Assert.That(objv2.DecimalValue, Is.EqualTo(new Decimal128(324.987m))); - Assert.That(objv2.DoubleValue, Is.EqualTo(-999.87654321)); - Assert.That(objv2.IntValue, Is.EqualTo(42)); - Assert.That(objv2.ObjectIdValue, Is.EqualTo(oid)); - Assert.That(objv2.StringValue, Is.EqualTo("bla bla")); - Assert.That(objv2.UuidValue, Is.EqualTo(uuid)); - Assert.That(objv2.BinaryValue, Is.EqualTo(binary)); - - realmv0.Write(() => - { - objv0.BoolValue = null; - objv0.DateValue = null; - objv0.DecimalValue = null; - objv0.DoubleValue = null; - objv0.IntValue = null; - objv0.ObjectIdValue = null; - objv0.StringValue = null; - objv0.UuidValue = null; - objv0.BinaryValue = null; - }); - - await WaitForUploadAsync(realmv0); - await WaitForDownloadAsync(realmv1); - await WaitForDownloadAsync(realmv2); - - Assert.That(objv1.BoolValue, Is.EqualTo(false)); - Assert.That(objv1.DateValue, Is.EqualTo(new DateTimeOffset(1, 1, 1, 0, 0, 0, TimeSpan.Zero))); - Assert.That(objv1.DecimalValue, Is.EqualTo(Decimal128.Zero)); - Assert.That(objv1.DoubleValue, Is.EqualTo(0)); - Assert.That(objv1.IntValue, Is.EqualTo(0)); - Assert.That(objv1.ObjectIdValue, Is.EqualTo(ObjectId.Empty)); - Assert.That(objv1.StringValue, Is.EqualTo(string.Empty)); - Assert.That(objv1.UuidValue, Is.EqualTo(Guid.Empty)); - Assert.That(objv1.BinaryValue, Is.EqualTo(Array.Empty())); - Assert.That(objv1.WillBeRemoved, Is.EqualTo(string.Empty)); - - Assert.That(objv2.BoolValue, Is.Null); - Assert.That(objv2.DateValue, Is.Null); - Assert.That(objv2.DecimalValue, Is.Null); - Assert.That(objv2.DoubleValue, Is.Null); - Assert.That(objv2.IntValue, Is.Null); - Assert.That(objv2.ObjectIdValue, Is.Null); - Assert.That(objv2.StringValue, Is.Null); - Assert.That(objv2.UuidValue, Is.Null); - Assert.That(objv2.BinaryValue, Is.Null); - }); - } - - [Test] - public void Model_CanRemoveField() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var differentiator = ObjectId.GenerateNewId(); - var realmv1 = await OpenRealm(differentiator, typeof(NullablesV1), schemaVersion: 1); - - var objv1 = realmv1.Write(() => realmv1.Add(new NullablesV1 - { - Differentiator = differentiator, - BoolValue = true, - DateValue = DateTimeOffset.UtcNow, - DecimalValue = Decimal128.MaxValue, - DoubleValue = 5.555, - IntValue = 123, - ObjectIdValue = ObjectId.GenerateNewId(), - StringValue = "foo bar", - UuidValue = Guid.NewGuid(), - BinaryValue = Array.Empty(), - WillBeRemoved = "this should go away!" - })); - - Assert.That(objv1.WillBeRemoved, Is.EqualTo("this should go away!")); - - await WaitForUploadAsync(realmv1); - - var realmv2 = await OpenRealm(differentiator, typeof(NullablesV0), schemaVersion: 2); - var id2 = realmv2.Write(() => realmv2.Add(new NullablesV0 - { - Differentiator = differentiator - })).Id; - - await WaitForUploadAsync(realmv2); - await WaitForDownloadAsync(realmv1); - - var objv2 = realmv1.Find(id2)!; - Assert.That(objv2.WillBeRemoved, Is.EqualTo(string.Empty)); - }); - } - - [Test] - public void Migration_FailsWithFutureVersion() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var ex = await TestHelpers.AssertThrows(() => OpenRealm(ObjectId.GenerateNewId(), typeof(NullablesV0), schemaVersion: 3)); - Assert.That(ex.Message, Does.Contain("Client provided invalid schema version: client presented schema version \"3\" is greater than latest schema version \"2\"")); - }); - } - - [Test, Ignore("TODO: restore once https://mongodb.slack.com/archives/C04NACGT7J7/p1716383151806129 is resolved")] - public void SameRealm_CanBeMigratedThroughConsecutiveVersions() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var differentiator = ObjectId.GenerateNewId(); - var realm = await OpenRealm(differentiator, typeof(NullablesV0), schemaVersion: 0); - var id = ObjectId.GenerateNewId(); - realm.Write(() => realm.Add(new NullablesV0 - { - Id = id, - Differentiator = differentiator - })); - - realm.Dispose(); - - var configv1 = realm.Config.Clone(); - - configv1.SchemaVersion = 1; - configv1.Schema = new[] - { - typeof(NullablesV1) - }; - - realm = await GetRealmAsync(configv1); - - var objv1 = realm.All().Single(); - - Assert.That(objv1.Id, Is.EqualTo(id)); - Assert.That(objv1.Differentiator, Is.EqualTo(differentiator)); - Assert.That(objv1.BoolValue, Is.EqualTo(false)); - Assert.That(objv1.DateValue, Is.EqualTo(new DateTimeOffset(1, 1, 1, 0, 0, 0, TimeSpan.Zero))); - Assert.That(objv1.DecimalValue, Is.EqualTo(Decimal128.Zero)); - Assert.That(objv1.DoubleValue, Is.EqualTo(0)); - Assert.That(objv1.IntValue, Is.EqualTo(0)); - Assert.That(objv1.ObjectIdValue, Is.EqualTo(ObjectId.Empty)); - Assert.That(objv1.StringValue, Is.EqualTo(string.Empty)); - Assert.That(objv1.UuidValue, Is.EqualTo(Guid.Empty)); - Assert.That(objv1.BinaryValue, Is.EqualTo(Array.Empty())); - Assert.That(objv1.WillBeRemoved, Is.EqualTo(string.Empty)); - - realm.Dispose(); - - var configv2 = realm.Config.Clone(); - - configv2.SchemaVersion = 2; - configv2.Schema = new[] - { - typeof(NullablesV0) - }; - - realm = await GetRealmAsync(configv2); - var objv2 = realm.All().Single(); - - Assert.That(objv2.Id, Is.EqualTo(id)); - Assert.That(objv2.Differentiator, Is.EqualTo(differentiator)); - Assert.That(objv2.BoolValue, Is.Null); - Assert.That(objv2.DateValue, Is.Null); - Assert.That(objv2.DecimalValue, Is.Null); - Assert.That(objv2.DoubleValue, Is.Null); - Assert.That(objv2.IntValue, Is.Null); - Assert.That(objv2.ObjectIdValue, Is.Null); - Assert.That(objv2.StringValue, Is.Null); - Assert.That(objv2.UuidValue, Is.Null); - Assert.That(objv2.BinaryValue, Is.Null); - }); - } - - [Test, Ignore("TODO: restore once https://mongodb.slack.com/archives/C04NACGT7J7/p1716383151806129 is resolved")] - public void SameRealm_CanBeMigratedSkippingVersions() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var differentiator = ObjectId.GenerateNewId(); - var realm = await OpenRealm(differentiator, typeof(NullablesV0), schemaVersion: 0); - var id = ObjectId.GenerateNewId(); - realm.Write(() => realm.Add(new NullablesV0 - { - Id = id, - Differentiator = differentiator - })); - - realm.Dispose(); - - var configv2 = realm.Config.Clone(); - configv2.SchemaVersion = 2; - - realm = await GetRealmAsync(configv2); - var objv2 = realm.All().Single(); - - Assert.That(objv2.Id, Is.EqualTo(id)); - Assert.That(objv2.Differentiator, Is.EqualTo(differentiator)); - Assert.That(objv2.BoolValue, Is.Null); - Assert.That(objv2.DateValue, Is.Null); - Assert.That(objv2.DecimalValue, Is.Null); - Assert.That(objv2.DoubleValue, Is.Null); - Assert.That(objv2.IntValue, Is.Null); - Assert.That(objv2.ObjectIdValue, Is.Null); - Assert.That(objv2.StringValue, Is.Null); - Assert.That(objv2.UuidValue, Is.Null); - Assert.That(objv2.BinaryValue, Is.Null); - }); - } - } -} diff --git a/Tests/Realm.Tests/Sync/SyncTestBase.cs b/Tests/Realm.Tests/Sync/SyncTestBase.cs deleted file mode 100644 index 57e862fcdb..0000000000 --- a/Tests/Realm.Tests/Sync/SyncTestBase.cs +++ /dev/null @@ -1,319 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Concurrent; -using System.Threading; -using System.Threading.Tasks; -using Baas; -using MongoDB.Bson; -using Realms.Schema; -using Realms.Sync; -using Realms.Sync.Exceptions; -using static Realms.Tests.TestHelpers; - -namespace Realms.Tests.Sync -{ - [Preserve(AllMembers = true)] - public abstract class SyncTestBase : RealmTest - { - private readonly ConcurrentQueue> _sessions = new(); - private readonly ConcurrentQueue> _apps = new(); - private readonly ConcurrentQueue> _clientResetAppsToRestore = new(); - - protected App DefaultApp => CreateApp(); - - protected App CreateApp(AppConfiguration? config = null) - { - config ??= SyncTestHelpers.GetAppConfig(); - - var app = App.Create(config); - _apps.Enqueue(app); - - return app; - } - - protected override void CustomTearDown() - { - _sessions.DrainQueue(session => session?.CloseHandle()); - - base.CustomTearDown(); - - _apps.DrainQueue(app => - { - if (!app.Handle.IsClosed) - { - app.Handle.ResetForTesting(); - } - }); - - _clientResetAppsToRestore.DrainQueueAsync(appConfigType => SyncTestHelpers.SetRecoveryModeOnServer(appConfigType, enabled: true)); - } - - protected void CleanupOnTearDown(Session session) - { - _sessions.Enqueue(session); - } - - protected Session GetSession(Realm realm) - { - var result = realm.SyncSession; - CleanupOnTearDown(result); - return result; - } - - protected static async Task WaitForUploadAsync(Realm realm) - { - var session = realm.SyncSession; - await session.WaitForUploadAsync(); - session.CloseHandle(); - } - - protected static async Task WaitForDownloadAsync(Realm realm) - { - var session = realm.SyncSession; - await session.WaitForDownloadAsync(); - session.CloseHandle(); - } - - // TODO: this method should go away once https://github.com/realm/realm-core/issues/5705 is resolved. - protected static async Task WaitForSubscriptionsAsync(Realm realm) - { - await realm.Subscriptions.WaitForSynchronizationAsync(); - await WaitForDownloadAsync(realm); - } - - protected static async Task WaitForObjectAsync(T obj, Realm realm2, string? message = null) - where T : IRealmObject - { - var id = obj.DynamicApi.Get("_id"); - - return (await WaitForConditionAsync(() => realm2.FindCore(id), o => o != null, errorMessage: message))!; - } - - protected async Task GetUserAsync(App? app = null, string? username = null, string? password = null) - { - app ??= DefaultApp; - username ??= SyncTestHelpers.GetVerifiedUsername(); - password ??= SyncTestHelpers.DefaultPassword; - await app.EmailPasswordAuth.RegisterUserAsync(username, password).Timeout(10_000, detail: "Failed to register user"); - var credentials = Credentials.EmailPassword(username, password); - - for (var i = 0; i < 5; i++) - { - try - { - return await app.LogInAsync(credentials).Timeout(10_000, "Failed to login user"); - } - catch (AppException ex) when (ex.Message.Contains("confirmation required")) - { - } - } - - throw new Exception("Could not login user after 5 attempts."); - } - - protected User GetFakeUser(App? app = null, string? id = null, string? refreshToken = null, string? accessToken = null) - { - app ??= DefaultApp; - id ??= Guid.NewGuid().ToString(); - refreshToken ??= "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoicmVmcmVzaCB0b2tlbiIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoyNTM2MjM5MDIyfQ.SWH98a-UYBEoJ7DLxpP7mdibleQFeCbGt4i3CrsyT2M"; - accessToken ??= "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWNjZXNzIHRva2VuIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MzYyMzkwMjJ9.bgnlxP_mGztBZsImn7HaF-6lDevFDn2U_K7D8WUC2GQ"; - var handle = app.Handle.GetUserForTesting(id, refreshToken, accessToken); - return new User(handle, app); - } - - // This could be useful when opening a "fake" sync realm locally - protected void SetFakeSyncRoute(App? app) - { - app ??= DefaultApp; - app.Handle.SetFakeSyncRouteForTesting(); - } - - protected async Task GetIntegrationRealmAsync(string? partition = null, App? app = null, int timeout = 10000) - { - var config = await GetIntegrationConfigAsync(partition, app); - return await GetRealmAsync(config, timeout); - } - - protected async Task GetIntegrationConfigAsync(string? partition = null, App? app = null, string? optionalPath = null, User? user = null) - { - app ??= DefaultApp; - partition ??= Guid.NewGuid().ToString(); - - user ??= await GetUserAsync(app); - return UpdateConfig(new PartitionSyncConfiguration(partition, user, optionalPath)); - } - - protected async Task GetIntegrationConfigAsync(long? partition, App? app = null, string? optionalPath = null) - { - app ??= App.Create(SyncTestHelpers.GetAppConfig(AppConfigType.IntPartitionKey)); - - var user = await GetUserAsync(app); - return UpdateConfig(new PartitionSyncConfiguration(partition, user, optionalPath)); - } - - protected static PartitionSyncConfiguration GetIntegrationConfig(User user, string? partition = null, string? optionalPath = null) - { - partition ??= Guid.NewGuid().ToString(); - return UpdateConfig(new PartitionSyncConfiguration(partition, user, optionalPath)); - } - - protected async Task GetIntegrationConfigAsync(ObjectId? partition, App? app = null, string? optionalPath = null) - { - app ??= App.Create(SyncTestHelpers.GetAppConfig(AppConfigType.ObjectIdPartitionKey)); - - var user = await GetUserAsync(app); - return UpdateConfig(new PartitionSyncConfiguration(partition, user, optionalPath)); - } - - protected async Task GetIntegrationConfigAsync(Guid? partition, App? app = null, string? optionalPath = null) - { - app ??= App.Create(SyncTestHelpers.GetAppConfig(AppConfigType.UUIDPartitionKey)); - - var user = await GetUserAsync(app); - return UpdateConfig(new PartitionSyncConfiguration(partition, user, optionalPath)); - } - - protected async Task GetFLXIntegrationConfigAsync(App? app = null, string? optionalPath = null) - { - app ??= App.Create(SyncTestHelpers.GetAppConfig(AppConfigType.FlexibleSync)); - var user = await GetUserAsync(app); - return GetFLXIntegrationConfig(user, optionalPath); - } - - protected static FlexibleSyncConfiguration GetFLXIntegrationConfig(User user, string? optionalPath = null) - { - return UpdateConfig(new FlexibleSyncConfiguration(user, optionalPath)); - } - - protected async Task GetFLXIntegrationRealmAsync(App? app = null) - { - var config = await GetFLXIntegrationConfigAsync(app); - return await GetRealmAsync(config); - } - - protected async Task DisableClientResetRecoveryOnServer(string appConfigType) - { - await SyncTestHelpers.SetRecoveryModeOnServer(appConfigType, false); - _clientResetAppsToRestore.Enqueue(appConfigType); - } - - protected async Task GetRealmAsync(SyncConfigurationBase config, bool waitForSync = false, int timeout = 10000, CancellationToken cancellationToken = default) - { - var realm = await GetRealmAsync(config, timeout, cancellationToken); - if (waitForSync) - { - await WaitForUploadAsync(realm); - } - - return realm; - } - - private static T UpdateConfig(T config) - where T : SyncConfigurationBase - { - var schema = new RealmSchema.Builder() - { - typeof(HugeSyncObject), - typeof(PrimaryKeyStringObject), - typeof(ObjectIdPrimaryKeyWithValueObject), - typeof(SyncCollectionsObject), - typeof(IntPropertyObject), - typeof(EmbeddedIntPropertyObject), - typeof(SyncAllTypesObject), - typeof(ObjectWithPartitionValue), - typeof(RemappedTypeObject), - }; - - if (config is FlexibleSyncConfiguration) - { - // We need to add all objects ever used by sync to the flx schema due to the way breaking schema changes work - // in dev mode. When a client connects with a subset of the server schema, they'll experience client reset as the - // server removes the missing tables and re-bootstraps. - schema.Add(typeof(BasicAsymmetricObject)); - schema.Add(typeof(AsymmetricObjectWithAllTypes)); - schema.Add(typeof(AsymmetricObjectWithEmbeddedRecursiveObject)); - schema.Add(typeof(EmbeddedLevel1)); - schema.Add(typeof(EmbeddedLevel2)); - schema.Add(typeof(EmbeddedLevel3)); - schema.Add(typeof(RealmValueObject)); - schema.Add(typeof(AsymmetricObjectWithEmbeddedDictionaryObject)); - schema.Add(typeof(AsymmetricObjectWithEmbeddedListObject)); - schema.Add(typeof(PrimaryKeyInt32Object)); - schema.Add(typeof(CounterObject)); - schema.Add(typeof(LinksObject)); - schema.Add(typeof(ObjectWithEmbeddedProperties)); - schema.Add(typeof(EmbeddedAllTypesObject)); - } - - config.Schema = schema; - config.SessionStopPolicy = SessionStopPolicy.Immediately; - - return config; - } - - protected PartitionSyncConfiguration GetFakeConfig(App? app = null, string? userId = null, - string? optionalPath = null, bool setFakeSyncRoute = true) - { - var user = GetFakeUser(app, userId); - - if (setFakeSyncRoute) - { - SetFakeSyncRoute(user.App); - } - - return UpdateConfig(new PartitionSyncConfiguration(Guid.NewGuid().ToString(), user, optionalPath)); - } - - protected FlexibleSyncConfiguration GetFakeFLXConfig(App? app = null, string? userId = null, - string? optionalPath = null, bool setFakeSyncRoute = true) - { - var user = GetFakeUser(app, userId); - - if (setFakeSyncRoute) - { - SetFakeSyncRoute(user.App); - } - - return UpdateConfig(new FlexibleSyncConfiguration(user, optionalPath)); - } - - protected async Task TriggerClientReset(Realm realm, bool restartSession = true) - { - if (realm.Config is not SyncConfigurationBase syncConfig) - { - throw new Exception("This should only be invoked for sync realms."); - } - - var session = GetSession(realm); - - if (restartSession) - { - session.Stop(); - } - - await SyncTestHelpers.TriggerClientResetOnServer(syncConfig).Timeout(10_000, detail: "Trigger client reset"); - - if (restartSession) - { - session.Start(); - } - } - } -} diff --git a/Tests/Realm.Tests/Sync/SyncTestHelpers.cs b/Tests/Realm.Tests/Sync/SyncTestHelpers.cs deleted file mode 100644 index 40a0546ca8..0000000000 --- a/Tests/Realm.Tests/Sync/SyncTestHelpers.cs +++ /dev/null @@ -1,211 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; -using Baas; -using Nito.AsyncEx; -using NUnit.Framework; -using Realms.Logging; -using Realms.Sync; - -namespace Realms.Tests.Sync -{ - public static class SyncTestHelpers - { - public const string DefaultPassword = "123456"; - private const string DummyAppId = "myapp-123"; - - private static readonly string? _baaSaasApiKey; - - private static IDictionary _apps = new Dictionary - { - [AppConfigType.Default] = new(string.Empty, DummyAppId, AppConfigType.Default), - }; - - public static Uri? BaasUri; - private static BaasClient? _baasClient; - - static SyncTestHelpers() - { - var uri = ConfigHelpers.GetSetting("BaasUrl"); - if (uri != null) - { - BaasUri = new Uri(uri); - } - - _baaSaasApiKey = ConfigHelpers.GetSetting("BaaSaasApiKey"); - } - - private static int _appCounter; - - public static AppConfiguration GetAppConfig(string type = AppConfigType.Default) => new(_apps[type].ClientAppId) - { - BaseUri = BaasUri ?? new Uri("http://localhost:12345"), - MetadataPersistenceMode = MetadataPersistenceMode.NotEncrypted, -#pragma warning disable CA1837 // Use Environment.ProcessId instead of Process.GetCurrentProcess().Id - BaseFilePath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), $"rt-sync-{Process.GetCurrentProcess().Id}-{_appCounter++}")).FullName -#pragma warning restore CA1837 // Use Environment.ProcessId instead of Process.GetCurrentProcess().Id - }; - - public static string RemoteMongoDBName(string prefix = "Schema") => $"{prefix}_{_baasClient?.Differentiator}"; - - public static string SyncMongoDBName(string type = AppConfigType.Default) => _baasClient!.GetSyncDatabaseName(type); - - public static void RunBaasTestAsync(Func testFunc, int timeout = 30000) - { - if (BaasUri == null && _baaSaasApiKey == null) - { - Assert.Ignore("Atlas App Services are not setup."); - } - - AsyncContext.Run(async () => - { - await CreateBaasAppsAsync(); - }); - - TestHelpers.RunAsyncTest(testFunc, timeout); - - // TODO: remove when https://github.com/realm/realm-core/issues/6052 is fixed - Task.Delay(1000).Wait(); - } - - public static string GetVerifiedUsername() => $"realm_tests_do_autoverify-{Guid.NewGuid()}@g.it"; - - public static string GetUnconfirmedUsername() => $"realm_tests_do_not_confirm-{Guid.NewGuid()}@g.it"; - - public static async Task TriggerClientResetOnServer(SyncConfigurationBase config) - { - var userId = config.User.Id; - var appId = string.Empty; - - if (config is FlexibleSyncConfiguration) - { - appId = _apps[AppConfigType.FlexibleSync].AppId; - } - - var result = await config.User.Functions.CallAsync("triggerClientResetOnSyncServer", userId, appId); - if (result.Deleted > 0) - { - // This is kind of a hack, but it appears like there's a race condition on the server, where the deletion might not be - // registered and the server will not respond with a client reset. Doing the request again gives the server some extra time - // to process the deletion. - result = await config.User.Functions.CallAsync("triggerClientResetOnSyncServer", userId, appId); - Assert.That(result.Deleted, Is.EqualTo(0)); - } - } - - public static async Task ExtractBaasSettingsAsync(string[] args) - { - string[] remainingArgs; - (_baasClient, BaasUri, remainingArgs) = await BaasClient.CreateClientFromArgs(args, TestHelpers.Output); - - if (_baasClient != null) - { - _apps = await _baasClient.GetOrCreateApps(); - } - - return remainingArgs; - } - - private class LogArgs - { - public string? RealmLogLevel { get; set; } - - public string? RealmLogFile { get; set; } - } - - public static (string[] RemainingArgs, IDisposable? Logger) SetLoggerFromArgs(string[] args) - { - var (extracted, remaining) = BaasClient.ExtractArguments(args); - - if (!string.IsNullOrEmpty(extracted.RealmLogLevel)) - { - var logLevel = (LogLevel)Enum.Parse(typeof(LogLevel), extracted.RealmLogLevel!); - TestHelpers.Output.WriteLine($"Setting log level to {logLevel}"); - - RealmLogger.SetLogLevel(logLevel); - } - - RealmLogger.AsyncFileLogger? logger = null; - if (!string.IsNullOrEmpty(extracted.RealmLogFile)) - { - if (!Process.GetCurrentProcess().ProcessName.ToLower().Contains("testhost")) - { - TestHelpers.Output.WriteLine($"Setting sync logger to file: {extracted.RealmLogFile}"); - - // We're running in a test runner, so we need to use the sync logger - RealmLogger.Default = RealmLogger.File(extracted.RealmLogFile!); - } - else - { - TestHelpers.Output.WriteLine($"Setting async logger to file: {extracted.RealmLogFile}"); - - // We're running standalone (likely on CI), so we use the async logger - RealmLogger.Default = logger = new RealmLogger.AsyncFileLogger(extracted.RealmLogFile!); - } - } - - return (remaining, logger); - } - - public static string[] ExtractBaasSettings(string[] args) => AsyncContext.Run(() => ExtractBaasSettingsAsync(args)); - - private static async Task CreateBaasAppsAsync() - { - if (_apps[AppConfigType.Default].AppId != string.Empty || (BaasUri == null && _baaSaasApiKey == null)) - { - return; - } - - var cluster = ConfigHelpers.GetSetting("Cluster")!; - var apiKey = ConfigHelpers.GetSetting("ApiKey")!; - var privateApiKey = ConfigHelpers.GetSetting("PrivateApiKey")!; - var groupId = ConfigHelpers.GetSetting("GroupId")!; - var differentiator = ConfigHelpers.GetSetting("Differentiator") ?? "local"; - - if (_baaSaasApiKey != null) - { - BaasUri = await BaasClient.GetOrDeployContainer(_baaSaasApiKey, differentiator, TestHelpers.Output); - _baasClient = await BaasClient.Docker(BaasUri, differentiator, TestHelpers.Output); - } - else if (!string.IsNullOrEmpty(cluster) && - !string.IsNullOrEmpty(apiKey) && - !string.IsNullOrEmpty(privateApiKey) && - !string.IsNullOrEmpty(groupId)) - { - _baasClient = await BaasClient.Atlas(BaasUri!, differentiator, TestHelpers.Output, cluster, apiKey, privateApiKey, groupId); - } - else - { - _baasClient = await BaasClient.Docker(BaasUri!, "local", TestHelpers.Output); - } - - _apps = await _baasClient.GetOrCreateApps(); - } - - public static Task SetRecoveryModeOnServer(string appConfigType, bool enabled) - { - var app = _apps[appConfigType]; - return _baasClient!.SetAutomaticRecoveryEnabled(app, enabled); - } - } -} diff --git a/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs b/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs deleted file mode 100644 index c2bd1259a6..0000000000 --- a/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs +++ /dev/null @@ -1,946 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using NUnit.Framework; -using Realms.Exceptions; -using Realms.Logging; -using Realms.Schema; -using Realms.Sync; -using Realms.Sync.ErrorHandling; -using Realms.Sync.Exceptions; - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class SynchronizedInstanceTests : SyncTestBase - { - private const int OneMegabyte = 1024 * 1024; - private const int NumberOfObjects = 4; - - [Test] - public void Compact_ShouldReduceSize([Values(true, false)] bool encrypt, [Values(true, false)] bool populate) - { - TestHelpers.RunAsyncTest(async () => - { - var config = GetFakeConfig(); - if (encrypt) - { - config.EncryptionKey = TestHelpers.GetEncryptionKey(5); - } - - using (var realm = GetRealm(config)) - { - var session = GetSession(realm); - session.Stop(); - if (populate) - { - AddDummyData(realm, singleTransaction: false); - } - - session.CloseHandle(); - } - - var initialSize = new FileInfo(config.DatabasePath).Length; - - await TestHelpers.WaitForConditionAsync(() => Realm.Compact(config), errorMessage: "Expected compact to succeed, but it didn't"); - - var finalSize = new FileInfo(config.DatabasePath).Length; - Assert.That(initialSize, Is.GreaterThanOrEqualTo(finalSize)); - - using (var realm = GetRealm(config)) - { - Assert.That(realm.All().Count(), Is.EqualTo(populate ? DummyDataSize / 2 : 0)); - } - }); - } - - [Test] - public void ShouldCompact_IsInvokedAfterOpening([Values(true, false)] bool shouldCompact, [Values(true, false)] bool useSync) - { - RealmConfigurationBase config = useSync ? GetFakeConfig() : new RealmConfiguration(Guid.NewGuid().ToString()); - - using (var realm = GetRealm(config)) - { - AddDummyData(realm, singleTransaction: false); - } - - var oldSize = new FileInfo(config.DatabasePath).Length; - long projectedNewSize = 0; - var hasPrompted = false; - config.ShouldCompactOnLaunch = (totalBytes, bytesUsed) => - { - Assert.That(totalBytes, Is.EqualTo(oldSize)); - hasPrompted = true; - projectedNewSize = (long)bytesUsed; - return shouldCompact; - }; - - using (var realm = GetRealm(config)) - { - Assert.That(hasPrompted, Is.True); - var newSize = new FileInfo(config.DatabasePath).Length; - if (shouldCompact) - { - // Less than or equal because of the new online compaction mechanism - it's possible - // that the Realm was already at the optimal size. - Assert.That(newSize, Is.LessThanOrEqualTo(oldSize)); - - // Less than 20% error in projections - Assert.That((newSize - projectedNewSize) / newSize, Is.LessThan(0.2)); - } - else - { - Assert.That(newSize, Is.EqualTo(oldSize)); - } - - Assert.That(realm.All().Count(), Is.EqualTo(DummyDataSize / 2)); - } - } - - [Test] - public void GetInstanceAsync_ShouldDownloadRealm([Values(true, false)] bool singleTransaction) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partition = Guid.NewGuid().ToString(); - - var config = await GetIntegrationConfigAsync(partition); - var asyncConfig = await GetIntegrationConfigAsync(partition); - - using var realm = GetRealm(config); - AddDummyData(realm, singleTransaction); - - await WaitForUploadAsync(realm); - - using var asyncRealm = await GetRealmAsync(asyncConfig); - Assert.That(asyncRealm.All().Count(), Is.EqualTo(DummyDataSize / 2)); - }, timeout: 120000); - } - - [Test] - public void GetInstanceAsync_CreatesNonExistentRealm() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - await GetRealmAsync(config); - }); - } - - [Test] - public void GetInstanceAsync_ReportsProgress() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - - await PopulateData(config); - - var callbacksInvoked = 0; - - var lastProgress = default(SyncProgress); - config = await GetIntegrationConfigAsync((string?)config.Partition); - config.OnProgress = (progress) => - { - callbacksInvoked++; - lastProgress = progress; - }; - - using var realm = await GetRealmAsync(config); - Assert.That(realm.All().Count(), Is.EqualTo(NumberOfObjects)); - Assert.That(callbacksInvoked, Is.GreaterThan(0)); - Assert.That(lastProgress.ProgressEstimate, Is.GreaterThan(0.0)); - }, 60000); - } - - [Test] - public void GetInstanceAsync_WithOnProgress_DoesntThrowWhenOnProgressIsSetToNull() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - - await PopulateData(config); - - var callbacksInvoked = 0; - - var lastProgress = default(SyncProgress); - config = await GetIntegrationConfigAsync((string?)config.Partition); - config.OnProgress = (progress) => - { - callbacksInvoked++; - lastProgress = progress; - }; - - var realmTask = GetRealmAsync(config); - config.OnProgress = null; - - using var realm = await realmTask; - - Assert.That(realm.All().Count(), Is.EqualTo(NumberOfObjects)); - Assert.That(callbacksInvoked, Is.GreaterThan(0)); - Assert.That(lastProgress.ProgressEstimate, Is.GreaterThan(0.0)); - }, 60000); - } - - [Test] - public void GetInstanceAsync_WithOnProgressThrowing_ReportsErrorToLogs() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - - await PopulateData(config); - - var logger = new RealmLogger.InMemoryLogger(); - RealmLogger.Default = logger; - - config = await GetIntegrationConfigAsync((string?)config.Partition); - config.OnProgress = _ => throw new Exception("Exception in OnProgress"); - - var realmTask = GetRealmAsync(config); - config.OnProgress = null; - - using var realm = await realmTask; - - Assert.That(realm.All().Count(), Is.EqualTo(NumberOfObjects)); - - // Notifications are delivered async, so let's wait a little - await TestHelpers.WaitForConditionAsync(() => logger.GetLog().Contains("Exception in OnProgress")); - Assert.That(logger.GetLog(), Does.Contain("Exception in OnProgress")); - }, 60000); - } - - [Test] - public void GetInstanceAsync_Cancel_ShouldCancelWait() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - await PopulateData(config); - - config = await GetIntegrationConfigAsync((string?)config.Partition); - - using var cts = new CancellationTokenSource(10); - - try - { - var realm = await Realm.GetInstanceAsync(config, cts.Token); - CleanupOnTearDown(realm); - Assert.Fail("Expected task to be cancelled."); - } - catch (Exception ex) - { - Assert.That(ex, Is.InstanceOf()); - } - }); - } - - [Test] - public void GetInstance_WhenDynamic_ReadsSchemaFromDisk() - { - var config = GetFakeConfig(); - config.Schema = new[] { typeof(IntPrimaryKeyWithValueObject) }; - - // Create the realm and add some objects - using (var realm = GetRealm(config)) - { - realm.Write(() => realm.Add(new IntPrimaryKeyWithValueObject - { - Id = 42, - StringValue = "This is a string!" - })); - } - - config.IsDynamic = true; - - using var dynamicRealm = GetRealm(config); - Assert.That(dynamicRealm.Schema.Count == 1); - - Assert.That(dynamicRealm.Schema.TryFindObjectSchema(nameof(IntPrimaryKeyWithValueObject), out var objectSchema), Is.True); - Assert.That(objectSchema, Is.Not.Null); - - Assert.That(objectSchema!.TryFindProperty(nameof(IntPrimaryKeyWithValueObject.StringValue), out var stringProp)); - Assert.That(stringProp.Type, Is.EqualTo(PropertyType.String | PropertyType.Nullable)); - - var dynamicObj = dynamicRealm.DynamicApi.All(nameof(IntPrimaryKeyWithValueObject)).Single(); - Assert.That(dynamicObj.DynamicApi.Get(nameof(IntPrimaryKeyWithValueObject.StringValue)), Is.EqualTo("This is a string!")); - } - - [Test] - public void GetInstance_WhenDynamicAndDoesntExist_ReturnsEmptySchema() - { - var config = GetFakeConfig(); - config.Schema = null!; - config.IsDynamic = true; - - using var realm = GetRealm(config); - Assert.That(realm.Schema, Is.Empty); - } - - [Test, Ignore("Doesn't work due to a OS bug")] - public void InvalidSchemaChange_RaisesClientReset() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var errorTcs = new TaskCompletionSource(); - var config = await GetIntegrationConfigAsync(); - config.ClientResetHandler = new ManualRecoveryHandler((error) => - { - errorTcs.TrySetResult(error); - }); - - var backupLocation = config.DatabasePath + "_backup"; - using (var realm = await GetRealmAsync(config)) - { - // Backup the file - File.Copy(config.DatabasePath, backupLocation); - - realm.Write(() => realm.Add(new HugeSyncObject(1024))); - - await WaitForUploadAsync(realm); - } - - // restore the backup - while (true) - { - try - { - File.Copy(backupLocation, config.DatabasePath, overwrite: true); - break; - } - catch - { - await Task.Delay(50); - } - } - - using var realm2 = GetRealm(config); - - var clientEx = await errorTcs.Task.Timeout(5000); - - Assert.That(clientEx.ErrorCode, Is.EqualTo(ErrorCode.InvalidSchemaChange)); - - var realmPath = config.DatabasePath; - - Assert.That(File.Exists(realmPath)); - - Assert.That(clientEx.InitiateClientReset(), Is.True); - - Assert.That(File.Exists(realmPath), Is.False); - }); - } - - [Test] - public void EmbeddedObject_WhenAdditiveExplicit_ShouldThrow() - { - var conf = GetFakeConfig(); - conf.Schema = new[] { typeof(EmbeddedLevel3) }; - - Assert.Throws(() => Realm.GetInstance(conf), $"Embedded object {nameof(EmbeddedLevel3)} is unreachable by any link path from top level objects"); - } - - [Test] - public void WriteCopy_CanSynchronizeData([Values(true, false)] bool originalEncrypted, - [Values(true, false)] bool copyEncrypted) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partition = Guid.NewGuid().ToString(); - - var originalConfig = await GetIntegrationConfigAsync(partition); - if (originalEncrypted) - { - originalConfig.EncryptionKey = TestHelpers.GetEncryptionKey(42); - } - - var copyConfig = await GetIntegrationConfigAsync(partition); - Assert.That(originalConfig.Partition, Is.EqualTo(copyConfig.Partition)); - if (copyEncrypted) - { - copyConfig.EncryptionKey = TestHelpers.GetEncryptionKey(14); - } - - using var originalRealm = GetRealm(originalConfig); - - AddDummyData(originalRealm, true); - - await WaitForUploadAsync(originalRealm); - await WaitForDownloadAsync(originalRealm); - - originalRealm.WriteCopy(copyConfig); - - using var copiedRealm = GetRealm(copyConfig); - - Assert.That(copiedRealm.All().Count(), Is.EqualTo(originalRealm.All().Count())); - - var fromCopy = copiedRealm.Write(() => copiedRealm.Add(new ObjectIdPrimaryKeyWithValueObject - { - StringValue = "Added from copy" - })); - - await WaitForUploadAsync(copiedRealm); - await WaitForDownloadAsync(originalRealm); - - var itemInOriginal = originalRealm.Find(fromCopy.Id); - Assert.That(itemInOriginal, Is.Not.Null); - Assert.That(itemInOriginal!.StringValue, Is.EqualTo(fromCopy.StringValue)); - - var fromOriginal = originalRealm.Write(() => originalRealm.Add(new ObjectIdPrimaryKeyWithValueObject - { - StringValue = "Added from original" - })); - - await WaitForUploadAsync(originalRealm); - await WaitForDownloadAsync(copiedRealm); - - var itemInCopy = copiedRealm.Find(fromOriginal.Id); - Assert.That(itemInCopy, Is.Not.Null); - Assert.That(itemInCopy!.StringValue, Is.EqualTo(fromOriginal.StringValue)); - }); - } - - [Test] - public void WriteCopy_LocalToSync([Values(true, false)] bool originalEncrypted, - [Values(true, false)] bool copyEncrypted) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var originalConfig = new RealmConfiguration(Guid.NewGuid().ToString()) - { - Schema = new[] { typeof(ObjectIdPrimaryKeyWithValueObject) } - }; - if (originalEncrypted) - { - originalConfig.EncryptionKey = TestHelpers.GetEncryptionKey(42); - } - - var copyConfig = await GetIntegrationConfigAsync(Guid.NewGuid().ToString()); - if (copyEncrypted) - { - copyConfig.EncryptionKey = TestHelpers.GetEncryptionKey(23); - } - - using var originalRealm = GetRealm(originalConfig); - - AddDummyData(originalRealm, true); - - var addedObjects = originalRealm.All().Count(); - - originalRealm.WriteCopy(copyConfig); - - if (copyEncrypted) - { - var validKey = copyConfig.EncryptionKey; - copyConfig.EncryptionKey = null; - - Assert.Throws(() => GetRealm(copyConfig)); - - copyConfig.EncryptionKey = TestHelpers.GetEncryptionKey(1, 2, 3); - Assert.Throws(() => GetRealm(copyConfig)); - - copyConfig.EncryptionKey = validKey; - } - - using var copiedRealm = GetRealm(copyConfig); - - Assert.That(copiedRealm.All().Count(), Is.EqualTo(addedObjects)); - - await WaitForUploadAsync(copiedRealm); - - var anotherUserRealm = await GetIntegrationRealmAsync(copyConfig.Partition.AsString()); - - Assert.That(anotherUserRealm.All().Count(), Is.EqualTo(addedObjects)); - - var addedObject = anotherUserRealm.Write(() => anotherUserRealm.Add(new ObjectIdPrimaryKeyWithValueObject - { - StringValue = "abc" - })); - - await WaitForUploadAsync(anotherUserRealm); - await WaitForDownloadAsync(copiedRealm); - - var syncedObject = copiedRealm.Find(addedObject.Id)!; - - Assert.That(syncedObject.StringValue, Is.EqualTo("abc")); - }); - } - - [Test, Ignore("Maybe crashing on evergreen")] - public void WriteCopy_SyncToLocal([Values(true, false)] bool originalEncrypted, - [Values(true, false)] bool copyEncrypted) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var originalConfig = await GetIntegrationConfigAsync(Guid.NewGuid().ToString()); - if (originalEncrypted) - { - originalConfig.EncryptionKey = TestHelpers.GetEncryptionKey(42); - } - - var copyConfig = new RealmConfiguration(Guid.NewGuid().ToString()); - if (copyEncrypted) - { - copyConfig.EncryptionKey = TestHelpers.GetEncryptionKey(23); - } - - using var originalRealm = GetRealm(originalConfig); - - AddDummyData(originalRealm, true); - - await WaitForUploadAsync(originalRealm); - - originalRealm.WriteCopy(copyConfig); - - // In the server-side schema for each model there's the field to hold what partition each object belongs to, namely realm_id. - // Such field is optional and we don't declare it in our models. - // When the conversion from sync to local happens realm_id is written in the local realm. The discrepancy generated by the conversion - // starts a schema migration. Bumping up the SchemaVersion triggers an implicit migration that removes realm_id. - copyConfig.SchemaVersion++; - using var copiedRealm = GetRealm(copyConfig); - - Assert.That(copiedRealm.All().Count(), Is.EqualTo(originalRealm.All().Count())); - }); - } - - [Test] - public void WriteCopy_FailsWhenPartitionsDiffer([Values(true, false)] bool originalEncrypted, - [Values(true, false)] bool copyEncrypted) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var originalPartition = Guid.NewGuid().ToString(); - var originalConfig = await GetIntegrationConfigAsync(originalPartition); - if (originalEncrypted) - { - originalConfig.EncryptionKey = TestHelpers.GetEncryptionKey(42); - } - - var copiedPartition = Guid.NewGuid().ToString(); - var copyConfig = await GetIntegrationConfigAsync(copiedPartition); - if (copyEncrypted) - { - copyConfig.EncryptionKey = TestHelpers.GetEncryptionKey(14); - } - - Assert.That(originalConfig.Partition, !Is.EqualTo(copyConfig.Partition)); - - using var originalRealm = GetRealm(originalConfig); - - Assert.Throws(() => originalRealm.WriteCopy(copyConfig)); - }); - } - - [Test] - public void RemoveAll_RemovesAllElements([Values(true, false)] bool originalEncrypted) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var realmConfig = await GetIntegrationConfigAsync(Guid.NewGuid().ToString()); - if (originalEncrypted) - { - realmConfig.EncryptionKey = TestHelpers.GetEncryptionKey(42); - } - - var realm = GetRealm(realmConfig); - - AddDummyData(realm, true); - - await WaitForUploadAsync(realm); - - Assert.That(realm.All().Count(), Is.EqualTo(DummyDataSize / 2)); - - realm.Write(() => - { - realm.RemoveAll(); - }); - - Assert.That(realm.All().Count(), Is.EqualTo(0)); - await WaitForUploadAsync(realm); - realm.Dispose(); - - // Ensure that the Realm can be deleted from the filesystem. If the sync - // session was still using it, we would get a permission denied error. - Assert.That(DeleteRealmWithRetries(realm.Config), Is.True); - - using var asyncRealm = await GetRealmAsync(realmConfig); - Assert.That(asyncRealm.All().Count(), Is.EqualTo(0)); - }); - } - - [Test] - public void WriteCopy_FailsWhenNotFinished([Values(true, false)] bool originalEncrypted, - [Values(true, false)] bool copyEncrypted) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partition = Guid.NewGuid().ToString(); - - var originalConfig = await GetIntegrationConfigAsync(partition); - if (originalEncrypted) - { - originalConfig.EncryptionKey = TestHelpers.GetEncryptionKey(42); - } - - var copyConfig = await GetIntegrationConfigAsync(partition); - if (copyEncrypted) - { - copyConfig.EncryptionKey = TestHelpers.GetEncryptionKey(14); - } - - using var originalRealm = GetRealm(originalConfig); - - AddDummyData(originalRealm, true); - - // The error is thrown as a generic `RealmError` by Core which translates to a generic `RealmException` on our side. - Assert.Throws(() => originalRealm.WriteCopy(copyConfig)); - }); - } - - [Test] - public void WriteCopy_FailsWithEmptyConfig() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partition = Guid.NewGuid().ToString(); - var originalConfig = await GetIntegrationConfigAsync(partition); - using var originalRealm = GetRealm(originalConfig); - Assert.Throws(() => originalRealm.WriteCopy(null!)); - }); - } - - [Test] - public void WriteCopy_ThrowsWhenConvertingFromLocalToFLX() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var localRealm = GetRealm(); - var flexConfig = await GetFLXIntegrationConfigAsync(); - - var ex = Assert.Throws(() => localRealm.WriteCopy(flexConfig))!; - Assert.That(ex.Message, Does.Contain("Writing a copy to a flexible sync realm is not supported unless flexible sync is already enabled")); - }); - } - - [Test] - public void WriteCopy_ThrowsWhenConvertingFromPBSToFLX() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var pbsRealm = await GetIntegrationRealmAsync(); - var flexConfig = await GetFLXIntegrationConfigAsync(); - - var ex = Assert.Throws(() => pbsRealm.WriteCopy(flexConfig))!; - Assert.That(ex.Message, Does.Contain("Writing a copy to a flexible sync realm is not supported unless flexible sync is already enabled")); - }); - } - - [Test] - public void WriteCopy_ThrowsWhenConvertingFromFLXToPBS() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - using var flxRealm = await GetFLXIntegrationRealmAsync(); - var pbsConfig = await GetIntegrationConfigAsync(); - - var ex = Assert.Throws(() => flxRealm.WriteCopy(pbsConfig))!; - Assert.That(ex.Message, Does.Contain("Changing from flexible sync sync to partition based sync is not supported when writing a Realm copy")); - }); - } - - [Test] - public void DeleteRealmWorksIfCalledMultipleTimes() - { - var config = GetFakeConfig(); - var openRealm = GetRealm(config); - openRealm.Dispose(); - Assert.That(File.Exists(config.DatabasePath)); - - Assert.That(() => DeleteRealmWithRetries(openRealm.Config), Is.True); - Assert.That(() => DeleteRealmWithRetries(openRealm.Config), Is.True); - } - - [Test] - public void DeleteRealm_AfterDispose_Succeeds([Values(true, false)] bool singleTransaction) - { - // This test verifies that disposing a Realm will eventually close its session and - // release the file, so that we can delete it. - SyncTestHelpers.RunBaasTestAsync(async () => - { - var partition = Guid.NewGuid().ToString(); - - var config = await GetIntegrationConfigAsync(partition); - var asyncConfig = await GetIntegrationConfigAsync(partition); - - var realm = GetRealm(config); - AddDummyData(realm, singleTransaction); - - await WaitForUploadAsync(realm); - realm.Dispose(); - - // Ensure that the Realm can be deleted from the filesystem. If the sync - // session was still using it, we would get a permission denied error. - Assert.That(DeleteRealmWithRetries(realm.Config), Is.True); - - using var asyncRealm = await GetRealmAsync(asyncConfig); - Assert.That(asyncRealm.All().Count(), Is.EqualTo(DummyDataSize / 2)); - }, timeout: 120000); - } - - [Test] - public void RealmDispose_ClosesSessions() - { - var config = GetFakeConfig(); - var realm = GetRealm(config); - var session = GetSession(realm); - realm.Dispose(); - - Assert.That(session.IsClosed); - - // Dispose should close the session and allow us to delete the Realm. - Assert.That(DeleteRealmWithRetries(realm.Config), Is.True); - } - - [Test] - public void SyncTimeouts_ArePassedCorrectlyToCore() - { - var logger = new RealmLogger.InMemoryLogger(); - RealmLogger.Default = logger; - RealmLogger.SetLogLevel(LogLevel.Debug); - - SyncTestHelpers.RunBaasTestAsync(async () => - { - var appConfig = SyncTestHelpers.GetAppConfig(); - appConfig.SyncTimeoutOptions.ConnectTimeout = TimeSpan.FromMilliseconds(1234); - appConfig.SyncTimeoutOptions.ConnectionLingerTime = TimeSpan.FromMilliseconds(3456); - appConfig.SyncTimeoutOptions.PingKeepAlivePeriod = TimeSpan.FromMilliseconds(5678); - appConfig.SyncTimeoutOptions.PongKeepAliveTimeout = TimeSpan.FromMilliseconds(7890); - appConfig.SyncTimeoutOptions.FastReconnectLimit = TimeSpan.FromMilliseconds(9012); - var app = CreateApp(appConfig); - var config = await GetIntegrationConfigAsync(app: app); - - using var realm = await GetRealmAsync(config); - - var logs = logger.GetLog(); - - Assert.That(logs, Does.Contain("Config param: connect_timeout = 1234 ms")); - Assert.That(logs, Does.Contain("Config param: connection_linger_time = 3456 ms")); - Assert.That(logs, Does.Contain("Config param: ping_keepalive_period = 5678 ms")); - Assert.That(logs, Does.Contain("Config param: pong_keepalive_timeout = 7890 ms")); - Assert.That(logs, Does.Contain("Config param: fast_reconnect_limit = 9012 ms")); - }); - } - - [Test, Ignore("Enable when https://github.com/realm/realm-core/issues/6301 is addressed")] - public void CancelAsyncOperationsOnNonFatalErrors_WhenTrue_ShouldCancelAsyncOperationsOnTimeout() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var appConfig = SyncTestHelpers.GetAppConfig(); - - // 1 ms timeout is way too short to establish a connection - appConfig.SyncTimeoutOptions.ConnectTimeout = TimeSpan.FromMilliseconds(1); - - var app = CreateApp(appConfig); - var config = await GetIntegrationConfigAsync(app: app); - config.CancelAsyncOperationsOnNonFatalErrors = true; - - var ex = await TestHelpers.AssertThrows(() => GetRealmAsync(config)); - Assert.That(ex.InnerException, Is.TypeOf().And.Message.Contains("Sync connection was not fully established in time")); - }); - } - - [Test, Ignore("Enable when https://github.com/realm/realm-core/issues/6301 is addressed")] - public void CancelAsyncOperationsOnNonFatalErrors_WhenFalse_ShouldNotCancelAsyncOperationsOnTimeout() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var appConfig = SyncTestHelpers.GetAppConfig(); - - // 1 ms timeout is way too short to establish a connection - appConfig.SyncTimeoutOptions.ConnectTimeout = TimeSpan.FromMilliseconds(1); - - var app = CreateApp(appConfig); - var config = await GetIntegrationConfigAsync(app: app); - config.CancelAsyncOperationsOnNonFatalErrors = false; - - // Connection should timeout immediately, but we should continue retrying until we eventually - // timeout the GetRealmAsync operation - var ex = await TestHelpers.AssertThrows(() => GetRealmAsync(config), timeout: 1000); - Assert.That(ex.Message, Does.Contain("The operation has timed out after 1000 ms")); - }); - } - - [Test] - public void SyncLogger_WhenLevelChanges_LogsAtNewLevel() - { - var logs = new Dictionary>(); - foreach (LogLevel level in Enum.GetValues(typeof(LogLevel))) - { - logs[level] = new(); - } - - var regex = new Regex("Connection\\[\\d+] Session\\[\\d+]"); - var logger = RealmLogger.Function((level, msg) => - { - if (regex.IsMatch(msg)) - { - logs[level].Add(msg); - } - }); - - RealmLogger.SetLogLevel(LogLevel.Info); - RealmLogger.Default = logger; - - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - - using var realm = await GetRealmAsync(config); - - var initialInfoLogs = logs[LogLevel.Info].Count; - Assert.That(initialInfoLogs, Is.GreaterThan(0)); - Assert.That(logs[LogLevel.Debug].Count, Is.EqualTo(0)); - - RealmLogger.SetLogLevel(LogLevel.Debug); - - realm.Write(() => - { - realm.Add(new PrimaryKeyStringObject - { - Id = Guid.NewGuid().ToString(), - }); - }); - - await WaitForUploadAsync(realm); - - Assert.That(logs[LogLevel.Info].Count, Is.GreaterThan(0)); - Assert.That(logs[LogLevel.Debug].Count, Is.GreaterThan(0)); - }); - } - - private const int DummyDataSize = 100; - - private static void AddDummyData(Realm realm, bool singleTransaction) - { - Action write; - Transaction? currentTransaction = null; - - if (singleTransaction) - { - write = action => action(); - currentTransaction = realm.BeginWrite(); - } - else - { - write = realm.Write; - } - - for (var i = 0; i < DummyDataSize; i++) - { - write(() => - { - realm.Add(new ObjectIdPrimaryKeyWithValueObject - { - StringValue = "Super secret product " + i - }); - }); - } - - if (singleTransaction) - { - currentTransaction!.Commit(); - currentTransaction = realm.BeginWrite(); - } - - var objs = realm.All(); - for (var i = 0; i < DummyDataSize / 2; i++) - { - write(() => - { - var item = objs.ElementAt(i); - realm.Remove(item); - }); - } - - if (singleTransaction) - { - currentTransaction!.Commit(); - } - } - - private async Task PopulateData(PartitionSyncConfiguration config, int numberOfObjects = NumberOfObjects) - { - using var realm = GetRealm(config); - - // Split in 2 because MDB Realm has a limit of 16 MB per changeset - var firstBatch = numberOfObjects / 2; - var secondBatch = numberOfObjects - firstBatch; - - realm.Write(() => - { - for (var i = 0; i < firstBatch; i++) - { - realm.Add(new HugeSyncObject(OneMegabyte)); - } - }); - - realm.Write(() => - { - for (var i = 0; i < secondBatch; i++) - { - realm.Add(new HugeSyncObject(OneMegabyte)); - } - }); - - await WaitForUploadAsync(realm); - } - - /* Code to generate the legacy Realm - private static async Task GenerateLegacyRealm(bool encrypt) - { - var config = await SyncTestHelpers.GetFakeConfigAsync("a@a"); - if (encrypt) - { - config.EncryptionKey = new byte[64]; - config.EncryptionKey[0] = 42; - } - - using (var realm = Realm.GetInstance(config)) - { - realm.Write(() => - { - realm.Add(new Person - { - FirstName = "John", - LastName = "Smith" - }); - }); - } - - return config.DatabasePath; - }*/ - } -} diff --git a/Tests/Realm.Tests/Sync/UserManagementTests.cs b/Tests/Realm.Tests/Sync/UserManagementTests.cs deleted file mode 100644 index c36b8cf8b6..0000000000 --- a/Tests/Realm.Tests/Sync/UserManagementTests.cs +++ /dev/null @@ -1,1213 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; -using NUnit.Framework; -using Realms.Sync; -using Realms.Sync.Exceptions; - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class UserManagementTests : SyncTestBase - { - [Test] - public void AppCurrentUser_WhenThereAreNoUsers_ShouldReturnNull() - { - Assert.That(() => DefaultApp.CurrentUser, Is.Null); - } - - [Test] - public void AppCurrentUser_WhenThereIsOneUser_ShouldReturnThatUser() - { - var user = GetFakeUser(); - var currentUser = DefaultApp.CurrentUser; - - Assert.That(currentUser, Is.EqualTo(user)); - } - - [Test] - public void AppCurrentUser_WhenThereIsMoreThanOneUser_ShouldReturnLastOne() - { - var first = GetFakeUser(); - var second = GetFakeUser(); - - Assert.That(DefaultApp.CurrentUser, Is.EqualTo(second)); - Assert.That(DefaultApp.CurrentUser, Is.Not.EqualTo(first)); - } - - [Test] - public void AppAllUsers_WhenThereAreNoUsers_ShouldReturnEmptyCollection() - { - var users = DefaultApp.AllUsers; - Assert.That(users, Is.Empty); - } - - [Test] - public void AppAllUsers_WhenThereIsOneUser_ShouldReturnThatUser() - { - var user = GetFakeUser(); - - var users = DefaultApp.AllUsers; - - Assert.That(users.Length, Is.EqualTo(1)); - Assert.That(users[0], Is.EqualTo(user)); - } - - [Test] - public void AppAllUsers_WhenThereAreNineUsers_ShouldReturnAllOfThem() - { - var users = new List(); - for (var i = 0; i < 9; i++) - { - users.Add(GetFakeUser()); - } - - var current = DefaultApp.AllUsers; - - Assert.That(current, Is.EquivalentTo(users)); - } - - [Test] - public void AppSwitchUser_SwitchesCurrentUser() - { - var first = GetFakeUser(); - var second = GetFakeUser(); - - Assert.That(DefaultApp.CurrentUser, Is.EqualTo(second)); - - DefaultApp.SwitchUser(first); - - Assert.That(DefaultApp.CurrentUser, Is.EqualTo(first)); - } - - [Test] - public void AppSwitchUser_WhenUserIsCurrent_DoesNothing() - { - var first = GetFakeUser(); - Assert.That(DefaultApp.CurrentUser, Is.EqualTo(first)); - - var second = GetFakeUser(); - - Assert.That(DefaultApp.CurrentUser, Is.EqualTo(second)); - - DefaultApp.SwitchUser(second); - - Assert.That(DefaultApp.CurrentUser, Is.EqualTo(second)); - } - - [Test] - public void AppSwitchUser_WhenUserIsNull_Throws() - { - Assert.That(() => DefaultApp.SwitchUser(null!), Throws.InstanceOf()); - } - - [Test] - public void AppRemoveUser_RemovesUser() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var first = await GetUserAsync(); - var second = await GetUserAsync(); - - Assert.That(DefaultApp.CurrentUser, Is.EqualTo(second)); - - var secondId = second.Id; - - await DefaultApp.RemoveUserAsync(second); - - // TODO: validate that the refresh token is invalidated. - Assert.That(second.State, Is.EqualTo(UserState.Removed)); - Assert.That(second.AccessToken, Is.Empty); - Assert.That(second.RefreshToken, Is.Empty); - Assert.That(second.Id, Is.EqualTo(secondId)); - - Assert.That(DefaultApp.CurrentUser, Is.EqualTo(first)); - }); - } - - [Test] - public void AppDeleteUserFromServer_RemovesUser() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var username = SyncTestHelpers.GetVerifiedUsername(); - var password = SyncTestHelpers.DefaultPassword; - var user = await GetUserAsync(app: null, username, password); - - Assert.That(DefaultApp.CurrentUser, Is.EqualTo(user)); - - await DefaultApp.DeleteUserFromServerAsync(user); - Assert.That(DefaultApp.CurrentUser, Is.Null); - - var ex = await TestHelpers.AssertThrows(() => DefaultApp.LogInAsync(Credentials.EmailPassword(username, password))); - Assert.That(ex.Message, Is.EqualTo("InvalidPassword: unauthorized")); - }); - } - - [Test] - public void EmailPasswordRegisterUser_Works() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var username = SyncTestHelpers.GetVerifiedUsername(); - await DefaultApp.EmailPasswordAuth.RegisterUserAsync(username, SyncTestHelpers.DefaultPassword); - - var user = await DefaultApp.LogInAsync(Credentials.EmailPassword(username, SyncTestHelpers.DefaultPassword)); - - Assert.That(user, Is.Not.Null); - Assert.That(user.State, Is.EqualTo(UserState.LoggedIn)); - Assert.That(user.AccessToken, Is.Not.Empty); - Assert.That(user.RefreshToken, Is.Not.Empty); - - Assert.That(DefaultApp.CurrentUser, Is.EqualTo(user)); - }); - } - - [Test] - public void UserCustomData_ReadsFromAccessToken() - { - const string tokenWithCustomData = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWNjZXNzIHRva2VuIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MzYyMzkwMjIsInVzZXJfZGF0YSI6eyJuYW1lIjoiVGltb3RoeSIsImVtYWlsIjoiYmlnX3RpbUBnbWFpbC5jb20iLCJhZGRyZXNzZXMiOlt7ImNpdHkiOiJOWSIsInN0cmVldCI6IjQybmQifSx7ImNpdHkiOiJTRiIsInN0cmVldCI6Ik1haW4gU3QuIn1dLCJmYXZvcml0ZUlkcyI6WzEsMiwzXX19.wYYtavafunx-iEKFNwXC6DR0C3vBDunwhvIox6XgqDE"; - var user = GetFakeUser(accessToken: tokenWithCustomData); - - var customData = user.GetCustomData()!; - Assert.That(customData, Is.Not.Null); - Assert.That(customData["name"].AsString, Is.EqualTo("Timothy")); - Assert.That(customData["email"].AsString, Is.EqualTo("big_tim@gmail.com")); - Assert.That(customData["addresses"].AsBsonArray.Count, Is.EqualTo(2)); - Assert.That(customData["addresses"][0]["city"].AsString, Is.EqualTo("NY")); - Assert.That(customData["addresses"][0]["street"].AsString, Is.EqualTo("42nd")); - Assert.That(customData["addresses"][1]["city"].AsString, Is.EqualTo("SF")); - Assert.That(customData["addresses"][1]["street"].AsString, Is.EqualTo("Main St.")); - Assert.That(customData["favoriteIds"].AsBsonArray.Select(i => i.AsInt64), Is.EquivalentTo(new[] { 1, 2, 3 })); - } - - [Preserve(AllMembers = true)] - private class AccessTokenCustomData - { - [BsonElement("name")] - [Preserve] - public string Name { get; set; } = string.Empty; - - [BsonElement("email")] - [Preserve] - public string Email { get; set; } = string.Empty; - - [BsonElement("addresses")] - [Preserve] - public Address[] Addresses { get; set; } = Array.Empty
(); - - [BsonElement("favoriteIds")] - [Preserve] - public long[] FavoriteIds { get; set; } = Array.Empty(); - - [Preserve(AllMembers = true)] - public class Address - { - [BsonElement("city")] - [Preserve] - public string City { get; set; } = string.Empty; - - [BsonElement("street")] - [Preserve] - public string Street { get; set; } = string.Empty; - } - } - - [Test] - public void UserCustomData_Generic_ReadsFromAccessToken() - { - const string tokenWithCustomData = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWNjZXNzIHRva2VuIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MzYyMzkwMjIsInVzZXJfZGF0YSI6eyJuYW1lIjoiVGltb3RoeSIsImVtYWlsIjoiYmlnX3RpbUBnbWFpbC5jb20iLCJhZGRyZXNzZXMiOlt7ImNpdHkiOiJOWSIsInN0cmVldCI6IjQybmQifSx7ImNpdHkiOiJTRiIsInN0cmVldCI6Ik1haW4gU3QuIn1dLCJmYXZvcml0ZUlkcyI6WzEsMiwzXX19.wYYtavafunx-iEKFNwXC6DR0C3vBDunwhvIox6XgqDE"; - var user = GetFakeUser(accessToken: tokenWithCustomData); - - var customData = user.GetCustomData()!; - Assert.That(customData, Is.Not.Null); - Assert.That(customData.Name, Is.EqualTo("Timothy")); - Assert.That(customData.Email, Is.EqualTo("big_tim@gmail.com")); - Assert.That(customData.Addresses.Length, Is.EqualTo(2)); - Assert.That(customData.Addresses[0].City, Is.EqualTo("NY")); - Assert.That(customData.Addresses[0].Street, Is.EqualTo("42nd")); - Assert.That(customData.Addresses[1].City, Is.EqualTo("SF")); - Assert.That(customData.Addresses[1].Street, Is.EqualTo("Main St.")); - Assert.That(customData.FavoriteIds, Is.EquivalentTo(new[] { 1, 2, 3 })); - } - - [Test] - public void UserCustomData_WhenEmpty_ReturnsNull() - { - var user = GetFakeUser(); - - Assert.That(user.GetCustomData(), Is.Null); - } - - [Test] - public void User_LinkCredentials_AllowsLoginWithNewCredentials() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await DefaultApp.LogInAsync(Credentials.Anonymous()); - - Assert.That(user.Identities, Has.Length.EqualTo(1)); - Assert.That(user.Identities[0].Provider, Is.EqualTo(Credentials.AuthProvider.Anonymous)); - Assert.That(user.Identities[0].Id, Is.Not.Null); - - var email = SyncTestHelpers.GetVerifiedUsername(); - await DefaultApp.EmailPasswordAuth.RegisterUserAsync(email, SyncTestHelpers.DefaultPassword); - var linkedUser = await user.LinkCredentialsAsync(Credentials.EmailPassword(email, SyncTestHelpers.DefaultPassword)); - - Assert.That(user.Identities, Has.Length.EqualTo(2)); - Assert.That(user.Identities[1].Provider, Is.EqualTo(Credentials.AuthProvider.EmailPassword)); - Assert.That(user.Identities[1].Id, Is.Not.Null); - - Assert.That(linkedUser.Identities, Has.Length.EqualTo(2)); - Assert.That(linkedUser.Id, Is.EqualTo(user.Id)); - Assert.That(linkedUser.Identities, Is.EquivalentTo(user.Identities)); - - var emailPasswordUser = await DefaultApp.LogInAsync(Credentials.EmailPassword(email, SyncTestHelpers.DefaultPassword)); - - Assert.That(emailPasswordUser.Id, Is.EqualTo(user.Id)); - Assert.That(emailPasswordUser.Identities, Is.EquivalentTo(user.Identities)); - }); - } - - [Test] - public void User_LinkCredentials_MultipleTimes_AllowsLoginWithAllCredentials() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await DefaultApp.LogInAsync(Credentials.Anonymous()); - - var email = SyncTestHelpers.GetVerifiedUsername(); - await DefaultApp.EmailPasswordAuth.RegisterUserAsync(email, SyncTestHelpers.DefaultPassword); - var linkedUser1 = await user.LinkCredentialsAsync(Credentials.EmailPassword(email, SyncTestHelpers.DefaultPassword)); - Assert.That(linkedUser1.Id, Is.EqualTo(user.Id)); - - var functionId = Guid.NewGuid().ToString(); - var linkedUser2 = await user.LinkCredentialsAsync(Credentials.Function(new { realmCustomAuthFuncUserId = functionId })); - Assert.That(linkedUser2.Id, Is.EqualTo(user.Id)); - - var emailPasswordUser = await DefaultApp.LogInAsync(Credentials.EmailPassword(email, SyncTestHelpers.DefaultPassword)); - Assert.That(emailPasswordUser.Id, Is.EqualTo(user.Id)); - - var functionUser = await DefaultApp.LogInAsync(Credentials.Function(new { realmCustomAuthFuncUserId = functionId })); - Assert.That(functionUser.Id, Is.EqualTo(user.Id)); - - Assert.That(user.Identities, Has.Length.EqualTo(3)); - Assert.That(user.Identities[0].Provider, Is.EqualTo(Credentials.AuthProvider.Anonymous)); - Assert.That(user.Identities[0].Id, Is.Not.Null); - - Assert.That(user.Identities[1].Provider, Is.EqualTo(Credentials.AuthProvider.EmailPassword)); - Assert.That(user.Identities[1].Id, Is.Not.Null); - - Assert.That(user.Identities[2].Provider, Is.EqualTo(Credentials.AuthProvider.Function)); - Assert.That(user.Identities[2].Id, Is.EqualTo(functionId)); - }); - } - - [Test] - public void User_LinkCredentials_MultipleTimesSameCredentials_IsNoOp() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await DefaultApp.LogInAsync(Credentials.Anonymous()); - - var functionId = Guid.NewGuid().ToString(); - var linkedUser = await user.LinkCredentialsAsync(Credentials.Function(new { realmCustomAuthFuncUserId = functionId })); - Assert.That(linkedUser.Id, Is.EqualTo(user.Id)); - - var sameLinkedUser = await user.LinkCredentialsAsync(Credentials.Function(new { realmCustomAuthFuncUserId = functionId })); - Assert.That(sameLinkedUser.Id, Is.EqualTo(user.Id)); - - var functionUser = await DefaultApp.LogInAsync(Credentials.Function(new { realmCustomAuthFuncUserId = functionId })); - Assert.That(functionUser.Id, Is.EqualTo(user.Id)); - - Assert.That(user.Identities, Has.Length.EqualTo(2)); - Assert.That(user.Identities[1].Id, Is.EqualTo(functionId)); - Assert.That(user.Identities[1].Provider, Is.EqualTo(Credentials.AuthProvider.Function)); - }); - } - - [Test] - public void User_LinkCredentials_WhenMultipleEmailPassword_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var email2 = SyncTestHelpers.GetVerifiedUsername(); - await DefaultApp.EmailPasswordAuth.RegisterUserAsync(email2, SyncTestHelpers.DefaultPassword); - - var ex = await TestHelpers.AssertThrows(() => user.LinkCredentialsAsync(Credentials.EmailPassword(email2, SyncTestHelpers.DefaultPassword))); - - // TODO: this should be bad request when https://jira.mongodb.org/browse/REALMC-7028 is fixed - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.InternalServerError)); - Assert.That(ex.Message, Does.Contain("unauthorized")); - }); - } - - [Test] - public void User_LinkCredentials_WhenAnonymous_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var ex = await TestHelpers.AssertThrows(() => user.LinkCredentialsAsync(Credentials.Anonymous())); - Assert.That(ex.Message, Does.Contain("Cannot add anonymous credentials to an existing user")); - }); - } - - [Test] - public void User_LinkCredentials_WhenInUse_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var existingEmail = SyncTestHelpers.GetVerifiedUsername(); - await DefaultApp.EmailPasswordAuth.RegisterUserAsync(existingEmail, SyncTestHelpers.DefaultPassword); - await DefaultApp.LogInAsync(Credentials.EmailPassword(existingEmail, SyncTestHelpers.DefaultPassword)); - - var anonUser = await DefaultApp.LogInAsync(Credentials.Anonymous()); - - var ex = await TestHelpers.AssertThrows(() => anonUser.LinkCredentialsAsync(Credentials.EmailPassword(existingEmail, SyncTestHelpers.DefaultPassword))); - - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - Assert.That(ex.Message, Does.Contain("unauthorized")); - }); - } - - [Test] - public void User_RetryCustomConfirmationAsync_RerunsConfirmation() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - // Standard case - var unconfirmedMail = SyncTestHelpers.GetUnconfirmedUsername(); - var credentials = Credentials.EmailPassword(unconfirmedMail, SyncTestHelpers.DefaultPassword); - - // The first time the confirmation function is called we return "pending", so the user needs to be confirmed. - // At the same time we save the user email in a collection. - await DefaultApp.EmailPasswordAuth.RegisterUserAsync(unconfirmedMail, SyncTestHelpers.DefaultPassword).Timeout(10_000, detail: "Failed to register user"); - - var ex3 = await TestHelpers.AssertThrows(() => DefaultApp.LogInAsync(credentials)); - Assert.That(ex3.Message, Does.Contain("AuthError: unauthorized")); - - // The second time we call the confirmation function we find the email we saved in the collection and return "success", so the user - // gets confirmed and can log in. - await DefaultApp.EmailPasswordAuth.RetryCustomConfirmationAsync(unconfirmedMail); - var user = await DefaultApp.LogInAsync(credentials); - Assert.That(user.State, Is.EqualTo(UserState.LoggedIn)); - - // Logged in user case - var loggedInUser = await GetUserAsync(); - var ex = await TestHelpers.AssertThrows(() => DefaultApp.EmailPasswordAuth.RetryCustomConfirmationAsync(loggedInUser.Profile.Email!)); - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - Assert.That(ex.Message, Does.Contain("already confirmed")); - - // Unknown user case - var invalidEmail = "test@gmail.com"; - var ex2 = await TestHelpers.AssertThrows(() => DefaultApp.EmailPasswordAuth.RetryCustomConfirmationAsync(invalidEmail)); - Assert.That(ex2.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - Assert.That(ex2.Message, Does.Contain("user not found")); - }); - } - - [Test] - public void User_ConfirmUserAsync_ConfirmsUser() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var unconfirmedMail = SyncTestHelpers.GetUnconfirmedUsername(); - var credentials = Credentials.EmailPassword(unconfirmedMail, SyncTestHelpers.DefaultPassword); - - // The first time the confirmation function is called we return "pending", so the user needs to be confirmed. - // At the same time we save the user email, token and tokenId in a collection. - await DefaultApp.EmailPasswordAuth.RegisterUserAsync(unconfirmedMail, SyncTestHelpers.DefaultPassword).Timeout(10_000, detail: "Failed to register user"); - - var ex = await TestHelpers.AssertThrows(() => DefaultApp.LogInAsync(credentials)); - Assert.That(ex.Message, Does.Contain("AuthError: unauthorized")); - - // This retrieves the token and tokenId we saved in the confirmation function - var functionUser = await GetUserAsync(); - var result = await functionUser.Functions.CallAsync("confirmationInfo", unconfirmedMail); - var token = result["token"].AsString; - var tokenId = result["tokenId"].AsString; - - await DefaultApp.EmailPasswordAuth.ConfirmUserAsync(token, tokenId); - var user = await DefaultApp.LogInAsync(credentials); - Assert.That(user.State, Is.EqualTo(UserState.LoggedIn)); - }); - } - - [Test] - public void User_CallResetPasswordFunctionAsync_ResetsUserPassword() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - var email = user.Profile.Email!; - - await user.LogOutAsync(); - Assert.That(user.State, Is.EqualTo(UserState.Removed)); - - var newPassword = "realm_tests_do_reset-testPassword"; - await DefaultApp.EmailPasswordAuth.CallResetPasswordFunctionAsync(email, newPassword); - - user = await DefaultApp.LogInAsync(Credentials.EmailPassword(email, newPassword)); - Assert.That(user.State, Is.EqualTo(UserState.LoggedIn)); - }); - } - - [Test] - public void User_ResetPasswordAsync_ConfirmsResetPassword() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - var email = user.Profile.Email!; - - await user.LogOutAsync(); - Assert.That(user.State, Is.EqualTo(UserState.Removed)); - - // This returns "pending" the first time, so the password change is not valid yet. We save the token and tokenId - // passed to the reset function, to confirm the password change later. - var newPassword = "realm_tests_do_not_reset-testPassword"; - await DefaultApp.EmailPasswordAuth.CallResetPasswordFunctionAsync(email, newPassword); - - var ex = await TestHelpers.AssertThrows(() => DefaultApp.LogInAsync(Credentials.EmailPassword(email, newPassword))); - Assert.That(ex.Message, Does.Contain("InvalidPassword: unauthorized")); - - // This retrieves the token and tokenId we saved in the password reset function. - var functionUser = await GetUserAsync(); - var result = await functionUser.Functions.CallAsync("resetInfo", email); - var token = result["token"].AsString; - var tokenId = result["tokenId"].AsString; - - await DefaultApp.EmailPasswordAuth.ResetPasswordAsync(newPassword, token, tokenId); - - user = await DefaultApp.LogInAsync(Credentials.EmailPassword(email, newPassword)); - Assert.That(user.State, Is.EqualTo(UserState.LoggedIn)); - }); - } - - [Test] - public void User_JWT_LogsInAndReadsDataFromToken() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - const string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NTY3ODkwIiwic3ViIjoiMTIzNDU2Nzg5MCIsIm5hbWUiOnsiZmlyc3QiOiJKb2huIiwibGFzdCI6IkRvZSJ9LCJqb2JUaXRsZSI6IkJyZWFrZXIgb2YgdGhpbmdzIiwiZW1haWwiOiJqb2huQGRvZS5jb20iLCJwaWN0dXJlVXJsIjoiaHR0cHM6Ly9kb2UuY29tL215cGljdHVyZSIsImdlbmRlciI6Im90aGVyIiwiYmlydGhkYXkiOiIxOTM0LTA1LTE1IiwibWluQWdlIjoiODAiLCJtYXhBZ2UiOiI5MCIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoyMDE2MjM5MDIyLCJhdWQiOiJteS1hdWRpZW5jZSJ9.B6u3SkU-pzCH_LA_HsevAJF1EI1LbAOfL6GP3bhjVpP4FBtrmZYQD_b7Z_wJLE0vaffX1eN6U_vE9t26bmXz2ig4jJRmbg7Kx9ka1BkcE7MF9nmdC90ffHgNBvU40yKpMBtVL9VNQCe-F6mSvUqpox2tQQpNKaXf8yQslAf_tfvqTvF0mPXnqU1v_5KtieMybOb7O8nV6LITrjsAA5ff4spWSgcskjXcyjq6DIdWbLlVJycodr-MjKu94fNXXsBLf0iK5XHYpL1Bs-ILs494_aK_Pf2GD3pYa56XjqN-nO_cYbIxzmsBkNtAp0hvg_Gp0O6QFi66Qkr7ORbkRasGAg"; - var credentials = Credentials.JWT(token); - var user = await DefaultApp.LogInAsync(credentials); - - Assert.That(user.Profile.FirstName, Is.EqualTo("John")); - Assert.That(user.Profile.LastName, Is.EqualTo("Doe")); - Assert.That(user.Profile.Email, Is.EqualTo("john@doe.com")); - Assert.That(user.Profile.Birthday, Is.EqualTo("1934-05-15")); - Assert.That(user.Profile.Gender, Is.EqualTo("other")); - Assert.That(user.Profile.MinAge, Is.EqualTo("80")); - Assert.That(user.Profile.MaxAge, Is.EqualTo("90")); - Assert.That(user.Profile.PictureUrl!.AbsoluteUri, Is.EqualTo("https://doe.com/mypicture")); - - // TODO: add other checks once https://github.com/realm/realm-core/issues/4131 is implemented. - }); - } - - [Test, Ignore("Requires manually getting a fb token")] - public void User_Facebook_LogsInAndReadsDataFromFacebook() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - const string fbToken = "EAAFYw2aZAL1EBAHBBH22XBDZAutJFQ65KxH0bZAexYul5KtsHcjhI722XYEr4jKlaNvlosFsdZCT8dGUQNy2euZB684mpvtIIJEWWYMoH66bbEbKIrHRWqZBC8KMpSscoyzhFTJMpDYsrIilZBRN1A6bicXGaUNXVz5A0ucyZB7WkmQ8uUmdRWel9q6S8BJH3ZBCZAzWtcZCYmgEwZDZD"; - var credentials = Credentials.Facebook(fbToken); - var user = await DefaultApp.LogInAsync(credentials); - - Assert.That(user.Id, Is.Not.Null); - - Assert.That(user.Profile.FirstName, Is.Not.Null); - Assert.That(user.Profile.LastName, Is.Not.Null); - }); - } - - #region API Keys - - [Test] - public void UserApiKeys_Create_CreatesApiKeyAndRevealsValue() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - var apiKey = await user.ApiKeys.CreateAsync("my-api-key"); - - Assert.That(apiKey.IsEnabled); - Assert.That(apiKey.Name, Is.EqualTo("my-api-key")); - Assert.That(apiKey.Value, Is.Not.Null); - Assert.That(apiKey.Id, Is.Not.EqualTo(default(ObjectId))); - }); - } - - [Test] - public void UserApiKeys_Create_WithInvalidName_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var ex = await TestHelpers.AssertThrows(() => user.ApiKeys.CreateAsync("My very cool key")); - - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - Assert.That(ex.Message, Does.Contain("InvalidParameter")); - Assert.That(ex.Message, Does.Contain("can only contain ASCII letters, numbers, underscores, and hyphens")); - Assert.That(ex.HelpLink, Does.Contain("logs?co_id=")); - }); - } - - [Test] - public void UserApiKeys_Fetch_WhenNoneExist_ReturnsNull() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var apiKey = await user.ApiKeys.FetchAsync(ObjectId.GenerateNewId()); - - Assert.That(apiKey, Is.Null); - }); - } - - [Test] - public void UserApiKeys_Fetch_WhenIdDoesntMatch_ReturnsNull() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - await user.ApiKeys.CreateAsync("foo"); - - var apiKey = await user.ApiKeys.FetchAsync(ObjectId.GenerateNewId()); - - Assert.That(apiKey, Is.Null); - }); - } - - [Test] - public void UserApiKeys_Fetch_WhenIdMatches_ReturnsKey() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var key = await user.ApiKeys.CreateAsync("foo"); - - var fetched = await user.ApiKeys.FetchAsync(key.Id); - - AssertKeysAreSame(key, fetched); - }); - } - - [Test] - public void UserApiKeys_FetchAll_WithNoKeys() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var keys = await user.ApiKeys.FetchAllAsync(); - Assert.That(keys, Is.Empty); - }); - } - - [Test] - public void UserApiKeys_FetchAll_WithOneKey() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var key1 = await user.ApiKeys.CreateAsync("foo"); - - var keys = await user.ApiKeys.FetchAllAsync(); - Assert.That(keys.Count(), Is.EqualTo(1)); - - AssertKeysAreSame(key1, keys.Single()); - }); - } - - [Test] - public void UserApiKeys_FetchAll_WithMultipleKeys() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var originals = new List(); - for (var i = 0; i < 5; i++) - { - originals.Add(await user.ApiKeys.CreateAsync($"key-{i}")); - } - - var keys = await user.ApiKeys.FetchAllAsync(); - Assert.That(keys.Count(), Is.EqualTo(originals.Count)); - - for (var i = 0; i < originals.Count; i++) - { - AssertKeysAreSame(originals[i], keys.ElementAt(i)); - } - }); - } - - [Test] - public void UserApiKeys_DeleteKey_WithExistingId() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var toDelete = await user.ApiKeys.CreateAsync("to-delete"); - var toRemain = await user.ApiKeys.CreateAsync("to-remain"); - - await user.ApiKeys.DeleteAsync(toDelete.Id); - - var fetchedDeleted = await user.ApiKeys.FetchAsync(toDelete.Id); - Assert.That(fetchedDeleted, Is.Null); - - var fetchedRemained = await user.ApiKeys.FetchAsync(toRemain.Id); - AssertKeysAreSame(toRemain, fetchedRemained); - - var allKeys = await user.ApiKeys.FetchAllAsync(); - - Assert.That(allKeys.Count(), Is.EqualTo(1)); - AssertKeysAreSame(toRemain, allKeys.Single()); - }); - } - - [Test] - public void UserApiKeys_DeleteKey_WithNonExistingId() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var first = await user.ApiKeys.CreateAsync("first"); - var second = await user.ApiKeys.CreateAsync("second"); - - await user.ApiKeys.DeleteAsync(ObjectId.GenerateNewId()); - - var allKeys = await user.ApiKeys.FetchAllAsync(); - - Assert.That(allKeys.Count(), Is.EqualTo(2)); - AssertKeysAreSame(first, allKeys.ElementAt(0)); - AssertKeysAreSame(second, allKeys.ElementAt(1)); - }); - } - - [Test] - public void UserApiKeys_DisableApiKey_WhenNonExistent_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var id = ObjectId.GenerateNewId(); - var ex = await TestHelpers.AssertThrows(() => user.ApiKeys.DisableAsync(id)); - - Assert.That(ex.Message, Does.Contain("doesn't exist")); - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - Assert.That(ex.Message, Does.Contain(id.ToString())); - Assert.That(ex.HelpLink, Does.Contain("logs?co_id=")); - }); - } - - [Test] - public void UserApiKeys_EnableApiKey_WhenNonExistent_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var id = ObjectId.GenerateNewId(); - var ex = await TestHelpers.AssertThrows(() => user.ApiKeys.EnableAsync(id)); - - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - Assert.That(ex.Message, Does.Contain("doesn't exist")); - Assert.That(ex.Message, Does.Contain(id.ToString())); - Assert.That(ex.HelpLink, Does.Contain("logs?co_id=")); - }); - } - - [Test] - public void UserApiKeys_EnableApiKey_WhenEnabled_IsNoOp() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var key = await user.ApiKeys.CreateAsync("foo"); - Assert.That(key.IsEnabled); - - await user.ApiKeys.EnableAsync(key.Id); - - var fetched = await user.ApiKeys.FetchAsync(key.Id); - Assert.That(fetched!.IsEnabled); - }); - } - - [Test] - public void UserApiKeys_DisableApiKey_WhenDisabled_IsNoOp() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var key = await user.ApiKeys.CreateAsync("foo"); - Assert.That(key.IsEnabled); - - await user.ApiKeys.DisableAsync(key.Id); - - var fetched = await user.ApiKeys.FetchAsync(key.Id); - Assert.IsFalse(fetched!.IsEnabled); - - await user.ApiKeys.DisableAsync(key.Id); - - var refetched = await user.ApiKeys.FetchAsync(key.Id); - Assert.IsFalse(refetched!.IsEnabled); - }); - } - - [Test] - public void UserApiKeys_Disable_DisablesKey() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var first = await user.ApiKeys.CreateAsync("first"); - var second = await user.ApiKeys.CreateAsync("second"); - - Assert.That(first.IsEnabled); - Assert.That(second.IsEnabled); - - await user.ApiKeys.DisableAsync(first.Id); - - var keys = await user.ApiKeys.FetchAllAsync(); - - Assert.That(keys.ElementAt(0).Id, Is.EqualTo(first.Id)); - Assert.IsFalse(keys.ElementAt(0).IsEnabled); - - Assert.That(keys.ElementAt(1).Id, Is.EqualTo(second.Id)); - Assert.IsTrue(keys.ElementAt(1).IsEnabled); - }); - } - - [Test] - public void UserApiKeys_Enable_ReenablesKey() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var first = await user.ApiKeys.CreateAsync("first"); - var second = await user.ApiKeys.CreateAsync("second"); - - Assert.That(first.IsEnabled); - Assert.That(second.IsEnabled); - - await user.ApiKeys.DisableAsync(first.Id); - - var keys = await user.ApiKeys.FetchAllAsync(); - - Assert.That(keys.ElementAt(0).Id, Is.EqualTo(first.Id)); - Assert.IsFalse(keys.ElementAt(0).IsEnabled); - - Assert.That(keys.ElementAt(1).Id, Is.EqualTo(second.Id)); - Assert.IsTrue(keys.ElementAt(1).IsEnabled); - - await user.ApiKeys.EnableAsync(first.Id); - - keys = await user.ApiKeys.FetchAllAsync(); - - Assert.That(keys.ElementAt(0).Id, Is.EqualTo(first.Id)); - Assert.IsTrue(keys.ElementAt(0).IsEnabled); - - Assert.That(keys.ElementAt(1).Id, Is.EqualTo(second.Id)); - Assert.IsTrue(keys.ElementAt(1).IsEnabled); - }); - } - - [Test] - public void UserApiKeys_CanLoginWithGeneratedKey() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - var apiKey = await user.ApiKeys.CreateAsync("my-api-key"); - - var credentials = Credentials.ApiKey(apiKey.Value!); - var apiKeyUser = await DefaultApp.LogInAsync(credentials); - - Assert.That(apiKeyUser.Id, Is.EqualTo(user.Id)); - - Assert.That(apiKeyUser.Identities.Select(i => i.Provider), Does.Contain(Credentials.AuthProvider.ApiKey)); - Assert.That(apiKeyUser.RefreshToken, Is.EqualTo(user.RefreshToken)); - }); - } - - [Test] - public void UserApiKeys_CanLoginWithReenabledKey() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - var apiKey = await user.ApiKeys.CreateAsync("my-api-key"); - - await user.ApiKeys.DisableAsync(apiKey.Id); - - var credentials = Credentials.ApiKey(apiKey.Value!); - - var ex = await TestHelpers.AssertThrows(() => DefaultApp.LogInAsync(credentials)); - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - Assert.That(ex.HelpLink, Does.Contain("logs?co_id=")); - Assert.That(ex.Message, Is.EqualTo("AuthError: unauthorized")); - - await user.ApiKeys.EnableAsync(apiKey.Id); - - var apiKeyUser = await DefaultApp.LogInAsync(credentials); - - Assert.That(apiKeyUser.Id, Is.EqualTo(user.Id)); - - Assert.That(apiKeyUser.RefreshToken, Is.EqualTo(user.RefreshToken)); - }); - } - - [Test] - public void UserApiKeys_CantLoginWithDisabledKey() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - var apiKey = await user.ApiKeys.CreateAsync("my-api-key"); - - await user.ApiKeys.DisableAsync(apiKey.Id); - - var credentials = Credentials.ApiKey(apiKey.Value!); - - var ex = await TestHelpers.AssertThrows(() => DefaultApp.LogInAsync(credentials)); - - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - Assert.That(ex.Message, Is.EqualTo("AuthError: unauthorized")); - Assert.That(ex.HelpLink, Does.Contain("logs?co_id=")); - }); - } - - [Test] - public void UserApiKeys_CantLoginWithDeletedKey() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - var apiKey = await user.ApiKeys.CreateAsync("my-api-key"); - - await user.ApiKeys.DeleteAsync(apiKey.Id); - - var credentials = Credentials.ApiKey(apiKey.Value!); - - var ex = await TestHelpers.AssertThrows(() => DefaultApp.LogInAsync(credentials)); - - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - Assert.That(ex.Message, Is.EqualTo("AuthError: unauthorized")); - Assert.That(ex.HelpLink, Does.Contain("logs?co_id=")); - }); - } - - private static void AssertKeysAreSame(ApiKey original, ApiKey? fetched) - { - Assert.That(fetched, Is.Not.Null); - Assert.That(fetched!.Id, Is.EqualTo(original.Id)); - Assert.That(fetched.IsEnabled, Is.EqualTo(original.IsEnabled)); - Assert.That(fetched.Name, Is.EqualTo(original.Name)); - Assert.That(fetched.Value, Is.Null); - } - - #endregion - - [Test] - public void UserCustomData() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - Assert.That(user.GetCustomData(), Is.Null); - - var updatedData = await user.RefreshCustomDataAsync(); - Assert.That(updatedData, Is.Null); - - var collection = user.GetMongoClient("BackingDB").GetDatabase(SyncTestHelpers.RemoteMongoDBName()).GetCollection("users"); - - var customDataDoc = BsonDocument.Parse(@"{ - _id: ObjectId(""" + ObjectId.GenerateNewId() + @"""), - user_id: """ + user.Id + @""", - age: 153, - interests: [ ""painting"", ""sci-fi"" ] - }"); - - await collection.InsertOneAsync(customDataDoc); - - updatedData = await user.RefreshCustomDataAsync(); - - Assert.That(updatedData, Is.Not.Null); - Assert.That(updatedData!["age"].AsInt32, Is.EqualTo(153)); - Assert.That(updatedData["interests"].AsBsonArray.Select(i => i.AsString), Is.EquivalentTo(new[] { "painting", "sci-fi" })); - - Assert.That(user.GetCustomData(), Is.Not.Null); - Assert.That(user.GetCustomData()!["age"].AsInt32, Is.EqualTo(153)); - }); - } - - [Test] - public void UserCustomData_Generic() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - Assert.That(user.GetCustomData(), Is.Null); - - var updatedData = await user.RefreshCustomDataAsync(); - Assert.That(updatedData, Is.Null); - - var collection = user.GetMongoClient("BackingDB").GetDatabase(SyncTestHelpers.RemoteMongoDBName()).GetCollection("users"); - - var customDataDoc = new CustomDataDocument - { - UserId = user.Id, - Age = 45, - Interests = new[] { "swimming", "biking" } - }; - - await collection.InsertOneAsync(customDataDoc); - - updatedData = await user.RefreshCustomDataAsync(); - - Assert.That(updatedData, Is.Not.Null); - Assert.That(updatedData!.Age, Is.EqualTo(45)); - Assert.That(updatedData.Interests, Is.EquivalentTo(new[] { "swimming", "biking" })); - - var customData = user.GetCustomData(); - - Assert.That(customData, Is.Not.Null); - Assert.That(customData!.Age, Is.EqualTo(45)); - Assert.That(customData.Interests, Is.EquivalentTo(new[] { "swimming", "biking" })); - }); - } - - [Test] - public void UserAnonymous([Values(true, false)] bool firstReuse, [Values(true, false)] bool secondReuse) - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await DefaultApp.LogInAsync(Credentials.Anonymous(reuseExisting: firstReuse)); - Assert.That(user, Is.Not.Null); - Assert.That(user.Id, Is.Not.Null); - - var anotherUser = await DefaultApp.LogInAsync(Credentials.Anonymous(reuseExisting: secondReuse)); - Assert.That(anotherUser, Is.Not.Null); - Assert.That(anotherUser.Id, Is.Not.Null); - - // We only expect both users to be the same if they both reused their credentials - Assert.That(user.Id == anotherUser.Id, Is.EqualTo(secondReuse), $"Expected Ids to {(secondReuse ? string.Empty : "not ")}match"); - Assert.That(user == anotherUser, Is.EqualTo(secondReuse), $"Expected Users to {(secondReuse ? string.Empty : "not ")}match"); - }); - } - - [Test] - public void UserAnonymous_CombiningReuseAndNotReuse() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var anonA = await DefaultApp.LogInAsync(Credentials.Anonymous(reuseExisting: false)); - var reusedA1 = await DefaultApp.LogInAsync(Credentials.Anonymous()); - var reusedA2 = await DefaultApp.LogInAsync(Credentials.Anonymous()); - var anonB = await DefaultApp.LogInAsync(Credentials.Anonymous(reuseExisting: false)); - var reusedB = await DefaultApp.LogInAsync(Credentials.Anonymous()); - - Assert.That(anonA, Is.EqualTo(reusedA1)); - Assert.That(anonA, Is.EqualTo(reusedA2)); - - Assert.That(anonB, Is.Not.EqualTo(anonA)); - Assert.That(anonB, Is.EqualTo(reusedB)); - - await anonB.LogOutAsync(); - - Assert.That(anonB.State, Is.EqualTo(UserState.Removed)); - Assert.That(reusedB.State, Is.EqualTo(UserState.Removed)); - - var reusedA3 = await DefaultApp.LogInAsync(Credentials.Anonymous()); - - Assert.That(reusedA3, Is.EqualTo(anonA)); - }); - } - - [Test] - public void UserEqualsOverrides() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await DefaultApp.LogInAsync(Credentials.Anonymous(reuseExisting: false)); - var currentUser = DefaultApp.CurrentUser!; - - Assert.That(user.Id, Is.EqualTo(currentUser.Id)); - Assert.That(user.Equals(currentUser)); - Assert.That(user == currentUser); - - var anotherUser = await DefaultApp.LogInAsync(Credentials.Anonymous(reuseExisting: false)); - Assert.That(user.Id, Is.Not.EqualTo(anotherUser.Id)); - Assert.That(user.Equals(anotherUser), Is.False); - Assert.That(user != anotherUser); - }); - } - - [Test] - public void UserToStringOverride() - { - var user = GetFakeUser(); - Assert.That(user.ToString(), Does.Contain(user.Id)); - } - - [Test] - public void UserLogOut_RaisesChanged() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - - var tcs = new TaskCompletionSource(); - user.Changed += (s, _) => - { - try - { - Assert.That(s, Is.EqualTo(user)); - Assert.That(user.State, Is.EqualTo(UserState.Removed).Or.EqualTo(UserState.LoggedOut)); - tcs.TrySetResult(); - } - catch (Exception ex) - { - tcs.TrySetException(ex); - } - }; - - await user.LogOutAsync(); - - await tcs.Task; - - Assert.That(user.State, Is.EqualTo(UserState.Removed)); - }); - } - - [Test] - public void UserChanged_DoesntKeepObjectAlive() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var references = await new Func>(async () => - { - var user = await GetUserAsync(); - user.Changed += (_, _) => { }; - - return new WeakReference(user); - })(); - - await TestHelpers.WaitUntilReferencesAreCollected(10000, references); - }); - } - - [Test] - public void UserCustomDataChange_RaisesChanged() - { - var tcs = new TaskCompletionSource(); - SyncTestHelpers.RunBaasTestAsync(async () => - { - var user = await GetUserAsync(); - user.Changed += OnUserChanged; - - var collection = user.GetMongoClient("BackingDB").GetDatabase(SyncTestHelpers.RemoteMongoDBName()).GetCollection("users"); - - var customDataDoc = new BsonDocument - { - ["_id"] = ObjectId.GenerateNewId(), - ["user_id"] = user.Id, - ["age"] = 5 - }; - - await collection.InsertOneAsync(customDataDoc); - - var customUserData = await user.RefreshCustomDataAsync(); - Assert.That(customUserData!["age"].AsInt32, Is.EqualTo(5)); - - await tcs.Task; - - // Unsubscribe and verify that it no longer raises user changed - user.Changed -= OnUserChanged; - - tcs = new(); - - var filter = BsonDocument.Parse(@"{ - user_id: { $eq: """ + user.Id + @""" } - }"); - var update = BsonDocument.Parse(@"{ - $set: { - age: 199 - } - }"); - - await collection.UpdateOneAsync(filter, update); - - customUserData = await user.RefreshCustomDataAsync(); - Assert.That(customUserData!["age"].AsInt32, Is.EqualTo(199)); - - await TestHelpers.AssertThrows(() => tcs.Task.Timeout(2000)); - }); - - void OnUserChanged(object? sender, EventArgs e) - { - tcs.TrySetResult(); - } - } - - private class CustomDataDocument - { - [Preserve] - [BsonElement("_id")] - public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); - - [Preserve] - [BsonElement("user_id")] - public string UserId { get; set; } = string.Empty; - - [Preserve] - [BsonElement("age")] - public int Age { get; set; } - - [Preserve] - [BsonElement("interests")] - public string[] Interests { get; set; } = Array.Empty(); - } - } -} diff --git a/Tests/Tests.UWP/MainPage.xaml.cs b/Tests/Tests.UWP/MainPage.xaml.cs index b731a2c7e5..9f947bab43 100644 --- a/Tests/Tests.UWP/MainPage.xaml.cs +++ b/Tests/Tests.UWP/MainPage.xaml.cs @@ -19,7 +19,6 @@ using System; using System.IO; using NUnit.Runner.Services; -using Realms.Tests.Sync; using Windows.ApplicationModel.Core; using Windows.Storage; using Windows.UI.Xaml.Navigation; @@ -56,7 +55,7 @@ protected override async void OnNavigatedTo(NavigationEventArgs e) if (e.Parameter != null && e.Parameter is string launchParams) { - var args = await SyncTestHelpers.ExtractBaasSettingsAsync(TestHelpers.SplitArguments(launchParams)); + var args = TestHelpers.SplitArguments(launchParams); if (TestHelpers.IsHeadlessRun(args)) { _nunit.Options.AutoRun = true; diff --git a/Tests/Tests.XamarinMac/Main.cs b/Tests/Tests.XamarinMac/Main.cs index 20681f4dc5..03c462f3c2 100644 --- a/Tests/Tests.XamarinMac/Main.cs +++ b/Tests/Tests.XamarinMac/Main.cs @@ -17,7 +17,6 @@ //////////////////////////////////////////////////////////////////////////// using AppKit; -using Realms.Tests.Sync; namespace Realms.Tests.XamarinMac { @@ -28,7 +27,7 @@ internal static class MainClass public static void Main(string[] args) { NSApplication.Init(); - Args = SyncTestHelpers.ExtractBaasSettings(args); + Args = args; NSApplication.Main(args); } } diff --git a/Tests/Tests.iOS/AppDelegate.cs b/Tests/Tests.iOS/AppDelegate.cs index bf10c2362b..a9cb5f06a8 100644 --- a/Tests/Tests.iOS/AppDelegate.cs +++ b/Tests/Tests.iOS/AppDelegate.cs @@ -16,13 +16,11 @@ // //////////////////////////////////////////////////////////////////////////// -using System; using System.Linq; using System.Threading.Tasks; using Foundation; using NUnit.Runner; using NUnit.Runner.Services; -using Realms.Tests.Sync; using UIKit; using Xamarin.Forms; using Xamarin.Forms.Platform.iOS; @@ -49,8 +47,6 @@ public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary .Select(a => a.Replace("-app-arg=", string.Empty)) .ToArray(); - arguments = SyncTestHelpers.ExtractBaasSettings(arguments); - if (TestHelpers.IsHeadlessRun(arguments)) { options.AutoRun = true; diff --git a/Tools/DeployApps/BaasClient.cs b/Tools/DeployApps/BaasClient.cs deleted file mode 100644 index 5559b819b3..0000000000 --- a/Tools/DeployApps/BaasClient.cs +++ /dev/null @@ -1,1222 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Reflection; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using MongoDB.Bson; -using MongoDB.Bson.Serialization; -using MongoDB.Bson.Serialization.Attributes; -using MongoDB.Bson.Serialization.Serializers; - -namespace Baas -{ - public static class AppConfigType - { - public const string Default = "pbs-str"; - public const string IntPartitionKey = "pbs-int"; - public const string ObjectIdPartitionKey = "pbs-oid"; - public const string UUIDPartitionKey = "pbs-uuid"; - public const string FlexibleSync = "flx"; - public const string StaticSchema = "schema"; - } - - public class BaasClient - { - public class FunctionReturn - { - public int Deleted { get; set; } - } - - private const string ConfirmFuncSource = - @"exports = async function ({ token, tokenId, username }) { - // process the confirm token, tokenId and username - if (username.includes(""realm_tests_do_autoverify"")) { - return { status: 'success' }; - } - - if (username.includes(""realm_tests_do_not_confirm"")) { - const mongodb = context.services.get('BackingDB'); - let collection = mongodb.db('test_db').collection('not_confirmed'); - let result = await collection.findOne({'email': username}); - - if(result === null) - { - let newVal = { - 'email': username, - 'token': token, - 'tokenId': tokenId, - } - - await collection.insertOne(newVal); - return { status: 'pending' }; - } - - return { status: 'success' }; - } - - // fail the user confirmation - return { status: 'fail' }; - };"; - - private const string ResetFuncSource = - @"exports = async function ({ token, tokenId, username, password, currentPasswordValid }) { - // process the reset token, tokenId, username and password - if (password.includes(""realm_tests_do_reset"")) { - return { status: 'success' }; - } - - if (password.includes(""realm_tests_do_not_reset"")) { - const mongodb = context.services.get('BackingDB'); - let collection = mongodb.db('test_db').collection('not_reset'); - let result = await collection.findOne({'email': username}); - - if(result === null) - { - let newVal = { - 'email': username, - 'token': token, - 'tokenId': tokenId, - } - - await collection.insertOne(newVal); - return { status: 'pending' }; - } - - return { status: 'success' }; - } - - // will not reset the password - return { status: 'fail' }; - };"; - - private const string TriggerClientResetOnSyncServerFuncSource = - @"exports = async function(userId, appId = '') { - const mongodb = context.services.get('BackingDB'); - console.log('user.id: ' + context.user.id); - try { - let dbName = '__realm_sync'; - if (appId !== '') - { - dbName += `_${appId}`; - } - const deletionResult = await mongodb.db(dbName).collection('clientfiles').deleteMany({ ownerId: userId }); - console.log('Deleted documents: ' + deletionResult.deletedCount); - - return { Deleted: deletionResult.deletedCount }; - } catch(err) { - throw 'Deletion failed: ' + err; - } - };"; - - private const string ConfirmationInfoFuncSource = - @"exports = async function(username){ - const mongodb = context.services.get('BackingDB'); - let collection = mongodb.db('test_db').collection('not_confirmed'); - return await collection.findOne({'email': username}); - };"; - - private const string ResetPasswordInfoFuncSource = - @"exports = async function(username){ - const mongodb = context.services.get('BackingDB'); - let collection = mongodb.db('test_db').collection('not_reset'); - return await collection.findOne({'email': username}); - };"; - - private readonly HttpClient _client = new(); - - private readonly string? _clusterName; - - private readonly TextWriter _output; - - private string _groupId = null!; - private string? _refreshToken; - - private string _shortSuffix - { - get - { - var completeSuffix = $"{Differentiator}-{_clusterName}"; - if (completeSuffix.Length < 8) - { - return completeSuffix; - } - - using var sha = SHA256.Create(); - var inputBytes = Encoding.ASCII.GetBytes(completeSuffix); - var hashBytes = sha.ComputeHash(inputBytes); - - var sb = new StringBuilder(); - for (var i = 0; i < 4; i++) - { - sb.Append(hashBytes[i].ToString("X2")); - } - - return sb.ToString().ToLower(); - } - } - - private string _appSuffix => $"-{_shortSuffix}"; - - public string Differentiator { get; } - - static BaasClient() - { - BsonSerializer.RegisterSerializer(new ObjectSerializer(_ => true)); - } - - private BaasClient(Uri baseUri, string differentiator, TextWriter output, string? clusterName = null) - { - _client.BaseAddress = new Uri(baseUri, "api/admin/v3.0/"); - _client.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "application/json"); - _clusterName = clusterName; - Differentiator = differentiator; - _output = output; - } - - public static async Task Docker(Uri baseUri, string differentiator, TextWriter output) - { - var result = new BaasClient(baseUri, differentiator, output); - - await result.Authenticate("local-userpass", new - { - username = "unique_user@domain.com", - password = "password" - }); - - var groupDoc = await result.GetAsync("auth/profile"); - result._groupId = groupDoc!["roles"].AsBsonArray[0].AsBsonDocument["group_id"].AsString; - - return result; - } - - public static async Task Atlas(Uri baseUri, string differentiator, TextWriter output, string clusterName, string apiKey, string privateApiKey, string groupId) - { - var result = new BaasClient(baseUri, differentiator, output, clusterName); - await result.Authenticate("mongodb-cloud", new - { - username = apiKey, - apiKey = privateApiKey - }); - - result._groupId = groupId; - - return result; - } - - private class BaasArgs - { - public string? BaasaasApiKey { get; set; } - - public string? BaasUrl { get; set; } - - public string? BaasCluster { get; set; } - - public string? BaasApiKey { get; set; } - - public string? BaasPrivateApiKey { get; set; } - - public string? BaasProjectId { get; set; } - - public string BaasDifferentiator { get; set; } = "local"; - - [MemberNotNullWhen(false, nameof(BaasCluster), nameof(BaasApiKey), nameof(BaasPrivateApiKey), nameof(BaasProjectId))] - public bool UseDocker => BaasCluster == null; - } - - public static async Task<(BaasClient? Client, Uri? BaseUrl, string[] RemainingArgs)> CreateClientFromArgs(string[] args, TextWriter output) - { - if (args == null) - { - throw new ArgumentNullException(nameof(args)); - } - - var (extracted, remaining) = ExtractArguments(args); - - var differentiator = extracted.BaasDifferentiator; - - BaasClient client; - Uri baseUri; - - if (!string.IsNullOrEmpty(extracted.BaasaasApiKey)) - { - baseUri = await GetOrDeployContainer(extracted.BaasaasApiKey!, differentiator, output); - client = await Docker(baseUri, differentiator, output); - } - else - { - if (string.IsNullOrEmpty(extracted.BaasUrl)) - { - return (null, null, remaining); - } - - baseUri = new Uri(extracted.BaasUrl!); - - client = extracted.UseDocker - ? await Docker(baseUri, differentiator, output) - : await Atlas(baseUri, differentiator, output, extracted.BaasCluster, extracted.BaasApiKey, extracted.BaasPrivateApiKey, extracted.BaasProjectId); - } - - return (client, baseUri, remaining); - } - - public static async Task TerminateBaasFromArgs(string[] args, TextWriter output) - { - if (args == null) - { - throw new ArgumentNullException(nameof(args)); - } - - var (extracted, _) = ExtractArguments(args); - - var differentiator = extracted.BaasDifferentiator; - - if (string.IsNullOrEmpty(extracted.BaasaasApiKey)) - { - throw new InvalidOperationException("Need a Baasaas API key to terminate containers"); - } - - await StopContainer(extracted.BaasaasApiKey!, differentiator, output); - } - - public string GetSyncDatabaseName(string appType = AppConfigType.Default) => $"Sync_{Differentiator}_{appType}"; - - private async Task Authenticate(string provider, object credentials) - { - var authDoc = await PostAsync($"auth/providers/{provider}/login", credentials); - - _refreshToken = authDoc!["refresh_token"].AsString; - _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authDoc["access_token"].AsString); - } - - public static async Task GetOrDeployContainer(string baasaasApiKey, string differentiator, TextWriter output) - { - var baaSaasClient = new BaasaasClient(baasaasApiKey); - var uriString = await baaSaasClient.GetOrDeployContainer(differentiator, output); - return new Uri(uriString); - } - - public static async Task StopContainer(string baaSaasApiKey, string differentiator, TextWriter output) - { - var baaSaasClient = new BaasaasClient(baaSaasApiKey); - await baaSaasClient.StopContainersForDifferentiator(differentiator, output); - } - - public async Task> GetOrCreateApps() - { - var apps = await GetApps(); - - _output.WriteLine($"Found {apps.Length} apps."); - - var result = new Dictionary(); - await GetOrCreateApp(result, AppConfigType.Default, apps, CreateDefaultApp); - await GetOrCreateApp(result, AppConfigType.FlexibleSync, apps, name => CreateFlxApp(name, enableDevMode: true)); - await GetOrCreateApp(result, AppConfigType.StaticSchema, apps, name => CreateFlxApp(name, enableDevMode: false)); - await GetOrCreateApp(result, AppConfigType.IntPartitionKey, apps, name => CreatePbsApp(name, "long")); - await GetOrCreateApp(result, AppConfigType.UUIDPartitionKey, apps, name => CreatePbsApp(name, "uuid")); - await GetOrCreateApp(result, AppConfigType.ObjectIdPartitionKey, apps, name => CreatePbsApp(name, "objectId")); - - return result; - } - - private async Task GetOrCreateApp(IDictionary result, string name, BaasApp[] apps, Func> creator) - { - var app = apps.SingleOrDefault(a => a.Name.StartsWith(name)); - if (app == null) - { - app = await creator(name); - } - else - { - _output.WriteLine($"Found {app.Name} with id {app.ClientAppId}."); - } - - result[app.Name] = app; - } - - private async Task CreateDefaultApp(string name) - { - var app = await CreatePbsApp(name, "string", setupCollections: true); - - var authFuncId = await CreateFunction(app, "authFunc", @"exports = (loginPayload) => { - return loginPayload[""realmCustomAuthFuncUserId""]; - };"); - - await CreateFunction(app, "triggerClientResetOnSyncServer", TriggerClientResetOnSyncServerFuncSource, runAsSystem: true); - await CreateFunction(app, "confirmationInfo", ConfirmationInfoFuncSource, runAsSystem: true); - await CreateFunction(app, "resetInfo", ResetPasswordInfoFuncSource, runAsSystem: true); - - await CreateFunction(app, "documentFunc", @"exports = function(first, second){ - return { - intValue: first.intValue + second.intValue, - floatValue: first.floatValue + second.floatValue, - stringValue: first.stringValue + second.stringValue, - objectId: first.objectId, - date: second.date, - child: { - intValue: first.child.intValue + second.child.intValue - }, - arr: [ first.arr[0], second.arr[0] ] - } - };"); - - await CreateFunction(app, "mirror", @"exports = function(arg){ - return arg; - };"); - - await CreateFunction(app, "sumFunc", @"exports = function(...args) { - return args.reduce((a,b) => a + b, 0); - };"); - - await EnableProvider(app, "api-key"); - - await EnableProvider(app, "custom-function", new - { - authFunctionName = "authFunc", - authFunctionId = authFuncId - }); - - await EnableProvider(app, "custom-token", new - { - audience = "my-audience", - signingAlgorithm = "RS256", - useJWKURI = false, - signingKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntjcGTEsm1r7UqEYgovi\nUX3SV6+26ExRHFOGfUVXG+nUejq5Px/vYHl5f0w+MBZ5Pz8IlyTuPod2zm8iyR/I\npqreOjpNH+RdmMQuJohNdzXPUHCHkcZWIU84cpI2ap+/W/0GubHxg6ItHllsDun/\n9Tgc47sJGRLwGrH7JAE/IsUDLdA+ayl18IBE5aq4SqdXbqLQw6wi+xVj4PF+ITpp\n3ZHg3lJUN2QIe2ewdUuesGDkxTM7d4rAO9MuiVQozdViNeW7kYH8JG+WyXRrZX0v\niseQHyOLiAhJrsyk4J/MN0rtm2rzHYFDFaHsQPIkv7n8G7hySJbQfZpPG2JsMQ2L\nywIDAQAB\n-----END PUBLIC KEY-----", - }, new[] - { - new AuthMetadataField("userId", "externalUserId", true), - new AuthMetadataField("name.first", "first_name"), - new AuthMetadataField("name.last", "last_name"), - new AuthMetadataField("jobTitle", "title"), - new AuthMetadataField("email", "email"), - new AuthMetadataField("pictureUrl", "picture_url"), - new AuthMetadataField("gender", "gender"), - new AuthMetadataField("birthday", "birthday"), - new AuthMetadataField("minAge", "min_age"), - new AuthMetadataField("maxAge", "max_age"), - }); - - return app; - } - - private async Task CreatePbsApp(string name, string partitionKeyType, bool setupCollections = false) - { - _output.WriteLine($"Creating PBS app {name}..."); - - var (app, mongoServiceId) = await CreateAppCore(name, new - { - sync = new - { - state = "enabled", - database_name = GetSyncDatabaseName(name), - partition = new - { - key = "realm_id", - type = partitionKeyType, - permissions = new - { - read = true, - write = new BsonDocument - { - { - "%%partition", new BsonDocument - { - { "$ne", "read-only" } - } - } - }, - } - } - } - }); - - await PutAsync($"groups/{_groupId}/apps/{app}/sync/config", new - { - development_mode_enabled = true, - }); - - if (setupCollections) - { - var (salesSchema, salesRules) = Schemas.Sales(partitionKeyType, Differentiator); - var (usersSchema, usersRules) = Schemas.Users(partitionKeyType, Differentiator); - var (foosSchema, foosRules) = Schemas.Foos(partitionKeyType, Differentiator); - - await CreateSchema(app, mongoServiceId, salesSchema, salesRules); - await CreateSchema(app, mongoServiceId, usersSchema, usersRules); - await CreateSchema(app, mongoServiceId, foosSchema, foosRules); - - await PatchAsync($"groups/{_groupId}/apps/{app}/custom_user_data", new - { - mongo_service_id = mongoServiceId, - enabled = true, - database_name = $"Schema_{Differentiator}", - collection_name = "users", - user_id_field = "user_id" - }); - } - - return app; - } - - private async Task CreateFlxApp(string name, bool enableDevMode) - { - _output.WriteLine($"Creating FLX app {name}..."); - - var (app, mongoServiceId) = await CreateAppCore(name, new - { - flexible_sync = new - { - state = "enabled", - database_name = GetSyncDatabaseName(name), - queryable_fields_names = new[] { "Int64Property", "GuidProperty", "DoubleProperty", "Int", "Guid", "Id", "PartitionLike", "Differentiator" }, - } - }, enableDevMode); - - await PostAsync($"groups/{_groupId}/apps/{app}/services/{mongoServiceId}/default_rule", new - { - roles = new[] - { - new - { - name = "all", - apply_when = new { }, - read = true, - write = true, - insert = true, - delete = true, - document_filters = new - { - read = true, - write = true, - } - } - } - }); - - await CreateFunction(app, "triggerClientResetOnSyncServer", TriggerClientResetOnSyncServerFuncSource, runAsSystem: true); - - if (!enableDevMode) - { - var schemaV0 = Schemas.Nullables(Differentiator, required: false); - var schemaId = await CreateSchema(app, mongoServiceId, schemaV0, null); - - var schemaV1 = Schemas.Nullables(Differentiator, required: true); - await UpdateSchema(app, schemaId, schemaV1); - - // Revert to schema_v0 - await UpdateSchema(app, schemaId, schemaV0); - - await WaitForSchemaVersion(app, 2); - } - - return app; - } - - public async Task SetAutomaticRecoveryEnabled(BaasApp app, bool enabled) - { - var services = await GetAsync($"groups/{_groupId}/apps/{app}/services"); - var mongoServiceId = services!.Single(s => s.AsBsonDocument["name"] == "BackingDB")["_id"].AsString; - var config = await GetAsync($"groups/{_groupId}/apps/{app}/services/{mongoServiceId}/config"); - - var syncType = config!.Contains("flexible_sync") ? "flexible_sync" : "sync"; - config[syncType]["is_recovery_mode_disabled"] = !enabled; - - // An empty fragment with just the sync configuration is necessary, - // as the "conf" document that we retrieve has a bunch of extra fields that we are supposed - // to be use/return to the server when PATCH-ing - var fragment = new BsonDocument - { - [syncType] = config[syncType] - }; - - await PatchAsync($"groups/{_groupId}/apps/{app}/services/{mongoServiceId}/config", fragment); - } - - private async Task<(BaasApp App, string MongoServiceId)> CreateAppCore(string name, object syncConfig, bool enableDeveloperMode = true) - { - var doc = await PostAsync($"groups/{_groupId}/apps", new { name = $"{name}{_appSuffix}" }); - var appId = doc!["_id"].AsString; - var clientAppId = doc["client_app_id"].AsString; - var app = new BaasApp(appId, clientAppId, name); - - var confirmFuncId = await CreateFunction(app, "confirmFunc", ConfirmFuncSource); - var resetFuncId = await CreateFunction(app, "resetFunc", ResetFuncSource); - - await EnableProvider(app, "anon-user"); - await EnableProvider(app, "local-userpass", new - { - autoConfirm = false, - confirmEmailSubject = string.Empty, - confirmationFunctionName = "confirmFunc", - confirmationFunctionId = confirmFuncId, - emailConfirmationUrl = "http://localhost/confirmEmail", - resetFunctionName = "resetFunc", - resetFunctionId = resetFuncId, - resetPasswordSubject = string.Empty, - resetPasswordUrl = "http://localhost/resetPassword", - runConfirmationFunction = true, - runResetFunction = true, - }); - - var mongoServiceId = await CreateMongodbService(app, syncConfig); - - if (enableDeveloperMode) - { - await PutAsync($"groups/{_groupId}/apps/{app}/sync/config", new - { - development_mode_enabled = true, - }); - } - - return (app, mongoServiceId); - } - - private async Task EnableProvider(BaasApp app, string type, object? config = null, AuthMetadataField[]? metadataFields = null) - { - _output.WriteLine($"Enabling provider {type} for {app.Name}..."); - - var url = $"groups/{_groupId}/apps/{app}/auth_providers"; - - // Api key is slightly special, thus this annoying custom handling. - if (type == "api-key") - { - var providers = await GetAsync(url); - var apiKeyProviderId = providers!.Select(p => p.AsBsonDocument) - .Single(p => p["type"] == "api-key")["_id"].AsString; - - await PutAsync($"{url}/{apiKeyProviderId}/enable", new { }); - } - else - { - await PostAsync(url, new - { - name = type, - type, - disabled = false, - config, - metadata_fields = metadataFields, - }); - } - } - - private async Task CreateFunction(BaasApp app, string name, string source, bool runAsSystem = false) - { - _output.WriteLine($"Creating function {name} for {app.Name}..."); - - var response = await PostAsync($"groups/{_groupId}/apps/{app}/functions", new - { - name, - run_as_system = runAsSystem, - can_evaluate = new { }, - @private = false, - source - }); - - return response!["_id"].AsString; - } - - private async Task GetApps() - { - var response = await GetAsync($"groups/{_groupId}/apps"); - return response! - .Select(x => x.AsBsonDocument) - .Where(doc => doc["name"].AsString.EndsWith(_appSuffix)) - .Select(doc => - { - var name = doc["name"].AsString; - - var appName = name[..^_appSuffix.Length]; - return new BaasApp(doc["_id"].AsString, doc["client_app_id"].AsString, appName); - }) - .Where(a => a != null) - .ToArray(); - } - - private async Task CreateService(BaasApp app, string name, string type, object config) - { - _output.WriteLine($"Creating service {name} for {app.Name}..."); - - var response = await PostAsync($"groups/{_groupId}/apps/{app}/services", new - { - name, - type, - config - }); - - return response!["_id"].AsString; - } - - private async Task CreateMongodbService(BaasApp app, object syncConfig) - { - var serviceName = _clusterName == null ? "mongodb" : "mongodb-atlas"; - object mongoConfig = _clusterName == null ? new { uri = "mongodb://localhost:26000" } : new { clusterName = _clusterName }; - - var mongoServiceId = await CreateService(app, "BackingDB", serviceName, mongoConfig); - - // The cluster linking must be separated from enabling sync because Atlas - // takes a few seconds to provision a user for BaaS, meaning enabling sync - // will fail if we attempt to do it with the same request. It's nondeterministic - // how long it'll take, so we must retry for a while. - var attempt = 0; - while (true) - { - try - { - await PatchAsync($"groups/{_groupId}/apps/{app}/services/{mongoServiceId}/config", syncConfig); - break; - } - catch - { - if (attempt++ < 120) - { - _output.WriteLine($"Failed to update service after {attempt * 5} seconds. Will keep retrying..."); - - await Task.Delay(5000); - } - else - { - throw; - } - } - } - - return mongoServiceId; - } - - private async Task CreateSchema(BaasApp app, string mongoServiceId, object schema, object? rule) - { - _output.WriteLine($"Creating schema for {app.Name}..."); - - var createResponse = await PostAsync($"groups/{_groupId}/apps/{app}/schemas", schema); - if (rule != null) - { - await PostAsync($"groups/{_groupId}/apps/{app}/services/{mongoServiceId}/rules", rule); - } - - return createResponse!["_id"].AsString; - } - - private async Task UpdateSchema(BaasApp app, string schemaId, object schema) - { - _output.WriteLine($"Creating schema for {app.Name}..."); - - await PutAsync($"groups/{_groupId}/apps/{app}/schemas/{schemaId}?bypass_service_change=SyncSchemaVersionIncrease", schema); - } - - private async Task WaitForSchemaVersion(BaasApp app, int expectedVersion) - { - while (true) - { - var response = await GetAsync($"groups/{_groupId}/apps/{app}/sync/schemas/versions"); - if (response!["versions"].AsBsonArray.Any(version => version.AsBsonDocument["version_major"].AsInt32 >= expectedVersion)) - { - return; - } - } - } - - private async Task RefreshAccessTokenAsync() - { - using var message = new HttpRequestMessage(HttpMethod.Post, new Uri("auth/session", UriKind.Relative)); - message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _refreshToken); - - var response = await _client.SendAsync(message); - if (!response.IsSuccessStatusCode) - { - var content = await response.Content.ReadAsStringAsync(); - throw new Exception($"Failed to refresh access token - {response.StatusCode}: {content}"); - } - - var json = await response.Content.ReadAsStringAsync(); - var doc = BsonSerializer.Deserialize(json); - - _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", doc["access_token"].AsString); - } - - private Task PostAsync(string relativePath, object obj) => SendAsync(HttpMethod.Post, relativePath, obj); - - private Task GetAsync(string relativePath) => SendAsync(HttpMethod.Get, relativePath); - - private Task PutAsync(string relativePath, object obj) => SendAsync(HttpMethod.Put, relativePath, obj); - - private Task PatchAsync(string relativePath, object obj) => SendAsync(new HttpMethod("PATCH"), relativePath, obj); - - private async Task SendAsync(HttpMethod method, string relativePath, object? payload = null) - { - using var message = new HttpRequestMessage(method, new Uri(relativePath, UriKind.Relative)); - if (payload != null) - { - message.Content = GetJsonContent(payload); - } - - var response = await _client.SendAsync(message); - if (!response.IsSuccessStatusCode) - { - if (response.StatusCode == HttpStatusCode.Unauthorized && _refreshToken != null) - { - await RefreshAccessTokenAsync(); - return await SendAsync(method, relativePath, payload); - } - - var content = await response.Content.ReadAsStringAsync(); - throw new Exception($"An error ({response.StatusCode}) occurred while executing {method} {relativePath}: {content}"); - } - - var json = await response.Content.ReadAsStringAsync(); - - if (!string.IsNullOrWhiteSpace(json)) - { - return BsonSerializer.Deserialize(json); - } - - return default; - } - - public static HttpContent GetJsonContent(object obj) - { - string jsonContent; - - if (obj is Array arr) - { - var bsonArray = new BsonArray(); - foreach (var elem in arr) - { - bsonArray.Add(elem.ToBsonDocument()); - } - - jsonContent = bsonArray.ToJson(); - } - else - { - jsonContent = obj is BsonDocument doc ? doc.ToJson() : obj.ToJson(); - } - - var content = new StringContent(jsonContent); - - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - - return content; - } - - // Extract arguments and populate a type T with the extracted values. Only string readwrite properties - // will be considered. - public static (T Extracted, string[] RemainingArgs) ExtractArguments(string[] args) - where T : new() - { - if (args == null) - { - throw new ArgumentNullException(nameof(args)); - } - - var argsToExtract = typeof(T).GetProperties() - .Where(p => p.PropertyType == typeof(string) && p is { CanRead: true, CanWrite: true }) - .Select(p => (ArgName: GetArgName(p.Name), PropertyInfo: p)) - .ToArray(); - - var extracted = new T(); - return (extracted, args.Where(a => !argsToExtract.Any(kvp => ExtractArg(a, kvp))).ToArray()); - - bool ExtractArg(string arg, (string ArgName, PropertyInfo Info) prop) - { - if (arg.StartsWith($"--{prop.ArgName}=")) - { - prop.Info.SetValue(extracted, arg.Replace($"--{prop.ArgName}=", string.Empty)); - return true; - } - - return false; - } - - static string GetArgName(string propertyName) - { - return Regex.Replace(propertyName, "(? AppId; - } - - private class AuthMetadataField - { - [BsonElement("name")] - public string Name { get; } - - [BsonElement("field_name")] - public string FieldName { get; } - - [BsonElement("required")] - public bool Required { get; } - - public AuthMetadataField(string name, string fieldName, bool required = false) - { - Name = name; - FieldName = fieldName; - Required = required; - } - } - - private static class Schemas - { - private static object _defaultRoles => new - { - name = "default", - apply_when = new { }, - insert = true, - delete = true, - search = true, - additional_fields = new { } - }; - - private static object Metadata(string differentiator, string collectionName) => new - { - database = $"Schema_{differentiator}", - collection = collectionName, - data_source = "BackingDB" - }; - - private static object GenericBaasRule(string differentiator, string collectionName) => new - { - collection = collectionName, - database = $"Schema_{differentiator}", - roles = new[] { _defaultRoles } - }; - - public static (object Schema, object Rules) Sales(string partitionKeyType, string differentiator) => - (new - { - metadata = Metadata(differentiator, "sales"), - schema = new - { - title = "Sale", - bsonType = "object", - properties = new - { - _id = new { bsonType = "int" }, - date = new { bsonType = "date" }, - item = new { bsonType = "string" }, - price = new { bsonType = "decimal" }, - quantity = new { bsonType = "decimal" }, - realm_id = new { bsonType = partitionKeyType } - }, - required = new[] { "_id" }, - }, - }, - GenericBaasRule(differentiator, "sales")); - - public static (object Schema, object Rules) Users(string partitionKeyType, string differentiator) => - (new - { - metadata = Metadata(differentiator, "users"), - schema = new - { - title = "User", - bsonType = "object", - properties = new - { - _id = new { bsonType = "objectId" }, - realm_id = new { bsonType = partitionKeyType }, - user_id = new { bsonType = "string" } - }, - required = new[] { "_id" }, - } - }, - GenericBaasRule(differentiator, "users")); - - public static (object Schema, object Rules) Foos(string partitionKeyType, string differentiator) => - (new - { - metadata = Metadata(differentiator, "foos"), - schema = new - { - title = "Foo", - bsonType = "object", - properties = new - { - _id = new { bsonType = "objectId" }, - realm_id = new { bsonType = partitionKeyType }, - longValue = new { bsonType = "long" }, - stringValue = new { bsonType = "string" }, - }, - required = new[] { "_id" }, - } - }, - GenericBaasRule(differentiator, "foos")); - - public static object Nullables(string differentiator, bool required) - { - var schema = new - { - title = "Nullables", - bsonType = "object", - properties = new Dictionary - { - ["_id"] = new { bsonType = "objectId" }, - ["Differentiator"] = new { bsonType = "objectId" }, - ["BoolValue"] = new { bsonType = "bool" }, - ["IntValue"] = new { bsonType = "long" }, - ["FloatValue"] = new { bsonType = "float" }, - ["DoubleValue"] = new { bsonType = "double" }, - ["DecimalValue"] = new { bsonType = "decimal" }, - ["DateValue"] = new { bsonType = "date" }, - ["StringValue"] = new { bsonType = "string" }, - ["ObjectIdValue"] = new { bsonType = "objectId" }, - ["UuidValue"] = new { bsonType = "uuid" }, - ["BinaryValue"] = new { bsonType = "binData" }, - }, - required = new List(), - }; - - if (required) - { - // For schema v1, we add an extra property - schema.properties["WillBeRemoved"] = new - { - bsonType = "string" - }; - } - - schema.required.AddRange(required ? schema.properties.Keys : new[] - { - "_id", "Differentiator" - }); - - return new - { - metadata = Metadata(differentiator, "Nullables"), - schema, - }; - } - } - - private class BaasaasClient - { - private const string _baseUrl = "https://us-east-1.aws.data.mongodb-api.com/app/baas-container-service-autzb/endpoint/"; - private readonly HttpClient _client; - - public BaasaasClient(string apiKey) - { - _client = new(); - _client.BaseAddress = new Uri(_baseUrl); - _client.DefaultRequestHeaders.TryAddWithoutValidation("apiKey", apiKey); - } - - public async Task GetOrDeployContainer(string differentiator, TextWriter output) - { - output.WriteLine("Looking for existing containers on BaaSaas."); - var containers = await GetContainers(); - - if (containers?.Length > 0) - { - var userId = await GetCurrentUserId(); - var existingContainer = containers - .FirstOrDefault(c => c.CreatorId == userId && c.Tags.Any(t => t.Key == "DIFFERENTIATOR" && t.Value == differentiator)); - - if (existingContainer is not null) - { - output.WriteLine($"Container with id {existingContainer.ContainerId} found."); - - if (!existingContainer.IsRunning) - { - output.WriteLine($"Waiting for container with id {existingContainer.ContainerId} to be running."); - await WaitForContainer(existingContainer.ContainerId); - } - - return existingContainer.HttpUrl; - } - } - - output.WriteLine("No container found, starting a new one."); - var containerId = await StartContainer(differentiator); - - output.WriteLine($"Container with id {containerId} started, waiting for it to be running."); - var container = await WaitForContainer(containerId); - - return container.HttpUrl; - } - - private Task GetContainers() - { - return CallEndpointAsync(HttpMethod.Get, "listContainers"); - } - - public async Task StopContainersForDifferentiator(string differentiator, TextWriter output) - { - var containers = await GetContainers(); - var userId = await GetCurrentUserId(); - - var existingContainers = containers - .Where(c => c.CreatorId == userId && c.Tags.Any(t => t.Key == "DIFFERENTIATOR" && t.Value == differentiator)); - - foreach (var container in existingContainers) - { - await StopContainer(container.ContainerId); - output.WriteLine($"Stopped container with id={container.ContainerId} and differentiator={differentiator}"); - } - } - - private Task StopContainer(string id) - { - return CallEndpointAsync(HttpMethod.Post, $"stopContainer?id={id}"); - } - - private async Task GetCurrentUserId() - { - return (await CallEndpointAsync(HttpMethod.Get, "userinfo"))!["id"].AsString; - } - - private async Task StartContainer(string differentiator) - { - var response = await CallEndpointAsync(HttpMethod.Post, "startContainer", new[] - { - new - { - key = "DIFFERENTIATOR", - value = differentiator, - } - }); - - return response?["id"].AsString!; - } - - private async Task WaitForContainer(string containerId, int maxRetries = 100) - { - for (var i = 0; i < maxRetries; i++) - { - try - { - var containers = await GetContainers(); - var container = containers!.FirstOrDefault(c => c.ContainerId == containerId); - - if (container?.IsRunning == true) - { - // Checking that Baas started correctly, and not only the container - var response = await _client.GetAsync($"{container.HttpUrl}/api/private/v1.0/version"); - if (response.IsSuccessStatusCode) - { - return container; - } - } - } - catch - { - } - - await Task.Delay(2000); - } - - throw new Exception($"Container with id={containerId} was not found or ready after {maxRetries} retries"); - } - - private async Task CallEndpointAsync(HttpMethod method, string relativePath, object? payload = null) - { - using var message = new HttpRequestMessage(method, new Uri(relativePath, UriKind.Relative)); - - if (payload is not null) - { - message.Content = GetJsonContent(payload); - } - - var response = await _client.SendAsync(message); - response.EnsureSuccessStatusCode(); - - var json = await response.Content.ReadAsStringAsync(); - - if (!string.IsNullOrWhiteSpace(json)) - { - return BsonSerializer.Deserialize(json); - } - - return default!; - } - - [BsonIgnoreExtraElements] - public class ContainerInfo - { - [BsonElement("id")] - public string ContainerId { get; set; } = null!; - - [BsonElement("httpUrl")] - public string HttpUrl { get; set; } = null!; - - [BsonElement("lastStatus")] - public string LastStatus { get; set; } = null!; - - [BsonElement("tags")] - public List Tags { get; set; } = null!; - - [BsonElement("creatorId")] - public string CreatorId { get; set; } = null!; - - public bool IsRunning => LastStatus == "RUNNING"; - } - - public class Tag - { - [BsonElement("key")] - public string Key { get; set; } = null!; - - [BsonElement("value")] - public string Value { get; set; } = null!; - } - } - } - -#if !NETCOREAPP2_1_OR_GREATER - internal static class DictionaryExtensions - { - [return: NotNullIfNotNull("defaultValue")] - public static T? GetValueOrDefault(this IDictionary dictionary, string key, T? defaultValue = default) - { - if (dictionary.TryGetValue(key, out var value)) - { - return value; - } - - return defaultValue; - } - } -#endif - -} diff --git a/Tools/DeployApps/DeployApps.csproj b/Tools/DeployApps/DeployApps.csproj deleted file mode 100644 index 6220bad62a..0000000000 --- a/Tools/DeployApps/DeployApps.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - net8.0 - Exe - Baas - ../../global.ruleset - true - enable - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - \ No newline at end of file diff --git a/Tools/DeployApps/GlobalSuppressions.cs b/Tools/DeployApps/GlobalSuppressions.cs deleted file mode 100644 index d7a2724ac9..0000000000 --- a/Tools/DeployApps/GlobalSuppressions.cs +++ /dev/null @@ -1,17 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1601:Partial elements should be documented", Justification = "No docs needed for tests.", Scope = "module")] -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "No docs needed for tests.", Scope = "module")] -[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "We underscore private members", Scope = "module")] -[assembly: SuppressMessage("Security", "CA5351:Do Not Use Broken Cryptographic Algorithms", Justification = "We're not after cryptography", Scope = "member", Target = "~P:DeployApps.BaasClient._shortDifferentiator")] -[assembly: SuppressMessage("Globalization", "CA1310:Specify StringComparison for correctness", Justification = "We're controlling inputs", Scope = "module")] -[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "We underscore private members", Scope = "module")] -[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:Parameter should not span multiple lines", Justification = "Function body is long", Scope = "module")] -[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "This is fine for tests.", Scope = "module")] -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "This is fine for tests.", Scope = "module")] -[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1000:Keywords should be spaced correctly", Justification = "In C# 9.0 we can use new() to instantiate objects and we don't need a space there", Scope = "module")] diff --git a/Tools/DeployApps/Program.cs b/Tools/DeployApps/Program.cs deleted file mode 100644 index 6db4282f23..0000000000 --- a/Tools/DeployApps/Program.cs +++ /dev/null @@ -1,48 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Threading.Tasks; - -namespace Baas -{ - public sealed class Program - { - public static async Task Main(string[] args) - { - if (args == null) - { - throw new ArgumentNullException(nameof(args)); - } - - if (args[0] == "deploy-apps") - { - var (client, _, _) = await BaasClient.CreateClientFromArgs(args, Console.Out); - await client!.GetOrCreateApps(); - } - else if (args[0] == "terminate-baas") - { - await BaasClient.TerminateBaasFromArgs(args, Console.Out); - } - else - { - throw new Exception($"Invalid command line option: {args[0]}"); - } - } - } -} diff --git a/wrappers/src/CMakeLists.txt b/wrappers/src/CMakeLists.txt index 158fae781e..d6b9b39b01 100644 --- a/wrappers/src/CMakeLists.txt +++ b/wrappers/src/CMakeLists.txt @@ -12,14 +12,7 @@ set(SOURCES scheduler_cs.cpp schema_cs.cpp shared_realm_cs.cpp - app_cs.cpp - async_open_task_cs.cpp - subscription_set_cs.cpp - sync_session_cs.cpp - sync_user_cs.cpp - transport_cs.cpp guid_representation_migration.cpp - websocket_cs.cpp ) set(HEADERS @@ -31,11 +24,7 @@ set(HEADERS realm_export_decls.hpp schema_cs.hpp shared_realm_cs.hpp - app_cs.hpp - sync_session_cs.hpp - transport_cs.hpp notifications_cs.hpp - websocket_cs.hpp ) if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") diff --git a/wrappers/src/app_cs.cpp b/wrappers/src/app_cs.cpp deleted file mode 100644 index ccf3f694d1..0000000000 --- a/wrappers/src/app_cs.cpp +++ /dev/null @@ -1,444 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include "marshalling.hpp" -#include "error_handling.hpp" -#include "realm_export_decls.hpp" -#include "shared_realm_cs.hpp" -#include "sync_session_cs.hpp" -#include "transport_cs.hpp" -#include "debug.hpp" -#include "app_cs.hpp" -#include "websocket_cs.hpp" - -#include -#include -#include -#include -#include -#include -#include - -using namespace realm; -using namespace realm::binding; -using namespace app; - -using SharedSyncUser = std::shared_ptr; - -using UserCallbackT = void(void* tcs_ptr, SharedSyncUser* user, MarshaledAppError err); -using VoidCallbackT = void(void* tcs_ptr, MarshaledAppError err); -using StringCallbackT = void(void* tcs_ptr, realm_value_t response, MarshaledAppError err); -using ApiKeysCallbackT = void(void* tcs_ptr, UserApiKey* api_keys, size_t api_keys_len, MarshaledAppError err); - -namespace realm { - namespace binding { - std::string s_framework; - std::string s_framework_version; - std::string s_sdk_version; - std::string s_platform_version; - std::string s_device_name; - std::string s_device_version; - std::string s_bundle_id; - - std::function s_user_callback; - std::function s_void_callback; - std::function s_string_callback; - std::function s_api_keys_callback; - - struct AppConfiguration - { - uint16_t* app_id; - size_t app_id_len; - - uint16_t* base_file_path; - size_t base_file_path_len; - - uint16_t* base_url; - size_t base_url_len; - - uint64_t request_timeout_ms; - - app::AppConfig::MetadataMode metadata_mode; - - bool metadata_mode_has_value; - - void* managed_http_client; - - void* managed_websocket_provider; - - uint64_t sync_connect_timeout_ms; - - uint64_t sync_connection_linger_time_ms; - - uint64_t sync_ping_keep_alive_period_ms; - - uint64_t sync_pong_keep_alive_timeout_ms; - - uint64_t sync_fast_reconnect_limit; - - bool use_cache; - }; - } -} - -extern "C" { - REALM_EXPORT void shared_app_initialize(uint16_t* framework, size_t framework_len, - uint16_t* framework_version, size_t framework_version_len, - uint16_t* sdk_version, size_t sdk_version_len, - uint16_t* platform_version, size_t platform_version_len, - uint16_t* device_name, size_t device_name_len, - uint16_t* device_version, size_t device_version_len, - uint16_t* bundle_id, size_t bundle_id_len, - UserCallbackT* user_callback, - VoidCallbackT* void_callback, - StringCallbackT* string_callback, - ApiKeysCallbackT* api_keys_callback) - { - s_framework = Utf16StringAccessor(framework, framework_len); - s_framework_version = Utf16StringAccessor(framework_version, framework_version_len); - s_sdk_version = Utf16StringAccessor(sdk_version, sdk_version_len); - s_platform_version = Utf16StringAccessor(platform_version, platform_version_len); - s_device_name = Utf16StringAccessor(device_name, device_name_len); - s_device_version = Utf16StringAccessor(device_version, device_version_len); - s_bundle_id = Utf16StringAccessor(bundle_id, bundle_id_len); - - s_user_callback = wrap_managed_callback(user_callback); - s_void_callback = wrap_managed_callback(void_callback); - s_string_callback = wrap_managed_callback(string_callback); - s_api_keys_callback = wrap_managed_callback(api_keys_callback); - - realm::binding::s_can_call_managed = true; - } - - REALM_EXPORT SharedApp* shared_app_create(AppConfiguration app_config, uint8_t* encryption_key, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - app::AppConfig config; - config.app_id = Utf16StringAccessor(app_config.app_id, app_config.app_id_len); - - config.device_info.framework_name = s_framework; - config.device_info.framework_version = s_framework_version; - config.device_info.sdk_version = s_sdk_version; - config.device_info.sdk = "Dotnet"; - config.device_info.platform_version = s_platform_version; - config.device_info.device_name = s_device_name; - config.device_info.device_version = s_device_version; - config.device_info.bundle_id = s_bundle_id; - - config.transport = std::make_shared(app_config.managed_http_client); - config.base_url = Utf16StringAccessor(app_config.base_url, app_config.base_url_len).to_string(); - - if (app_config.request_timeout_ms > 0) { - config.default_request_timeout_ms = app_config.request_timeout_ms; - } - - config.base_file_path = Utf16StringAccessor(app_config.base_file_path, app_config.base_file_path_len); - config.sync_client_config.timeouts.connection_linger_time = app_config.sync_connection_linger_time_ms; - config.sync_client_config.timeouts.connect_timeout = app_config.sync_connect_timeout_ms; - config.sync_client_config.timeouts.fast_reconnect_limit = app_config.sync_fast_reconnect_limit; - config.sync_client_config.timeouts.ping_keepalive_period = app_config.sync_ping_keep_alive_period_ms; - config.sync_client_config.timeouts.pong_keepalive_timeout = app_config.sync_pong_keep_alive_timeout_ms; - - if (app_config.managed_websocket_provider) { - config.sync_client_config.socket_provider = make_websocket_provider(app_config.managed_websocket_provider); - } - - if (app_config.metadata_mode_has_value) { - config.metadata_mode = app_config.metadata_mode; - } - else { -#if REALM_PLATFORM_APPLE && !TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST - config.metadata_mode = app::AppConfig::MetadataMode::Encryption; -#else - config.metadata_mode = app::AppConfig::MetadataMode::NoEncryption; -#endif - } - - if (encryption_key) { - auto& key = *reinterpret_cast*>(encryption_key); - config.custom_encryption_key = std::vector(key.begin(), key.end()); - } - - SharedApp app = App::get_app(app_config.use_cache ? - realm::app::App::CacheMode::Enabled : realm::app::App::CacheMode::Disabled, - std::move(config)); - - return new SharedApp(app); - }); - } - - REALM_EXPORT SharedSyncUser* shared_app_get_current_user(SharedApp& app, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() -> SharedSyncUser* { - auto ptr = app->current_user(); - if (ptr == nullptr) { - return nullptr; - } - - return new SharedSyncUser(ptr); - }); - } - - REALM_EXPORT size_t shared_app_get_logged_in_users(SharedApp& app, SharedSyncUser** buffer, size_t buffer_length, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() -> size_t { - auto users = app->all_users(); - if (users.size() > buffer_length) { - return users.size(); - } - - if (users.size() <= 0) { - return 0; - } - - for (size_t i = 0; i < users.size(); i++) { - buffer[i] = new SharedSyncUser(users.at(i)); - } - - return users.size(); - }); - } - - REALM_EXPORT void shared_app_switch_user(SharedApp& app, SharedSyncUser& user, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - app->switch_user(user); - }); - } - - REALM_EXPORT void shared_app_login_user(SharedApp& app, Credentials credentials, void* tcs_ptr, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - auto app_credentials = credentials.to_app_credentials(); - app->log_in_with_credentials(app_credentials, get_user_callback_handler(tcs_ptr)); - }); -} - - REALM_EXPORT SharedSyncUser* shared_app_get_user_for_testing( - SharedApp& app, - uint16_t* id_buf, size_t id_len, - uint16_t* refresh_token_buf, size_t refresh_token_len, - uint16_t* access_token_buf, size_t access_token_len, - NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - Utf16StringAccessor id(id_buf, id_len); - Utf16StringAccessor refresh_token(refresh_token_buf, refresh_token_len); - Utf16StringAccessor access_token(access_token_buf, access_token_len); - - auto user = app->create_fake_user_for_testing(id, access_token, refresh_token); - return new SharedSyncUser(user); - }); - } - - REALM_EXPORT void shared_app_set_fake_sync_route_for_testing(SharedApp& app, - NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - app->sync_manager()->set_sync_route("realm://www.test.com:1000", true); - }); - } - - - REALM_EXPORT void shared_app_remove_user(SharedApp& app, SharedSyncUser& user, void* tcs_ptr, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - app->remove_user(user, [tcs_ptr](util::Optional) { - // ignore errors - s_void_callback(tcs_ptr, MarshaledAppError()); - }); - }); - } - - REALM_EXPORT void shared_app_delete_user(SharedApp& app, SharedSyncUser& user, void* tcs_ptr, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - app->delete_user(user, get_callback_handler(tcs_ptr)); - }); - } - - REALM_EXPORT bool shared_app_sync_immediately_run_file_actions(SharedApp& app, uint16_t* pathbuffer, size_t pathbuffer_len, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - std::string path(Utf16StringAccessor(pathbuffer, pathbuffer_len)); - return app->immediately_run_file_actions(path); - }); - } - - REALM_EXPORT void shared_app_sync_reconnect(SharedApp& app) - { - app->sync_manager()->reconnect(); - } - - REALM_EXPORT void shared_app_destroy(SharedApp* app) - { - delete app; - } - - REALM_EXPORT void shared_app_reset_for_testing(SharedApp& app) { - - // If the logger is empty then tear_down_for_testing has been called already - if (!app->sync_manager()->get_logger()) { - return; - } - - auto users = app->all_users(); - for (size_t i = 0; i < users.size(); i++) { - auto &user = users[i]; - user->log_out(); - } - - // This method will crash the application if called more than once for the same app. - app->sync_manager()->tear_down_for_testing(); - - App::clear_cached_apps(); - } - - REALM_EXPORT size_t shared_app_get_base_file_path(SharedApp& app, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - std::string base_file_path(app->config().base_file_path); - return stringdata_to_csharpstringbuffer(base_file_path, buffer, buffer_length); - }); - } - - REALM_EXPORT size_t shared_app_get_base_uri(SharedApp& app, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - std::string url(app->get_base_url()); - return stringdata_to_csharpstringbuffer(url, buffer, buffer_length); - }); - } - - REALM_EXPORT realm_string_t shared_app_get_id(SharedApp& app, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - return to_capi(app->config().app_id); - }); - } - - REALM_EXPORT realm_string_t shared_app_get_default_url(NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - return to_capi(App::default_base_url()); - }); - } - - REALM_EXPORT bool shared_app_is_same_instance(SharedApp& lhs, SharedApp& rhs, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - return lhs == rhs; // just compare raw pointers inside the smart pointers - }); - } - - REALM_EXPORT void shared_app_update_base_url(SharedApp& app, uint16_t* url_buf, size_t url_len, void* tcs_ptr, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - std::string url(Utf16StringAccessor(url_buf, url_len)); - - app->update_base_url(url, [tcs_ptr](util::Optional err) { - if (err) { - auto& err_copy = *err; - MarshaledAppError app_error(err_copy); - - s_void_callback(tcs_ptr, app_error); - } - else { - s_void_callback(tcs_ptr, MarshaledAppError()); - } - }); - }); - } - -#pragma region EmailPassword - - REALM_EXPORT void shared_app_email_register_user(SharedApp& app, uint16_t* username_buf, size_t username_len, uint16_t* password_buf, size_t password_len, void* tcs_ptr, NativeException::Marshallable& ex) - { - handle_errors(ex, [&]() { - Utf16StringAccessor username(username_buf, username_len); - Utf16StringAccessor password(password_buf, password_len); - app->provider_client().register_email(username, password, get_callback_handler(tcs_ptr)); - }); - } - - REALM_EXPORT void shared_app_email_confirm_user(SharedApp& app, uint16_t* token_buf, size_t token_len, uint16_t* token_id_buf, size_t token_id_len, void* tcs_ptr, NativeException::Marshallable& ex) - { - handle_errors(ex, [&]() { - Utf16StringAccessor token(token_buf, token_len); - Utf16StringAccessor token_id(token_id_buf, token_id_len); - app->provider_client().confirm_user(token, token_id, get_callback_handler(tcs_ptr)); - }); - } - - REALM_EXPORT void shared_app_email_resend_confirmation_email(SharedApp& app, uint16_t* email_buf, size_t email_len, void* tcs_ptr, NativeException::Marshallable& ex) - { - handle_errors(ex, [&]() { - Utf16StringAccessor email(email_buf, email_len); - app->provider_client().resend_confirmation_email(email, get_callback_handler(tcs_ptr)); - }); - } - - REALM_EXPORT void shared_app_email_retry_custom_confirmation(SharedApp& app, uint16_t* email_buf, size_t email_len, void* tcs_ptr, NativeException::Marshallable& ex) - { - handle_errors(ex, [&]() { - Utf16StringAccessor email(email_buf, email_len); - app->provider_client().retry_custom_confirmation(email, get_callback_handler(tcs_ptr)); - }); - } - - REALM_EXPORT void shared_app_email_send_reset_password_email(SharedApp& app, uint16_t* email_buf, size_t email_len, void* tcs_ptr, NativeException::Marshallable& ex) - { - handle_errors(ex, [&]() { - Utf16StringAccessor email(email_buf, email_len); - app->provider_client().send_reset_password_email(email, get_callback_handler(tcs_ptr)); - }); - } - - REALM_EXPORT void shared_app_email_reset_password(SharedApp& app, uint16_t* password_buf, size_t password_len, uint16_t* token_buf, size_t token_len, uint16_t* token_id_buf, size_t token_id_len, void* tcs_ptr, NativeException::Marshallable& ex) - { - handle_errors(ex, [&]() { - Utf16StringAccessor password(password_buf, password_len); - Utf16StringAccessor token(token_buf, token_len); - Utf16StringAccessor token_id(token_id_buf, token_id_len); - app->provider_client().reset_password(password, token, token_id, get_callback_handler(tcs_ptr)); - }); - } - - REALM_EXPORT void shared_app_email_call_reset_password_function(SharedApp& app, uint16_t* username_buf, size_t username_len, uint16_t* password_buf, size_t password_len, uint16_t* args_buf, size_t args_len, void* tcs_ptr, NativeException::Marshallable& ex) - { - handle_errors(ex, [&]() { - Utf16StringAccessor username(username_buf, username_len); - Utf16StringAccessor password(password_buf, password_len); - Utf16StringAccessor serialized_args(args_buf, args_len); - - auto args = static_cast(bson::parse(serialized_args.to_string())); - app->provider_client().call_reset_password_function(std::move(username), std::move(password), std::move(args), get_callback_handler(tcs_ptr)); - }); - } - - REALM_EXPORT void shared_app_clear_cached_apps(NativeException::Marshallable& ex) - { - handle_errors(ex, [&]() { - app::App::clear_cached_apps(); - }); - } - -#pragma endregion - -} diff --git a/wrappers/src/app_cs.hpp b/wrappers/src/app_cs.hpp deleted file mode 100644 index 8136ac73b5..0000000000 --- a/wrappers/src/app_cs.hpp +++ /dev/null @@ -1,228 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef APP_CS_HPP -#define APP_CS_HPP - -#include -#include -#include -#include -#include -#include - -using namespace realm; -using namespace realm::app; - -using SharedSyncUser = std::shared_ptr; - -namespace realm { -namespace binding { - struct MarshaledAppError - { - bool is_null = true; - realm_value_t message = realm_value_t{}; - realm_value_t error_category = realm_value_t{}; - realm_value_t logs_link = realm_value_t{}; - int http_status_code = 0; - - MarshaledAppError() - { - } - - MarshaledAppError(AppError& err) - { - is_null = false; - - message = to_capi_value(err.reason()); - error_category = to_capi_value(err.code_string()); - logs_link = to_capi_value(err.link_to_server_logs); - http_status_code = err.additional_status_code.value_or(0); - } - }; - - struct Credentials - { - AuthProvider provider; - - uint16_t* token; - size_t token_len; - - uint16_t* additional_info; - size_t additional_info_len; - - AppCredentials to_app_credentials() { - switch (provider) - { - case AuthProvider::ANONYMOUS: { - Utf16StringAccessor reuse_existing(additional_info, additional_info_len); - return AppCredentials::anonymous(reuse_existing == "true"); - } - - case AuthProvider::FACEBOOK: - return AppCredentials::facebook(Utf16StringAccessor(token, token_len)); - - case AuthProvider::GOOGLE: { - Utf16StringAccessor google_credential_type(additional_info, additional_info_len); - if (google_credential_type == "AuthCode") { - return AppCredentials::google(AuthCode{ Utf16StringAccessor(token, token_len).to_string() }); - } - - if (google_credential_type == "IdToken") { - return AppCredentials::google(IdToken{ Utf16StringAccessor(token, token_len).to_string() }); - } - - realm::util::terminate("Invalid google credential type", __FILE__, __LINE__); - } - case AuthProvider::APPLE: - return AppCredentials::apple(Utf16StringAccessor(token, token_len)); - - case AuthProvider::CUSTOM: - return AppCredentials::custom(Utf16StringAccessor(token, token_len)); - - case AuthProvider::USERNAME_PASSWORD: - return AppCredentials::username_password(Utf16StringAccessor(token, token_len), Utf16StringAccessor(additional_info, additional_info_len)); - - case AuthProvider::FUNCTION: - return AppCredentials::function(Utf16StringAccessor(token, token_len)); - - case AuthProvider::API_KEY: - return AppCredentials::api_key(Utf16StringAccessor(token, token_len)); - - default: - REALM_UNREACHABLE(); - } - } - }; - - struct UserApiKey { - realm_value_t id = realm_value_t{}; - - realm_value_t key = realm_value_t{}; - - realm_value_t name = realm_value_t{}; - - bool disabled; - }; - - using UserCallbackT = void(void* tcs_ptr, SharedSyncUser* user, MarshaledAppError err); - using VoidCallbackT = void(void* tcs_ptr, MarshaledAppError err); - using StringCallbackT = void(void* tcs_ptr, realm_value_t response, MarshaledAppError err); - using ApiKeysCallbackT = void(void* tcs_ptr, UserApiKey* api_keys, size_t api_keys_len, MarshaledAppError err); - - extern std::function s_void_callback; - extern std::function s_user_callback; - extern std::function s_string_callback; - extern std::function s_api_keys_callback; - - inline auto get_string_callback_handler(void* tcs_ptr) { - return [tcs_ptr](const std::string* response, util::Optional err) { - if (err) { - auto& err_copy = *err; - MarshaledAppError app_error(err_copy); - - s_string_callback(tcs_ptr, realm_value_t{}, app_error); - } else if (response) { - s_string_callback(tcs_ptr, to_capi_value(*response), MarshaledAppError()); - } - else { - s_string_callback(tcs_ptr, realm_value_t{}, MarshaledAppError()); - } - }; - } - - inline auto get_user_callback_handler(void* tcs_ptr) { - return [tcs_ptr](std::shared_ptr user, util::Optional err) { - if (err) { - auto& err_copy = *err; - MarshaledAppError app_error(err_copy); - - s_user_callback(tcs_ptr, nullptr, app_error); - } - else { - s_user_callback(tcs_ptr, new SharedSyncUser(user), MarshaledAppError()); - } - }; - } - - inline auto get_callback_handler(void* tcs_ptr) { - return [tcs_ptr](util::Optional err) { - if (err) { - auto& err_copy = *err; - MarshaledAppError app_error(err_copy); - - s_void_callback(tcs_ptr, app_error); - } - else { - s_void_callback(tcs_ptr, MarshaledAppError()); - } - }; - } - - inline void invoke_api_key_callback(void* tcs_ptr, std::vector keys, util::Optional err) { - if (err) { - auto& err_copy = *err; - MarshaledAppError app_error(err_copy); - - s_api_keys_callback(tcs_ptr, nullptr, 0, app_error); - } - else { - std::vector marshalled_keys(keys.size()); - std::vector id_storage(keys.size()); - - for (size_t i = 0; i < keys.size(); i++) { - auto& api_key = keys[i]; - UserApiKey marshaled_key{}; - - id_storage[i] = api_key.id.to_string(); - marshaled_key.id = to_capi_value(id_storage[i]); - - if (api_key.key) { - marshaled_key.key = to_capi_value(*api_key.key); - } - - marshaled_key.name = to_capi_value(api_key.name); - marshaled_key.disabled = api_key.disabled; - - marshalled_keys[i] = marshaled_key; - } - - s_api_keys_callback(tcs_ptr, marshalled_keys.data(), marshalled_keys.size(), MarshaledAppError()); - } - } - - inline void invoke_api_key_callback(void* tcs_ptr, App::UserAPIKey key, util::Optional err) { - std::vector api_keys; - api_keys.push_back(key); - - invoke_api_key_callback(tcs_ptr, api_keys, err); - } - - - inline bson::BsonDocument to_document(uint16_t* buf, size_t len) { - if (buf == nullptr) { - return bson::BsonDocument(); - } - - Utf16StringAccessor json(buf, len); - return static_cast(bson::parse(json.to_string())); - } -} -} - -#endif /* defined(APP_CS_HPP) */ diff --git a/wrappers/src/async_open_task_cs.cpp b/wrappers/src/async_open_task_cs.cpp deleted file mode 100644 index ad10ef68ca..0000000000 --- a/wrappers/src/async_open_task_cs.cpp +++ /dev/null @@ -1,59 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include "error_handling.hpp" -#include "marshalling.hpp" -#include "realm_export_decls.hpp" -#include -#include "sync_session_cs.hpp" - -using namespace realm; -using namespace realm::binding; -using SharedAsyncOpenTask = std::shared_ptr; - -extern "C" { -REALM_EXPORT void realm_asyncopentask_destroy(SharedAsyncOpenTask* task) -{ - delete task; -} - -REALM_EXPORT void realm_asyncopentask_cancel(SharedAsyncOpenTask& task, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - task->cancel(); - }); -} - -REALM_EXPORT uint64_t realm_asyncopentask_register_progress_notifier(const SharedAsyncOpenTask& task, void* managed_state, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - return task->register_download_progress_notifier([managed_state](uint64_t transferred, uint64_t transferable, double progress_estimate) { - s_progress_callback(managed_state, progress_estimate); - }); - }); -} - -REALM_EXPORT void realm_asyncopentask_unregister_progress_notifier(const SharedAsyncOpenTask& task, uint64_t token, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - task->unregister_download_progress_notifier(token); - }); -} - -} diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index e2a6e33c00..b16605b02f 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -56,10 +56,6 @@ using ErrorCallbackT = void(SharedSyncSession* session, realm_sync_error error, using ShouldCompactCallbackT = void*(void* managed_delegate, uint64_t total_size, uint64_t data_size, bool* should_compact); using DataInitializationCallbackT = void*(void* managed_delegate, realm::SharedRealm& realm); -using SharedAsyncOpenTask = std::shared_ptr; -using SharedSyncSession = std::shared_ptr; -using SharedSubscriptionSet = std::shared_ptr; - namespace realm { std::function s_object_notification_callback; std::function s_dictionary_notification_callback; @@ -104,7 +100,7 @@ namespace binding { }; } -Realm::Config get_shared_realm_config(Configuration configuration, std::optional sync_configuration = {}) +Realm::Config get_shared_realm_config(Configuration configuration) { Realm::Config config; config.path = capi_to_std(configuration.path); @@ -174,71 +170,6 @@ Realm::Config get_shared_realm_config(Configuration configuration, std::optional config.cache = configuration.enable_cache; - if (sync_configuration) { - config.schema_mode = sync_configuration->schema_mode; - - if (sync_configuration->is_flexible_sync) { - config.sync_config = std::make_shared(*sync_configuration->user, realm::SyncConfig::FLXSyncEnabled{}); - } - else { - std::string partition(Utf16StringAccessor(sync_configuration->partition, sync_configuration->partition_len)); - config.sync_config = std::make_shared(*sync_configuration->user, partition); - } - - config.sync_config->error_handler = [configuration_handle](SharedSyncSession session, SyncError error) { - std::vector> user_info_pairs; - std::vector compensating_writes; - - for (const auto& p : error.user_info) { - user_info_pairs.push_back(std::make_pair(to_capi(p.first), to_capi(p.second))); - } - - for (const auto& cw : error.compensating_writes_info) { - compensating_writes.push_back(realm_sync_error_compensating_write_info_t{ - to_capi(cw.reason), - to_capi(cw.object_name), - to_capi(cw.primary_key) - }); - } - - realm_sync_error marshaled_error{ - error.status.code(), - to_capi(error.simple_message), - to_capi(error.logURL), - error.is_client_reset_requested(), - user_info_pairs, - compensating_writes, - }; - - s_session_error_callback(new SharedSyncSession(session), marshaled_error, configuration_handle->handle()); - }; - - config.sync_config->stop_policy = sync_configuration->session_stop_policy; - config.sync_config->client_resync_mode = sync_configuration->client_resync_mode; - config.sync_config->cancel_waits_on_nonfatal_error = sync_configuration->cancel_waits_on_nonfatal_error; - - if (sync_configuration->client_resync_mode == ClientResyncMode::DiscardLocal || - sync_configuration->client_resync_mode == ClientResyncMode::Recover || - sync_configuration->client_resync_mode == ClientResyncMode::RecoverOrDiscard) { - - config.sync_config->notify_before_client_reset = [configuration_handle](SharedRealm before_frozen) { - auto error = s_notify_before_callback(before_frozen, configuration_handle->handle()); - if (error) { - throw ManagedExceptionDuringCallback("Managed exception happened in a BeforeReset callback.", error); - } - }; - - config.sync_config->notify_after_client_reset = [configuration_handle](SharedRealm before_frozen, ThreadSafeReference after_reference, bool did_recover) { - auto after = Realm::get_shared_realm(std::move(after_reference)); - auto error = s_notify_after_callback(before_frozen, after, configuration_handle->handle(), did_recover); - if (error) { - throw ManagedExceptionDuringCallback("Managed exception happened in an AfterReset callback.", error); - } - }; - } - - } - return config; } @@ -359,34 +290,6 @@ REALM_EXPORT SharedRealm* shared_realm_open(Configuration configuration, NativeE }); } -REALM_EXPORT SharedAsyncOpenTask* shared_realm_open_with_sync_async(Configuration configuration, SyncConfiguration sync_configuration, void* task_completion_source, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - auto config = get_shared_realm_config(configuration, sync_configuration); - - auto task = Realm::get_synchronized_realm(config); - task->start([task_completion_source](ThreadSafeReference ref, std::exception_ptr error) { - if (error) { - auto native_ex = realm::convert_exception(error).for_marshalling(); - s_open_realm_callback(task_completion_source, nullptr, std::move(native_ex)); - } - else { - s_open_realm_callback(task_completion_source, new ThreadSafeReference(std::move(ref)), { ErrorCodes::Error::OK}); - } - }); - - return new SharedAsyncOpenTask(task); - }); -} - -REALM_EXPORT SharedRealm* shared_realm_open_with_sync(Configuration configuration, SyncConfiguration sync_configuration, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - auto config = get_shared_realm_config(configuration, sync_configuration); - return new_realm(Realm::get_shared_realm(std::move(config))); - }); -} - REALM_EXPORT void shared_realm_set_managed_state_handle(SharedRealm& realm, void* managed_state_handle, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { @@ -591,14 +494,11 @@ REALM_EXPORT void thread_safe_reference_destroy(ThreadSafeReference* reference) delete reference; } -REALM_EXPORT void shared_realm_write_copy(const SharedRealm& realm, Configuration configuration, bool use_sync, NativeException::Marshallable& ex) +REALM_EXPORT void shared_realm_write_copy(const SharedRealm& realm, Configuration configuration, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { Realm::Config config; - // force_sync_history tells Core to synthesize/copy the sync history from the source. - config.force_sync_history = use_sync; - config.path = capi_to_std(configuration.path); if (configuration.encryption_key.items) { auto& key = *reinterpret_cast*>(configuration.encryption_key.items); @@ -798,28 +698,6 @@ REALM_EXPORT bool shared_realm_remove_all(const SharedRealm& realm, NativeExcept }); } -REALM_EXPORT SharedSyncSession* shared_realm_get_sync_session(SharedRealm& realm, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - return new SharedSyncSession(realm->sync_session()); - }); -} - -REALM_EXPORT SharedSubscriptionSet* shared_realm_get_subscriptions(SharedRealm& realm, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - auto p = new SubscriptionSet(realm->get_latest_subscription_set()); - return new SharedSubscriptionSet(p); - }); -} - -REALM_EXPORT int64_t shared_realm_get_subscriptions_version(SharedRealm& realm, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - return realm->get_latest_subscription_set().version(); - }); -} - REALM_EXPORT bool shared_realm_refresh_async(SharedRealm& realm, void* managed_tcs, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { if (realm->is_frozen()) { diff --git a/wrappers/src/shared_realm_cs.hpp b/wrappers/src/shared_realm_cs.hpp index 85edbb9318..9b8f162847 100644 --- a/wrappers/src/shared_realm_cs.hpp +++ b/wrappers/src/shared_realm_cs.hpp @@ -19,7 +19,6 @@ #pragma once #include "schema_cs.hpp" -#include "sync_session_cs.hpp" #include #include @@ -66,24 +65,6 @@ struct Configuration bool automatically_migrate_embedded; }; -struct SyncConfiguration -{ - SharedSyncUser* user; - - uint16_t* partition; - size_t partition_len; - - SyncSessionStopPolicy session_stop_policy; - - SchemaMode schema_mode; - - bool is_flexible_sync; - - ClientResyncMode client_resync_mode; - - bool cancel_waits_on_nonfatal_error; -}; - inline const TableRef get_table(const SharedRealm& realm, TableKey table_key) { return realm->read_group().get_table(table_key); diff --git a/wrappers/src/subscription_set_cs.cpp b/wrappers/src/subscription_set_cs.cpp deleted file mode 100644 index a7591704f2..0000000000 --- a/wrappers/src/subscription_set_cs.cpp +++ /dev/null @@ -1,358 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include -#include "error_handling.hpp" -#include "marshalling.hpp" -#include "realm_export_decls.hpp" -#include -#include -#include -#include "sync_session_cs.hpp" -#include - -using namespace realm; -using namespace realm::binding; -using namespace realm::sync; - -using SharedSubscriptionSet = std::shared_ptr; -using SharedMutableSubscriptionSet = std::shared_ptr; - -struct CSharpSubscription { - realm_value_t id = realm_value_t{}; - - realm_value_t name = realm_value_t{}; - - realm_value_t object_type = realm_value_t{}; - - realm_value_t query = realm_value_t{}; - - realm_value_t created_at = realm_value_t{}; - - realm_value_t updated_at = realm_value_t{}; - - bool has_value = false; -}; - -enum class CSharpState : uint8_t { - Pending = 0, - Complete, - Error, - Superseded -}; - -using StateWaitCallbackT = void(void* task_completion_source, CSharpState state, realm_string_t message, ErrorCodes::Error error_code); -using SubscriptionCallbackT = void(void* managed_callback, CSharpSubscription sub); - -namespace realm { -namespace binding { - std::function s_state_wait_callback; - std::function s_get_subscription_callback; - - inline void get_subscription(void* callback, NativeException::Marshallable& ex, const std::function()>& lambda) - { - handle_errors(ex, [&] { - auto sub = lambda(); - if (sub) { - auto& sub_value = *sub; - auto csharp_sub = CSharpSubscription{ - to_capi_value(sub_value.id), - to_capi_value(sub_value.name), - to_capi_value(sub_value.object_class_name), - to_capi_value(sub_value.query_string), - to_capi_value(sub_value.created_at), - to_capi_value(sub_value.updated_at), - true - }; - s_get_subscription_callback(callback, csharp_sub); - } - else { - s_get_subscription_callback(callback, CSharpSubscription{}); - } - }); - } - - inline CSharpState core_to_csharp_state(SubscriptionSet::State state) { - switch (state) { - case SubscriptionSet::State::Uncommitted: - case SubscriptionSet::State::Pending: - case SubscriptionSet::State::Bootstrapping: - return CSharpState::Pending; - case SubscriptionSet::State::Complete: - return CSharpState::Complete; - case SubscriptionSet::State::Error: - return CSharpState::Error; - case SubscriptionSet::State::Superseded: - return CSharpState::Superseded; - default: - REALM_UNREACHABLE(); - } - } -} -} -extern "C" { - -REALM_EXPORT void realm_subscriptionset_install_callbacks(SubscriptionCallbackT* get_subscription_callback, StateWaitCallbackT* state_wait_callback) -{ - s_get_subscription_callback = wrap_managed_callback(get_subscription_callback); - s_state_wait_callback = wrap_managed_callback(state_wait_callback); - realm::binding::s_can_call_managed = true; -} - -REALM_EXPORT size_t realm_subscriptionset_get_count(SharedSubscriptionSet& subs, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - return subs->size(); - }); -} - - - -REALM_EXPORT CSharpState realm_subscriptionset_get_state(SharedSubscriptionSet& subs, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - return core_to_csharp_state(subs->state()); - }); -} - -REALM_EXPORT int64_t realm_subscriptionset_get_version(SharedSubscriptionSet& subs, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - return subs->version(); - }); -} - - -REALM_EXPORT void realm_subscriptionset_get_at_index(SharedSubscriptionSet& subs, size_t index, void* callback, NativeException::Marshallable& ex) -{ - get_subscription(callback, ex, [&] { - const size_t count = subs->size(); - if (index >= count) - throw IndexOutOfRangeException("Get from SubscriptionSet", index, count); - - return subs->at(index); - }); -} - -REALM_EXPORT void realm_subscriptionset_find_by_name(SharedSubscriptionSet& subs, uint16_t* name_buf, size_t name_len, void* callback, NativeException::Marshallable& ex) -{ - get_subscription(callback, ex, [&]() -> util::Optional { - Utf16StringAccessor name(name_buf, name_len); - if (auto sub = subs->find(name)) { - return *sub; - } - - return util::none; - }); -} - -REALM_EXPORT void realm_subscriptionset_find_by_query(SharedSubscriptionSet& subs, Results& results, void* callback, NativeException::Marshallable& ex) -{ - get_subscription(callback, ex, [&]() -> util::Optional { - if (auto sub = subs->find(results.get_query())) { - return *sub; - } - - return util::none; - }); -} - -REALM_EXPORT void realm_subscriptionset_add_results(SharedMutableSubscriptionSet& subs, - Results& results, - uint16_t* name_buf, size_t name_len, - bool update_existing, void* callback, NativeException::Marshallable& ex) -{ - get_subscription(callback, ex, [&]() -> util::Optional { - auto object_type = results.get_object_type(); - auto query_str = results.get_query().get_description(); - - if (name_buf) { - Utf16StringAccessor name(name_buf, name_len); - auto sub = subs->find(name); - if (sub == nullptr || update_existing || (sub->object_class_name == object_type && sub->query_string == query_str)) { - return *subs->insert_or_assign(name.to_string(), results.get_query()).first; - } - else { - throw DuplicateSubscriptionException(name.to_string(), sub->query_string, query_str); - } - } - else { - return *subs->insert_or_assign(results.get_query()).first; - } - }); -} - -REALM_EXPORT bool realm_subscriptionset_remove(SharedMutableSubscriptionSet& subs, uint16_t* name_buf, size_t name_len, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - Utf16StringAccessor name(name_buf, name_len); - return subs->erase(name); - }); -} - -REALM_EXPORT bool realm_subscriptionset_remove_by_id(SharedMutableSubscriptionSet& subs, realm_value_t id, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - auto subId = from_capi(id.object_id); - for (auto it = subs->begin(); it != subs->end(); it++) { - if (it->id == subId) { - subs->erase(it); - return true; - } - } - - return false; - }); -} - -REALM_EXPORT size_t realm_subscriptionset_remove_by_query(SharedMutableSubscriptionSet& subs, Results& results, bool remove_named, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - size_t removed = 0; - - const auto query_desc = results.get_query().get_description(); - const auto class_name = results.get_object_type(); - - for (auto it = subs->begin(); it != subs->end();) { - if (it->object_class_name == class_name && it->query_string == query_desc && (remove_named || !it->name)) { - it = subs->erase(it); - removed++; - } - else { - it++; - } - } - - return removed; - }); -} - -REALM_EXPORT size_t realm_subscriptionset_remove_by_type(SharedMutableSubscriptionSet& subs, uint16_t* type_buf, size_t type_len, bool remove_named, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - size_t removed = 0; - Utf16StringAccessor type(type_buf, type_len); - - for (auto it = subs->begin(); it != subs->end();) { - if (it->object_class_name == type && (remove_named || !it->name)) { - it = subs->erase(it); - removed++; - } - else { - it++; - } - } - - return removed; - }); -} - -REALM_EXPORT size_t realm_subscriptionset_remove_all(SharedMutableSubscriptionSet& subs, bool remove_named, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - if (remove_named) { - auto size = subs->size(); - subs->clear(); - return size; - } - - size_t removed = 0; - for (auto it = subs->begin(); it != subs->end();) { - if (!it->name) { - it = subs->erase(it); - removed++; - } - else { - it++; - } - } - - return removed; - }); -} - -REALM_EXPORT void realm_subscriptionset_destroy(SharedSubscriptionSet* subs) -{ - delete subs; -} - -REALM_EXPORT void realm_subscriptionset_destroy_mutable(SharedMutableSubscriptionSet* subs) -{ - delete subs; -} - -REALM_EXPORT SharedMutableSubscriptionSet* realm_subscriptionset_begin_write(SharedSubscriptionSet& subs, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - auto p = new MutableSubscriptionSet(subs->make_mutable_copy()); - return new SharedMutableSubscriptionSet(p); - }); -} - -REALM_EXPORT SharedSubscriptionSet* realm_subscriptionset_commit_write(SharedMutableSubscriptionSet& subs, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - auto p = new SubscriptionSet(std::move(*subs).commit()); - return new SharedSubscriptionSet(p); - }); -} - -REALM_EXPORT size_t realm_subscriptionset_get_error(SharedSubscriptionSet& subs, uint16_t* buffer, size_t buffer_length, bool& is_null, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - is_null = subs->error_str().is_null(); - if (is_null) { - return (size_t)0; - } - - return stringdata_to_csharpstringbuffer(subs->error_str(), buffer, buffer_length); - }); -} - -REALM_EXPORT void realm_subscriptionset_wait_for_state(SharedSubscriptionSet& subs, void* task_completion_source, NativeException::Marshallable& ex) -{ - using WeakSubscriptionSet = std::weak_ptr; - handle_errors(ex, [&] { - subs->get_state_change_notification(SubscriptionSet::State::Complete) - .get_async([task_completion_source, weak_subs=WeakSubscriptionSet(subs)](StatusWith status) mutable noexcept { - try { - // Here -1 being sent to the wait callback indicates the wait was cancelled. - if (auto subs = weak_subs.lock()) { - subs->refresh(); - if (status.is_ok()) { - s_state_wait_callback(task_completion_source, core_to_csharp_state(status.get_value()), realm_string_t{}, ErrorCodes::Error::OK); - } - else { - s_state_wait_callback(task_completion_source, CSharpState::Error, to_capi(status.get_status().reason()), status.get_status().code()); - } - } - else { - s_state_wait_callback(task_completion_source, CSharpState::Error, to_capi(std::string("operation aborted")), ErrorCodes::Error::OperationAborted); - } - } - catch (...) { - auto inner_ex = convert_exception(); - s_state_wait_callback(task_completion_source, CSharpState::Error, to_capi(inner_ex.to_string()), ErrorCodes::Error::RuntimeError); - } - }); - }); -} - -} - diff --git a/wrappers/src/sync_session_cs.cpp b/wrappers/src/sync_session_cs.cpp deleted file mode 100644 index c7f9527cec..0000000000 --- a/wrappers/src/sync_session_cs.cpp +++ /dev/null @@ -1,213 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include -#include "error_handling.hpp" -#include "marshalling.hpp" -#include "realm_export_decls.hpp" -#include -#include -#include -#include "sync_session_cs.hpp" -#include -#include - -namespace realm::binding { -enum class NotifiableProperty : uint8_t { - ConnectionState = 0, -}; - -using WaitCallbackT = void(void* task_completion_source, int32_t error_code, realm_value_t message); -using PropertyChangedCallbackT = void(void* managed_session_handle, NotifiableProperty property); - -std::function s_session_error_callback; -std::function s_progress_callback; -std::function s_wait_callback; -std::function s_property_changed_callback; -std::function s_notify_before_callback; -std::function s_notify_after_callback; - -extern "C" { -REALM_EXPORT std::shared_ptr* realm_syncsession_get_user(const SharedSyncSession& session) -{ - if (session->user() == nullptr) { - return nullptr; - } - - return new std::shared_ptr(std::dynamic_pointer_cast(session->user())); -} - -enum class CSharpSessionState : uint8_t { - Active = 0, - Inactive -}; - -REALM_EXPORT CSharpSessionState realm_syncsession_get_state(const SharedSyncSession& session, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - switch (session->state()) { - case SyncSession::State::Inactive: - case SyncSession::State::Dying: - case SyncSession::State::Paused: - return CSharpSessionState::Inactive; - default: - return CSharpSessionState::Active; - } - }); -} - -REALM_EXPORT SyncSession::ConnectionState realm_syncsession_get_connection_state(const SharedSyncSession& session, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - return session->connection_state(); - }); -} - -REALM_EXPORT size_t realm_syncsession_get_path(const SharedSyncSession& session, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - return stringdata_to_csharpstringbuffer(session->path(), buffer, buffer_length); - }); -} - -REALM_EXPORT SyncSession* realm_syncsession_get_raw_pointer(const SharedSyncSession& session) -{ - return session.get(); -} - -REALM_EXPORT void realm_syncsession_destroy(SharedSyncSession* session) -{ - delete session; -} - -REALM_EXPORT void realm_syncsession_install_callbacks(SessionErrorCallbackT* session_error_callback, ProgressCallbackT* progress_callback, WaitCallbackT* wait_callback, PropertyChangedCallbackT* property_changed_callback, NotifyBeforeClientResetCallbackT notify_before, NotifyAfterClientResetCallbackT notify_after) -{ - s_session_error_callback = wrap_managed_callback(session_error_callback); - s_progress_callback = wrap_managed_callback(progress_callback); - s_wait_callback = wrap_managed_callback(wait_callback); - s_property_changed_callback = wrap_managed_callback(property_changed_callback); - s_notify_before_callback = wrap_managed_callback(notify_before); - s_notify_after_callback = wrap_managed_callback(notify_after); - - realm::binding::s_can_call_managed = true; -} - -enum class CSharpNotifierType : uint8_t { - Upload = 0, - Download = 1 -}; - -REALM_EXPORT uint64_t realm_syncsession_register_progress_notifier(const SharedSyncSession& session, void* managed_state, CSharpNotifierType direction, bool is_streaming, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - auto notifier_direction = direction == CSharpNotifierType::Upload - ? SyncSession::ProgressDirection::upload - : SyncSession::ProgressDirection::download; - - return session->register_progress_notifier([managed_state](uint64_t transferred, uint64_t transferable, double progress_estimate) { - s_progress_callback(managed_state, progress_estimate); - }, notifier_direction, is_streaming); - }); -} - -REALM_EXPORT void realm_syncsession_unregister_progress_notifier(const SharedSyncSession& session, uint64_t token, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - session->unregister_progress_notifier(token); - }); -} - -typedef struct PropertyChangedNotificationToken { - uint64_t connection_state; -} PropertyChangedNotificationToken; - -REALM_EXPORT PropertyChangedNotificationToken realm_syncsession_register_property_changed_callback(const SharedSyncSession& session, void* managed_session_handle, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - auto connection_state_token = session->register_connection_change_callback([managed_session_handle](realm::SyncSession::ConnectionState old_state, realm::SyncSession::ConnectionState new_state) { - s_property_changed_callback(managed_session_handle, NotifiableProperty::ConnectionState); - }); - - PropertyChangedNotificationToken notification_token { connection_state_token }; - return notification_token; - }); -} - -REALM_EXPORT void realm_syncsession_unregister_property_changed_callback(const SharedSyncSession& session, PropertyChangedNotificationToken tokens, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - session->unregister_connection_change_callback(tokens.connection_state); - }); -} - -REALM_EXPORT void realm_syncsession_wait(const SharedSyncSession& session, void* task_completion_source, CSharpNotifierType direction, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - auto waiter = [task_completion_source](realm::Status status) { - s_wait_callback(task_completion_source, status.code(), to_capi_value(status.reason())); - }; - - if (direction == CSharpNotifierType::Upload) { - session->wait_for_upload_completion(waiter); - } else { - session->wait_for_download_completion(waiter); - } - }); -} - -enum class SessionErrorCategory : uint8_t { - ClientError = 0, - SessionError = 1 -}; - -REALM_EXPORT void realm_syncsession_report_error_for_testing(const SharedSyncSession& session, int err, const uint16_t* message_buf, size_t message_len, bool is_fatal, int server_requests_action) -{ - Utf16StringAccessor message(message_buf, message_len); - std::error_code error_code; - - sync::ProtocolErrorInfo protocol_error(err, message, is_fatal); - sync::SessionErrorInfo error(protocol_error); - error.server_requests_action = static_cast(server_requests_action); - - SyncSession::OnlyForTesting::handle_error(*session, std::move(error)); -} - -REALM_EXPORT void realm_syncsession_stop(const SharedSyncSession& session, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - session->pause(); - }); -} - -REALM_EXPORT void realm_syncsession_start(const SharedSyncSession& session, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - session->resume(); - }); -} - -REALM_EXPORT void realm_syncsession_shutdown_and_wait(const SharedSyncSession& session, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - session->shutdown_and_wait(); - }); -} - -} // extern "C" -} // namespace realm::binding diff --git a/wrappers/src/sync_session_cs.hpp b/wrappers/src/sync_session_cs.hpp deleted file mode 100644 index dacc7b336c..0000000000 --- a/wrappers/src/sync_session_cs.hpp +++ /dev/null @@ -1,38 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include "marshalling.hpp" -#include - -namespace realm::binding { - -using SharedSyncSession = std::shared_ptr; -using SessionErrorCallbackT = void(SharedSyncSession* session, realm_sync_error error, void* managed_sync_config); -using ProgressCallbackT = void(void* state, double progress_estimate); -using NotifyBeforeClientResetCallbackT = void*(SharedRealm& before_frozen, void* managed_sync_config); -using NotifyAfterClientResetCallbackT = void*(SharedRealm& before_frozen, SharedRealm& after, void* managed_sync_config, bool did_recover); - -extern std::function s_session_error_callback; -extern std::function s_progress_callback; -extern std::function s_notify_before_callback; -extern std::function s_notify_after_callback; - -} // namespace realm::binding - diff --git a/wrappers/src/sync_user_cs.cpp b/wrappers/src/sync_user_cs.cpp deleted file mode 100644 index 7f9093b375..0000000000 --- a/wrappers/src/sync_user_cs.cpp +++ /dev/null @@ -1,368 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - - -// #include json.hpp needs to be before #include realm.hpp due to https://github.com/nlohmann/json/issues/2129 -#include - -#include -#include "error_handling.hpp" -#include "marshalling.hpp" -#include "realm_export_decls.hpp" -#include -#include -#include -#include -#include "app_cs.hpp" -#include "marshalling.hpp" -#include - -using namespace realm; -using namespace realm::binding; - -using SharedSyncUser = std::shared_ptr; -using SharedSyncSession = std::shared_ptr; -using UserChangedCallbackT = void(void* managed_user_handle); - -std::function s_user_changed_callback; - -namespace realm { -namespace binding { -inline AuthProvider to_auth_provider(const std::string& provider) { - if (provider == IdentityProviderAnonymous) { - return AuthProvider::ANONYMOUS; - } - - if (provider == IdentityProviderFacebook) { - return AuthProvider::FACEBOOK; - } - - if (provider == IdentityProviderGoogle) { - return AuthProvider::GOOGLE; - } - - if (provider == IdentityProviderApple) { - return AuthProvider::APPLE; - } - - if (provider == IdentityProviderCustom) { - return AuthProvider::CUSTOM; - } - - if (provider == IdentityProviderUsernamePassword) { - return AuthProvider::USERNAME_PASSWORD; - } - - if (provider == IdentityProviderFunction) { - return AuthProvider::FUNCTION; - } - - if (provider == IdentityProviderAPIKey) { - return AuthProvider::API_KEY; - } - - return (AuthProvider)999; -} -} -} - -namespace realm::app { -void to_json(nlohmann::json& j, const UserIdentity& i) -{ - j = nlohmann::json{ - { "Id", i.id }, - { "Provider", to_auth_provider(i.provider_type)} - }; -} -} - -extern "C" { - REALM_EXPORT void realm_syncuser_install_callbacks(UserChangedCallbackT* user_changed_callback) - { - s_user_changed_callback = wrap_managed_callback(user_changed_callback); - - realm::binding::s_can_call_managed = true; - } - - REALM_EXPORT void realm_syncuser_log_out(SharedSyncUser& user, NativeException::Marshallable& ex) - { - handle_errors(ex, [&] { - user->log_out(); - }); - } - - REALM_EXPORT size_t realm_syncuser_get_id(SharedSyncUser& user, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&] { - std::string user_id(user->user_id()); - return stringdata_to_csharpstringbuffer(user_id, buffer, buffer_length); - }); - } - - REALM_EXPORT size_t realm_syncuser_get_refresh_token(SharedSyncUser& user, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&] { - std::string refresh_token(user->refresh_token()); - return stringdata_to_csharpstringbuffer(refresh_token, buffer, buffer_length); - }); - } - - REALM_EXPORT size_t realm_syncuser_get_access_token(SharedSyncUser& user, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&] { - std::string access_token(user->access_token()); - return stringdata_to_csharpstringbuffer(access_token, buffer, buffer_length); - }); - } - - REALM_EXPORT size_t realm_syncuser_get_device_id(SharedSyncUser& user, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&] { - std::string device_id(user->device_id()); - return stringdata_to_csharpstringbuffer(device_id, buffer, buffer_length); - }); - } - - REALM_EXPORT SyncUser::State realm_syncuser_get_state(SharedSyncUser& user, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&] { - return user->state(); - }); - } - - REALM_EXPORT size_t realm_syncuser_get_custom_data(SharedSyncUser& user, uint16_t* buffer, size_t buffer_length, bool& is_null, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&] { - if (user->custom_data()) { - is_null = false; - std::string serialized_data = bson::Bson(*user->custom_data()).to_string(); - return stringdata_to_csharpstringbuffer(serialized_data, buffer, buffer_length); - } - - is_null = true; - return (size_t)0; - }); - } - - REALM_EXPORT void realm_syncuser_refresh_custom_data(SharedSyncUser& user, void* tcs_ptr, NativeException::Marshallable& ex) - { - handle_errors(ex, [&] { - user->refresh_custom_data(get_callback_handler(tcs_ptr)); - }); - } - - REALM_EXPORT Subscribable::Token* realm_syncuser_register_changed_callback(SharedSyncUser& user, void* managed_user_handle, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&] { - auto token = user->subscribe([managed_user_handle](const SyncUser&) { - s_user_changed_callback(managed_user_handle); - }); - return new Subscribable::Token(std::move(token)); - }); - } - - REALM_EXPORT void realm_syncuser_unregister_property_changed_callback(SharedSyncUser& user, Subscribable::Token& token, NativeException::Marshallable& ex) - { - handle_errors(ex, [&] { - user->unsubscribe(token); - }); - } - - enum class UserProfileField : uint8_t { - name, - email, - picture_url, - first_name, - last_name, - gender, - birthday, - min_age, - max_age, - }; - - REALM_EXPORT size_t realm_syncuser_get_profile_data(SharedSyncUser& user, UserProfileField profile_field, uint16_t* string_buffer, size_t buffer_size, bool& is_null, NativeException::Marshallable& ex) { - return handle_errors(ex, [&]() { - util::Optional field; - - switch (profile_field) - { - case UserProfileField::name: - field = user->user_profile().name(); - break; - case UserProfileField::email: - field = user->user_profile().email(); - break; - case UserProfileField::picture_url: - field = user->user_profile().picture_url(); - break; - case UserProfileField::first_name: - field = user->user_profile().first_name(); - break; - case UserProfileField::last_name: - field = user->user_profile().last_name(); - break; - case UserProfileField::gender: - field = user->user_profile().gender(); - break; - case UserProfileField::birthday: - field = user->user_profile().birthday(); - break; - case UserProfileField::min_age: - field = user->user_profile().min_age(); - break; - case UserProfileField::max_age: - field = user->user_profile().max_age(); - break; - default: - REALM_UNREACHABLE(); - } - - if ((is_null = !field)) { - return (size_t)0; - } - - return stringdata_to_csharpstringbuffer(*field, string_buffer, buffer_size); - }); - } - - REALM_EXPORT size_t realm_syncuser_get_serialized_identities(SharedSyncUser& user, uint16_t* string_buffer, size_t buffer_size, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() -> size_t { - nlohmann::json j = user->identities(); - return stringdata_to_csharpstringbuffer(j.dump(), string_buffer, buffer_size); - }); - } - - REALM_EXPORT SharedApp* realm_syncuser_get_app(SharedSyncUser& user, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&] { - // If the user is detached from the sync manager, we'll hit an assert, so this early check avoids that. - if (user->state() != SyncUser::State::Removed) - { - if (auto shared_app = user->app()) { - return new SharedApp(shared_app); - } - } - - return (SharedApp*)nullptr; - }); - } - - REALM_EXPORT void realm_syncuser_call_function(SharedSyncUser& user, - SharedApp& app, - uint16_t* function_name_buf, size_t function_name_len, - uint16_t* args_buf, size_t args_len, - uint16_t* service_buf, size_t service_len, - void* tcs_ptr, NativeException::Marshallable& ex) - { - handle_errors(ex, [&] { - Utf16StringAccessor function_name(function_name_buf, function_name_len); - Utf16StringAccessor args(args_buf, args_len); - if (service_buf) { - Utf16StringAccessor service(service_buf, service_len); - app->call_function(user, function_name, args, service, get_string_callback_handler(tcs_ptr)); - } - else { - app->call_function(user, function_name, args, std::nullopt, get_string_callback_handler(tcs_ptr)); - } - }); - } - - REALM_EXPORT void realm_syncuser_link_credentials(SharedSyncUser& user, SharedApp& app, Credentials credentials, void* tcs_ptr, NativeException::Marshallable& ex) { - handle_errors(ex, [&]() { - auto app_credentials = credentials.to_app_credentials(); - app->link_user(user, app_credentials, get_user_callback_handler(tcs_ptr)); - }); - } - -#pragma region ApiKeys - - REALM_EXPORT void realm_syncuser_api_key_create(SharedSyncUser& user, SharedApp& app, uint16_t* name_buf, size_t name_len, void* tcs_ptr, NativeException::Marshallable& ex) - { - handle_errors(ex, [&] { - Utf16StringAccessor name(name_buf, name_len); - app->provider_client().create_api_key(name, user, [tcs_ptr](App::UserAPIKey api_key, util::Optional err) { - invoke_api_key_callback(tcs_ptr, api_key, err); - }); - }); - } - - REALM_EXPORT void realm_syncuser_api_key_fetch(SharedSyncUser& user, SharedApp& app, realm_value_t id, void* tcs_ptr, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&] { - app->provider_client().fetch_api_key(from_capi(id.object_id), user, [tcs_ptr](App::UserAPIKey api_key, util::Optional err) { - invoke_api_key_callback(tcs_ptr, api_key, err); - }); - }); - } - - REALM_EXPORT void realm_syncuser_api_key_fetch_all(SharedSyncUser& user, SharedApp& app, void* tcs_ptr, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&] { - app->provider_client().fetch_api_keys(user, [tcs_ptr](std::vector api_keys, util::Optional err) { - invoke_api_key_callback(tcs_ptr, api_keys, err); - }); - }); - } - - REALM_EXPORT void realm_syncuser_api_key_delete(SharedSyncUser& user, SharedApp& app, realm_value_t id, void* tcs_ptr, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&] { - app->provider_client().delete_api_key(from_capi(id.object_id), user, get_callback_handler(tcs_ptr)); - }); - } - - REALM_EXPORT void realm_syncuser_api_key_disable(SharedSyncUser& user, SharedApp& app, realm_value_t id, void* tcs_ptr, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&] { - app->provider_client().disable_api_key(from_capi(id.object_id), user, get_callback_handler(tcs_ptr)); - }); - } - - REALM_EXPORT void realm_syncuser_api_key_enable(SharedSyncUser& user, SharedApp& app, realm_value_t id, void* tcs_ptr, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&] { - app->provider_client().enable_api_key(from_capi(id.object_id), user, get_callback_handler(tcs_ptr)); - }); - } - -#pragma endregion - - REALM_EXPORT void realm_syncuser_destroy(SharedSyncUser* user) - { - delete user; - } - - REALM_EXPORT size_t realm_syncuser_get_path_for_realm(SharedSyncUser& user, uint16_t* partition_buf, size_t partition_len, uint16_t* pathbuffer, size_t pathbuffer_len, NativeException::Marshallable& ex) - { - return handle_errors(ex, [&]() { - std::string path; - if (partition_buf) { - Utf16StringAccessor partition(partition_buf, partition_len); - auto sync_config = SyncConfig(user, partition); - path = user->path_for_realm(std::move(sync_config)); - } - else { - auto sync_config = SyncConfig(user, realm::SyncConfig::FLXSyncEnabled{}); - path = user->path_for_realm(std::move(sync_config), "default"); - } - - return stringdata_to_csharpstringbuffer(path, pathbuffer, pathbuffer_len); - }); - } -} diff --git a/wrappers/src/transport_cs.cpp b/wrappers/src/transport_cs.cpp deleted file mode 100644 index 927e106365..0000000000 --- a/wrappers/src/transport_cs.cpp +++ /dev/null @@ -1,106 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include "transport_cs.hpp" -#include "marshalling.hpp" - -#include -#include -#include "realm_export_decls.hpp" -#include -#include - -namespace realm::binding { - -struct HttpClientRequest { - HttpMethod method; - - realm_string_t url; - - uint64_t timeout_ms; - - MarshaledVector> headers; - - realm_string_t body; - - void* managed_http_client; -}; - -using ExecuteRequestT = void(HttpClientRequest request, void* callback); -using ResponseFunction = util::UniqueFunction; - -std::function s_execute_request; - -struct HttpClientResponse { - int http_status_code; - int custom_status_code; - - MarshaledVector> headers; - - realm_string_t body; -}; - -HttpClientTransport::HttpClientTransport(GCHandleHolder managed_http_client) : m_managed_http_client(std::move(managed_http_client)) {} - -void HttpClientTransport::send_request_to_server(const Request& request, ResponseFunction&& completionBlock) { - std::vector> headers; - for (auto& kvp : request.headers) { - headers.push_back(std::make_pair(to_capi(kvp.first), to_capi(kvp.second))); - } - - HttpClientRequest client_request = { - request.method, - to_capi(request.url), - request.timeout_ms, - headers, - to_capi(request.body), - m_managed_http_client.handle(), - }; - - s_execute_request(std::move(client_request), new ResponseFunction(std::move(completionBlock))); -} - -extern "C" { - REALM_EXPORT void realm_http_transport_install_callbacks(ExecuteRequestT* execute) - { - s_execute_request = wrap_managed_callback(execute); - - realm::binding::s_can_call_managed = true; - } - - REALM_EXPORT void realm_http_transport_respond(HttpClientResponse client_response, void* function_ptr) - { - std::unique_ptr func(reinterpret_cast(function_ptr)); - - std::map headers_map; - for (auto i = 0; i < client_response.headers.count; i++) { - auto& header = client_response.headers.items[i]; - headers_map.emplace(from_capi(header.first), from_capi(header.second)); - } - - Response response = { - client_response.http_status_code, - client_response.custom_status_code, - std::move(headers_map), - from_capi(client_response.body) - }; - - (*func)(std::move(response)); - } -} // extern "C" -} // namespace realm::binding diff --git a/wrappers/src/transport_cs.hpp b/wrappers/src/transport_cs.hpp deleted file mode 100644 index 306ac44344..0000000000 --- a/wrappers/src/transport_cs.hpp +++ /dev/null @@ -1,39 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef TRANSPORT_CS_HPP -#define TRANSPORT_CS_HPP - -#include -#include "shared_realm_cs.hpp" - -using namespace realm::app; - -namespace realm { -namespace binding { -struct HttpClientTransport : public GenericNetworkTransport { -public: - HttpClientTransport(GCHandleHolder managed_http_client); - void send_request_to_server(const Request& request, util::UniqueFunction&& completionBlock) override; -private: - GCHandleHolder m_managed_http_client; -}; -} -} - -#endif /* defined(TRANSPORT_CS_HPP) */ diff --git a/wrappers/src/websocket_cs.cpp b/wrappers/src/websocket_cs.cpp deleted file mode 100644 index 635c8b399c..0000000000 --- a/wrappers/src/websocket_cs.cpp +++ /dev/null @@ -1,194 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include - -#include - -#include "websocket_cs.hpp" -#include "realm_export_decls.hpp" -#include "marshalling.hpp" - -namespace realm::binding { -using namespace realm::sync; - -struct MarshaledEndpoint { - realm_string_t address; - uint16_t port; - realm_string_t path; - MarshaledVector protocols; - bool is_ssl; -}; - -using CreateTimerT = void*(void* managed_provider, int64_t delay_ms, SyncSocketProvider::FunctionHandler* callback); -using CancelTimerT = void(void* managed_timer); -using PostWorkT = void(void* managed_provider, SyncSocketProvider::FunctionHandler* callback); -using WebSocketConnectT = void*(void* managed_provider, WebSocketObserver* observer, MarshaledEndpoint endpoint); -using WebSocketWriteT = void(void* managed_websocket, realm_binary_t data, SyncSocketProvider::FunctionHandler* callback); -using WebSocketCloseT = void(void* managed_websocket); -using SyncProviderDisposeT = void(void* managed_provider); - -std::function s_create_timer; -std::function s_cancel_timer; -std::function s_post_work; -std::function s_websocket_connect; -std::function s_websocket_write; -std::function s_websocket_close; -std::function s_provider_dispose; - -struct Timer final : SyncSocketProvider::Timer { - using LongMiliseconds = std::chrono::duration; - -public: - Timer(std::chrono::milliseconds delay, SyncSocketProvider::FunctionHandler&& handler, void* managed_provider) - : m_managed_timer(s_create_timer(managed_provider, LongMiliseconds(delay).count(), new SyncSocketProvider::FunctionHandler(std::move(handler)))) - { - } - ~Timer() final - { - cancel(); - } - void cancel() final - { - if (m_managed_timer) { - s_cancel_timer(m_managed_timer); - m_managed_timer = nullptr; - } - } - -private: - void* m_managed_timer; -}; - -struct WebSocket final : WebSocketInterface { -public: - WebSocket(std::unique_ptr observer, WebSocketEndpoint&& endpoint, void* managed_provider) - : m_observer(std::move(observer)) - { - MarshaledEndpoint marshaled_endpoint; - marshaled_endpoint.address = to_capi(endpoint.address); - marshaled_endpoint.port = endpoint.port; - marshaled_endpoint.path = to_capi(endpoint.path); - marshaled_endpoint.is_ssl = endpoint.is_ssl; - - std::vector protocols; - protocols.reserve(endpoint.protocols.size()); - for (const auto& protocol : endpoint.protocols) { - protocols.push_back(to_capi(protocol)); - } - marshaled_endpoint.protocols = protocols; - - m_managed_websocket = s_websocket_connect(managed_provider, m_observer.get(), marshaled_endpoint); - } - - std::string_view get_appservices_request_id() const noexcept final { - return {}; - } - - void async_write_binary(util::Span data, SyncSocketProvider::FunctionHandler&& handler) final { - realm_binary_t binary; - binary.data = reinterpret_cast(data.data()); - binary.size = data.size(); - - s_websocket_write(m_managed_websocket, binary, new SyncSocketProvider::FunctionHandler(std::move(handler))); - } - - ~WebSocket() { - s_websocket_close(m_managed_websocket); - } - -private: - void* m_managed_websocket; - std::unique_ptr m_observer; -}; - -class SocketProvider final : public SyncSocketProvider { -public: - SocketProvider(void* managed_provider) - : m_managed_provider(managed_provider) - {} - - ~SocketProvider() { - s_provider_dispose(m_managed_provider); - } - - SyncTimer create_timer(std::chrono::milliseconds delay, FunctionHandler&& handler) final { - return std::make_unique(delay, std::move(handler), m_managed_provider); - } - - void post(FunctionHandler&& handler) final { - s_post_work(m_managed_provider, new FunctionHandler(std::move(handler))); - } - - std::unique_ptr connect(std::unique_ptr observer, WebSocketEndpoint&& endpoint) final { - return std::make_unique(std::move(observer), std::move(endpoint), m_managed_provider); - } - -private: - void* m_managed_provider; -}; - -std::shared_ptr make_websocket_provider(void* managed_provider) { return std::make_shared(managed_provider); } - -extern "C" { - REALM_EXPORT void realm_websocket_install_callbacks(PostWorkT* post_work, SyncProviderDisposeT* provider_dispose, CreateTimerT* create_timer, CancelTimerT* cancel_timer, - WebSocketConnectT* websocket_connect, WebSocketWriteT* websocket_write, WebSocketCloseT* websocket_close) { - s_post_work = wrap_managed_callback(post_work); - s_provider_dispose = wrap_managed_callback(provider_dispose); - s_create_timer = wrap_managed_callback(create_timer); - s_cancel_timer = wrap_managed_callback(cancel_timer); - s_websocket_connect = wrap_managed_callback(websocket_connect); - s_websocket_write = wrap_managed_callback(websocket_write); - s_websocket_close = wrap_managed_callback(websocket_close); - - realm::binding::s_can_call_managed = true; - } - - REALM_EXPORT void realm_websocket_run_callback(SyncSocketProvider::FunctionHandler* handler, ErrorCodes::Error error_code, realm_string_t reason) { - std::unique_ptr safe_handler(handler); - - Status status = Status::OK(); - if (error_code != ErrorCodes::OK) { - status = Status(error_code, from_capi(reason)); - } - - (*safe_handler)(std::move(status)); - } - - REALM_EXPORT void realm_websocket_delete_callback(SyncSocketProvider::FunctionHandler* handler) { - delete handler; - } - - REALM_EXPORT void realm_websocket_observer_connected_handler(WebSocketObserver* observer, realm_string_t protocol) { - observer->websocket_connected_handler(from_capi(protocol)); - } - - REALM_EXPORT void realm_websocket_observer_error_handler(WebSocketObserver* observer) { - observer->websocket_error_handler(); - } - - REALM_EXPORT void realm_websocket_observer_binary_message_received(WebSocketObserver* observer, realm_binary_t data) { - observer->websocket_binary_message_received({reinterpret_cast(data.data), data.size}); - } - - REALM_EXPORT void realm_websocket_observer_closed_handler(WebSocketObserver* observer, bool was_clean, websocket::WebSocketError error_code, realm_string_t reason) { - observer->websocket_closed_handler(was_clean, error_code, capi_to_std(reason)); - } -} - -} // namespace realm::binding diff --git a/wrappers/src/websocket_cs.hpp b/wrappers/src/websocket_cs.hpp deleted file mode 100644 index d2008f84e1..0000000000 --- a/wrappers/src/websocket_cs.hpp +++ /dev/null @@ -1,27 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include - -namespace realm::binding { - -extern std::shared_ptr make_websocket_provider(void* managed_provider); - -} From 6e1c22b431c187c8e956b7206de0dd2532dfb3e3 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Mon, 19 Aug 2024 16:25:49 +0200 Subject: [PATCH 02/17] Fix gha workflows --- .github/pkl-workflows/helpers/BaaS.pkl | 47 ---------- .github/pkl-workflows/helpers/Common.pkl | 21 ++--- .github/pkl-workflows/helpers/Test.pkl | 52 +++-------- .github/pkl-workflows/main.pkl | 12 +-- .github/pkl-workflows/pr.pkl | 6 +- .github/pkl-workflows/publish-release.pkl | 2 +- .github/workflows/main.yml | 105 ++-------------------- .github/workflows/pr.yml | 67 ++------------ .github/workflows/publish-release.yml | 8 -- 9 files changed, 37 insertions(+), 283 deletions(-) delete mode 100644 .github/pkl-workflows/helpers/BaaS.pkl diff --git a/.github/pkl-workflows/helpers/BaaS.pkl b/.github/pkl-workflows/helpers/BaaS.pkl deleted file mode 100644 index ae228a428a..0000000000 --- a/.github/pkl-workflows/helpers/BaaS.pkl +++ /dev/null @@ -1,47 +0,0 @@ -module baas - -import "../GithubAction/GithubAction.pkl" as gha -import "Common.pkl" -import "Steps.pkl" - -function deploy(differentiators: Listing): gha.MatrixJob = new { - name = "Deploy BaaS" - `runs-on` = new gha.UbuntuLatest{} - strategy { - matrix { - ["differentiator"] = differentiators - } - } - `if` = Common.ifNotCanceledCondition - steps { - ...Steps.checkout(false) - Steps.setupDotnet("8.0.x") - ...deployStep("${{ matrix.differentiator }}", true) - } -} - -function deployStep(differentiator: Common.SyncDifferentiator?, shouldDeploy: Boolean): List = if (shouldDeploy && differentiator != null) List(new gha.Step { - name = "Deploy Apps" - run = "dotnet run deploy-apps --baasaas-api-key=${{ secrets.BAASAAS_API_KEY }} --baas-differentiator=\(differentiator)-${{ github.run_id }}-${{ github.run_attempt }}" - `working-directory` = "Tools/DeployApps" -}) else List() - -function cleanup(differentiators: Listing): gha.MatrixJob = new { - name = "Cleanup BaaS" - `runs-on` = new gha.UbuntuLatest{} - strategy { - matrix { - ["differentiator"] = differentiators - } - } - `if` = Common.ifNotCanceledCondition - steps { - ...Steps.checkout(false) - Steps.setupDotnet("8.0.x") - new { - name = "Terminate Baas" - run = "dotnet run terminate-baas --baasaas-api-key=${{ secrets.BAASAAS_API_KEY }} --baas-differentiator=${{ matrix.differentiator }}-${{ github.run_id }}-${{ github.run_attempt }}" - `working-directory` = "Tools/DeployApps" - } - } -} \ No newline at end of file diff --git a/.github/pkl-workflows/helpers/Common.pkl b/.github/pkl-workflows/helpers/Common.pkl index 7b013bed94..b19abd7a3a 100644 --- a/.github/pkl-workflows/helpers/Common.pkl +++ b/.github/pkl-workflows/helpers/Common.pkl @@ -1,7 +1,6 @@ module common import "../GithubAction/GithubAction.pkl" as gha -import "BaaS.pkl" import "Lint.pkl" import "Package.pkl" import "Test.pkl" as TestJobs @@ -50,12 +49,11 @@ const packages: List = nugetPackages + List("Realm.UnityUtils", "Realm.U const testTimeout: Int = 60 -const function defaultBuildJobs(baasDifferentiators: Listing, netCoreVersions: Listing): Mapping = new { +const function defaultBuildJobs(netCoreVersions: Listing): Mapping = new { [job_Wrappers] = new gha.ReusableWorkflowJob { uses = "./.github/workflows/wrappers.yml" name = "Wrappers" } - [job_Baas] = BaaS.deploy(baasDifferentiators) [job_Packages] = (Package.nuget("contains(github.head_ref, 'release')")){ needs { job_Wrappers @@ -68,23 +66,20 @@ const function defaultBuildJobs(baasDifferentiators: Listing ...TestJobs.unity(new TestJobs.UnityTestConfig { os = "windows" }) - ["test-net-framework"] = TestJobs.netFramework(baasDifferentiators) - ["test-uwp"] = TestJobs.uwp(baasDifferentiators) + ["test-net-framework"] = TestJobs.netFramework() + ["test-uwp"] = TestJobs.uwp() ["test-net-core"] = TestJobs.netCore(netCoreVersions) ["test-macos-xamarin"] = TestJobs.macOS_Xamarin() - ["test-macos-maui"] = TestJobs.macOS_Maui(baasDifferentiators) + ["test-macos-maui"] = TestJobs.macOS_Maui() ["test-ios-xamarin"] = TestJobs.iOS_Xamarin() - ["test-ios-maui"] = TestJobs.iOS_Maui(baasDifferentiators) - ["test-tvos"] = TestJobs.tvOS(baasDifferentiators) + ["test-ios-maui"] = TestJobs.iOS_Maui() + ["test-tvos"] = TestJobs.tvOS() ["test-android-xamarin"] = TestJobs.android_Xamarin() - ["test-android-maui"] = TestJobs.android_Maui(baasDifferentiators) + ["test-android-maui"] = TestJobs.android_Maui() ["test-woven-classes"] = TestJobs.wovenClasses() ["test-source-generation"] = TestJobs.sourceGeneration() ["test-weaver"] = TestJobs.weaver() - ["test-code-coverage"] = TestJobs.codeCoverage(job_Wrappers, baasDifferentiators) - ["cleanup-baas"] = (BaaS.cleanup(baasDifferentiators)) { - needs = baasDifferentiators.toList().map((d) -> "test-\(d)").toListing() - } + ["test-code-coverage"] = TestJobs.codeCoverage(job_Wrappers) ["verify-namespaces"] = Lint.verifyNamespaces() ["lint"] = Lint.lint() } diff --git a/.github/pkl-workflows/helpers/Test.pkl b/.github/pkl-workflows/helpers/Test.pkl index 7b6dcc7c0b..879e33d434 100644 --- a/.github/pkl-workflows/helpers/Test.pkl +++ b/.github/pkl-workflows/helpers/Test.pkl @@ -3,7 +3,6 @@ module test import "../GithubAction/GithubAction.pkl" as gha import "Common.pkl" import "Steps.pkl" -import "BaaS.pkl" import "Package.pkl" local const actionReportTestResults = "dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5" @@ -12,12 +11,10 @@ local const outputFile = "TestResults.xml" local const executableExpression = "${{ steps.dotnet-publish.outputs.executable-path }}" // Public test targets -function netFramework(_syncDifferentiators: Listing): gha.StepJobBase = testJob( +function netFramework(): gha.StepJobBase = testJob( new TestConfig { title = ".NET Framework" needsPackages = true - syncDifferentiator = "net-framework" - syncDifferentiators = _syncDifferentiators }, new gha.WindowsLatest{}, null, @@ -34,7 +31,7 @@ function netFramework(_syncDifferentiators: Listing): gha.StepJobBase = }) new { name = "Run the tests" - run = "./Tests/Realm.Tests/bin/\(Common.configuration)/net461/Realm.Tests.exe --result=\(outputFile) --labels=After \(baasTestArgs(config))" + run = "./Tests/Realm.Tests/bin/\(Common.configuration)/net461/Realm.Tests.exe --result=\(outputFile) --labels=After" } ...reportTestResults(config) }) @@ -155,12 +152,10 @@ function wovenClasses(): gha.StepJobBase = testJob( } ) -function tvOS(_syncDifferentiators: Listing): gha.StepJobBase = testJob( +function tvOS(): gha.StepJobBase = testJob( new TestConfig { needsPackages = true title = "Xamarin.tvOS" - syncDifferentiator = "tvos" - syncDifferentiators = _syncDifferentiators }, "macos-12", null, @@ -176,7 +171,7 @@ function tvOS(_syncDifferentiators: Listing): gha.Ste }) Steps.runSimulator(new Steps.SimulatorConfig{ appPath = "Tests/Tests.XamarinTVOS/bin/iPhoneSimulator/\(Common.configuration)/Tests.XamarinTVOS.app" - arguments = "--headless --result=${{ github.workspace }}/\(outputFile) --labels=All \(baasTestArgs(config))" + arguments = "--headless --result=${{ github.workspace }}/\(outputFile) --labels=All" bundleId = "io.realm.Tests-XamarinTVOS" iphoneToSimulate = "Apple-TV-1080p" os = "tvOS" @@ -203,7 +198,7 @@ function iOS_Xamarin(): gha.StepJobBase = testJob( }) Steps.runSimulator(new Steps.SimulatorConfig{ appPath = "Tests/Tests.iOS/bin/iPhoneSimulator/\(Common.configuration)/Tests.iOS.app" - arguments = "--headless --result=${{ github.workspace }}/\(outputFile) --labels=All \(baasTestArgs(config))" + arguments = "--headless --result=${{ github.workspace }}/\(outputFile) --labels=All " bundleId = "io.realm.dotnettests" iphoneToSimulate = "iPhone-8" os = "iOS" @@ -211,12 +206,10 @@ function iOS_Xamarin(): gha.StepJobBase = testJob( ...reportTestResults(config) }) -function iOS_Maui(_syncDifferentiators: Listing): gha.StepJobBase = testJob( +function iOS_Maui(): gha.StepJobBase = testJob( new TestConfig { needsPackages = true title = "Maui.iOS" - syncDifferentiator = "ios-maui" - syncDifferentiators = _syncDifferentiators transformResults = true }, "macos-13", @@ -228,7 +221,7 @@ function iOS_Maui(_syncDifferentiators: Listing): gha Steps.dotnetBuild("Tests/Tests.Maui", "net8.0-ios", null, getTestProps(false)) Steps.runSimulator(new Steps.SimulatorConfig{ appPath = "Tests/Tests.Maui/bin/\(Common.configuration)/net8.0-ios/iossimulator-x64/Tests.Maui.app" - arguments = "--headless --result=${{ github.workspace }}/\(outputFile) --labels=All \(baasTestArgs(config))" + arguments = "--headless --result=${{ github.workspace }}/\(outputFile) --labels=All" bundleId = "io.realm.mauitests" iphoneToSimulate = "iPhone-15" os = "iOS" @@ -257,12 +250,10 @@ function macOS_Xamarin(): gha.StepJobBase = testJob( ...reportTestResults(config) }) -function macOS_Maui(_syncDifferentiators: Listing): gha.StepJobBase = testJob( +function macOS_Maui(): gha.StepJobBase = testJob( new TestConfig { needsPackages = true title = "Maui.MacCatalyst" - syncDifferentiator = "macos-maui" - syncDifferentiators = _syncDifferentiators transformResults = true }, "macos-13", @@ -274,17 +265,15 @@ function macOS_Maui(_syncDifferentiators: Listing): g Steps.dotnetBuild("Tests/Tests.Maui", "net8.0-maccatalyst", null, getTestProps(false)) new { name = "Run the tests" - run = "Tests/Tests.Maui/bin/\(Common.configuration)/net8.0-maccatalyst/maccatalyst-x64/Tests.Maui.app/Contents/MacOS/Tests.Maui --headless --result=${{ github.workspace }}/\(outputFile) --labels=All \(baasTestArgs(config))" + run = "Tests/Tests.Maui/bin/\(Common.configuration)/net8.0-maccatalyst/maccatalyst-x64/Tests.Maui.app/Contents/MacOS/Tests.Maui --headless --result=${{ github.workspace }}/\(outputFile) --labels=All" } ...reportTestResults(config) }) -function uwp(_syncDifferentiators: Listing): gha.StepJobBase = testJob( +function uwp(): gha.StepJobBase = testJob( new TestConfig { needsPackages = true title = "UWP" - syncDifferentiators = _syncDifferentiators - syncDifferentiator = "uwp" }, new gha.WindowsLatest{}, null, @@ -314,7 +303,7 @@ function uwp(_syncDifferentiators: Listing): gha.Step }) new { name = "Run the tests" - run = "./Tests/Tests.UWP/RunTests.ps1 -ExtraAppArgs '\(baasTestArgs(config))'" + run = "./Tests/Tests.UWP/RunTests.ps1 -ExtraAppArgs" shell = "powershell" } ...reportTestResultsWithCustomFile("${{ env.TEST_RESULTS }}", config) @@ -347,12 +336,10 @@ function android_Xamarin(): gha.StepJobBase = testJob( ...reportTestResultsWithCustomFile("${{ steps.run_tests.outputs.test-results-path }}", config) }) -function android_Maui(_syncDifferentiators: Listing): gha.StepJobBase = testJob( +function android_Maui(): gha.StepJobBase = testJob( new TestConfig { needsPackages = true title = "Maui.Android" - syncDifferentiator = "android-maui" - syncDifferentiators = _syncDifferentiators transformResults = true }, new gha.WindowsLatest{}, @@ -369,11 +356,9 @@ function android_Maui(_syncDifferentiators: Listing): ...reportTestResultsWithCustomFile("${{ steps.run_tests.outputs.test-results-path }}", config) }) -function codeCoverage(wrappersJob: String, _syncDifferentiators: Listing): gha.StepJobBase = (testJob( +function codeCoverage(wrappersJob: String): gha.StepJobBase = (testJob( new TestConfig { title = "Code Coverage" - syncDifferentiator = "code-coverage" - syncDifferentiators = _syncDifferentiators usedWrappers = List("linux-x86_64") }, new gha.UbuntuLatest{}, @@ -391,7 +376,7 @@ function codeCoverage(wrappersJob: String, _syncDifferentiators: Listing enableCoreDumps(true) archiveCoreDump() ...Steps.publishCoverage("./report.lcov") @@ -531,9 +516,6 @@ local function testJob(config: TestConfig, runsOn: gha.Machine | String, _strate when (config.needsPackages) { needs { Common.job_Packages - when (config.runSyncTests) { - Common.job_Baas - } } } `if` = Common.ifNotCanceledCondition @@ -545,7 +527,6 @@ local function prepareTests(config: TestConfig(needsPackages == true || !usedWra ...Steps.checkout(false) ...cleanWorkspace(config.shouldCleanWorkspace) ...fetchTestArtifacts(config.usedWrappers) - ...BaaS.deployStep(config.syncDifferentiator, config.runSyncTests) } local function cleanWorkspace(shouldClean: Boolean): Listing = new Listing { @@ -564,8 +545,6 @@ local function buildTests(config: Steps.MSBuildConfig): Listing = Step } }) -local function baasTestArgs(config: TestConfig): String = if (config.runSyncTests) " --baasaas-api-key=${{ secrets.BAASAAS_API_KEY}} --baas-differentiator=\(config.syncDifferentiator)-${{ github.run_id }}-${{ github.run_attempt }}" else "" - local function reportTestResults(config: TestConfig): Listing = reportTestResultsWithCustomFile(outputFile, config) local function reportTestResultsWithCustomFile(_outputFile: String, config: TestConfig): Listing = new { @@ -637,8 +616,5 @@ local class TestConfig { needsPackages: Boolean = false usedWrappers: List(every((wrapper) -> Common.wrapperBinaryNames.contains(wrapper))) shouldCleanWorkspace: Boolean = false - syncDifferentiator: Common.SyncDifferentiator? = null transformResults: Boolean = false - syncDifferentiators: Listing? - runSyncTests: Boolean = syncDifferentiator != null && (syncDifferentiators?.toList()?.contains(syncDifferentiator) ?? false) } diff --git a/.github/pkl-workflows/main.pkl b/.github/pkl-workflows/main.pkl index 20e7df3ca8..c721c84f04 100644 --- a/.github/pkl-workflows/main.pkl +++ b/.github/pkl-workflows/main.pkl @@ -6,16 +6,6 @@ import "helpers/Common.pkl" import "helpers/Steps.pkl" import "helpers/Test.pkl" as TestJobs -local baasDifferentiators: Listing = new { - "code-coverage" - "net-framework" - "uwp" - "macos-maui" - "android-maui" - "ios-maui" - "macos-maui" -} - local netCoreFrameworks: Listing = new { "net6.0" "net8.0" @@ -46,7 +36,7 @@ on { env = Common.defaultEnv -jobs = (Common.defaultBuildJobs(baasDifferentiators, netCoreFrameworks)) { +jobs = (Common.defaultBuildJobs(netCoreFrameworks)) { ["publish-packages-to-sleet"] = new Job { `runs-on` = new UbuntuLatest{} name = "Publish package to S3" diff --git a/.github/pkl-workflows/pr.pkl b/.github/pkl-workflows/pr.pkl index 3f8de3eaef..684673dcae 100644 --- a/.github/pkl-workflows/pr.pkl +++ b/.github/pkl-workflows/pr.pkl @@ -4,10 +4,6 @@ amends "GithubAction/GithubAction.pkl" import "helpers/Common.pkl" -local baasDifferentiators: Listing = new { - "code-coverage" -} - local netCoreFrameworks: Listing = new { "net6.0" } @@ -41,4 +37,4 @@ concurrency { `cancel-in-progress` = true } -jobs = Common.defaultBuildJobs(baasDifferentiators, netCoreFrameworks) \ No newline at end of file +jobs = Common.defaultBuildJobs(netCoreFrameworks) \ No newline at end of file diff --git a/.github/pkl-workflows/publish-release.pkl b/.github/pkl-workflows/publish-release.pkl index e4023f8cc3..40110af234 100644 --- a/.github/pkl-workflows/publish-release.pkl +++ b/.github/pkl-workflows/publish-release.pkl @@ -21,7 +21,7 @@ jobs { Steps.downloadAllArtifacts() Steps.readVersionFromPackage() Steps.configureAWSCredentials("DOCS_S3_ACCESS_KEY", "DOCS_S3_SECRET_KEY", "us-east-2") - uploadDocs() +// uploadDocs() ...uploadToNuGet() ...Steps.uploadToNPM("latest") ...mergeReleasePR() diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 92b2880a4c..0e252418eb 100755 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,37 +23,6 @@ jobs: build-wrappers: name: Wrappers uses: ./.github/workflows/wrappers.yml - deploy-baas: - name: Deploy BaaS - if: always() && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - with: - submodules: false - ref: ${{ github.event.pull_request.head.sha }} - - name: Register problem matchers - run: |- - echo "::add-matcher::.github/problem-matchers/csc.json" - echo "::add-matcher::.github/problem-matchers/msvc.json" - - uses: actions/setup-dotnet@5d1464d5da459f3d7085106d52e499f4dc5d0f59 - with: - dotnet-version: 8.0.x - - name: Deploy Apps - working-directory: Tools/DeployApps - run: dotnet run deploy-apps --baasaas-api-key=${{ secrets.BAASAAS_API_KEY }} --baas-differentiator=${{ matrix.differentiator }}-${{ github.run_id }}-${{ github.run_attempt }} - strategy: - matrix: - differentiator: - - code-coverage - - net-framework - - uwp - - macos-maui - - android-maui - - ios-maui - - macos-maui - fail-fast: false build-packages: name: Package NuGet needs: @@ -511,7 +480,6 @@ jobs: name: Test .NET Framework needs: - build-packages - - deploy-baas if: always() && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') timeout-minutes: 60 runs-on: windows-latest @@ -535,16 +503,13 @@ jobs: with: name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Deploy Apps - working-directory: Tools/DeployApps - run: dotnet run deploy-apps --baasaas-api-key=${{ secrets.BAASAAS_API_KEY }} --baas-differentiator=net-framework-${{ github.run_id }}-${{ github.run_attempt }} - name: Add msbuild to PATH if: ${{ runner.os == 'Windows' }} uses: microsoft/setup-msbuild@70b70342ae97ca98d5eaad06cafd26d30f9592a9 - name: Build Tests/Realm.Tests run: msbuild Tests/Realm.Tests -restore -p:Configuration=Release -p:TargetFramework=net461 -p:RestoreConfigFile=Tests/Test.NuGet.Config -p:UseRealmNupkgsWithVersion=${{ needs.build-packages.outputs.package_version }} -p:RealmTestsStandaloneExe=true - name: Run the tests - run: ./Tests/Realm.Tests/bin/Release/net461/Realm.Tests.exe --result=TestResults.xml --labels=After --baasaas-api-key=${{ secrets.BAASAAS_API_KEY}} --baas-differentiator=net-framework-${{ github.run_id }}-${{ github.run_attempt }} + run: ./Tests/Realm.Tests/bin/Release/net461/Realm.Tests.exe --result=TestResults.xml --labels=After - name: Publish Unit Test Results if: always() uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 @@ -559,7 +524,6 @@ jobs: name: Test UWP needs: - build-packages - - deploy-baas if: always() && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') timeout-minutes: 60 runs-on: windows-latest @@ -583,9 +547,6 @@ jobs: with: name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Deploy Apps - working-directory: Tools/DeployApps - run: dotnet run deploy-apps --baasaas-api-key=${{ secrets.BAASAAS_API_KEY }} --baas-differentiator=uwp-${{ github.run_id }}-${{ github.run_attempt }} - name: Import test certificate run: |- $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}") @@ -599,7 +560,7 @@ jobs: - name: Build Tests/Tests.UWP run: msbuild Tests/Tests.UWP -restore -p:Configuration=Release -p:AppxBundle=Always -p:PackageCertificateKeyFile=${{ github.workspace }}\Tests\Tests.UWP\Tests.UWP_TemporaryKey.pfx -p:PackageCertificatePassword=${{ secrets.Pfx_Password }} -p:UseDotNetNativeToolchain=false -p:AppxBundlePlatforms=x64 -p:RestoreConfigFile=Tests/Test.NuGet.Config -p:UseRealmNupkgsWithVersion=${{ needs.build-packages.outputs.package_version }} - name: Run the tests - run: ./Tests/Tests.UWP/RunTests.ps1 -ExtraAppArgs ' --baasaas-api-key=${{ secrets.BAASAAS_API_KEY}} --baas-differentiator=uwp-${{ github.run_id }}-${{ github.run_attempt }}' + run: ./Tests/Tests.UWP/RunTests.ps1 -ExtraAppArgs shell: powershell - name: Publish Unit Test Results if: always() @@ -747,7 +708,6 @@ jobs: name: Test Maui.MacCatalyst needs: - build-packages - - deploy-baas if: always() && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') timeout-minutes: 60 runs-on: macos-13 @@ -771,9 +731,6 @@ jobs: with: name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Deploy Apps - working-directory: Tools/DeployApps - run: dotnet run deploy-apps --baasaas-api-key=${{ secrets.BAASAAS_API_KEY }} --baas-differentiator=macos-maui-${{ github.run_id }}-${{ github.run_attempt }} - uses: actions/setup-dotnet@5d1464d5da459f3d7085106d52e499f4dc5d0f59 with: dotnet-version: 8.0.x @@ -786,7 +743,7 @@ jobs: - name: Build Tests/Tests.Maui run: dotnet build Tests/Tests.Maui -c Release -f net8.0-maccatalyst -p:RestoreConfigFile=Tests/Test.NuGet.Config -p:UseRealmNupkgsWithVersion=${{ needs.build-packages.outputs.package_version }} - name: Run the tests - run: Tests/Tests.Maui/bin/Release/net8.0-maccatalyst/maccatalyst-x64/Tests.Maui.app/Contents/MacOS/Tests.Maui --headless --result=${{ github.workspace }}/TestResults.xml --labels=All --baasaas-api-key=${{ secrets.BAASAAS_API_KEY}} --baas-differentiator=macos-maui-${{ github.run_id }}-${{ github.run_attempt }} + run: Tests/Tests.Maui/bin/Release/net8.0-maccatalyst/maccatalyst-x64/Tests.Maui.app/Contents/MacOS/Tests.Maui --headless --result=${{ github.workspace }}/TestResults.xml --labels=All - name: Transform Results run: xsltproc --output TestResults.xml_transformed.xml Tests/Realm.Tests/EmbeddedResources/nunit3-junit.xslt TestResults.xml - name: Publish Unit Test Results @@ -853,7 +810,6 @@ jobs: name: Test Maui.iOS needs: - build-packages - - deploy-baas if: always() && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') timeout-minutes: 60 runs-on: macos-13 @@ -877,9 +833,6 @@ jobs: with: name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Deploy Apps - working-directory: Tools/DeployApps - run: dotnet run deploy-apps --baasaas-api-key=${{ secrets.BAASAAS_API_KEY }} --baas-differentiator=ios-maui-${{ github.run_id }}-${{ github.run_attempt }} - uses: actions/setup-dotnet@5d1464d5da459f3d7085106d52e499f4dc5d0f59 with: dotnet-version: 8.0.x @@ -897,7 +850,7 @@ jobs: appPath: Tests/Tests.Maui/bin/Release/net8.0-ios/iossimulator-x64/Tests.Maui.app bundleId: io.realm.mauitests iphoneToSimulate: iPhone-15 - arguments: --headless --result=${{ github.workspace }}/TestResults.xml --labels=All --baasaas-api-key=${{ secrets.BAASAAS_API_KEY}} --baas-differentiator=ios-maui-${{ github.run_id }}-${{ github.run_attempt }} + arguments: --headless --result=${{ github.workspace }}/TestResults.xml --labels=All os: iOS - name: Transform Results run: xsltproc --output TestResults.xml_transformed.xml Tests/Realm.Tests/EmbeddedResources/nunit3-junit.xslt TestResults.xml @@ -949,7 +902,7 @@ jobs: appPath: Tests/Tests.XamarinTVOS/bin/iPhoneSimulator/Release/Tests.XamarinTVOS.app bundleId: io.realm.Tests-XamarinTVOS iphoneToSimulate: Apple-TV-1080p - arguments: '--headless --result=${{ github.workspace }}/TestResults.xml --labels=All ' + arguments: --headless --result=${{ github.workspace }}/TestResults.xml --labels=All os: tvOS - name: Publish Unit Test Results if: always() @@ -1026,7 +979,6 @@ jobs: name: Test Maui.Android needs: - build-packages - - deploy-baas if: always() && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') timeout-minutes: 60 runs-on: windows-latest @@ -1055,9 +1007,6 @@ jobs: with: name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Deploy Apps - working-directory: Tools/DeployApps - run: dotnet run deploy-apps --baasaas-api-key=${{ secrets.BAASAAS_API_KEY }} --baas-differentiator=android-maui-${{ github.run_id }}-${{ github.run_attempt }} - uses: actions/setup-dotnet@5d1464d5da459f3d7085106d52e499f4dc5d0f59 with: dotnet-version: 8.0.x @@ -1248,9 +1197,6 @@ jobs: with: name: wrappers-linux-x86_64 path: wrappers/build - - name: Deploy Apps - working-directory: Tools/DeployApps - run: dotnet run deploy-apps --baasaas-api-key=${{ secrets.BAASAAS_API_KEY }} --baas-differentiator=code-coverage-${{ github.run_id }}-${{ github.run_attempt }} - name: Setup Coverlet & Report Generator run: |- dotnet tool install coverlet.console --tool-path tools @@ -1266,7 +1212,7 @@ jobs: env: DOTNET_DbgEnableMiniDump: 1 DOTNET_EnableCrashReport: 1 - run: ./tools/coverlet ./Tests/Realm.Tests/bin/Release/net8.0/linux-x64 -t ${{ steps.dotnet-publish.outputs.executable-path }} -a '--result=TestResults.xml --labels=After --baasaas-api-key=${{ secrets.BAASAAS_API_KEY}} --baas-differentiator=code-coverage-${{ github.run_id }}-${{ github.run_attempt }}' -f lcov -o ./report.lcov --exclude '[Realm.Tests]*' --exclude '[Realm.Fody]*' --exclude '[Realm.PlatformHelpers]*' + run: ./tools/coverlet ./Tests/Realm.Tests/bin/Release/net8.0/linux-x64 -t ${{ steps.dotnet-publish.outputs.executable-path }} -a '--result=TestResults.xml --labels=After' -f lcov -o ./report.lcov --exclude '[Realm.Tests]*' --exclude '[Realm.Fody]*' --exclude '[Realm.PlatformHelpers]*' - name: Archive core dump if: ${{ failure() && runner.os != 'Windows' }} uses: actions/upload-artifact@v4 @@ -1295,45 +1241,6 @@ jobs: list-suites: failed path-replace-backslashes: true fail-on-error: true - cleanup-baas: - name: Cleanup BaaS - needs: - - test-code-coverage - - test-net-framework - - test-uwp - - test-macos-maui - - test-android-maui - - test-ios-maui - - test-macos-maui - if: always() && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - with: - submodules: false - ref: ${{ github.event.pull_request.head.sha }} - - name: Register problem matchers - run: |- - echo "::add-matcher::.github/problem-matchers/csc.json" - echo "::add-matcher::.github/problem-matchers/msvc.json" - - uses: actions/setup-dotnet@5d1464d5da459f3d7085106d52e499f4dc5d0f59 - with: - dotnet-version: 8.0.x - - name: Terminate Baas - working-directory: Tools/DeployApps - run: dotnet run terminate-baas --baasaas-api-key=${{ secrets.BAASAAS_API_KEY }} --baas-differentiator=${{ matrix.differentiator }}-${{ github.run_id }}-${{ github.run_attempt }} - strategy: - matrix: - differentiator: - - code-coverage - - net-framework - - uwp - - macos-maui - - android-maui - - ios-maui - - macos-maui - fail-fast: false verify-namespaces: name: Verify Namespaces needs: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 755fcbe25f..6d66070185 100755 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -29,31 +29,6 @@ jobs: build-wrappers: name: Wrappers uses: ./.github/workflows/wrappers.yml - deploy-baas: - name: Deploy BaaS - if: always() && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - with: - submodules: false - ref: ${{ github.event.pull_request.head.sha }} - - name: Register problem matchers - run: |- - echo "::add-matcher::.github/problem-matchers/csc.json" - echo "::add-matcher::.github/problem-matchers/msvc.json" - - uses: actions/setup-dotnet@5d1464d5da459f3d7085106d52e499f4dc5d0f59 - with: - dotnet-version: 8.0.x - - name: Deploy Apps - working-directory: Tools/DeployApps - run: dotnet run deploy-apps --baasaas-api-key=${{ secrets.BAASAAS_API_KEY }} --baas-differentiator=${{ matrix.differentiator }}-${{ github.run_id }}-${{ github.run_attempt }} - strategy: - matrix: - differentiator: - - code-coverage - fail-fast: false build-packages: name: Package NuGet needs: @@ -540,7 +515,7 @@ jobs: - name: Build Tests/Realm.Tests run: msbuild Tests/Realm.Tests -restore -p:Configuration=Release -p:TargetFramework=net461 -p:RestoreConfigFile=Tests/Test.NuGet.Config -p:UseRealmNupkgsWithVersion=${{ needs.build-packages.outputs.package_version }} -p:RealmTestsStandaloneExe=true - name: Run the tests - run: './Tests/Realm.Tests/bin/Release/net461/Realm.Tests.exe --result=TestResults.xml --labels=After ' + run: ./Tests/Realm.Tests/bin/Release/net461/Realm.Tests.exe --result=TestResults.xml --labels=After - name: Publish Unit Test Results if: always() uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 @@ -591,7 +566,7 @@ jobs: - name: Build Tests/Tests.UWP run: msbuild Tests/Tests.UWP -restore -p:Configuration=Release -p:AppxBundle=Always -p:PackageCertificateKeyFile=${{ github.workspace }}\Tests\Tests.UWP\Tests.UWP_TemporaryKey.pfx -p:PackageCertificatePassword=${{ secrets.Pfx_Password }} -p:UseDotNetNativeToolchain=false -p:AppxBundlePlatforms=x64 -p:RestoreConfigFile=Tests/Test.NuGet.Config -p:UseRealmNupkgsWithVersion=${{ needs.build-packages.outputs.package_version }} - name: Run the tests - run: ./Tests/Tests.UWP/RunTests.ps1 -ExtraAppArgs '' + run: ./Tests/Tests.UWP/RunTests.ps1 -ExtraAppArgs shell: powershell - name: Publish Unit Test Results if: always() @@ -773,7 +748,7 @@ jobs: - name: Build Tests/Tests.Maui run: dotnet build Tests/Tests.Maui -c Release -f net8.0-maccatalyst -p:RestoreConfigFile=Tests/Test.NuGet.Config -p:UseRealmNupkgsWithVersion=${{ needs.build-packages.outputs.package_version }} - name: Run the tests - run: 'Tests/Tests.Maui/bin/Release/net8.0-maccatalyst/maccatalyst-x64/Tests.Maui.app/Contents/MacOS/Tests.Maui --headless --result=${{ github.workspace }}/TestResults.xml --labels=All ' + run: Tests/Tests.Maui/bin/Release/net8.0-maccatalyst/maccatalyst-x64/Tests.Maui.app/Contents/MacOS/Tests.Maui --headless --result=${{ github.workspace }}/TestResults.xml --labels=All - name: Transform Results run: xsltproc --output TestResults.xml_transformed.xml Tests/Realm.Tests/EmbeddedResources/nunit3-junit.xslt TestResults.xml - name: Publish Unit Test Results @@ -880,7 +855,7 @@ jobs: appPath: Tests/Tests.Maui/bin/Release/net8.0-ios/iossimulator-x64/Tests.Maui.app bundleId: io.realm.mauitests iphoneToSimulate: iPhone-15 - arguments: '--headless --result=${{ github.workspace }}/TestResults.xml --labels=All ' + arguments: --headless --result=${{ github.workspace }}/TestResults.xml --labels=All os: iOS - name: Transform Results run: xsltproc --output TestResults.xml_transformed.xml Tests/Realm.Tests/EmbeddedResources/nunit3-junit.xslt TestResults.xml @@ -932,7 +907,7 @@ jobs: appPath: Tests/Tests.XamarinTVOS/bin/iPhoneSimulator/Release/Tests.XamarinTVOS.app bundleId: io.realm.Tests-XamarinTVOS iphoneToSimulate: Apple-TV-1080p - arguments: '--headless --result=${{ github.workspace }}/TestResults.xml --labels=All ' + arguments: --headless --result=${{ github.workspace }}/TestResults.xml --labels=All os: tvOS - name: Publish Unit Test Results if: always() @@ -1227,9 +1202,6 @@ jobs: with: name: wrappers-linux-x86_64 path: wrappers/build - - name: Deploy Apps - working-directory: Tools/DeployApps - run: dotnet run deploy-apps --baasaas-api-key=${{ secrets.BAASAAS_API_KEY }} --baas-differentiator=code-coverage-${{ github.run_id }}-${{ github.run_attempt }} - name: Setup Coverlet & Report Generator run: |- dotnet tool install coverlet.console --tool-path tools @@ -1245,7 +1217,7 @@ jobs: env: DOTNET_DbgEnableMiniDump: 1 DOTNET_EnableCrashReport: 1 - run: ./tools/coverlet ./Tests/Realm.Tests/bin/Release/net8.0/linux-x64 -t ${{ steps.dotnet-publish.outputs.executable-path }} -a '--result=TestResults.xml --labels=After --baasaas-api-key=${{ secrets.BAASAAS_API_KEY}} --baas-differentiator=code-coverage-${{ github.run_id }}-${{ github.run_attempt }}' -f lcov -o ./report.lcov --exclude '[Realm.Tests]*' --exclude '[Realm.Fody]*' --exclude '[Realm.PlatformHelpers]*' + run: ./tools/coverlet ./Tests/Realm.Tests/bin/Release/net8.0/linux-x64 -t ${{ steps.dotnet-publish.outputs.executable-path }} -a '--result=TestResults.xml --labels=After' -f lcov -o ./report.lcov --exclude '[Realm.Tests]*' --exclude '[Realm.Fody]*' --exclude '[Realm.PlatformHelpers]*' - name: Archive core dump if: ${{ failure() && runner.os != 'Windows' }} uses: actions/upload-artifact@v4 @@ -1274,33 +1246,6 @@ jobs: list-suites: failed path-replace-backslashes: true fail-on-error: true - cleanup-baas: - name: Cleanup BaaS - needs: - - test-code-coverage - if: always() && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - with: - submodules: false - ref: ${{ github.event.pull_request.head.sha }} - - name: Register problem matchers - run: |- - echo "::add-matcher::.github/problem-matchers/csc.json" - echo "::add-matcher::.github/problem-matchers/msvc.json" - - uses: actions/setup-dotnet@5d1464d5da459f3d7085106d52e499f4dc5d0f59 - with: - dotnet-version: 8.0.x - - name: Terminate Baas - working-directory: Tools/DeployApps - run: dotnet run terminate-baas --baasaas-api-key=${{ secrets.BAASAAS_API_KEY }} --baas-differentiator=${{ matrix.differentiator }}-${{ github.run_id }}-${{ github.run_attempt }} - strategy: - matrix: - differentiator: - - code-coverage - fail-fast: false verify-namespaces: name: Verify Namespaces needs: diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 7ddf53b098..ede03abc3c 100755 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -38,14 +38,6 @@ jobs: aws-access-key-id: ${{ secrets.DOCS_S3_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.DOCS_S3_SECRET_KEY }} aws-region: us-east-2 - - name: Upload docs - run: |- - Expand-Archive -Path Realm/packages/Docs.zip/Docs.zip -DestinationPath Realm/packages - $versions = "${{ steps.get-version.outputs.package_version }}", "latest" - Foreach ($ver in $versions) - { - aws s3 sync --acl public-read "${{ github.workspace }}\Realm\packages\_site" s3://realm-sdks/docs/realm-sdks/dotnet/$ver/ - } - name: NuGet Publish Realm.${{ steps.get-version.outputs.package_version }} run: dotnet nuget push ${{ github.workspace }}/Realm/packages/Realm.${{ steps.get-version.outputs.package_version }}/Realm.${{ steps.get-version.outputs.package_version }}.nupkg --skip-duplicate --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json - name: NuGet Publish Realm.PlatformHelpers.${{ steps.get-version.outputs.package_version }} From 1d3b4d655a3bf6a9005a68e4753574c2c5c3002a Mon Sep 17 00:00:00 2001 From: nirinchev Date: Mon, 19 Aug 2024 16:33:30 +0200 Subject: [PATCH 03/17] Turn off sync from Core --- wrappers/CMakeLists.txt | 2 +- wrappers/src/shared_realm_cs.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/wrappers/CMakeLists.txt b/wrappers/CMakeLists.txt index fd2905432e..5c5f89c982 100644 --- a/wrappers/CMakeLists.txt +++ b/wrappers/CMakeLists.txt @@ -23,7 +23,7 @@ option(REALM_DOTNET_BUILD_CORE_FROM_SOURCE "Build Realm Core from source, as opp if(REALM_DOTNET_BUILD_CORE_FROM_SOURCE) set(REALM_BUILD_LIB_ONLY ON) - set(REALM_ENABLE_SYNC ON) + set(REALM_ENABLE_SYNC OFF) set(REALM_ENABLE_ASSERTIONS ON CACHE BOOL "Enable release assertions") add_subdirectory(realm-core EXCLUDE_FROM_ALL) diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index b16605b02f..e129f92e7b 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -337,7 +337,6 @@ REALM_EXPORT void shared_realm_close_all_realms(NativeException::Marshallable& e handle_errors(ex, [&]() { realm::_impl::RealmCoordinator::clear_all_caches(); - app::App::clear_cached_apps(); }); } From 015611cc8aa18c302143ac997873e0271d404451 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Mon, 19 Aug 2024 17:51:04 +0200 Subject: [PATCH 04/17] Clean up asymmetric object and analytics --- .github/pkl-workflows/codeql.pkl | 3 - .github/pkl-workflows/helpers/Common.pkl | 3 +- .github/pkl-workflows/helpers/Test.pkl | 2 +- .github/workflows/codeql.yml | 4 - .github/workflows/main.yml | 94 +-- .github/workflows/pr.yml | 77 +-- .github/workflows/prepare-release.yml | 6 - .github/workflows/publish-prerelease.yml | 3 - .github/workflows/publish-release.yml | 3 - .github/workflows/wrappers.yml | 1 - Realm - Windows.sln | 102 --- Realm.sln | 54 -- Realm/Realm.Fody/ModuleWeaver.cs | 44 +- Realm/Realm.Fody/Realm.Fody.xcf | 44 +- .../Android/DeviceInfo.android.cs | 31 - .../Apple/DeviceInfo.ios.cs | 29 - .../Apple/DeviceInfo.mac.cs | 29 - .../Apple/NativeHelpers.ios.mac.cs | 65 -- .../AssemblyInfo.shared.cs | 23 - .../DeviceInfo.shared.cs | 27 - .../Realm.PlatformHelpers/Platform.shared.cs | 85 --- .../Realm.PlatformHelpers.csproj | 62 -- .../UWP/DeviceInfo.uwp.cs | 31 - .../fallback/DeviceInfo.netstandard.cs | 29 - Realm/Realm.SourceGenerator/Diagnostics.cs | 4 +- Realm/Realm.SourceGenerator/InfoClasses.cs | 1 - Realm/Realm.SourceGenerator/Utils.cs | 8 - Realm/Realm.UnityUtils/DeviceInfo.cs | 30 - Realm/Realm.UnityUtils/Initializer.cs | 3 - Realm/Realm.UnityWeaver/UnityWeaver.cs | 253 +------- Realm/Realm.Weaver/Analytics/Analytics.cs | 589 ------------------ .../Realm.Weaver/Analytics/AnalyticsUtils.cs | 309 --------- Realm/Realm.Weaver/Analytics/Metric.cs | 210 ------- .../PropertyDefinitionExtensions.cs | 3 - .../Extensions/TypeDefinitionExtensions.cs | 9 +- .../Extensions/TypeReferenceExtensions.cs | 5 +- Realm/Realm.Weaver/ImportedReferences.cs | 6 - Realm/Realm.Weaver/RealmWeaver.cs | 47 +- .../DatabaseTypes/Accessors/IRealmAccessor.cs | 10 +- Realm/Realm/DatabaseTypes/AsymmetricObject.cs | 36 -- Realm/Realm/DatabaseTypes/IRealmObjectBase.cs | 30 +- .../DatabaseTypes/RealmCollectionBase.cs | 3 - Realm/Realm/DatabaseTypes/RealmValue.cs | 2 +- .../Realm/Dynamic/DynamicAsymmetricObject.cs | 34 - .../Realm/Dynamic/DynamicRealmObjectHelper.cs | 3 - Realm/Realm/Dynamic/MetaRealmObject.cs | 3 - .../Extensions/FrozenObjectsExtensions.cs | 4 +- .../Realm/Extensions/ReflectionExtensions.cs | 7 - Realm/Realm/GlobalSuppressions.cs | 6 - Realm/Realm/Handles/ObjectHandle.cs | 12 +- Realm/Realm/Handles/SharedRealmHandle.cs | 1 - Realm/Realm/Helpers/RealmObjectSerializer.cs | 4 +- Realm/Realm/Linq/IRealmCollection.cs | 2 +- Realm/Realm/Linq/QueryMethods.cs | 3 - Realm/Realm/Logging/LogCategory.cs | 87 +-- Realm/Realm/Realm.cs | 76 --- Realm/Realm/Realm.csproj | 1 - Realm/Realm/Schema/ObjectSchema.cs | 13 +- Realm/Realm/Schema/RealmSchema.cs | 6 +- Tests/Benchmarks/Benchmarks/FodyWeavers.xml | 2 +- .../PerformanceTests/FodyWeavers.xml | 2 +- Tests/Realm.Tests/Database/DateTimeTests.cs | 4 - .../Database/DynamicEmbeddedTests.cs | 1 - .../Database/DynamicRelationshipTests.cs | 2 - .../GuidRepresentationMigrationTests.cs | 1 - Tests/Realm.Tests/Database/InstanceTests.cs | 3 +- .../Realm.Tests/Database/ObjectSchemaTests.cs | 5 - .../Database/PropertyChangedTests.cs | 2 - .../Realm.Tests/Database/RelationshipTests.cs | 2 - Tests/Realm.Tests/FodyWeavers.xml | 2 +- .../AsymmetricObjectWithAllTypes_generated.cs | 1 - Tests/Realm.Tests/Realm.Tests.csproj | 4 - .../FodyWeavers.xml | 2 +- .../FodyWeavers.xml | 2 +- ...bjectAndEmbeddedObjectClass.diagnostics.cs | 4 +- .../UnsupportedIndexableTypes.diagnostics.cs | 4 +- .../UnsupportedPrimaryKeyTypes.diagnostics.cs | 4 +- .../UnsupportedRequiredTypes.diagnostics.cs | 4 +- Tests/Tests.Android/MainActivity.cs | 3 +- Tests/Tests.Android/Tests.Android.csproj | 4 - Tests/Tests.Maui/MainPage.xaml.cs | 2 +- Tests/Tests.UWP/FodyWeavers.xml | 2 +- Tests/Tests.UWP/Tests.UWP.csproj | 4 - Tests/Tests.XUnit/FodyWeavers.xml | 2 +- .../Tests.XamarinMac/Tests.XamarinMac.csproj | 4 - Tests/Tests.XamarinTVOS/Main.cs | 2 +- .../Tests.XamarinTVOS.csproj | 4 - Tests/Tests.iOS/Tests.iOS.csproj | 4 - .../AnalyticsAssembly.csproj | 38 -- .../AsymmetricTestClass_generated.cs | 376 ----------- .../EmbeddedTestClass_generated.cs | 380 ----------- .../JustForObjectReference_generated.cs | 377 ----------- .../RootRealmClass_generated.cs | 375 ----------- Tests/Weaver/AnalyticsAssembly/Program.cs | 359 ----------- .../Weaver/AssemblyToProcess/FaultyClasses.cs | 13 +- Tests/Weaver/AssemblyToProcess/TestObjects.cs | 26 - .../Realm.FakeForWeaverTests/RealmObject.cs | 4 - .../SyncConfiguration.cs | 27 - .../Weaver/Realm.Fody.Tests/AnalyticsTests.cs | 247 -------- Tests/Weaver/Realm.Fody.Tests/WeaverTests.cs | 13 +- .../CommandLineOptions/RealmOptions.cs | 5 - 101 files changed, 64 insertions(+), 4929 deletions(-) delete mode 100644 Realm/Realm.PlatformHelpers/Android/DeviceInfo.android.cs delete mode 100644 Realm/Realm.PlatformHelpers/Apple/DeviceInfo.ios.cs delete mode 100644 Realm/Realm.PlatformHelpers/Apple/DeviceInfo.mac.cs delete mode 100644 Realm/Realm.PlatformHelpers/Apple/NativeHelpers.ios.mac.cs delete mode 100644 Realm/Realm.PlatformHelpers/AssemblyInfo.shared.cs delete mode 100644 Realm/Realm.PlatformHelpers/DeviceInfo.shared.cs delete mode 100644 Realm/Realm.PlatformHelpers/Platform.shared.cs delete mode 100644 Realm/Realm.PlatformHelpers/Realm.PlatformHelpers.csproj delete mode 100644 Realm/Realm.PlatformHelpers/UWP/DeviceInfo.uwp.cs delete mode 100644 Realm/Realm.PlatformHelpers/fallback/DeviceInfo.netstandard.cs delete mode 100644 Realm/Realm.UnityUtils/DeviceInfo.cs delete mode 100644 Realm/Realm.Weaver/Analytics/Analytics.cs delete mode 100644 Realm/Realm.Weaver/Analytics/AnalyticsUtils.cs delete mode 100644 Realm/Realm.Weaver/Analytics/Metric.cs delete mode 100644 Realm/Realm/DatabaseTypes/AsymmetricObject.cs delete mode 100644 Realm/Realm/Dynamic/DynamicAsymmetricObject.cs delete mode 100644 Tests/Weaver/AnalyticsAssembly/AnalyticsAssembly.csproj delete mode 100644 Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/AsymmetricTestClass_generated.cs delete mode 100644 Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/EmbeddedTestClass_generated.cs delete mode 100644 Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/JustForObjectReference_generated.cs delete mode 100644 Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/RootRealmClass_generated.cs delete mode 100644 Tests/Weaver/AnalyticsAssembly/Program.cs delete mode 100644 Tests/Weaver/Realm.FakeForWeaverTests/SyncConfiguration.cs delete mode 100644 Tests/Weaver/Realm.Fody.Tests/AnalyticsTests.cs diff --git a/.github/pkl-workflows/codeql.pkl b/.github/pkl-workflows/codeql.pkl index 6627000c37..228586e166 100644 --- a/.github/pkl-workflows/codeql.pkl +++ b/.github/pkl-workflows/codeql.pkl @@ -26,9 +26,6 @@ on { } } } -env { - ["REALM_DISABLE_ANALYTICS"] = true -} concurrency { group = "codeql-${{ github.head_ref || github.run_id }}" `cancel-in-progress` = true diff --git a/.github/pkl-workflows/helpers/Common.pkl b/.github/pkl-workflows/helpers/Common.pkl index b19abd7a3a..d56dac3868 100644 --- a/.github/pkl-workflows/helpers/Common.pkl +++ b/.github/pkl-workflows/helpers/Common.pkl @@ -36,7 +36,6 @@ const wrapperBinaryNames: List = + applePlatformTargets((platform, target) -> "\(platform)-\(target)") const defaultEnv: Mapping = new { - ["REALM_DISABLE_ANALYTICS"] = true ["DOTNET_NOLOGO"] = true } @@ -44,7 +43,7 @@ const function applePlatformTargets(_transform: (String, String) -> String): Lis const ifNotCanceledCondition = "always() && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled')" -const nugetPackages: List = List("Realm", "Realm.PlatformHelpers") +const nugetPackages: List = List("Realm") const packages: List = nugetPackages + List("Realm.UnityUtils", "Realm.UnityWeaver") const testTimeout: Int = 60 diff --git a/.github/pkl-workflows/helpers/Test.pkl b/.github/pkl-workflows/helpers/Test.pkl index 879e33d434..51b0fbd2c5 100644 --- a/.github/pkl-workflows/helpers/Test.pkl +++ b/.github/pkl-workflows/helpers/Test.pkl @@ -376,7 +376,7 @@ function codeCoverage(wrappersJob: String): gha.StepJobBase = (testJob( ...Steps.dotnetPublish("Tests/Realm.Tests", "net8.0", "linux-x64", new Mapping { ["RealmTestsStandaloneExe"] = "true" }) new gha.Step { name = "Run the tests" - run = "./tools/coverlet ./Tests/Realm.Tests/bin/\(Common.configuration)/net8.0/linux-x64 -t \(executableExpression) -a '--result=\(outputFile) --labels=After' -f lcov -o ./report.lcov --exclude '[Realm.Tests]*' --exclude '[Realm.Fody]*' --exclude '[Realm.PlatformHelpers]*'" + run = "./tools/coverlet ./Tests/Realm.Tests/bin/\(Common.configuration)/net8.0/linux-x64 -t \(executableExpression) -a '--result=\(outputFile) --labels=After' -f lcov -o ./report.lcov --exclude '[Realm.Tests]*' --exclude '[Realm.Fody]*'" } |> enableCoreDumps(true) archiveCoreDump() ...Steps.publishCoverage("./report.lcov") diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a7d2f8b2bf..29b790522c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -15,8 +15,6 @@ name: CodeQL push: branches: - main -env: - REALM_DISABLE_ANALYTICS: true concurrency: group: codeql-${{ github.head_ref || github.run_id }} cancel-in-progress: true @@ -65,8 +63,6 @@ jobs: uses: microsoft/setup-msbuild@70b70342ae97ca98d5eaad06cafd26d30f9592a9 - name: Build Realm/Realm run: msbuild Realm/Realm -restore -p:Configuration=Release -p:UseSharedCompilation=false - - name: Build Realm/Realm.PlatformHelpers - run: msbuild Realm/Realm.PlatformHelpers -restore -p:Configuration=Release -p:UseSharedCompilation=false - name: Build Realm/Realm.UnityUtils run: msbuild Realm/Realm.UnityUtils -restore -p:Configuration=Release -p:UseSharedCompilation=false - name: Build Realm/Realm.UnityWeaver diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0e252418eb..c350197d8d 100755 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,6 @@ name: Main Build required: false type: boolean env: - REALM_DISABLE_ANALYTICS: true DOTNET_NOLOGO: true jobs: build-wrappers: @@ -178,8 +177,6 @@ jobs: uses: microsoft/setup-msbuild@70b70342ae97ca98d5eaad06cafd26d30f9592a9 - name: Build Realm/Realm run: msbuild Realm/Realm -t:Pack -restore -p:Configuration=Release -p:PackageOutputPath=${{ github.workspace }}/Realm/packages -p:VersionSuffix=${{ steps.set-version-suffix.outputs.build_suffix }} - - name: Build Realm/Realm.PlatformHelpers - run: msbuild Realm/Realm.PlatformHelpers -t:Pack -restore -p:Configuration=Release -p:PackageOutputPath=${{ github.workspace }}/Realm/packages -p:VersionSuffix=${{ steps.set-version-suffix.outputs.build_suffix }} - name: Build Realm/Realm.UnityUtils run: msbuild Realm/Realm.UnityUtils -t:Pack -restore -p:Configuration=Release -p:PackageOutputPath=${{ github.workspace }}/Realm/packages -p:VersionSuffix=${{ steps.set-version-suffix.outputs.build_suffix }} - name: Build Realm/Realm.UnityWeaver @@ -198,13 +195,6 @@ jobs: path: Realm/packages/Realm.${{ steps.get-version.outputs.package_version }}.*nupkg retention-days: ${{ github.event_name != 'pull_request' && 30 || 1 }} if-no-files-found: error - - name: Store artifacts for Realm.PlatformHelpers.${{ steps.get-version.outputs.package_version }} - uses: actions/upload-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ steps.get-version.outputs.package_version }} - path: Realm/packages/Realm.PlatformHelpers.${{ steps.get-version.outputs.package_version }}.*nupkg - retention-days: ${{ github.event_name != 'pull_request' && 30 || 1 }} - if-no-files-found: error - name: Store artifacts for Realm.UnityUtils.${{ steps.get-version.outputs.package_version }} uses: actions/upload-artifact@v4 with: @@ -286,11 +276,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Fetch Realm.UnityUtils uses: actions/download-artifact@v4 with: @@ -498,11 +483,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Add msbuild to PATH if: ${{ runner.os == 'Windows' }} uses: microsoft/setup-msbuild@70b70342ae97ca98d5eaad06cafd26d30f9592a9 @@ -542,11 +522,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Import test certificate run: |- $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}") @@ -596,11 +571,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Clear nuget cache if: ${{ matrix.os.runner == 'win81' }} run: dotnet nuget locals all --clear @@ -682,11 +652,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Add msbuild to PATH if: ${{ runner.os == 'Windows' }} uses: microsoft/setup-msbuild@70b70342ae97ca98d5eaad06cafd26d30f9592a9 @@ -726,11 +691,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - uses: actions/setup-dotnet@5d1464d5da459f3d7085106d52e499f4dc5d0f59 with: dotnet-version: 8.0.x @@ -778,11 +738,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Add msbuild to PATH if: ${{ runner.os == 'Windows' }} uses: microsoft/setup-msbuild@70b70342ae97ca98d5eaad06cafd26d30f9592a9 @@ -828,11 +783,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - uses: actions/setup-dotnet@5d1464d5da459f3d7085106d52e499f4dc5d0f59 with: dotnet-version: 8.0.x @@ -886,11 +836,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Add msbuild to PATH if: ${{ runner.os == 'Windows' }} uses: microsoft/setup-msbuild@70b70342ae97ca98d5eaad06cafd26d30f9592a9 @@ -941,11 +886,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Add msbuild to PATH if: ${{ runner.os == 'Windows' }} uses: microsoft/setup-msbuild@70b70342ae97ca98d5eaad06cafd26d30f9592a9 @@ -1002,11 +942,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - uses: actions/setup-dotnet@5d1464d5da459f3d7085106d52e499f4dc5d0f59 with: dotnet-version: 8.0.x @@ -1066,11 +1001,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Publish Tests/Realm.Tests run: dotnet publish Tests/Realm.Tests -c Release -f net8.0 -r win-x64 -p:RestoreConfigFile=Tests/Test.NuGet.Config -p:UseRealmNupkgsWithVersion=${{ needs.build-packages.outputs.package_version }} -p:RealmTestsStandaloneExe=true -p:TestWeavedClasses=true --no-self-contained - name: Output executable path @@ -1212,7 +1142,7 @@ jobs: env: DOTNET_DbgEnableMiniDump: 1 DOTNET_EnableCrashReport: 1 - run: ./tools/coverlet ./Tests/Realm.Tests/bin/Release/net8.0/linux-x64 -t ${{ steps.dotnet-publish.outputs.executable-path }} -a '--result=TestResults.xml --labels=After' -f lcov -o ./report.lcov --exclude '[Realm.Tests]*' --exclude '[Realm.Fody]*' --exclude '[Realm.PlatformHelpers]*' + run: ./tools/coverlet ./Tests/Realm.Tests/bin/Release/net8.0/linux-x64 -t ${{ steps.dotnet-publish.outputs.executable-path }} -a '--result=TestResults.xml --labels=After' -f lcov -o ./report.lcov --exclude '[Realm.Tests]*' --exclude '[Realm.Fody]*' - name: Archive core dump if: ${{ failure() && runner.os != 'Windows' }} uses: actions/upload-artifact@v4 @@ -1257,11 +1187,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Fetch Realm.UnityUtils uses: actions/download-artifact@v4 with: @@ -1332,11 +1257,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - uses: actions/setup-dotnet@5d1464d5da459f3d7085106d52e499f4dc5d0f59 with: dotnet-version: 8.0.x @@ -1350,8 +1270,6 @@ jobs: aws-region: us-east-1 - name: NuGet Publish Realm.${{ needs.build-packages.outputs.package_version }} run: sleet push ${{ github.workspace }}/Realm/packages/Realm.${{ needs.build-packages.outputs.package_version }}.nupkg --config ${{ github.workspace }}/.github/sleet.json --source NugetSource - - name: NuGet Publish Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - run: sleet push ${{ github.workspace }}/Realm/packages/Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }}.nupkg --config ${{ github.workspace }}/.github/sleet.json --source NugetSource test-xunit: name: Test xUnit Compatibility needs: @@ -1374,11 +1292,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Publish Tests/Tests.XUnit run: dotnet publish Tests/Tests.XUnit -c Release -f net6.0 -r win-x64 --no-self-contained - name: Output executable path @@ -1411,11 +1324,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Clear nuget cache run: dotnet nuget locals all --clear - name: Publish Tests/Benchmarks/PerformanceTests diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6d66070185..407368cbdf 100755 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -20,7 +20,6 @@ name: PR Build - .github/actions/** - Tests/Tests.Android/Properties/AndroidManifest.xml env: - REALM_DISABLE_ANALYTICS: true DOTNET_NOLOGO: true concurrency: group: ${{ github.head_ref || github.run_id }} @@ -184,8 +183,6 @@ jobs: uses: microsoft/setup-msbuild@70b70342ae97ca98d5eaad06cafd26d30f9592a9 - name: Build Realm/Realm run: msbuild Realm/Realm -t:Pack -restore -p:Configuration=Release -p:PackageOutputPath=${{ github.workspace }}/Realm/packages -p:VersionSuffix=${{ steps.set-version-suffix.outputs.build_suffix }} - - name: Build Realm/Realm.PlatformHelpers - run: msbuild Realm/Realm.PlatformHelpers -t:Pack -restore -p:Configuration=Release -p:PackageOutputPath=${{ github.workspace }}/Realm/packages -p:VersionSuffix=${{ steps.set-version-suffix.outputs.build_suffix }} - name: Build Realm/Realm.UnityUtils run: msbuild Realm/Realm.UnityUtils -t:Pack -restore -p:Configuration=Release -p:PackageOutputPath=${{ github.workspace }}/Realm/packages -p:VersionSuffix=${{ steps.set-version-suffix.outputs.build_suffix }} - name: Build Realm/Realm.UnityWeaver @@ -204,13 +201,6 @@ jobs: path: Realm/packages/Realm.${{ steps.get-version.outputs.package_version }}.*nupkg retention-days: ${{ github.event_name != 'pull_request' && 30 || 1 }} if-no-files-found: error - - name: Store artifacts for Realm.PlatformHelpers.${{ steps.get-version.outputs.package_version }} - uses: actions/upload-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ steps.get-version.outputs.package_version }} - path: Realm/packages/Realm.PlatformHelpers.${{ steps.get-version.outputs.package_version }}.*nupkg - retention-days: ${{ github.event_name != 'pull_request' && 30 || 1 }} - if-no-files-found: error - name: Store artifacts for Realm.UnityUtils.${{ steps.get-version.outputs.package_version }} uses: actions/upload-artifact@v4 with: @@ -292,11 +282,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Fetch Realm.UnityUtils uses: actions/download-artifact@v4 with: @@ -504,11 +489,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Add msbuild to PATH if: ${{ runner.os == 'Windows' }} uses: microsoft/setup-msbuild@70b70342ae97ca98d5eaad06cafd26d30f9592a9 @@ -548,11 +528,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Import test certificate run: |- $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}") @@ -602,11 +577,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Clear nuget cache if: ${{ matrix.os.runner == 'win81' }} run: dotnet nuget locals all --clear @@ -687,11 +657,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Add msbuild to PATH if: ${{ runner.os == 'Windows' }} uses: microsoft/setup-msbuild@70b70342ae97ca98d5eaad06cafd26d30f9592a9 @@ -731,11 +696,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - uses: actions/setup-dotnet@5d1464d5da459f3d7085106d52e499f4dc5d0f59 with: dotnet-version: 8.0.x @@ -783,11 +743,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Add msbuild to PATH if: ${{ runner.os == 'Windows' }} uses: microsoft/setup-msbuild@70b70342ae97ca98d5eaad06cafd26d30f9592a9 @@ -833,11 +788,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - uses: actions/setup-dotnet@5d1464d5da459f3d7085106d52e499f4dc5d0f59 with: dotnet-version: 8.0.x @@ -891,11 +841,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Add msbuild to PATH if: ${{ runner.os == 'Windows' }} uses: microsoft/setup-msbuild@70b70342ae97ca98d5eaad06cafd26d30f9592a9 @@ -946,11 +891,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Add msbuild to PATH if: ${{ runner.os == 'Windows' }} uses: microsoft/setup-msbuild@70b70342ae97ca98d5eaad06cafd26d30f9592a9 @@ -1007,11 +947,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - uses: actions/setup-dotnet@5d1464d5da459f3d7085106d52e499f4dc5d0f59 with: dotnet-version: 8.0.x @@ -1071,11 +1006,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Publish Tests/Realm.Tests run: dotnet publish Tests/Realm.Tests -c Release -f net8.0 -r win-x64 -p:RestoreConfigFile=Tests/Test.NuGet.Config -p:UseRealmNupkgsWithVersion=${{ needs.build-packages.outputs.package_version }} -p:RealmTestsStandaloneExe=true -p:TestWeavedClasses=true --no-self-contained - name: Output executable path @@ -1217,7 +1147,7 @@ jobs: env: DOTNET_DbgEnableMiniDump: 1 DOTNET_EnableCrashReport: 1 - run: ./tools/coverlet ./Tests/Realm.Tests/bin/Release/net8.0/linux-x64 -t ${{ steps.dotnet-publish.outputs.executable-path }} -a '--result=TestResults.xml --labels=After' -f lcov -o ./report.lcov --exclude '[Realm.Tests]*' --exclude '[Realm.Fody]*' --exclude '[Realm.PlatformHelpers]*' + run: ./tools/coverlet ./Tests/Realm.Tests/bin/Release/net8.0/linux-x64 -t ${{ steps.dotnet-publish.outputs.executable-path }} -a '--result=TestResults.xml --labels=After' -f lcov -o ./report.lcov --exclude '[Realm.Tests]*' --exclude '[Realm.Fody]*' - name: Archive core dump if: ${{ failure() && runner.os != 'Windows' }} uses: actions/upload-artifact@v4 @@ -1262,11 +1192,6 @@ jobs: with: name: Realm.${{ needs.build-packages.outputs.package_version }} path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v4 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - name: Fetch Realm.UnityUtils uses: actions/download-artifact@v4 with: diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 1f55cf8e06..406cb68522 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -23,12 +23,6 @@ jobs: pkgVersion=$(grep "\bVERSION:" dependencies.yml | cut -d: -f2) echo "core-version=$pkgVersion" >> $GITHUB_OUTPUT shell: bash - - name: Update Analytics.cs - uses: jacobtomlinson/gha-find-replace@0dfd0777cc234ef6947ec1f20873c632114c4167 #! 0.1.4 - with: - find: 'CoreVersion = "\w*"' - replace: 'CoreVersion = "${{ steps.get-core-version.outputs.core-version }}"' - include: Realm/Realm.Weaver/Analytics/Analytics.cs - name: Update Changelog id: update-changelog uses: realm/ci-actions/update-changelog@6418e15ed9bbdb19b7d456a347e5623779f95cdf diff --git a/.github/workflows/publish-prerelease.yml b/.github/workflows/publish-prerelease.yml index 45019006a8..453b593488 100644 --- a/.github/workflows/publish-prerelease.yml +++ b/.github/workflows/publish-prerelease.yml @@ -5,7 +5,6 @@ name: Publish Prerelease 'on': workflow_dispatch: {} env: - REALM_DISABLE_ANALYTICS: true DOTNET_NOLOGO: true jobs: main: @@ -44,8 +43,6 @@ jobs: aws-region: us-east-1 - name: NuGet Publish Realm.${{ steps.get-version.outputs.package_version }} run: sleet push ${{ github.workspace }}/Realm/packages/Realm.${{ steps.get-version.outputs.package_version }}/Realm.${{ steps.get-version.outputs.package_version }}.nupkg --config ${{ github.workspace }}/.github/sleet.json --source NugetSource - - name: NuGet Publish Realm.PlatformHelpers.${{ steps.get-version.outputs.package_version }} - run: sleet push ${{ github.workspace }}/Realm/packages/Realm.PlatformHelpers.${{ steps.get-version.outputs.package_version }}/Realm.PlatformHelpers.${{ steps.get-version.outputs.package_version }}.nupkg --config ${{ github.workspace }}/.github/sleet.json --source NugetSource - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 with: node-version: 16.x diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index ede03abc3c..c9dd590ca2 100755 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -5,7 +5,6 @@ name: Publish Release 'on': workflow_dispatch: {} env: - REALM_DISABLE_ANALYTICS: true DOTNET_NOLOGO: true jobs: main: @@ -40,8 +39,6 @@ jobs: aws-region: us-east-2 - name: NuGet Publish Realm.${{ steps.get-version.outputs.package_version }} run: dotnet nuget push ${{ github.workspace }}/Realm/packages/Realm.${{ steps.get-version.outputs.package_version }}/Realm.${{ steps.get-version.outputs.package_version }}.nupkg --skip-duplicate --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json - - name: NuGet Publish Realm.PlatformHelpers.${{ steps.get-version.outputs.package_version }} - run: dotnet nuget push ${{ github.workspace }}/Realm/packages/Realm.PlatformHelpers.${{ steps.get-version.outputs.package_version }}/Realm.PlatformHelpers.${{ steps.get-version.outputs.package_version }}.nupkg --skip-duplicate --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 with: node-version: 16.x diff --git a/.github/workflows/wrappers.yml b/.github/workflows/wrappers.yml index 3274f7d0c1..d110c3f09e 100755 --- a/.github/workflows/wrappers.yml +++ b/.github/workflows/wrappers.yml @@ -5,7 +5,6 @@ name: wrappers 'on': workflow_call: {} env: - REALM_DISABLE_ANALYTICS: true DOTNET_NOLOGO: true jobs: check-cache: diff --git a/Realm - Windows.sln b/Realm - Windows.sln index 4687c9a975..f76554167e 100644 --- a/Realm - Windows.sln +++ b/Realm - Windows.sln @@ -87,10 +87,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGeneratorAssemblyToPr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Maui", "Tests\Tests.Maui\Tests.Maui.csproj", "{C84EBA8B-5F7F-4519-BB34-EDE93E275D66}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnalyticsAssembly", "Tests\Weaver\AnalyticsAssembly\AnalyticsAssembly.csproj", "{1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Realm.PlatformHelpers", "Realm\Realm.PlatformHelpers\Realm.PlatformHelpers.csproj", "{536C3309-F848-4485-ABF3-56DCD9C9F9E8}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.XamarinTVOS", "Tests\Tests.XamarinTVOS\Tests.XamarinTVOS.csproj", "{80B9697D-0C57-40E8-A71A-F5E81C7BF467}" EndProject Global @@ -1331,102 +1327,6 @@ Global {C84EBA8B-5F7F-4519-BB34-EDE93E275D66}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU {C84EBA8B-5F7F-4519-BB34-EDE93E275D66}.RelWithDebInfo|x86.Build.0 = Release|Any CPU {C84EBA8B-5F7F-4519-BB34-EDE93E275D66}.RelWithDebInfo|x86.Deploy.0 = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|ARM.ActiveCfg = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|ARM.Build.0 = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|iPhone.Build.0 = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|x64.ActiveCfg = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|x64.Build.0 = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|x86.ActiveCfg = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|x86.Build.0 = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|ARM.ActiveCfg = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|ARM.Build.0 = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|iPhone.ActiveCfg = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|iPhone.Build.0 = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|iPhoneSimulator.Build.0 = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|x64.Build.0 = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|x86.Build.0 = Debug|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|Any CPU.Build.0 = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|ARM.ActiveCfg = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|ARM.Build.0 = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|iPhone.ActiveCfg = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|iPhone.Build.0 = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|x64.ActiveCfg = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|x64.Build.0 = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|x86.ActiveCfg = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|x86.Build.0 = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|iPhone.ActiveCfg = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|iPhone.Build.0 = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|iPhoneSimulator.ActiveCfg = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|iPhoneSimulator.Build.0 = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|x64.Build.0 = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|x86.Build.0 = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|ARM.ActiveCfg = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|ARM.Build.0 = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|iPhone.Build.0 = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|x64.ActiveCfg = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|x64.Build.0 = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|x86.ActiveCfg = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Debug|x86.Build.0 = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.MinSizeRel|ARM.ActiveCfg = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.MinSizeRel|ARM.Build.0 = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.MinSizeRel|iPhone.ActiveCfg = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.MinSizeRel|iPhone.Build.0 = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.MinSizeRel|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.MinSizeRel|iPhoneSimulator.Build.0 = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.MinSizeRel|x64.Build.0 = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.MinSizeRel|x86.Build.0 = Debug|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Release|Any CPU.Build.0 = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Release|ARM.ActiveCfg = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Release|ARM.Build.0 = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Release|iPhone.ActiveCfg = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Release|iPhone.Build.0 = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Release|x64.ActiveCfg = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Release|x64.Build.0 = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Release|x86.ActiveCfg = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.Release|x86.Build.0 = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.RelWithDebInfo|iPhone.ActiveCfg = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.RelWithDebInfo|iPhone.Build.0 = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.RelWithDebInfo|iPhoneSimulator.ActiveCfg = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.RelWithDebInfo|iPhoneSimulator.Build.0 = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.RelWithDebInfo|x64.Build.0 = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU - {536C3309-F848-4485-ABF3-56DCD9C9F9E8}.RelWithDebInfo|x86.Build.0 = Release|Any CPU {80B9697D-0C57-40E8-A71A-F5E81C7BF467}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator {80B9697D-0C57-40E8-A71A-F5E81C7BF467}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator {80B9697D-0C57-40E8-A71A-F5E81C7BF467}.Debug|ARM.ActiveCfg = Debug|iPhoneSimulator @@ -1507,8 +1407,6 @@ Global {BCC2A759-231C-405C-BE9C-0C473365B232} = {EC97E75C-3A79-4B00-95BD-218D71C58746} {3C3CEB09-94C5-4FE4-BF75-1CEF4EAF6E47} = {EC97E75C-3A79-4B00-95BD-218D71C58746} {C84EBA8B-5F7F-4519-BB34-EDE93E275D66} = {D10BE048-9C20-4B8B-BE5B-48CC55F8BB07} - {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5} = {4FF9AAE6-210E-41F0-A5E1-8D7C4A864F51} - {536C3309-F848-4485-ABF3-56DCD9C9F9E8} = {50F058DF-2B41-403C-BB73-8B4180D1CF39} {80B9697D-0C57-40E8-A71A-F5E81C7BF467} = {D10BE048-9C20-4B8B-BE5B-48CC55F8BB07} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/Realm.sln b/Realm.sln index 8a089d30e6..ce90ce398e 100644 --- a/Realm.sln +++ b/Realm.sln @@ -81,10 +81,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.UWP", "Tests\Tests.UW EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SetupUnityPackage", "Tools\SetupUnityPackage\SetupUnityPackage.csproj", "{A9B5E8CA-E1B8-47E4-89D4-8A55327F4121}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Realm.PlatformHelpers", "Realm\Realm.PlatformHelpers\Realm.PlatformHelpers.csproj", "{D08C71CE-0F5B-4855-A77C-58062A5ECB78}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnalyticsAssembly", "Tests\Weaver\AnalyticsAssembly\AnalyticsAssembly.csproj", "{1E392D99-D783-4122-8B31-A4CA19ABADEE}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -677,54 +673,6 @@ Global {A9B5E8CA-E1B8-47E4-89D4-8A55327F4121}.Release|x64.Build.0 = Release|Any CPU {A9B5E8CA-E1B8-47E4-89D4-8A55327F4121}.Release|x86.ActiveCfg = Release|Any CPU {A9B5E8CA-E1B8-47E4-89D4-8A55327F4121}.Release|x86.Build.0 = Release|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Debug|ARM.ActiveCfg = Debug|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Debug|ARM.Build.0 = Debug|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Debug|iPhone.Build.0 = Debug|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Debug|x64.ActiveCfg = Debug|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Debug|x64.Build.0 = Debug|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Debug|x86.ActiveCfg = Debug|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Debug|x86.Build.0 = Debug|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|Any CPU.Build.0 = Release|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|ARM.ActiveCfg = Release|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|ARM.Build.0 = Release|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|iPhone.ActiveCfg = Release|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|iPhone.Build.0 = Release|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|x64.ActiveCfg = Release|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|x64.Build.0 = Release|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|x86.ActiveCfg = Release|Any CPU - {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|x86.Build.0 = Release|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|ARM.ActiveCfg = Debug|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|ARM.Build.0 = Debug|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|iPhone.Build.0 = Debug|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|x64.ActiveCfg = Debug|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|x64.Build.0 = Debug|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|x86.ActiveCfg = Debug|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|x86.Build.0 = Debug|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|Any CPU.Build.0 = Release|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|ARM.ActiveCfg = Release|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|ARM.Build.0 = Release|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|iPhone.ActiveCfg = Release|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|iPhone.Build.0 = Release|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|x64.ActiveCfg = Release|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|x64.Build.0 = Release|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|x86.ActiveCfg = Release|Any CPU - {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -758,8 +706,6 @@ Global {641367F9-5149-4E5D-9459-B19DAFC426A8} = {CC933D98-B002-4306-89C4-B1905658D9DE} {7EFF9E5C-5E74-469B-8DB7-C25C9AF0444E} = {D10BE048-9C20-4B8B-BE5B-48CC55F8BB07} {A9B5E8CA-E1B8-47E4-89D4-8A55327F4121} = {A25317DE-BB3A-47CC-8E65-F96C9B6AD984} - {D08C71CE-0F5B-4855-A77C-58062A5ECB78} = {50F058DF-2B41-403C-BB73-8B4180D1CF39} - {1E392D99-D783-4122-8B31-A4CA19ABADEE} = {4FF9AAE6-210E-41F0-A5E1-8D7C4A864F51} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BE5E0028-B74D-4BE1-B1DA-5FFCC8469C41} diff --git a/Realm/Realm.Fody/ModuleWeaver.cs b/Realm/Realm.Fody/ModuleWeaver.cs index 277dafbaa8..ac1d11a8e2 100644 --- a/Realm/Realm.Fody/ModuleWeaver.cs +++ b/Realm/Realm.Fody/ModuleWeaver.cs @@ -16,13 +16,11 @@ // //////////////////////////////////////////////////////////////////////////// -using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Versioning; using Mono.Cecil.Cil; using RealmWeaver; -using static RealmWeaver.Analytics; // ReSharper disable once CheckNamespace public class ModuleWeaver : Fody.BaseModuleWeaver, ILogger @@ -41,7 +39,7 @@ public override void Execute() var weaver = new Weaver(ModuleDefinition, this, frameworkName.Identifier); - var executionResult = weaver.Execute(GetAnalyticsConfig(frameworkName)); + var executionResult = weaver.Execute(); WriteInfo(executionResult.ToString()); } @@ -57,46 +55,6 @@ public override IEnumerable GetAssembliesForScanning() yield return "System.Threading"; } - private Config GetAnalyticsConfig(FrameworkName netFramework) - { - AnalyticsCollection analyticsCollection; - if (Enum.TryParse(Config.Attribute("AnalyticsCollection")?.Value, out var collection)) - { - analyticsCollection = collection; - } - else if (bool.TryParse(Config.Attribute("DisableAnalytics")?.Value, out var disableAnalytics)) - { - analyticsCollection = disableAnalytics ? AnalyticsCollection.Disabled : AnalyticsCollection.Full; - } - else if (Environment.GetEnvironmentVariable("REALM_DISABLE_ANALYTICS") != null || Environment.GetEnvironmentVariable("CI") != null) - { - analyticsCollection = AnalyticsCollection.Disabled; - } - else - { -#if DEBUG - analyticsCollection = AnalyticsCollection.DryRun; -#else - analyticsCollection = AnalyticsCollection.Full; -#endif - } - - var framework = AnalyticsUtils.GetFrameworkAndVersion(ModuleDefinition); - - return new( - targetOSName: AnalyticsUtils.GetTargetOsName(netFramework), - netFrameworkTarget: netFramework.Identifier, - netFrameworkTargetVersion: netFramework.Version.ToString(), - installationMethod: "Nuget", - frameworkName: framework.Name, - frameworkVersion: framework.Version, - compiler: "msbuild") - { - AnalyticsCollection = analyticsCollection, - AnalyticsLogPath = Config.Attribute("AnalyticsLogPath")?.Value, - }; - } - void ILogger.Debug(string message) { WriteDebug(message); diff --git a/Realm/Realm.Fody/Realm.Fody.xcf b/Realm/Realm.Fody/Realm.Fody.xcf index e147c2a8b4..4e21196052 100644 --- a/Realm/Realm.Fody/Realm.Fody.xcf +++ b/Realm/Realm.Fody/Realm.Fody.xcf @@ -1,47 +1,5 @@  - - - THIS IS DEPRECATED - USE `AnalyticsCollection` INSTEAD. Disables anonymized - usage information from being sent on build. Read more about what data is being collected and - why here: https://github.com/realm/realm-dotnet/blob/main/Realm/Realm.Weaver/Analytics.cs - - - - - Controls what anonymized usage information is being sent on build. Read more - about what data is being collected and why here: - https://github.com/realm/realm-dotnet/blob/main/Realm/Realm.Weaver/Analytics/Analytics.cs - - - - - - Analytics collection will run normally. This is the default behavior - and we hope you don't change it as the anonymized data collected is critical for - making the right decisions about the future of the Realm SDK. - - - - - Analytics collection will run but will not send it to the server. This - is useful in combination with `AnalyticsLogPath` if you want to review the data being - sent. - - - - - Analytics collection is disabled. No data will be sent on build. - - - - - - - - Controls where the payload for the anonymized metrics collection will be - stored. This can be useful if you want to review the data being collected by Realm. - - + \ No newline at end of file diff --git a/Realm/Realm.PlatformHelpers/Android/DeviceInfo.android.cs b/Realm/Realm.PlatformHelpers/Android/DeviceInfo.android.cs deleted file mode 100644 index 4c8749dc69..0000000000 --- a/Realm/Realm.PlatformHelpers/Android/DeviceInfo.android.cs +++ /dev/null @@ -1,31 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using Android.OS; - -using static Realms.PlatformHelpers.Platform; - -namespace Realms.PlatformHelpers -{ - internal class DeviceInfo : IDeviceInfo - { - public string Name => Build.Manufacturer ?? Unknown; - - public string Version => Build.Model ?? Unknown; - } -} diff --git a/Realm/Realm.PlatformHelpers/Apple/DeviceInfo.ios.cs b/Realm/Realm.PlatformHelpers/Apple/DeviceInfo.ios.cs deleted file mode 100644 index e972cdfbdb..0000000000 --- a/Realm/Realm.PlatformHelpers/Apple/DeviceInfo.ios.cs +++ /dev/null @@ -1,29 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using static Realms.PlatformHelpers.Platform; - -namespace Realms.PlatformHelpers -{ - internal class DeviceInfo : IDeviceInfo - { - public string Name => UIKit.UIDevice.CurrentDevice.Model; - - public string Version => NativeHelpers.GetSysctlProperty("hw.machine") ?? Unknown; - } -} diff --git a/Realm/Realm.PlatformHelpers/Apple/DeviceInfo.mac.cs b/Realm/Realm.PlatformHelpers/Apple/DeviceInfo.mac.cs deleted file mode 100644 index cd61225353..0000000000 --- a/Realm/Realm.PlatformHelpers/Apple/DeviceInfo.mac.cs +++ /dev/null @@ -1,29 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using static Realms.PlatformHelpers.Platform; - -namespace Realms.PlatformHelpers -{ - internal class DeviceInfo : IDeviceInfo - { - public string Name => "Apple"; - - public string Version => NativeHelpers.GetSysctlProperty("hw.model") ?? Unknown; - } -} diff --git a/Realm/Realm.PlatformHelpers/Apple/NativeHelpers.ios.mac.cs b/Realm/Realm.PlatformHelpers/Apple/NativeHelpers.ios.mac.cs deleted file mode 100644 index da44c819c3..0000000000 --- a/Realm/Realm.PlatformHelpers/Apple/NativeHelpers.ios.mac.cs +++ /dev/null @@ -1,65 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Runtime.InteropServices; -using ObjCRuntime; - -namespace Realms.PlatformHelpers -{ - internal static class NativeHelpers - { - [DllImport(Constants.SystemLibrary, EntryPoint = "sysctlbyname")] - internal static extern int SysctlByName([MarshalAs(UnmanagedType.LPStr)] string property, IntPtr output, IntPtr oldLen, IntPtr newp, uint newlen); - - internal static string? GetSysctlProperty(string property) - { - var lengthPtr = Marshal.AllocHGlobal(sizeof(int)); - - IntPtr? valuePtr = null; - try - { - SysctlByName(property, IntPtr.Zero, lengthPtr, IntPtr.Zero, 0); - - var propertyLength = Marshal.ReadInt32(lengthPtr); - - if (propertyLength > 0) - { - valuePtr = Marshal.AllocHGlobal(propertyLength); - SysctlByName(property, valuePtr.Value, lengthPtr, IntPtr.Zero, 0); - - return Marshal.PtrToStringAnsi(valuePtr.Value); - } - } - catch - { - } - finally - { - Marshal.FreeHGlobal(lengthPtr); - - if (valuePtr.HasValue) - { - Marshal.FreeHGlobal(valuePtr.Value); - } - } - - return null; - } - } -} diff --git a/Realm/Realm.PlatformHelpers/AssemblyInfo.shared.cs b/Realm/Realm.PlatformHelpers/AssemblyInfo.shared.cs deleted file mode 100644 index d73a6b33aa..0000000000 --- a/Realm/Realm.PlatformHelpers/AssemblyInfo.shared.cs +++ /dev/null @@ -1,23 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Realm")] -[assembly: InternalsVisibleTo("Realm.Tests")] -[assembly: InternalsVisibleTo("Realm.UnityUtils")] diff --git a/Realm/Realm.PlatformHelpers/DeviceInfo.shared.cs b/Realm/Realm.PlatformHelpers/DeviceInfo.shared.cs deleted file mode 100644 index 0080398e05..0000000000 --- a/Realm/Realm.PlatformHelpers/DeviceInfo.shared.cs +++ /dev/null @@ -1,27 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.PlatformHelpers -{ - internal interface IDeviceInfo - { - string Name { get; } - - string Version { get; } - } -} diff --git a/Realm/Realm.PlatformHelpers/Platform.shared.cs b/Realm/Realm.PlatformHelpers/Platform.shared.cs deleted file mode 100644 index 454b679b5d..0000000000 --- a/Realm/Realm.PlatformHelpers/Platform.shared.cs +++ /dev/null @@ -1,85 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Linq; -using System.Reflection; -using System.Security.Cryptography; -using System.Text; - -namespace Realms.PlatformHelpers -{ - internal static class Platform - { - public const string Unknown = ""; - - private static IDeviceInfo? _deviceInfo; - - private static readonly Lazy _deviceInfoLazy = new(() => _deviceInfo ?? new DeviceInfo()); - - public static IDeviceInfo DeviceInfo - { - get => _deviceInfoLazy.Value; - set - { - if (_deviceInfoLazy.IsValueCreated) - { - throw new Exception("DeviceInfo should only be configured once"); - } - - _deviceInfo = value; - } - } - - private static string? _bundleId; - public static string BundleId - { - get - { - var bundleId = _bundleId ?? Assembly.GetEntryAssembly()?.GetName().Name; - - if (bundleId == null) - { - // On Android, the entry assembly is null (there's no main() method), so we need to find - // the first assembly that has the ResourceDesignerAttribute with IsApplication = true. - var entryAssembly = AppDomain.CurrentDomain.GetAssemblies() - .FirstOrDefault(a => a.CustomAttributes.Any(att => - att.AttributeType.FullName == "Android.Runtime.ResourceDesignerAttribute" && - att.NamedArguments?.FirstOrDefault(arg => arg.MemberName == "IsApplication").TypedValue.Value is true)); - - bundleId = entryAssembly?.GetName().Name; - } - - return Sha256(bundleId); - } - set => _bundleId = value; - } - - internal static string Sha256(string? value) - { - if (value == null) - { - return Unknown; - } - - using var sha256 = SHA256.Create(); - var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(value)); - return Convert.ToBase64String(hash); - } - } -} diff --git a/Realm/Realm.PlatformHelpers/Realm.PlatformHelpers.csproj b/Realm/Realm.PlatformHelpers/Realm.PlatformHelpers.csproj deleted file mode 100644 index b04840911b..0000000000 --- a/Realm/Realm.PlatformHelpers/Realm.PlatformHelpers.csproj +++ /dev/null @@ -1,62 +0,0 @@ - - - - Realm Platform Helpers - - A set of platform-specific helpers used in conjunction with Realm. This package is always referenced by the main Realm package - and should never be added directly to your project. - - - netstandard2.0 - - - $(TargetFrameworks);net7.0-ios;net7.0-android;net7.0-maccatalyst;net7.0-tvos - - - - - $(TargetFrameworks);MonoAndroid5;Xamarin.iOS10;Xamarin.TVOS10;Xamarin.Mac20 - - $(TargetFrameworks);uap10.0.19041; - false - 9.0 - enable - Realms.PlatformHelpers - - NETSDK1202 - - - - - - - - - - - - - Windows Desktop Extensions for the UWP - - - Windows Mobile Extensions for the UWP - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Realm/Realm.PlatformHelpers/UWP/DeviceInfo.uwp.cs b/Realm/Realm.PlatformHelpers/UWP/DeviceInfo.uwp.cs deleted file mode 100644 index 17d78cb480..0000000000 --- a/Realm/Realm.PlatformHelpers/UWP/DeviceInfo.uwp.cs +++ /dev/null @@ -1,31 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using Windows.Security.ExchangeActiveSyncProvisioning; - -namespace Realms.PlatformHelpers -{ - internal class DeviceInfo : IDeviceInfo - { - private static EasClientDeviceInformation deviceInfo = new(); - - public string Name => deviceInfo.SystemManufacturer; - - public string Version => deviceInfo.SystemProductName; - } -} diff --git a/Realm/Realm.PlatformHelpers/fallback/DeviceInfo.netstandard.cs b/Realm/Realm.PlatformHelpers/fallback/DeviceInfo.netstandard.cs deleted file mode 100644 index 8b5a2e9b3d..0000000000 --- a/Realm/Realm.PlatformHelpers/fallback/DeviceInfo.netstandard.cs +++ /dev/null @@ -1,29 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using static Realms.PlatformHelpers.Platform; - -namespace Realms.PlatformHelpers -{ - internal class DeviceInfo : IDeviceInfo - { - public string Name => Unknown; - - public string Version => Unknown; - } -} diff --git a/Realm/Realm.SourceGenerator/Diagnostics.cs b/Realm/Realm.SourceGenerator/Diagnostics.cs index a3440068d0..a9af4c71da 100644 --- a/Realm/Realm.SourceGenerator/Diagnostics.cs +++ b/Realm/Realm.SourceGenerator/Diagnostics.cs @@ -92,7 +92,7 @@ public static Diagnostic ClassUnclearDefinition(string className, Location locat return CreateDiagnosticError( Id.ClassUnclearDefinition, "Realm classes cannot implement multiple class interfaces", - $"Class {className} is declared as implementing multiple class interfaces. A class can implement only one interface between IRealmObject, IEmbeddedObject, IAsymmetricObject.", + $"Class {className} is declared as implementing multiple class interfaces. A class can implement only one interface between IRealmObject, IEmbeddedObject.", location); } @@ -317,7 +317,7 @@ public static Diagnostic TypeNotSupported(string className, string propertyName, return CreateDiagnosticError( Id.TypeNotSupported, "Type not supported", - $"{className}.{propertyName} is of type '{propertyType}' which is not yet supported. If that is supposed to be a model class, make sure it implements IRealmObject/IEmbeddedObject/IAsymmetricObject.", + $"{className}.{propertyName} is of type '{propertyType}' which is not yet supported. If that is supposed to be a model class, make sure it implements IRealmObject/IEmbeddedObject.", location); } diff --git a/Realm/Realm.SourceGenerator/InfoClasses.cs b/Realm/Realm.SourceGenerator/InfoClasses.cs index c852de97d2..a67e4fd1c0 100644 --- a/Realm/Realm.SourceGenerator/InfoClasses.cs +++ b/Realm/Realm.SourceGenerator/InfoClasses.cs @@ -381,7 +381,6 @@ internal enum ObjectType None, RealmObject, EmbeddedObject, - AsymmetricObject } internal enum CollectionType diff --git a/Realm/Realm.SourceGenerator/Utils.cs b/Realm/Realm.SourceGenerator/Utils.cs index 7a0b6e4881..82e2e98519 100644 --- a/Realm/Realm.SourceGenerator/Utils.cs +++ b/Realm/Realm.SourceGenerator/Utils.cs @@ -107,10 +107,6 @@ public static IEnumerable ImplementingObjectTypes(this ITypeSymbol s { yield return ObjectType.EmbeddedObject; } - else if (IsIAsymmetricObjectInterface(i)) - { - yield return ObjectType.AsymmetricObject; - } } } @@ -120,14 +116,10 @@ public static IEnumerable ImplementingObjectTypes(this ITypeSymbol s public static bool IsEmbeddedObject(this ITypeSymbol symbol) => symbol.Interfaces.Any(IsIEmbeddedObjectInterface); - public static bool IsAsymmetricObject(this ITypeSymbol symbol) => symbol.Interfaces.Any(IsIAsymmetricObjectInterface); - private static bool IsIRealmObjectInterface(this INamedTypeSymbol interfaceSymbol) => interfaceSymbol.Name == "IRealmObject"; private static bool IsIEmbeddedObjectInterface(this INamedTypeSymbol interfaceSymbol) => interfaceSymbol.Name == "IEmbeddedObject"; - private static bool IsIAsymmetricObjectInterface(this INamedTypeSymbol interfaceSymbol) => interfaceSymbol.Name == "IAsymmetricObject"; - public static INamedTypeSymbol AsNamed(this ITypeSymbol symbol) { if (symbol is INamedTypeSymbol namedSymbol) diff --git a/Realm/Realm.UnityUtils/DeviceInfo.cs b/Realm/Realm.UnityUtils/DeviceInfo.cs deleted file mode 100644 index 50cf65e2cf..0000000000 --- a/Realm/Realm.UnityUtils/DeviceInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using Realms.PlatformHelpers; -using UnityEngine; - -namespace UnityUtils -{ - internal class UnityDeviceInfo : IDeviceInfo - { - public string Name => Platform.Unknown; - - public string Version => SystemInfo.deviceModel; - } -} diff --git a/Realm/Realm.UnityUtils/Initializer.cs b/Realm/Realm.UnityUtils/Initializer.cs index f8dd575389..b77c2e8abd 100644 --- a/Realm/Realm.UnityUtils/Initializer.cs +++ b/Realm/Realm.UnityUtils/Initializer.cs @@ -19,7 +19,6 @@ using System.Reflection; using System.Threading; using Realms; -using Realms.PlatformHelpers; using UnityEngine; namespace UnityUtils @@ -34,8 +33,6 @@ public static void Initialize() { if (Interlocked.CompareExchange(ref _isInitialized, 1, 0) == 0) { - Platform.DeviceInfo = new UnityDeviceInfo(); - Platform.BundleId = Application.productName; InteropConfig.AddPotentialStorageFolder(FileHelper.GetStorageFolder()); Realms.Logging.RealmLogger.Console = new UnityLogger(); Application.quitting += () => diff --git a/Realm/Realm.UnityWeaver/UnityWeaver.cs b/Realm/Realm.UnityWeaver/UnityWeaver.cs index e59064caa1..f56e9d7fa8 100644 --- a/Realm/Realm.UnityWeaver/UnityWeaver.cs +++ b/Realm/Realm.UnityWeaver/UnityWeaver.cs @@ -28,10 +28,7 @@ using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEditor.Compilation; -using UnityEditor.PackageManager; -using UnityEditor.PackageManager.Requests; using UnityEngine; -using static RealmWeaver.Analytics; using Assembly = UnityEditor.Compilation.Assembly; // ReSharper disable once CheckNamespace @@ -40,31 +37,12 @@ namespace RealmWeaver // Heavily influenced by https://github.com/ExtendRealityLtd/Malimbe and https://github.com/fody/fody public class UnityWeaver : IPostBuildPlayerScriptDLLs, IPreprocessBuildWithReport, IPostprocessBuildWithReport { - private const string EnableAnalyticsPref = "realm_enable_analytics"; - private const string EnableAnalyticsMenuItemPath = "Tools/Realm/Enable build-time analytics"; - private const string WeaveEditorAssembliesPref = "realm_weave_editor_assemblies"; private const string WeaveEditorAssembliesMenuItemPath = "Tools/Realm/Process editor assemblies"; - private const string UnityPackageName = "io.realm.unity"; private static readonly int UnityMajorVersion = int.Parse(Application.unityVersion.Split('.')[0]); - private static readonly TaskCompletionSource _installMethodTask = new(); - - private static bool _analyticsEnabled; - - private static bool AnalyticsEnabled - { - get => _analyticsEnabled; - set - { - _analyticsEnabled = value; - EditorPrefs.SetBool(EnableAnalyticsPref, value); - Menu.SetChecked(EnableAnalyticsMenuItemPath, value); - } - } private static bool _weaveEditorAssemblies; - private static ListRequest? _listRequest; private static bool WeaveEditorAssemblies { @@ -82,24 +60,9 @@ private static bool WeaveEditorAssemblies [InitializeOnLoadMethod] public static void Initialize() { - if (Application.isBatchMode) - { - // In batch mode, `update` won't get called until compilation is complete, - // which means we'll deadlock when we block compilation on the tcs completing - _installMethodTask.TrySetResult(Metric.Unknown()); - } - // We need to call that again after the editor is initialized to ensure that we populate the checkmark correctly. EditorApplication.delayCall += () => { - _listRequest = Client.List(); - - if (!Application.isBatchMode) - { - EditorApplication.update += OnEditorApplicationUpdate; - } - - AnalyticsEnabled = EditorPrefs.GetBool(EnableAnalyticsPref, defaultValue: true); WeaveEditorAssemblies = EditorPrefs.GetBool(WeaveEditorAssembliesPref, defaultValue: false); WeaverAssemblyResolver.ApplicationDataPath = Application.dataPath; WeaveAssembliesOnEditorLaunch(); @@ -119,36 +82,10 @@ public static void Initialize() return; } - var config = GetAnalyticsConfig(); - WeaveAssemblyCore(assemblyPath, assembly.allReferences, config); + WeaveAssemblyCore(assemblyPath, assembly.allReferences); }; } - private static void OnEditorApplicationUpdate() - { - if (_listRequest?.IsCompleted != true) - { - return; - } - - EditorApplication.update -= OnEditorApplicationUpdate; - - var installMethod = Metric.Unknown(); - - if (_listRequest.Status == StatusCode.Success) - { - var realmPackage = _listRequest.Result.FirstOrDefault(p => p.name == UnityPackageName); - installMethod = realmPackage?.source switch - { - PackageSource.LocalTarball => "Manual", - PackageSource.Registry => "NPM", - _ => Metric.Unknown(realmPackage?.source.ToString()), - }; - } - - _installMethodTask.TrySetResult(installMethod); - } - [MenuItem("Tools/Realm/Weave Assemblies")] public static async void WeaveAllAssembliesMenuItem() { @@ -156,12 +93,6 @@ public static async void WeaveAllAssembliesMenuItem() UnityLogger.Instance.Info($"Weaving completed. {assembliesWoven} assemblies needed weaving."); } - [MenuItem(EnableAnalyticsMenuItemPath)] - public static void DisableAnalyticsMenuItem() - { - AnalyticsEnabled = !AnalyticsEnabled; - } - [MenuItem(WeaveEditorAssembliesMenuItemPath)] public static void WeaveEditorAssembliesMenuItem() { @@ -199,13 +130,11 @@ private static async Task WeaveAllAssemblies() var assembliesToWeave = GetAssemblies(); var weaveResults = new List(); - var config = GetAnalyticsConfig(); - await Task.Run(() => { foreach (var assembly in assembliesToWeave) { - if (!WeaveAssemblyCore(assembly.outputPath, assembly.allReferences, config)) + if (!WeaveAssemblyCore(assembly.outputPath, assembly.allReferences)) { continue; } @@ -242,7 +171,7 @@ await Task.Run(() => return assembliesWoven; } - private static bool WeaveAssemblyCore(string assemblyPath, IEnumerable references, Config analyticsConfig) + private static bool WeaveAssemblyCore(string assemblyPath, IEnumerable references) { var name = Path.GetFileNameWithoutExtension(assemblyPath); @@ -263,7 +192,7 @@ private static bool WeaveAssemblyCore(string assemblyPath, IEnumerable r // using Mono, so we just hardcode Unity which is treated as Mono/.NET Framework by the weaver. var weaver = new Weaver(resolutionResult.Module, UnityLogger.Instance, "Unity"); - var results = weaver.Execute(analyticsConfig); + var results = weaver.Execute(); if (results.ErrorMessage != null) { @@ -305,11 +234,10 @@ public void OnPostBuildPlayerScriptDLLs(BuildReport report) .ToArray(); var assembliesToWeave = files.Where(f => f.role == "ManagedLibrary"); - var config = GetAnalyticsConfig(report.summary.platform); foreach (var file in assembliesToWeave) { - WeaveAssemblyCore(file.path, referencePaths, config); + WeaveAssemblyCore(file.path, referencePaths); } if (report.summary.platform != BuildTarget.iOS && report.summary.platform != BuildTarget.tvOS) @@ -415,53 +343,6 @@ private static Assembly[] GetAssemblies() return CompilationPipeline.GetAssemblies(AssembliesType.Player); } - private static string GetTargetOSName(BuildTarget? target) - { - // target is null for editor builds - in that case, we return the current OS - // as target. - if (target == null) - { - return Application.platform switch - { - RuntimePlatform.WindowsEditor => Metric.OperatingSystem.Windows, - RuntimePlatform.OSXEditor => Metric.OperatingSystem.MacOS, - RuntimePlatform.LinuxEditor => Metric.OperatingSystem.Linux, - _ => Metric.Unknown(Application.platform.ToString()), - }; - } - - // These have to match Analytics.GetConfig(FrameworkName) - return target switch - { - BuildTarget.StandaloneOSX => Metric.OperatingSystem.MacOS, - BuildTarget.StandaloneWindows or BuildTarget.StandaloneWindows64 or BuildTarget.WSAPlayer => Metric.OperatingSystem.Windows, - BuildTarget.iOS => Metric.OperatingSystem.Ios, - BuildTarget.Android => Metric.OperatingSystem.Android, - BuildTarget.StandaloneLinux64 => Metric.OperatingSystem.Linux, - BuildTarget.tvOS => Metric.OperatingSystem.TvOs, - BuildTarget.XboxOne => Metric.OperatingSystem.XboxOne, - _ => Metric.Unknown(target.ToString()), - }; - } - - private static string GetTargetOsVersion(BuildTarget? target) - { - // target is null for editor builds - in that case, we return the host OS - // version. - if (target == null) - { - return Environment.OSVersion.Version.ToString(); - } - - return target switch - { - BuildTarget.Android => ((int)PlayerSettings.Android.targetSdkVersion).ToString(), - BuildTarget.iOS => PlayerSettings.iOS.targetOSVersionString, - BuildTarget.tvOS => PlayerSettings.tvOS.targetOSVersionString, - _ => Metric.Unknown(), - }; - } - private static BuildFile[] GetFiles(BuildReport report) { try @@ -484,130 +365,6 @@ private static BuildFile[] GetFiles(BuildReport report) } } - private static string GetMinimumOsVersion(BuildTarget? target) - { - // target is null for editor builds - in that case, we return the host OS - // version. - if (target == null) - { - return Environment.OSVersion.Version.ToString(); - } - - return target switch - { - BuildTarget.Android => ((int)PlayerSettings.Android.minSdkVersion).ToString(), - BuildTarget.iOS => PlayerSettings.iOS.targetOSVersionString, - BuildTarget.tvOS => PlayerSettings.tvOS.targetOSVersionString, - _ => Metric.Unknown(), - }; - } - - private static Config GetAnalyticsConfig(BuildTarget? target = null) - { - var netFrameworkInfo = GetNetFrameworkInfo(target); - var compiler = PlayerSettings.GetScriptingBackend(BuildPipeline.GetBuildTargetGroup(target ?? EditorUserBuildSettings.activeBuildTarget)).ToString(); - - var analyticsEnabled = AnalyticsEnabled && - Environment.GetEnvironmentVariable("REALM_DISABLE_ANALYTICS") == null && - Environment.GetEnvironmentVariable("CI") == null; - - return new(targetOSName: GetTargetOSName(target), - compiler: compiler, - netFrameworkTarget: netFrameworkInfo.Name, - netFrameworkTargetVersion: netFrameworkInfo.Version, - installationMethod: _installMethodTask.Task.Wait(1000) ? _installMethodTask.Task.Result : Metric.Unknown(), - frameworkName: target == null ? Metric.Framework.UnityEditor : Metric.Framework.Unity, - frameworkVersion: Application.unityVersion) - { - AnalyticsCollection = analyticsEnabled ? AnalyticsCollection.Full : AnalyticsCollection.Disabled, - TargetArchitecture = GetCpuArchitecture(target), - TargetOsVersion = GetTargetOsVersion(target), - TargetOsMinimumVersion = GetMinimumOsVersion(target), - ProjectId = PlayerSettings.productName, - }; - } - - private static (string Name, string Version) GetNetFrameworkInfo(BuildTarget? buildTarget) - { - var targetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget ?? EditorUserBuildSettings.activeBuildTarget); - var apiTarget = PlayerSettings.GetApiCompatibilityLevel(targetGroup); - - // these consts are exactly mapped to what .NET reports in any .NET application - const string netStandardApi = ".NETStandard"; - const string netFrameworkApi = ".NETFramework"; - - var unityVersion = new Version(Application.unityVersion.Substring(0, 6)); - - // conversion necessary as after unity version 2021.1, entry NET_4_6 and NET_Standard_2_0 - // are actually representing .NET 4.8 and .NET Standard 2.1 - // https://github.com/Unity-Technologies/UnityCsReference/blob/664dfe30cee8ee2ef7dd8c5e9db6235915245ecb/Editor/Mono/PlayerSettings.bindings.cs#L158 - if (unityVersion >= new Version("2021.2")) - { - if (apiTarget == ApiCompatibilityLevel.NET_Standard_2_0) - { - return (netStandardApi, "2.1"); - } - - if (apiTarget == ApiCompatibilityLevel.NET_4_6) - { - return (netFrameworkApi, "4.8"); - } - } - - if (apiTarget == ApiCompatibilityLevel.NET_Standard_2_0) - { - return (netStandardApi, "2.0"); - } - - if (apiTarget == ApiCompatibilityLevel.NET_4_6) - { - return (netFrameworkApi, "4.6"); - } - - // this should really never be the case - return (apiTarget.ToString(), ""); - } - - private static string GetCpuArchitecture(BuildTarget? buildTarget) - { - // buildTarget is null when we're building for the editor - if (buildTarget == null) - { - if (SystemInfo.processorType.IndexOf("ARM", StringComparison.OrdinalIgnoreCase) > -1) - { - return Environment.Is64BitProcess ? Metric.CpuArchitecture.Arm64 : Metric.CpuArchitecture.Arm; - } - - // Must be in the x86 family. - return Environment.Is64BitProcess ? Metric.CpuArchitecture.X64 : Metric.CpuArchitecture.X86; - } - - return buildTarget switch - { - BuildTarget.iOS or BuildTarget.tvOS => Metric.CpuArchitecture.Arm64, - BuildTarget.StandaloneOSX => EditorUserBuildSettings.GetPlatformSettings(BuildPipeline.GetBuildTargetName(buildTarget.Value), "Architecture") switch - { - "ARM64" => Metric.CpuArchitecture.Arm64, - "x64" => Metric.CpuArchitecture.X64, - _ => Metric.CpuArchitecture.Universal, - }, - BuildTarget.StandaloneWindows => Metric.CpuArchitecture.X86, - BuildTarget.Android => PlayerSettings.Android.targetArchitectures switch - { - AndroidArchitecture.ARMv7 => Metric.CpuArchitecture.Arm, - AndroidArchitecture.ARM64 => Metric.CpuArchitecture.Arm64, - - // These two don't have enum values in our Unity reference dll, but exist in newer versions - // See https://github.com/Unity-Technologies/UnityCsReference/blob/70abf502c521c169ee8a302aa48c5600fc7c39fc/Editor/Mono/PlayerSettingsAndroid.bindings.cs#L14 - (AndroidArchitecture)(1 << 2) => Metric.CpuArchitecture.X86, - (AndroidArchitecture)(1 << 3) => Metric.CpuArchitecture.X64, - _ => Metric.CpuArchitecture.Universal, - }, - BuildTarget.StandaloneWindows64 or BuildTarget.StandaloneLinux64 or BuildTarget.XboxOne => Metric.CpuArchitecture.X64, - _ => Metric.Unknown(), - }; - } - private class UnityLogger : ILogger { public static UnityLogger Instance { get; } = new UnityLogger(); diff --git a/Realm/Realm.Weaver/Analytics/Analytics.cs b/Realm/Realm.Weaver/Analytics/Analytics.cs deleted file mode 100644 index 0a83896d66..0000000000 --- a/Realm/Realm.Weaver/Analytics/Analytics.cs +++ /dev/null @@ -1,589 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Mono.Cecil; -using Mono.Cecil.Cil; -using static RealmWeaver.AnalyticsUtils; - -using Feature = RealmWeaver.Metric.Feature; -using UserEnvironment = RealmWeaver.Metric.Environment; - -namespace RealmWeaver -{ - // Asynchronously submits build information to Realm when the assembly weaver - // is running - // - // To be clear: this does *not* run when your app is in production or on - // your end-user's devices; it will only run when you build your app from source. - // - // Why are we doing this? Because it helps us build a better product for you. - // None of the data personally identifies you, your employer or your app, but it - // *will* help us understand what Realm version you use, what host OS you use, - // etc. Having this info will help with prioritizing our time, adding new - // features and deprecating old ones. Collecting an anonymized assembly name & - // anonymized MAC is the only way for us to count actual usage of the other - // metrics accurately. If we don't have a way to deduplicate the info reported, - // it will be useless, as a single developer building their app on Windows ten - // times would report 10 times more than a single developer that only builds - // once from Mac OS X, making the data all but useless. No one likes sharing - // data unless it's necessary, we get it, and we've debated adding this for a - // long long time. Since Realm is a free product without an email sign-up, we - // feel this is a necessary step so we can collect relevant data to build a - // better product for you. - // - // Currently the following information is reported: - // - What OS and CPU architecture you are running on - // - What OS and CPU architecture you are building for - // - What version of the Realm SDK you're using - // - What framework and what framework version Realm is being used with (e.g. Xamarin, MAUI, etc.) - // - How the Realm SDK was installed (e.g. Nuget, manual, etc.) - // - What APIs of the Realm SDK you're using - // - An anonymized identifier and assembly name ID to aggregate the other information on. - internal class Analytics - { - // The value of this field is modified by CI in the "prepare-release" action, so do not change its name. - private const string CoreVersion = "13.15.0"; - - private readonly ImportedReferences _references; - private readonly ILogger _logger; - - private readonly Dictionary _realmEnvMetrics = new(); - private readonly Dictionary _realmFeaturesToAnalyze; - - private readonly Dictionary> _apiAnalysisSetters; - - private readonly Dictionary> _classAnalysisSetters; - - private readonly Config _config; - - private readonly Task _analyzeUserAssemblyTask; - - public Analytics(Config config, ImportedReferences references, ILogger logger, ModuleDefinition module) - { - _config = config; - _references = references; - _logger = logger; - - if (config.AnalyticsCollection == AnalyticsCollection.Disabled) - { - _realmFeaturesToAnalyze = new(); - _classAnalysisSetters = new(); - _apiAnalysisSetters = new(); - _analyzeUserAssemblyTask = Task.CompletedTask; - return; - } - - _realmFeaturesToAnalyze = Metric.SdkFeatures.Keys.ToDictionary(c => c, _ => (byte)0); - - _classAnalysisSetters = new() - { - ["Class"] = member => - member is PropertyDefinition property && property.PropertyType.IsAnyRealmObject(_references) ? - new(true, Feature.RealmObjectReference) : default, - [Feature.RealmValue] = _ => new(true, Feature.RealmValue), - ["IList`1"] = member => AnalyzeCollectionProperty(member, Feature.PrimitiveList, Feature.ReferenceList), - ["IDictionary`2"] = member => AnalyzeCollectionProperty(member, Feature.PrimitiveDictionary, Feature.ReferenceDictionary, 1), - ["ISet`1"] = member => AnalyzeCollectionProperty(member, Feature.PrimitiveSet, Feature.ReferenceSet), - ["RealmInteger`1"] = _ => new(true, Feature.RealmInteger), - [Feature.BacklinkAttribute] = _ => new(true, Feature.BacklinkAttribute) - }; - - _apiAnalysisSetters = new() - { - [Feature.GetInstanceAsync] = instruction => AnalyzeRealmApi(instruction, Feature.GetInstanceAsync), - [Feature.GetInstance] = instruction => AnalyzeRealmApi(instruction, Feature.GetInstance), - [Feature.Find] = instruction => AnalyzeRealmApi(instruction, Feature.Find), - [Feature.WriteAsync] = instruction => AnalyzeRealmApi(instruction, Feature.WriteAsync), - [Feature.ThreadSafeReference] = instruction => AnalyzeRealmApi(instruction, Feature.ThreadSafeReference), - - // check if it's the right signature, that is 2 params in total of which - // the second a bool and that it's set to true. - [Feature.Add] = instruction => - IsInRealmNamespace(instruction?.Operand) && - instruction?.Operand is MethodSpecification methodSpecification && - methodSpecification.Parameters.Count == 2 && - methodSpecification.Parameters[1].ParameterType.MetadataType == MetadataType.Boolean && - instruction.Previous?.OpCode == OpCodes.Ldc_I4_1 - ? new(true, Feature.Add) : default, - [Feature.ShouldCompactOnLaunch] = _ => new(true, Feature.ShouldCompactOnLaunch), - [Feature.MigrationCallback] = _ => new(true, Feature.MigrationCallback), - [Feature.RealmChanged] = _ => new(true, Feature.RealmChanged), - ["SubscribeForNotifications"] = instruction => - { - if (instruction?.Operand is not MethodSpecification methodSpecification || !IsInRealmNamespace(instruction?.Operand)) - { - return default; - } - - var collectionType = ((TypeSpecification)methodSpecification.Parameters[0].ParameterType).Name; - var key = collectionType switch - { - "IQueryable`1" or "IOrderedQueryable`1" => Feature.ResultSubscribeForNotifications, - "IList`1" => Feature.ListSubscribeForNotifications, - "ISet`1" => Feature.SetSubscribeForNotifications, - "IDictionary`2" => Feature.DictionarySubscribeForNotifications, - _ => $"{collectionType} unknown collection" - }; - - var shouldDelete = ContainsAllRelatedFeatures(key, - Feature.ResultSubscribeForNotifications, - Feature.ListSubscribeForNotifications, - Feature.SetSubscribeForNotifications, - Feature.DictionarySubscribeForNotifications); - - return new(shouldDelete, key); - }, - ["PropertyChanged"] = instruction => - { - string? key = null; - if (instruction.Operand is MemberReference reference) - { - if (reference.DeclaringType.IsAnyRealmObject(_references)) - { - key = Feature.ObjectNotification; - } - } - - if (key == null) - { - return default; - } - - var shouldDelete = ContainsAllRelatedFeatures(key, Feature.ObjectNotification, Feature.ConnectionNotification); - return new(shouldDelete, key); - }, - [Feature.RecoverOrDiscardUnsyncedChangesHandler] = _ => new(true, Feature.RecoverOrDiscardUnsyncedChangesHandler), - [Feature.RecoverUnsyncedChangesHandler] = _ => new(true, Feature.RecoverUnsyncedChangesHandler), - [Feature.DiscardUnsyncedChangesHandler] = _ => new(true, Feature.DiscardUnsyncedChangesHandler), - [Feature.ManualRecoveryHandler] = _ => new(true, Feature.ManualRecoveryHandler), - [Feature.GetProgressObservable] = _ => new(true, Feature.GetProgressObservable), - [Feature.PartitionSyncConfiguration] = _ => new(true, Feature.PartitionSyncConfiguration), - [Feature.FlexibleSyncConfiguration] = _ => new(true, Feature.FlexibleSyncConfiguration), - [Feature.Anonymous] = instruction => AnalyzeRealmApi(instruction, Feature.Anonymous), - [Feature.EmailPassword] = instruction => AnalyzeRealmApi(instruction, Feature.EmailPassword), - [Feature.Facebook] = instruction => AnalyzeRealmApi(instruction, Feature.Facebook), - [Feature.Google] = instruction => AnalyzeRealmApi(instruction, Feature.Google), - [Feature.Apple] = instruction => AnalyzeRealmApi(instruction, Feature.Apple), - [Feature.JWT] = instruction => AnalyzeRealmApi(instruction, Feature.JWT), - [Feature.ApiKey] = instruction => AnalyzeRealmApi(instruction, Feature.ApiKey), - [Feature.Function] = instruction => AnalyzeRealmApi(instruction, Feature.Function), - [Feature.CallAsync] = instruction => AnalyzeRealmApi(instruction, Feature.CallAsync), - [Feature.GetMongoClient] = _ => new(true, Feature.GetMongoClient), - [Feature.DynamicApi] = _ => new(true, Feature.DynamicApi) - }; - - _analyzeUserAssemblyTask = Task.Run(() => - { - AnalyzeUserAssembly(module); - }); - - FeatureAnalysisResult AnalyzeCollectionProperty(IMemberDefinition member, string primitiveKey, string referenceKey, int genericArgIndex = 0) - { - if (member is not PropertyDefinition property || - property.PropertyType is not GenericInstanceType genericType || - genericType.GenericArguments.Count < genericArgIndex + 1) - { - return default; - } - - var keyToAdd = genericType.GenericArguments[genericArgIndex].IsPrimitive ? - primitiveKey : referenceKey; - - var shouldDelete = ContainsAllRelatedFeatures(keyToAdd, referenceKey, primitiveKey); - return new(shouldDelete, keyToAdd); - } - - FeatureAnalysisResult AnalyzeRealmApi(Instruction instruction, string key) - { - if (IsInRealmNamespace(instruction?.Operand)) - { - return new(true, key); - } - - return default; - } - - bool ContainsAllRelatedFeatures(string key, params string[] features) - { - foreach (var feature in features) - { - if (feature != key && (!_realmFeaturesToAnalyze.TryGetValue(feature, out var value) || value == 0)) - { - return false; - } - } - - return true; - } - } - - private void AnalyzeUserAssembly(ModuleDefinition module) - { - try - { - // collect environment details - _realmEnvMetrics[UserEnvironment.UserId] = AnonymizedUserId; - _realmEnvMetrics[UserEnvironment.LegacyUserId] = LegacyAnonymizedUserId; - _realmEnvMetrics[UserEnvironment.ProjectId] = SHA256Hash(Encoding.UTF8.GetBytes(_config.ProjectId ?? module.Assembly.Name.Name)); - _realmEnvMetrics[UserEnvironment.RealmSdk] = "dotnet"; - _realmEnvMetrics[UserEnvironment.RealmSdkVersion] = Assembly.GetExecutingAssembly().GetName().Version.ToString(); - _realmEnvMetrics[UserEnvironment.Language] = "c#"; - _realmEnvMetrics[UserEnvironment.LanguageVersion] = InferLanguageVersion(_config.NetFrameworkTarget, _config.NetFrameworkTargetVersion); - _realmEnvMetrics[UserEnvironment.HostOsType] = GetHostOsName(); - _realmEnvMetrics[UserEnvironment.HostOsVersion] = Environment.OSVersion.Version.ToString(); - _realmEnvMetrics[UserEnvironment.HostCpuArch] = GetHostCpuArchitecture(); - _realmEnvMetrics[UserEnvironment.TargetOsType] = _config.TargetOSName; - _realmEnvMetrics[UserEnvironment.TargetCpuArch] = _config.TargetArchitecture; - _realmEnvMetrics[UserEnvironment.TargetOsVersion] = _config.TargetOsVersion; - _realmEnvMetrics[UserEnvironment.TargetOsMinimumVersion] = _config.TargetOsMinimumVersion; - _realmEnvMetrics[UserEnvironment.CoreVersion] = CoreVersion; - _realmEnvMetrics[UserEnvironment.FrameworkUsedInConjunction] = _config.FrameworkName; - _realmEnvMetrics[UserEnvironment.FrameworkUsedInConjunctionVersion] = _config.FrameworkVersion; - _realmEnvMetrics[UserEnvironment.SdkInstallationMethod] = _config.InstallationMethod; - _realmEnvMetrics[UserEnvironment.NetFramework] = _config.NetFrameworkTarget; - _realmEnvMetrics[UserEnvironment.NetFrameworkVersion] = _config.NetFrameworkTargetVersion; - _realmEnvMetrics[UserEnvironment.Compiler] = _config.Compiler; - - foreach (var type in module.Types.ToArray()) - { - InternalAnalyzeSdkApi(type); - } - - // We need to first analyze the features before we can set `IsSyncEnabled`. - _realmFeaturesToAnalyze.TryGetValue(Feature.PartitionSyncConfiguration, out var isPbsUsed); - _realmFeaturesToAnalyze.TryGetValue(Feature.FlexibleSyncConfiguration, out var isFlxSUsed); - _realmEnvMetrics[UserEnvironment.IsSyncEnabled] = (isPbsUsed == 1 || isFlxSUsed == 1).ToString().ToLower(); - } - catch (Exception e) - { - _logger.Warning($"Could not analyze the user's assembly. Please file an issue at https://github.com/realm/realm-dotnet/issues. {Environment.NewLine}{e}"); - } - } - - public void AnalyzeRealmClassProperties(WeaveTypeResult[] types) - { - if (_config.AnalyticsCollection == AnalyticsCollection.Disabled) - { - return; - } - - _analyzeUserAssemblyTask.Wait(); - - foreach (var type in types) - { - if (type.Properties == null) - { - continue; - } - - foreach (var propertyResult in type.Properties.Where(p => p.Woven)) - { - var property = propertyResult.Property; - - var key = property!.PropertyType.Name; - if (!_classAnalysisSetters.ContainsKey(key) && property.PropertyType.MetadataType == MetadataType.Class) - { - key = "Class"; - } - - AnalyzeClassFeature(key, property); - - foreach (var attribute in property.CustomAttributes) - { - AnalyzeClassFeature(attribute.AttributeType.Name, property); - } - } - } - - void AnalyzeClassFeature(string key, PropertyDefinition property) - { - if (_classAnalysisSetters.TryGetValue(key, out var featureFunc)) - { - var analysisResult = featureFunc(property); - - if (!string.IsNullOrEmpty(analysisResult.DictKey)) - { - _realmFeaturesToAnalyze[analysisResult.DictKey] = 1; - } - - if (analysisResult.ShouldDelete) - { - _classAnalysisSetters.Remove(key); - } - } - } - } - - private void InternalAnalyzeSdkApi(TypeDefinition type) - { - if (!type.IsClass) - { - return; - } - - if (type.IsIAsymmetricObjectImplementor(_references) || type.IsAsymmetricObjectDescendant(_references)) - { - _realmFeaturesToAnalyze[Feature.IAsymmetricObject] = 1; - } - else if (type.IsIEmbeddedObjectImplementor(_references) || type.IsEmbeddedObjectDescendant(_references)) - { - _realmFeaturesToAnalyze[Feature.IEmbeddedObject] = 1; - } - - AnalyzeClassMethods(type); - - foreach (var innerType in type.NestedTypes) - { - InternalAnalyzeSdkApi(innerType); - } - } - - private void AnalyzeClassMethods(TypeDefinition type) - { - var prefixes = new[] { "get_", "set_", "add_" }; - - foreach (var method in type.Methods) - { - if (!method.HasBody) - { - continue; - } - - foreach (var cil in method.Body.Instructions) - { - var key = (cil.Operand as MemberReference)?.Name; - if (key.IsNullOrEmpty()) - { - continue; - } - - var (prefix, index) = prefixes.Select(p => (p, key.IndexOf(p, StringComparison.Ordinal))) - .OrderByDescending(p => p.Item2) - .First(); - - if (index > -1) - { - // when dealing with: - // set_ShouldCompactOnLaunch - // add_RealmChanged - // add_PropertyChanged - // get_DynamicApi - key = key[prefix.Length..]; - } - - if (!_apiAnalysisSetters.ContainsKey(key) && - cil.Operand is MethodReference methodReference && - methodReference.ReturnType.DeclaringType != null) - { - // when dealing with ThreadSafeReference - key = methodReference.ReturnType.DeclaringType.Name; - } - - if (!_apiAnalysisSetters.ContainsKey(key) && key == ".ctor") - { - key = ((MemberReference)cil.Operand).DeclaringType.Name; - } - - if (!_apiAnalysisSetters.TryGetValue(key, out var featureFunc)) - { - continue; - } - - var analysisResult = featureFunc.Invoke(cil); - - if (!string.IsNullOrEmpty(analysisResult.DictKey)) - { - _realmFeaturesToAnalyze[analysisResult.DictKey] = 1; - } - - if (analysisResult.ShouldDelete) - { - _apiAnalysisSetters.Remove(key); - } - } - } - } - - public async Task SubmitAnalytics() - { - var payload = "Analytics disabled"; - - // this is necessary since when not in the assembly that has the models - // AnalyzeRealmClassProperties won't be called - _analyzeUserAssemblyTask.Wait(); - - if (_config.AnalyticsCollection != AnalyticsCollection.Disabled) - { - try - { - const string sendAddr = "https://data.mongodb-api.com/app/realmsdkmetrics-zmhtm/endpoint/v2/metric?data="; - payload = GetJsonPayload(); - - if (_config.AnalyticsCollection != AnalyticsCollection.DryRun) - { - var base64Payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(payload)); - await SendRequest(sendAddr, base64Payload, string.Empty); - } - } - catch (Exception e) - { - payload = e.Message; - } - } - - if (!_config.AnalyticsLogPath.IsNullOrEmpty()) - { - File.WriteAllText(_config.AnalyticsLogPath, payload); - } - } - - private string GetJsonPayload() - { - var jsonPayload = new StringBuilder(); - - jsonPayload.Append('{'); - - jsonPayload.Append(GetJsonString(_realmEnvMetrics)); - - var featuresString = GetJsonString(_realmFeaturesToAnalyze, Metric.SdkFeatures); - if (!string.IsNullOrEmpty(featuresString)) - { - jsonPayload.Append(','); - jsonPayload.Append(featuresString); - } - - jsonPayload.Append('}'); - - return jsonPayload.ToString(); - - string GetJsonString(IDictionary dict, IDictionary? keyMapping = null) - { - var mapping = dict - .Select(kvp => - { - if (kvp.Value is byte and 0 || - (kvp.Value is string s && string.IsNullOrEmpty(s))) - { - // skip empty strings/0 - return null; - } - - var key = keyMapping == null ? kvp.Key : keyMapping[kvp.Key]; - var value = kvp.Value is string ? $"\"{kvp.Value}\"" : $"{kvp.Value}"; - return $"\"{key}\":{value}"; - }) - .Where(s => s != null); - - return string.Join(",", mapping); - } - } - - private static async Task SendRequest(string prefixAddr, string payload, string suffixAddr) - { - using var httpClient = new HttpClient(); - httpClient.Timeout = TimeSpan.FromSeconds(4); - await httpClient.GetAsync(new Uri(prefixAddr + payload + suffixAddr)); - } - - private static bool IsInRealmNamespace(object? operand) - { - if (operand is not MemberReference memberReference) - { - return false; - } - - return memberReference?.DeclaringType?.FullName?.StartsWith("Realms", StringComparison.Ordinal) == true; - } - - public class Config - { - public AnalyticsCollection AnalyticsCollection { get; init; } - - public string? AnalyticsLogPath { get; init; } - - public string TargetOSName { get; } - - public string NetFrameworkTarget { get; } - - public string NetFrameworkTargetVersion { get; } - - public string InstallationMethod { get; } - - public string FrameworkName { get; } - - public string FrameworkVersion { get; } - - public string Compiler { get; } - - // These are only available on Unity for now. - public string TargetArchitecture { get; init; } = Metric.Unknown(); - - public string TargetOsVersion { get; init; } = Metric.Unknown(); - - public string TargetOsMinimumVersion { get; init; } = Metric.Unknown(); - - public string? ProjectId { get; init; } - - public Config( - string targetOSName, - string netFrameworkTarget, - string netFrameworkTargetVersion, - string installationMethod, - string frameworkName, - string frameworkVersion, - string compiler) - { - TargetOSName = targetOSName; - NetFrameworkTarget = netFrameworkTarget; - NetFrameworkTargetVersion = netFrameworkTargetVersion; - InstallationMethod = installationMethod; - FrameworkName = frameworkName; - FrameworkVersion = frameworkVersion; - Compiler = compiler; - } - } - - public enum AnalyticsCollection - { - Disabled, - DryRun, - Full, - } - - private readonly struct FeatureAnalysisResult - { - public bool ShouldDelete { get; } - - public string DictKey { get; } - - public FeatureAnalysisResult(bool isToDelete = false, string dictKey = "") - { - ShouldDelete = isToDelete; - DictKey = dictKey; - } - } - } -} diff --git a/Realm/Realm.Weaver/Analytics/AnalyticsUtils.cs b/Realm/Realm.Weaver/Analytics/AnalyticsUtils.cs deleted file mode 100644 index b0cd35c052..0000000000 --- a/Realm/Realm.Weaver/Analytics/AnalyticsUtils.cs +++ /dev/null @@ -1,309 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net.NetworkInformation; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; -using Mono.Cecil; -using static RealmWeaver.Metric; -using OperatingSystem = RealmWeaver.Metric.OperatingSystem; - -namespace RealmWeaver -{ - internal static class AnalyticsUtils - { - private static readonly Lazy _anonymizedUserId = new(() => - { - var id = string.Empty; - try - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - var machineIdToParse = RunProcess("reg", "QUERY HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography -v MachineGuid"); - var regex = new Regex("\\s+MachineGuid\\s+\\w+\\s+((\\w+-?)+)", RegexOptions.Multiline); - var match = regex.Match(machineIdToParse); - - if (match.Groups.Count > 1) - { - id = match.Groups[1].Value; - } - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - var machineIdToParse = RunProcess("ioreg", "-rd1 -c IOPlatformExpertDevice"); - var regex = new Regex(".*\\\"IOPlatformUUID\\\"\\s=\\s\\\"(.+)\\\"", RegexOptions.Multiline); - var match = regex.Match(machineIdToParse); - - if (match.Groups.Count > 1) - { - id = match.Groups[1].Value; - } - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - id = File.ReadAllText("/etc/machine-id"); - } - - if (id.Length == 0) - { - return Unknown(); - } - - // We're salting the id with an hardcoded byte array just to avoid that a machine is recognizable across - // unrelated projects that use the same mechanics to obtain a machine's ID - const string salt = "Realm is great"; - var saltedId = Encoding.UTF8.GetBytes(id + salt); - return SHA256Hash(saltedId); - } - catch - { - return Unknown(); - } - }); - - private static readonly Lazy _legacyAnonymizedUserId = new(() => - { - try - { - var id = NetworkInterface.GetAllNetworkInterfaces() - .Where(n => n.Name == "en0" || (n.OperationalStatus == OperationalStatus.Up && n.NetworkInterfaceType != NetworkInterfaceType.Loopback)) - .Select(n => n.GetPhysicalAddress().GetAddressBytes()) - .First(); - return SHA256Hash(id, useLegacyEncoding: true); - } - catch - { - return Unknown(); - } - }); - - public static string AnonymizedUserId => _anonymizedUserId.Value; - - public static string LegacyAnonymizedUserId => _legacyAnonymizedUserId.Value; - - public static string GetTargetOsName(FrameworkName frameworkName) - { - var targetOs = frameworkName.Identifier; - - if (targetOs.ContainsIgnoreCase("android")) - { - return OperatingSystem.Android; - } - - if (targetOs.ContainsIgnoreCase("ios")) - { - return OperatingSystem.Ios; - } - - if (targetOs.ContainsIgnoreCase("mac")) - { - return OperatingSystem.MacOS; - } - - if (targetOs.ContainsIgnoreCase("tvos")) - { - return OperatingSystem.TvOs; - } - - if (targetOs.ContainsIgnoreCase("linux")) - { - return OperatingSystem.Linux; - } - - if (targetOs.ContainsIgnoreCase("win") || targetOs.ContainsIgnoreCase("net4")) - { - return OperatingSystem.Windows; - } - - if (targetOs.ContainsIgnoreCase("core") || - targetOs.ContainsIgnoreCase("standard") || - targetOs.ContainsIgnoreCase("net")) - { - return OperatingSystem.CrossPlatform; - } - - return Unknown(frameworkName.Identifier); - } - - public static FrameworkInfo GetFrameworkAndVersion(ModuleDefinition module) - { - // the order in the array matters as we first need to look at the libraries (maui and forms) - // and then at the frameworks (xamarin native, Catalyst and UWP) - var possibleFrameworks = new Dictionary - { - { "Microsoft.Maui", Framework.Maui }, - { "Xamarin.Forms.Core", Framework.XamarinForms }, - { "Xamarin.iOS", Framework.Xamarin }, - { "Xamarin.tvOS", Framework.Xamarin }, - { "Xamarin.Mac", Framework.Xamarin }, - { "Mono.Android", Framework.Xamarin }, - { "Microsoft.MacCatalyst", Framework.MacCatalyst }, - { "Windows.Foundation.UniversalApiContract", Framework.Uwp }, - }; - - foreach (var kvp in possibleFrameworks) - { - var frameworkUsedInConjunction = module.AssemblyReferences.SingleOrDefault(a => a.Name == kvp.Key); - if (frameworkUsedInConjunction != null) - { - return new(kvp.Value, frameworkUsedInConjunction.Version.ToString()); - } - } - - return new("No framework of interest", "0.0.0"); - } - - public static string SHA256Hash(byte[] bytes, bool useLegacyEncoding = false) - { - using var sha256 = SHA256.Create(); - var hash = sha256.ComputeHash(bytes); - return useLegacyEncoding ? BitConverter.ToString(hash) : Convert.ToBase64String(hash); - } - - public static string GetHostCpuArchitecture() => RuntimeInformation.OSArchitecture switch - { - Architecture.X86 => CpuArchitecture.X86, - Architecture.Arm => CpuArchitecture.Arm, - Architecture.Arm64 => CpuArchitecture.Arm64, - Architecture.X64 => CpuArchitecture.X64, - _ => Unknown(RuntimeInformation.OSArchitecture.ToString()) - }; - - public static string GetHostOsName() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return OperatingSystem.Windows; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return OperatingSystem.Linux; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return OperatingSystem.MacOS; - } - - return Unknown(System.Environment.OSVersion.Platform.ToString()); - } - - public static string InferLanguageVersion(string netFramework, string netFrameworkVersion) - { - // We don't have a reliable way to get the version in the weaver so we're using the default version - // associated with the framework. - // Values taken from https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/configure-language-version - if (!netFramework.ContainsIgnoreCase("net")) - { - // Likely this isn't the model assembly, but the platform specific one - return Unknown(netFramework); - } - - if (netFrameworkVersion.ContainsIgnoreCase("2.0") || - netFrameworkVersion.ContainsIgnoreCase("4.")) - { - return "7.3"; - } - - if (netFrameworkVersion.ContainsIgnoreCase("2.1") || - netFrameworkVersion.ContainsIgnoreCase("3.1")) - { - return "8"; - } - - if (netFrameworkVersion.ContainsIgnoreCase("5.0")) - { - return "9"; - } - - if (netFrameworkVersion.ContainsIgnoreCase("6.0")) - { - return "10"; - } - - if (netFrameworkVersion.ContainsIgnoreCase("7.0")) - { - return "11"; - } - - return Unknown(); - } - - private static bool ContainsIgnoreCase(this string @this, string strCompare) => - @this.IndexOf(strCompare, StringComparison.OrdinalIgnoreCase) > -1; - - private static string RunProcess(string filename, string arguments) - { - using var proc = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = filename, - Arguments = arguments, - UseShellExecute = false, - RedirectStandardOutput = true, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, -#if DEBUG - RedirectStandardError = true, -#endif - } - }; - - proc.Start(); - - var stdout = new StringBuilder(); - while (!proc.HasExited) - { - stdout.AppendLine(proc.StandardOutput.ReadToEnd()); -#if DEBUG - stdout.AppendLine(proc.StandardError.ReadToEnd()); -#endif - } - - stdout.AppendLine(proc.StandardOutput.ReadToEnd()); -#if DEBUG - stdout.AppendLine(proc.StandardError.ReadToEnd()); -#endif - - return stdout.ToString(); - } - - public readonly struct FrameworkInfo - { - public string Name { get; } - - public string Version { get; } - - public FrameworkInfo(string name, string version) - { - Name = name; - Version = version; - } - } - } -} diff --git a/Realm/Realm.Weaver/Analytics/Metric.cs b/Realm/Realm.Weaver/Analytics/Metric.cs deleted file mode 100644 index 2b372cc698..0000000000 --- a/Realm/Realm.Weaver/Analytics/Metric.cs +++ /dev/null @@ -1,210 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace RealmWeaver -{ - internal static class Metric - { - public static string Unknown(string? clarifier = null) - { - var result = "Unknown"; - if (!clarifier.IsNullOrEmpty()) - { - result += $" ({clarifier})"; - } - - return result; - } - - public static class OperatingSystem - { - public const string Linux = "Linux"; - public const string MacOS = "macOS"; - public const string Windows = "Windows"; - public const string CrossPlatform = "Cross Platform"; - public const string Android = "Android"; - public const string Ios = "iOS"; - public const string IpadOs = "iPadOS"; - public const string WatchOs = "watchOS"; - public const string TvOs = "tvOS"; - public const string XboxOne = "XboxOne"; - } - - public static class CpuArchitecture - { - public const string X86 = "x86"; - public const string X64 = "x64"; - public const string Arm = "Arm"; - public const string Arm64 = "Arm64"; - public const string Universal = "Universal"; - } - - public static class Framework - { - public const string Unity = "Unity"; - public const string UnityEditor = "Unity Editor"; - public const string Maui = "MAUI"; - public const string Xamarin = "Xamarin"; - public const string XamarinForms = "Xamarin Forms"; - public const string Uwp = "UWP"; - public const string MacCatalyst = "MacCatalyst"; - } - - public static class Environment - { - public const string LegacyUserId = "distinct_id"; - public const string UserId = "builder_id"; - public const string ProjectId = "Anonymized Bundle ID"; - public const string RealmSdk = "Binding"; - public const string RealmSdkVersion = "Realm Version"; - public const string Language = "Language"; - public const string LanguageVersion = "Language Version"; - public const string HostOsType = "Host OS Type"; - public const string HostOsVersion = "Host OS Version"; - public const string HostCpuArch = "Host CPU Arch"; - public const string TargetOsType = "Target OS Type"; - public const string TargetCpuArch = "Target CPU Arch"; - public const string TargetOsMinimumVersion = "Target OS Minimum Version"; - public const string TargetOsVersion = "Target OS Version"; - public const string CoreVersion = "Core Version"; - public const string IsSyncEnabled = "Sync Enabled"; - public const string FrameworkUsedInConjunction = "Framework"; // this refers to UI frameworks and similar Realm is used together with - public const string FrameworkUsedInConjunctionVersion = "Framework Version"; - public const string SdkInstallationMethod = "Installation Method"; - public const string NetFramework = "Net Framework"; - public const string NetFrameworkVersion = "Net Framework Version"; - public const string Compiler = "Compiler"; - - // These are not currently supported - [SuppressMessage("Performance", "CA1823:Avoid unused private fields", Justification = "Placeholder")] - [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Placeholder")] - private const string _IdeUsed = "IDE"; - - [SuppressMessage("Performance", "CA1823:Avoid unused private fields", Justification = "Placeholder")] - [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Placeholder")] - private const string _IdeUsedVersion = "IDE Version"; - } - - public static class Feature - { - // ReSharper disable InconsistentNaming - public const string IEmbeddedObject = nameof(IEmbeddedObject); - public const string IAsymmetricObject = nameof(IAsymmetricObject); - public const string ReferenceList = nameof(ReferenceList); - public const string PrimitiveList = nameof(PrimitiveList); - public const string ReferenceDictionary = nameof(ReferenceDictionary); - public const string PrimitiveDictionary = nameof(PrimitiveDictionary); - public const string ReferenceSet = nameof(ReferenceSet); - public const string PrimitiveSet = nameof(PrimitiveSet); - public const string RealmInteger = nameof(RealmInteger); - public const string RealmObjectReference = nameof(RealmObjectReference); - public const string RealmValue = nameof(RealmValue); - public const string BacklinkAttribute = nameof(BacklinkAttribute); - public const string GetInstanceAsync = nameof(GetInstanceAsync); - public const string GetInstance = nameof(GetInstance); - public const string Find = nameof(Find); - public const string WriteAsync = nameof(WriteAsync); - public const string ThreadSafeReference = nameof(ThreadSafeReference); - public const string Add = nameof(Add); - public const string ShouldCompactOnLaunch = nameof(ShouldCompactOnLaunch); - public const string MigrationCallback = nameof(MigrationCallback); - public const string RealmChanged = nameof(RealmChanged); - public const string ListSubscribeForNotifications = nameof(ListSubscribeForNotifications); - public const string SetSubscribeForNotifications = nameof(SetSubscribeForNotifications); - public const string DictionarySubscribeForNotifications = nameof(DictionarySubscribeForNotifications); - public const string ResultSubscribeForNotifications = nameof(ResultSubscribeForNotifications); - public const string RecoverOrDiscardUnsyncedChangesHandler = nameof(RecoverOrDiscardUnsyncedChangesHandler); - public const string RecoverUnsyncedChangesHandler = nameof(RecoverUnsyncedChangesHandler); - public const string DiscardUnsyncedChangesHandler = nameof(DiscardUnsyncedChangesHandler); - public const string ManualRecoveryHandler = nameof(ManualRecoveryHandler); - public const string GetProgressObservable = nameof(GetProgressObservable); - public const string PartitionSyncConfiguration = nameof(PartitionSyncConfiguration); - public const string FlexibleSyncConfiguration = nameof(FlexibleSyncConfiguration); - public const string Anonymous = nameof(Anonymous); - public const string EmailPassword = nameof(EmailPassword); - public const string Facebook = nameof(Facebook); - public const string Google = nameof(Google); - public const string Apple = nameof(Apple); - public const string JWT = nameof(JWT); - public const string ApiKey = nameof(ApiKey); - public const string Function = nameof(Function); - public const string CallAsync = nameof(CallAsync); - public const string GetMongoClient = nameof(GetMongoClient); - public const string DynamicApi = nameof(DynamicApi); - public const string ConnectionNotification = nameof(ConnectionNotification); - public const string ObjectNotification = nameof(ObjectNotification); - } - - // This holds a mapping from Feature -> the name we send to DW. - public static readonly Dictionary SdkFeatures = new() - { - [Feature.IEmbeddedObject] = "Embedded_Object", - [Feature.IAsymmetricObject] = "Asymmetric_Object", - [Feature.ReferenceList] = "Object_List", - [Feature.PrimitiveList] = "Primitive_List", - [Feature.ReferenceDictionary] = "Object_Dict", - [Feature.PrimitiveDictionary] = "Primitive_Dict", - [Feature.ReferenceSet] = "Object_Set", - [Feature.PrimitiveSet] = "Primitive_Set", - [Feature.RealmInteger] = "Counter", - [Feature.RealmObjectReference] = "Object_Link", - [Feature.RealmValue] = "Mixed", - [Feature.BacklinkAttribute] = "Backlink", - - [Feature.GetInstanceAsync] = "Async_Open", - [Feature.GetInstance] = "Sync_Open", - - [Feature.Find] = "Find_PK", - [Feature.WriteAsync] = "Write_Async", - [Feature.ThreadSafeReference] = "Thread_Safe_Reference", - [Feature.Add] = "Insert_Modified", - [Feature.ShouldCompactOnLaunch] = "Compact_On_Launch", - [Feature.MigrationCallback] = "Migration_Block", - [Feature.RealmChanged] = "Realm_Notifications", - [Feature.ListSubscribeForNotifications] = "List_Notifications", - [Feature.SetSubscribeForNotifications] = "Set_Notifications", - [Feature.DictionarySubscribeForNotifications] = "Dict_Notifications", - [Feature.ResultSubscribeForNotifications] = "Results_Notifications", - [Feature.ObjectNotification] = "Object_Notifications", - [Feature.RecoverOrDiscardUnsyncedChangesHandler] = "CR_Recover_Discard", - [Feature.RecoverUnsyncedChangesHandler] = "CR_Recover", - [Feature.DiscardUnsyncedChangesHandler] = "CR_Discard", - [Feature.ManualRecoveryHandler] = "CR_Manual", - [Feature.GetProgressObservable] = "Progress_Notification", - [Feature.PartitionSyncConfiguration] = "Pbs_Sync", - [Feature.FlexibleSyncConfiguration] = "Flx_Sync", - [Feature.Anonymous] = "Auth_Anon", - [Feature.EmailPassword] = "Auth_Email", - [Feature.Facebook] = "Auth_Facebook", - [Feature.Google] = "Auth_Google", - [Feature.Apple] = "Auth_Apple", - [Feature.JWT] = "Auth_JWT", - [Feature.ApiKey] = "Auth_API_Key", - [Feature.Function] = "Auth_Function", - [Feature.CallAsync] = "Remote_Function", - [Feature.GetMongoClient] = "Remote_Mongo", - [Feature.DynamicApi] = "Dynamic_API", - [Feature.ConnectionNotification] = "Connection_Notification", - - // ["NOT_SUPPORTED_YET"] = "Query_Async", - }; - } -} diff --git a/Realm/Realm.Weaver/Extensions/PropertyDefinitionExtensions.cs b/Realm/Realm.Weaver/Extensions/PropertyDefinitionExtensions.cs index d467a30f52..8e5303fb73 100644 --- a/Realm/Realm.Weaver/Extensions/PropertyDefinitionExtensions.cs +++ b/Realm/Realm.Weaver/Extensions/PropertyDefinitionExtensions.cs @@ -199,9 +199,6 @@ internal static bool IsIndexable(this PropertyDefinition property, ImportedRefer public static bool ContainsRealmObject(this PropertyDefinition property, ImportedReferences references) => property.PropertyType.Resolve().IsRealmObjectInheritor(references); - public static bool ContainsAsymmetricObject(this PropertyDefinition property, ImportedReferences references) => - property.PropertyType.Resolve().IsAsymmetricObjectInheritor(references); - public static bool ContainsEmbeddedObject(this PropertyDefinition property, ImportedReferences references) => property.PropertyType.Resolve().IsEmbeddedObjectInheritor(references); diff --git a/Realm/Realm.Weaver/Extensions/TypeDefinitionExtensions.cs b/Realm/Realm.Weaver/Extensions/TypeDefinitionExtensions.cs index 4f9e44be47..d29f2b4d14 100644 --- a/Realm/Realm.Weaver/Extensions/TypeDefinitionExtensions.cs +++ b/Realm/Realm.Weaver/Extensions/TypeDefinitionExtensions.cs @@ -29,20 +29,13 @@ public static bool IsEmbeddedObjectInheritor(this TypeDefinition type, ImportedR public static bool IsRealmObjectInheritor(this TypeDefinition type, ImportedReferences references) => type.BaseType.IsSameAs(references.RealmObject); - public static bool IsAsymmetricObjectInheritor(this TypeDefinition type, ImportedReferences references) => - type.BaseType.IsSameAs(references.AsymmetricObject); - public static bool IsValidRealmObjectBaseInheritor(this TypeDefinition type, ImportedReferences references) => type.IsRealmObjectInheritor(references) || - type.IsEmbeddedObjectInheritor(references) || - type.IsAsymmetricObjectInheritor(references); + type.IsEmbeddedObjectInheritor(references); public static bool IsIEmbeddedObjectImplementor(this TypeDefinition type, ImportedReferences references) => IsImplementorOf(type, references.IEmbeddedObject); - public static bool IsIAsymmetricObjectImplementor(this TypeDefinition type, ImportedReferences references) => - IsImplementorOf(type, references.IAsymmetricObject); - public static bool IsImplementorOf(TypeDefinition @this, params TypeReference[] targetInterfaces) { foreach (var @interface in @this.Interfaces) diff --git a/Realm/Realm.Weaver/Extensions/TypeReferenceExtensions.cs b/Realm/Realm.Weaver/Extensions/TypeReferenceExtensions.cs index 7969d41949..5ed116fe0a 100644 --- a/Realm/Realm.Weaver/Extensions/TypeReferenceExtensions.cs +++ b/Realm/Realm.Weaver/Extensions/TypeReferenceExtensions.cs @@ -55,10 +55,7 @@ public static bool IsAnyRealmObject(this TypeReference @this, ImportedReferences || @this.IsRealmObjectDescendant(references); public static bool IsRealmObjectDescendant(this TypeReference @this, ImportedReferences references) => - IsDescendantOf(@this, references.RealmObject, references.EmbeddedObject, references.AsymmetricObject); - - public static bool IsAsymmetricObjectDescendant(this TypeReference @this, ImportedReferences references) => - IsDescendantOf(@this, references.AsymmetricObject); + IsDescendantOf(@this, references.RealmObject, references.EmbeddedObject); public static bool IsEmbeddedObjectDescendant(this TypeReference @this, ImportedReferences references) => IsDescendantOf(@this, references.EmbeddedObject); diff --git a/Realm/Realm.Weaver/ImportedReferences.cs b/Realm/Realm.Weaver/ImportedReferences.cs index 8004090561..8513c6bdad 100644 --- a/Realm/Realm.Weaver/ImportedReferences.cs +++ b/Realm/Realm.Weaver/ImportedReferences.cs @@ -78,14 +78,10 @@ internal abstract class ImportedReferences public TypeReference IEmbeddedObject { get; private set; } - public TypeReference IAsymmetricObject { get; private set; } - public TypeReference ManagedAccessor { get; private set; } public TypeReference EmbeddedObject { get; private set; } - public TypeReference AsymmetricObject { get; private set; } - public MethodReference RealmObject_get_IsManaged { get; private set; } public MethodReference RealmObject_get_Realm { get; private set; } @@ -223,11 +219,9 @@ private void InitializeRealm(IMetadataScope realmAssembly) ManagedAccessor = new TypeReference("Realms", "ManagedAccessor", Module, realmAssembly); RealmObject = new TypeReference("Realms", "RealmObject", Module, realmAssembly); EmbeddedObject = new TypeReference("Realms", "EmbeddedObject", Module, realmAssembly); - AsymmetricObject = new TypeReference("Realms", "AsymmetricObject", Module, realmAssembly); RealmSchema_PropertyType = new TypeReference("Realms.Schema", "PropertyType", Module, realmAssembly, valueType: true); RealmValue = new TypeReference("Realms", "RealmValue", Module, realmAssembly, valueType: true); IEmbeddedObject = new TypeReference("Realms", "IEmbeddedObject", Module, realmAssembly, valueType: false); - IAsymmetricObject = new TypeReference("Realms", "IAsymmetricObject", Module, realmAssembly, valueType: false); RealmValue_GetNull = new MethodReference("get_Null", RealmValue, RealmValue) { HasThis = false }; { diff --git a/Realm/Realm.Weaver/RealmWeaver.cs b/Realm/Realm.Weaver/RealmWeaver.cs index f301457e41..6d02d19612 100644 --- a/Realm/Realm.Weaver/RealmWeaver.cs +++ b/Realm/Realm.Weaver/RealmWeaver.cs @@ -178,20 +178,12 @@ public Weaver(ModuleDefinition module, ILogger logger, string framework) _propertyChanged_DoNotNotify_Ctor = new Lazy(GetOrAddPropertyChanged_DoNotNotify); } - public WeaveModuleResult Execute(Analytics.Config analyticsConfig) + public WeaveModuleResult Execute() { - var analytics = new Analytics(analyticsConfig, _references, _logger, _moduleDefinition); - - var result = ExecuteInternal(out var weaveResults); - analytics.AnalyzeRealmClassProperties(weaveResults); - - // Don't wait for submission - _ = analytics.SubmitAnalytics(); - - return result; + return ExecuteInternal(); } - private WeaveModuleResult ExecuteInternal(out WeaveTypeResult[] weaveResults) + private WeaveModuleResult ExecuteInternal() { _logger.Debug("Weaving file: " + _moduleDefinition.FileName); @@ -199,20 +191,18 @@ private WeaveModuleResult ExecuteInternal(out WeaveTypeResult[] weaveResults) // specific code in another assembly, but we still want to report what target the user is building for if (_references.Realm == null) { - weaveResults = Array.Empty(); return WeaveModuleResult.Skipped($"Not weaving assembly '{_moduleDefinition.Assembly.Name}' because it doesn't reference Realm."); } var isWoven = _moduleDefinition.Assembly.CustomAttributes.Any(a => a.AttributeType.IsSameAs(_references.WovenAssemblyAttribute)); if (isWoven) { - weaveResults = Array.Empty(); return WeaveModuleResult.Skipped($"Not weaving assembly '{_moduleDefinition.Assembly.Name}' because it has already been processed."); } var matchingTypes = GetMatchingTypes().ToArray(); - weaveResults = matchingTypes.Select(matchingType => + var weaveResults = matchingTypes.Select(matchingType => { var type = matchingType.Type; var isGenerated = matchingType.IsGenerated; @@ -234,12 +224,9 @@ private WeaveModuleResult ExecuteInternal(out WeaveTypeResult[] weaveResults) _moduleDefinition.Assembly.CustomAttributes.Add(wovenAssemblyAttribute); var failedResults = weaveResults.Where(r => !r.IsSuccessful).ToArray(); - if (failedResults.Any()) - { - return WeaveModuleResult.Error($"The following types had errors when woven: {string.Join(", ", failedResults.Select(f => f.Type))}"); - } - - return WeaveModuleResult.Success(weaveResults); + return failedResults.Any() + ? WeaveModuleResult.Error($"The following types had errors when woven: {string.Join(", ", failedResults.Select(f => f.Type))}") + : WeaveModuleResult.Success(weaveResults); } private static void RemoveBackingFields(TypeDefinition type, HashSet backingFields) @@ -570,11 +557,6 @@ private WeavePropertyResult WeaveProperty(PropertyDefinition prop, TypeDefinitio return WeavePropertyResult.Warning($"{type.Name}.{prop.Name} is not an automatic property but its type is a RealmObject/EmbeddedObject which normally indicates a relationship."); } - if (prop.ContainsAsymmetricObject(_references)) - { - return WeavePropertyResult.Warning($"{type.Name}.{prop.Name} is not an automatic property but its type is a AsymmetricObject. This usually indicates a relationship but AsymmetricObjects are not allowed to be the receiving end of any relationships."); - } - return WeavePropertyResult.Skipped("Property is not autoimplemented"); } @@ -619,10 +601,6 @@ private WeavePropertyResult WeaveProperty(PropertyDefinition prop, TypeDefinitio return WeavePropertyResult.Error($"{type.Name}.{prop.Name} is an {collectionType} but its generic type is {elementType.Name} which is not supported by Realm."); } } - else if (elementType.IsAsymmetricObjectDescendant(_references)) - { - return WeavePropertyResult.Error($"{type.Name}.{prop.Name} is an {collectionType}, but AsymmetricObjects aren't allowed to be contained in any RealmObject inheritor."); - } if (prop.SetMethod != null) { @@ -656,10 +634,6 @@ private WeavePropertyResult WeaveProperty(PropertyDefinition prop, TypeDefinitio break; } } - else if (prop.ContainsAsymmetricObject(_references)) - { - return WeavePropertyResult.Error($"{type.Name}.{prop.Name} is of type AsymmetricObject, but AsymmetricObjects aren't allowed to be the receiving end of any relationship."); - } else if (prop.ContainsRealmObject(_references) || prop.ContainsEmbeddedObject(_references)) { if (prop.SetMethod == null) @@ -683,11 +657,6 @@ private WeavePropertyResult WeaveProperty(PropertyDefinition prop, TypeDefinitio return WeavePropertyResult.Error($"{type.Name}.{prop.Name} has a setter but also has [Backlink] applied, which only supports getters."); } - if (type.IsAsymmetricObjectDescendant(_references)) - { - return WeavePropertyResult.Error($"{type.Name}.{prop.Name} has [Backlink] applied which is not allowed on AsymmetricObject."); - } - var elementType = ((GenericInstanceType)prop.PropertyType).GenericArguments.Single(); var inversePropertyName = (string)backlinkAttribute.ConstructorArguments[0].Value; var inverseProperty = elementType.Resolve().Properties.SingleOrDefault(p => p.Name == inversePropertyName); @@ -724,7 +693,7 @@ private WeavePropertyResult WeaveProperty(PropertyDefinition prop, TypeDefinitio } else { - return WeavePropertyResult.Error($"{type.Name}.{prop.Name} is a '{prop.PropertyType}' which is not yet supported. If that is supposed to be a model class, make sure it inherits from RealmObject/EmbeddedObject/AsymmetricObject."); + return WeavePropertyResult.Error($"{type.Name}.{prop.Name} is a '{prop.PropertyType}' which is not yet supported. If that is supposed to be a model class, make sure it inherits from RealmObject/EmbeddedObject."); } var preserveAttribute = new CustomAttribute(_references.PreserveAttribute_Constructor); diff --git a/Realm/Realm/DatabaseTypes/Accessors/IRealmAccessor.cs b/Realm/Realm/DatabaseTypes/Accessors/IRealmAccessor.cs index ef89c9b6c6..40cfb431bb 100644 --- a/Realm/Realm/DatabaseTypes/Accessors/IRealmAccessor.cs +++ b/Realm/Realm/DatabaseTypes/Accessors/IRealmAccessor.cs @@ -65,10 +65,9 @@ public interface IRealmAccessor /// /// A collection of properties describing the underlying schema of this object. /// - /// This will always be available for models that use the Realm source generator tool (i.e. inheriting from , - /// , or ). It will be null for unmanaged objects if the models have - /// been processed by the Fody weaver (i.e. inheriting from , , or - /// ). + /// This will always be available for models that use the Realm source generator tool (i.e. inheriting from or + /// ). It will be null for unmanaged objects if the models have + /// been processed by the Fody weaver (i.e. inheriting from or ). /// ObjectSchema? ObjectSchema { get; } @@ -149,8 +148,7 @@ IQueryable GetBacklinks(string propertyName) /// /// Gets the parent of the embedded object. It can be either another - /// embedded object, a standalone realm object, - /// or an asymmetric object. + /// embedded object or a standalone realm object. /// /// The parent of the embedded object. IRealmObjectBase? GetParent(); diff --git a/Realm/Realm/DatabaseTypes/AsymmetricObject.cs b/Realm/Realm/DatabaseTypes/AsymmetricObject.cs deleted file mode 100644 index 276bde30a7..0000000000 --- a/Realm/Realm/DatabaseTypes/AsymmetricObject.cs +++ /dev/null @@ -1,36 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms -{ - /// - /// Base for any object that can be persisted in a but that cannot be retrieved, hence cannot be modified. - /// - /// - /// The benefit of using is that the performance of each sync operation is much higher. - /// The drawback is that an is synced unidirectionally, so it cannot be queried. You should - /// use this base when you have a write-heavy use case. If, instead you want to persist an object that you can also query - /// against, use instead. RealmObjects and - /// EmbeddedObjects can't link (or backlink) to AsymmetricObjects. - /// AsymmetricObjects can only link to EmbeddedObjects. - /// - /// - public class AsymmetricObject : RealmObjectBase, IAsymmetricObject - { - } -} diff --git a/Realm/Realm/DatabaseTypes/IRealmObjectBase.cs b/Realm/Realm/DatabaseTypes/IRealmObjectBase.cs index d8f5d56183..346e1318c3 100644 --- a/Realm/Realm/DatabaseTypes/IRealmObjectBase.cs +++ b/Realm/Realm/DatabaseTypes/IRealmObjectBase.cs @@ -104,33 +104,6 @@ public interface IRealmObject : IRealmObjectBase { } - /// - /// Base interface for any asymmetric object that can be persisted in a . - /// - /// - /// The benefit of using is that the performance of each sync operation is much higher. - /// The drawback is that an is synced unidirectionally, so it cannot be queried. - /// You should use this base when you have a write-heavy use case. - /// If, instead you want to persist an object that you can also query against, use instead. - ///
- /// This interface will be implemented automatically by the Realm source generator as long as your - /// model class is declared as partial. - ///
- /// - /// - /// public partial class SensorReading : IAsymmetricObject - /// { - /// public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.UtcNow; - /// - /// public double Value { get; set; } - /// } - /// - /// - /// - public interface IAsymmetricObject : IRealmObjectBase - { - } - /// /// Base interface for any embedded object that can be persisted in a . /// @@ -152,8 +125,7 @@ public interface IEmbeddedObject : IRealmObjectBase { /// /// Gets the parent of the embedded object. It can be either another - /// embedded object, a standalone realm object, - /// or an asymmetric object. + /// embedded object or a standalone realm object. /// /// The parent object that owns this . public IRealmObjectBase? Parent { get; } diff --git a/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs b/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs index 1ba7570d87..830b724764 100644 --- a/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs +++ b/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs @@ -265,9 +265,6 @@ protected void AddToRealmIfNecessary(in RealmValue value) case IEmbeddedObject: Debug.Assert(typeof(T) == typeof(RealmValue) || typeof(T) == typeof(KeyValuePair), $"Expected a RealmValue to contain the IEmbeddedObject, but was a {typeof(T).Name}"); throw new NotSupportedException("A RealmValue cannot contain an embedded object."); - case IAsymmetricObject: - Debug.Assert(typeof(T) == typeof(RealmValue) || typeof(T) == typeof(KeyValuePair), $"Expected a RealmValue to contain the IAsymmetricObject, but was a {typeof(T).Name}"); - throw new NotSupportedException("A RealmValue cannot contain an asymmetric object."); default: throw new NotSupportedException($"{robj.GetType().Name} is not a valid Realm object type."); } diff --git a/Realm/Realm/DatabaseTypes/RealmValue.cs b/Realm/Realm/DatabaseTypes/RealmValue.cs index f94deaefee..d2a8d987ee 100644 --- a/Realm/Realm/DatabaseTypes/RealmValue.cs +++ b/Realm/Realm/DatabaseTypes/RealmValue.cs @@ -35,7 +35,7 @@ namespace Realms /// A type that can represent any valid Realm data type. It is a valid type in and of itself, which /// means that it can be used to declare a property of type . /// Please note that a property in a managed realm object - /// cannot contain an embedded object or an asymmetric object. + /// cannot contain an embedded object. /// /// /// diff --git a/Realm/Realm/Dynamic/DynamicAsymmetricObject.cs b/Realm/Realm/Dynamic/DynamicAsymmetricObject.cs deleted file mode 100644 index 10cde3f39f..0000000000 --- a/Realm/Realm/Dynamic/DynamicAsymmetricObject.cs +++ /dev/null @@ -1,34 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.ComponentModel; -using System.Dynamic; -using System.Linq.Expressions; - -namespace Realms.Dynamic -{ - [EditorBrowsable(EditorBrowsableState.Never)] - [Ignored] - public class DynamicAsymmetricObject : DynamicRealmObjectBase, IAsymmetricObject, IDynamicMetaObjectProvider - { - public DynamicMetaObject GetMetaObject(Expression parameter) - { - return new MetaRealmObject(parameter, this); - } - } -} diff --git a/Realm/Realm/Dynamic/DynamicRealmObjectHelper.cs b/Realm/Realm/Dynamic/DynamicRealmObjectHelper.cs index 30790bffbe..4cdfb2ac72 100644 --- a/Realm/Realm/Dynamic/DynamicRealmObjectHelper.cs +++ b/Realm/Realm/Dynamic/DynamicRealmObjectHelper.cs @@ -27,7 +27,6 @@ internal class DynamicRealmObjectHelper : IRealmObjectHelper { private static readonly DynamicRealmObjectHelper _embeddedInstance = new(ObjectSchema.ObjectType.EmbeddedObject); private static readonly DynamicRealmObjectHelper _objectInstance = new(ObjectSchema.ObjectType.RealmObject); - private static readonly DynamicRealmObjectHelper _asymmetricInstance = new(ObjectSchema.ObjectType.AsymmetricObject); private readonly ObjectSchema.ObjectType _schemaType; @@ -36,7 +35,6 @@ internal static DynamicRealmObjectHelper Instance(ObjectSchema schema) => { ObjectSchema.ObjectType.RealmObject => _objectInstance, ObjectSchema.ObjectType.EmbeddedObject => _embeddedInstance, - ObjectSchema.ObjectType.AsymmetricObject => _asymmetricInstance, _ => throw new NotSupportedException($"{schema.BaseType} type not supported, yet."), }; @@ -55,7 +53,6 @@ public IRealmObjectBase CreateInstance() => { ObjectSchema.ObjectType.RealmObject => new DynamicRealmObject(), ObjectSchema.ObjectType.EmbeddedObject => new DynamicEmbeddedObject(), - ObjectSchema.ObjectType.AsymmetricObject => new DynamicAsymmetricObject(), _ => throw new NotSupportedException($"{_schemaType} type not supported, yet."), }; diff --git a/Realm/Realm/Dynamic/MetaRealmObject.cs b/Realm/Realm/Dynamic/MetaRealmObject.cs index c89943c483..3c9c311d08 100644 --- a/Realm/Realm/Dynamic/MetaRealmObject.cs +++ b/Realm/Realm/Dynamic/MetaRealmObject.cs @@ -160,7 +160,6 @@ public override DynamicMetaObject BindGetMember(GetMemberBinder binder) if (property.Type.UnderlyingType() == PropertyType.LinkingObjects) { - // no AsymmetricObjects involved here expression = IsTargetEmbedded(property) ? Expression.Call(RealmObjectGetBacklinksForHandle_EmbeddedObject, self, Expression.Constant(binder.Name), expression) : Expression.Call(RealmObjectGetBacklinksForHandle_RealmObject, self, Expression.Constant(binder.Name), expression); @@ -270,7 +269,6 @@ private static Type GetDynamicObjectType(ObjectSchema schema) => { ObjectSchema.ObjectType.RealmObject => typeof(DynamicRealmObject), ObjectSchema.ObjectType.EmbeddedObject => typeof(DynamicEmbeddedObject), - ObjectSchema.ObjectType.AsymmetricObject => typeof(DynamicAsymmetricObject), _ => throw new NotSupportedException($"{schema.BaseType} not supported yet."), }; @@ -285,7 +283,6 @@ private MethodInfo GetObjectGetCollectionMethod(Property property, CollectionTyp { ObjectSchema.ObjectType.RealmObject => GetCollectionGetter(collectionType), ObjectSchema.ObjectType.EmbeddedObject => GetCollectionGetter(collectionType), - ObjectSchema.ObjectType.AsymmetricObject => GetCollectionGetter(collectionType), _ => throw new NotSupportedException($"{metadata.Schema.BaseType} not supported yet."), }; diff --git a/Realm/Realm/Extensions/FrozenObjectsExtensions.cs b/Realm/Realm/Extensions/FrozenObjectsExtensions.cs index 9cfb3102ea..27155837c5 100644 --- a/Realm/Realm/Extensions/FrozenObjectsExtensions.cs +++ b/Realm/Realm/Extensions/FrozenObjectsExtensions.cs @@ -44,8 +44,8 @@ public static class FrozenObjectsExtensions /// Note: Keeping a large number of frozen objects with different versions alive can have a negative impact on the filesize /// of the Realm. In order to avoid such a situation it is possible to set . /// - /// The , , or instance that you want to create a frozen version of. - /// The type of the //. + /// The or instance that you want to create a frozen version of. + /// The type of the /. /// A new frozen instance of the passed in object or the object itself if it was already frozen. public static T Freeze(this T realmObj) where T : IRealmObjectBase diff --git a/Realm/Realm/Extensions/ReflectionExtensions.cs b/Realm/Realm/Extensions/ReflectionExtensions.cs index d0b0cce93d..999078afad 100644 --- a/Realm/Realm/Extensions/ReflectionExtensions.cs +++ b/Realm/Realm/Extensions/ReflectionExtensions.cs @@ -49,17 +49,10 @@ public static bool HasCustomAttribute(this MemberInfo member) public static bool IsEmbeddedObject(this Type type) => typeof(IEmbeddedObject).IsAssignableFrom(type); - public static bool IsAsymmetricObject(this Type type) => typeof(IAsymmetricObject).IsAssignableFrom(type); - public static bool IsRealmObject(this Type type) => typeof(IRealmObject).IsAssignableFrom(type); public static ObjectSchema.ObjectType GetRealmSchemaType(this Type type) { - if (type.IsAsymmetricObject()) - { - return ObjectSchema.ObjectType.AsymmetricObject; - } - if (type.IsEmbeddedObject()) { return ObjectSchema.ObjectType.EmbeddedObject; diff --git a/Realm/Realm/GlobalSuppressions.cs b/Realm/Realm/GlobalSuppressions.cs index 26ee2a8e66..0418b89096 100644 --- a/Realm/Realm/GlobalSuppressions.cs +++ b/Realm/Realm/GlobalSuppressions.cs @@ -5,7 +5,6 @@ [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Shouldn't be used directly.", Scope = "type", Target = "~T:Realms.Dynamic.DynamicRealmObject")] [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Shouldn't be used directly.", Scope = "type", Target = "~T:Realms.Dynamic.DynamicEmbeddedObject")] [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Shouldn't be used directly.", Scope = "type", Target = "~T:Realms.Dynamic.DynamicRealmObjectBase")] -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Shouldn't be used directly.", Scope = "type", Target = "~T:Realms.Dynamic.DynamicAsymmetricObject")] [assembly: SuppressMessage("Performance", "CA1820:Test for empty strings using string length", Justification = "We're only capturing the method group.", Scope = "type", Target = "~T:Realms.RealmResultsVisitor.Methods.String")] [assembly: SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "index is the argument name for ElementAt.", Scope = "member", Target = "~M:Realms.RealmResultsVisitor.VisitMethodCall(System.Linq.Expressions.MethodCallExpression)~System.Linq.Expressions.Expression")] [assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1121:Use built-in type alias", Justification = "Native structs use verbose names.", Scope = "namespaceanddescendants", Target = "~N:Realms.Native")] @@ -13,9 +12,6 @@ [assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Native methods are snake_case.", Scope = "type", Target = "~T:Realms.NativeCommon")] [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Native methods are snake_case.", Scope = "type", Target = "~T:Realms.NativeCommon")] [assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Native structs use snake_case fields.", Scope = "type", Target = "~T:Realms.NativeException")] -[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1121:Use built-in type alias", Justification = "Native structs use verbose names.", Scope = "namespaceanddescendants", Target = "~N:Realms.Sync.Native")] -[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Native structs use snake_case fields.", Scope = "namespaceanddescendants", Target = "~N:Realms.Sync.Native")] -[assembly: SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Private struct fields are important for correct offsets while marshalling", Scope = "namespaceanddescendants", Target = "~N:Realms.Sync.Native")] [assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Native methods are snake_case.", Scope = "type", Target = "~T:Realms.SynchronizationContextScheduler")] [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Native methods are snake_case.", Scope = "type", Target = "~T:Realms.SynchronizationContextScheduler")] [assembly: SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "Will be a breaking change to rename.", Scope = "type", Target = "~T:Realms.Schema.Property")] @@ -34,7 +30,5 @@ [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Builder is not supposed to be extended by users", Scope = "member", Target = "~M:Realms.Schema.ObjectSchema.Builder.GetKey(Realms.Schema.Property)~System.String")] [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Builder is not supposed to be extended by users", Scope = "member", Target = "~M:Realms.Schema.RealmSchema.Builder.GetKey(Realms.Schema.ObjectSchema)~System.String")] [assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1000:Keywords should be spaced correctly", Justification = "In C# 9.0 we can use new() to instantiate objects and we don't need a space there", Scope = "module")] -[assembly: SuppressMessage("Design", "CA1001:Types that own disposable fields should be disposable", Justification = "The message handler is disposed by the http client", Scope = "type", Target = "~T:Realms.Sync.AppConfiguration")] [assembly: SuppressMessage("Naming", "CA1720:Identifier contains type name", Justification = "The method creates and object, so the name is convenient.", Scope = "member", Target = "~M:Realms.RealmValue.Object(Realms.IRealmObjectBase)~Realms.RealmValue")] [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "The method is not supposed to be used by users", Scope = "member", Target = "~M:Realms.RealmValue.Object(Realms.IRealmObjectBase)~Realms.RealmValue")] -[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is a private event proxied through the public one", Scope = "member", Target = "~E:Realms.Sync.Session._propertyChanged")] diff --git a/Realm/Realm/Handles/ObjectHandle.cs b/Realm/Realm/Handles/ObjectHandle.cs index ae8f098cd4..6fd7601d78 100644 --- a/Realm/Realm/Handles/ObjectHandle.cs +++ b/Realm/Realm/Handles/ObjectHandle.cs @@ -187,7 +187,7 @@ public void SetValue(string propertyName, Metadata metadata, in RealmValue value { switch (value.AsIRealmObject()) { - case IRealmObject realmObj when !realmObj.IsManaged: + case IRealmObject { IsManaged: false } realmObj: realm.Add(realmObj); break; case IEmbeddedObject embeddedObj: @@ -213,14 +213,6 @@ public void SetValue(string propertyName, Metadata metadata, in RealmValue value var embeddedHandle = CreateEmbeddedObjectForProperty(propertyName, metadata); realm.ManageEmbedded(embeddedObj, embeddedHandle); return; - - // Asymmetric objects can't reach this path unless the user explicitly sets them as - // a RealmValue property on the object. - // This is because: - // * For plain asymmetric objects (not contained within a RealmValue), the weaver - // raises a compilation error since asymmetric objects can't be linked to. - case IAsymmetricObject: - throw new NotSupportedException($"Asymmetric objects cannot be linked to and cannot be contained in a RealmValue. Attempted to set {value} to {metadata.Schema.Name}.{propertyName}"); } } else if (value.Type.IsCollection()) @@ -236,8 +228,6 @@ public void SetValue(string propertyName, Metadata metadata, in RealmValue value case RealmValueType.Dictionary: CollectionHelpers.PopulateCollection(realm, new DictionaryHandle(Root!, collectionPtr), value); break; - default: - break; } return; diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index 16f8e043b7..4bf2582a6c 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -591,7 +591,6 @@ public void WriteCopy(RealmConfigurationBase config) using var arena = new Arena(); var nativeConfig = config.CreateNativeConfiguration(arena); - // TODO: community: remote useSync NativeMethods.write_copy(this, nativeConfig, out var nativeException); nativeException.ThrowIfNecessary(); } diff --git a/Realm/Realm/Helpers/RealmObjectSerializer.cs b/Realm/Realm/Helpers/RealmObjectSerializer.cs index d90165ae37..acfe8a3350 100644 --- a/Realm/Realm/Helpers/RealmObjectSerializer.cs +++ b/Realm/Realm/Helpers/RealmObjectSerializer.cs @@ -304,7 +304,7 @@ private static void WriteArray(BsonSerializationContext context, BsonSer var type = typeof(TValue); Action serialize = type switch { - _ when type.IsRealmObject() || type.IsAsymmetricObject() => RealmObjectSerializer.LookupSerializer(type)!.SerializeId, + _ when type.IsRealmObject() => RealmObjectSerializer.LookupSerializer(type)!.SerializeId, _ when type.IsEmbeddedObject() => RealmObjectSerializer.LookupSerializer(type)!.Serialize, _ => BsonSerializer.LookupSerializer().Serialize }; @@ -325,7 +325,7 @@ protected void WriteDictionary(BsonSerializationContext context, BsonSer var type = typeof(TValue); Action serialize = type switch { - _ when type.IsRealmObject() || type.IsAsymmetricObject() => RealmObjectSerializer.LookupSerializer(type)!.SerializeId, + _ when type.IsRealmObject() => RealmObjectSerializer.LookupSerializer(type)!.SerializeId, _ when type.IsEmbeddedObject() => RealmObjectSerializer.LookupSerializer(type)!.Serialize, _ => BsonSerializer.LookupSerializer().Serialize }; diff --git a/Realm/Realm/Linq/IRealmCollection.cs b/Realm/Realm/Linq/IRealmCollection.cs index 3ea30ec5d8..e5020cec7f 100644 --- a/Realm/Realm/Linq/IRealmCollection.cs +++ b/Realm/Realm/Linq/IRealmCollection.cs @@ -86,7 +86,7 @@ public interface IRealmCollection : IReadOnlyList, INotifyCollectionCh /// /// Gets the , describing the persisted properties of the - /// , , or instances + /// or instances /// contained in the collection. If the collection contains primitive values, will /// be null. /// diff --git a/Realm/Realm/Linq/QueryMethods.cs b/Realm/Realm/Linq/QueryMethods.cs index 0e6558292c..4c34d86357 100644 --- a/Realm/Realm/Linq/QueryMethods.cs +++ b/Realm/Realm/Linq/QueryMethods.cs @@ -118,9 +118,6 @@ public static bool FullTextSearch(string? str, string terms) /// } /// } /// - /// - /// Note that if you're using Sync, the name of the embedded object type must match exactly the title of - /// the embedded object defined in the GeoJson Schema on the server. /// public static bool GeoWithin(IEmbeddedObject? point, GeoShapeBase geoShape) { diff --git a/Realm/Realm/Logging/LogCategory.cs b/Realm/Realm/Logging/LogCategory.cs index 75095f31ba..58eb8b578a 100644 --- a/Realm/Realm/Logging/LogCategory.cs +++ b/Realm/Realm/Logging/LogCategory.cs @@ -35,20 +35,12 @@ namespace Realms.Logging /// │ ├─► Query /// │ ├─► Object /// │ └─► Notification - /// ├─► Sync - /// │ ├─► Client - /// │ │ ├─► Session - /// │ │ ├─► Changeset - /// │ │ ├─► Network - /// │ │ └─► Reset - /// │ └─► Server - /// ├─► App /// └─► SDK /// /// /// /// - /// LogCategory.Realm.Sync.Client + /// LogCategory.Realm.Storage.Transaction /// /// public class LogCategory @@ -71,14 +63,6 @@ public class LogCategory { Realm.Storage.Query.Name, Realm.Storage.Query }, { Realm.Storage.Object.Name, Realm.Storage.Object }, { Realm.Storage.Notification.Name, Realm.Storage.Notification }, - { Realm.Sync.Name, Realm.Sync }, - { Realm.Sync.Client.Name, Realm.Sync.Client }, - { Realm.Sync.Client.Session.Name, Realm.Sync.Client.Session }, - { Realm.Sync.Client.Changeset.Name, Realm.Sync.Client.Changeset }, - { Realm.Sync.Client.Network.Name, Realm.Sync.Client.Network }, - { Realm.Sync.Client.Reset.Name, Realm.Sync.Client.Reset }, - { Realm.Sync.Server.Name, Realm.Sync.Server }, - { Realm.App.Name, Realm.App }, { Realm.SDK.Name, Realm.SDK }, }; @@ -107,16 +91,6 @@ public class RealmLogCategory : LogCategory /// public StorageLogCategory Storage { get; } - /// - /// Gets the category for receiving log messages pertaining to Atlas Device Sync. - /// - public SyncLogCategory Sync { get; } - - /// - /// Gets the category for receiving log messages pertaining to Atlas App. - /// - public LogCategory App { get; } - /// /// Gets the category for receiving log messages pertaining to the SDK. /// @@ -125,8 +99,6 @@ public class RealmLogCategory : LogCategory internal RealmLogCategory() : base("Realm", null) { Storage = new StorageLogCategory(this); - Sync = new SyncLogCategory(this); - App = new LogCategory("App", this); SDK = new LogCategory("SDK", this); } } @@ -166,62 +138,5 @@ internal StorageLogCategory(LogCategory parent) : base("Storage", parent) Notification = new LogCategory("Notification", this); } } - - /// - /// The category for receiving log messages pertaining to Atlas Device Sync. - /// - public class SyncLogCategory : LogCategory - { - /// - /// Gets the category for receiving log messages pertaining to sync client operations. - /// - public ClientLogCategory Client { get; } - - /// - /// Gets the category for receiving log messages pertaining to sync server operations. - /// - public LogCategory Server { get; } - - internal SyncLogCategory(LogCategory parent) : base("Sync", parent) - { - Client = new ClientLogCategory(this); - Server = new LogCategory("Server", this); - } - } - - /// - /// The category for receiving log messages pertaining to sync client operations. - /// - public class ClientLogCategory : LogCategory - { - /// - /// Gets the category for receiving log messages pertaining to the sync session. - /// - public LogCategory Session { get; } - - /// - /// Gets the category for receiving log messages when receiving, uploading, and - /// integrating changesets. - /// - public LogCategory Changeset { get; } - - /// - /// Gets the category for receiving log messages pertaining to low-level network activity. - /// - public LogCategory Network { get; } - - /// - /// Gets the category for receiving log messages when there are client reset operations. - /// - public LogCategory Reset { get; } - - internal ClientLogCategory(LogCategory parent) : base("Client", parent) - { - Session = new LogCategory("Session", this); - Changeset = new LogCategory("Changeset", this); - Network = new LogCategory("Network", this); - Reset = new LogCategory("Reset", this); - } - } } } diff --git a/Realm/Realm/Realm.cs b/Realm/Realm/Realm.cs index 48a3c4e498..07d549462a 100644 --- a/Realm/Realm/Realm.cs +++ b/Realm/Realm/Realm.cs @@ -514,75 +514,6 @@ public void Add(IEnumerable objs, bool update = false) } } - /// - /// This will start managing an which has been created as a standalone object. - /// - /// - /// The Type T must not only be a but also have been processed by the Fody weaver, - /// so it has persistent properties. - /// - /// Must be a standalone , null not allowed. - /// - /// If you invoke this when there is no write active on the . - /// - /// - /// You can't manage an object with more than one . - /// - /// - /// If the object is already managed by this , this method does nothing. - /// This method modifies the object in-place, - /// meaning that after it has run, will be managed. - /// Once an becomes managed dereferencing any property - /// of the original reference throws an exception. - /// - public void Add(T obj) - where T : IAsymmetricObject - { - ThrowIfDisposed(); - Argument.NotNull(obj, nameof(obj)); - Argument.Ensure(!obj.IsManaged, $"{nameof(obj)} must not be already managed by a Realm.", nameof(obj)); - - AddInternal(obj, obj.GetType(), update: false); - } - - /// - /// Add a collection of standalone AsymmetricObjects to this . - /// - /// - /// The Type T must not only be a but also have been processed by the Fody weaver, - /// so it has persistent properties. - /// - /// A collection of instances that will be added to this . - /// - /// If you invoke this when there is no write active on the . - /// - /// - /// You can't manage an object with more than one . - /// - /// - /// If the collection contains items that are already managed by this , they will be ignored. - /// This method modifies the objects in-place, meaning that after it has run, all items in the collection will be managed. - /// Once an becomes managed and the transaction is committed, - /// dereferencing any property of the original reference throw an exception. - /// Hence, none of the properties of the elements in the collection can be dereferenced anymore after the transaction. - /// - public void Add(IEnumerable objs) - where T : IAsymmetricObject - { - ThrowIfDisposed(); - Argument.NotNull(objs, nameof(objs)); - foreach (var obj in objs) - { - Argument.Ensure(obj != null, $"{nameof(objs)} must not contain null values.", nameof(objs)); - Argument.Ensure(!obj.IsManaged, $"{nameof(objs)} must not contain already managed objects by a Realm.", nameof(objs)); - } - - foreach (var obj in objs) - { - AddInternal(obj, obj.GetType(), update: false); - } - } - internal void ManageEmbedded(IEmbeddedObject obj, ObjectHandle handle) { var objectType = obj.GetType(); @@ -1239,12 +1170,6 @@ public void RemoveAll() /// 1. The destination file cannot already exist. /// 2. When using a local Realm and this is called from within a transaction it writes the current data, /// and not the data as it was when the last transaction was committed. - /// 3. When using Sync, it is required that all local changes are synchronized with the server before the copy can be written. - /// This is to be sure that the file can be used as a starting point for a newly installed application. - /// The function will throw if there are pending uploads. - /// 4. Writing a copy to a flexible sync realm is not supported unless flexible sync is already enabled. - /// 5 Changing from flexible sync sync to partition based sync is not supported. - /// 6. Changing the partition to synchronize on is not supported. /// /// Configuration, specifying the path and optionally the encryption key for the copy. public void WriteCopy(RealmConfigurationBase config) @@ -1675,7 +1600,6 @@ public IQueryable All(string className) Argument.Ensure(_realm.Metadata.TryGetValue(className, out var metadata), $"The class {className} is not in the limited set of classes for this realm", nameof(className)); Argument.Ensure(metadata.Schema.BaseType != ObjectSchema.ObjectType.EmbeddedObject, $"The class {className} represents an embedded object and thus cannot be queried directly.", nameof(className)); - Argument.Ensure(metadata.Schema.BaseType != ObjectSchema.ObjectType.AsymmetricObject, $"The class {className} represents an asymmetric object and thus cannot be queried.", nameof(className)); return new RealmResults(_realm, metadata); } diff --git a/Realm/Realm/Realm.csproj b/Realm/Realm/Realm.csproj index 93e7caa304..a5c7743deb 100644 --- a/Realm/Realm/Realm.csproj +++ b/Realm/Realm/Realm.csproj @@ -42,7 +42,6 @@ false - false diff --git a/Realm/Realm/Schema/ObjectSchema.cs b/Realm/Realm/Schema/ObjectSchema.cs index ac45931e91..444cdea49b 100644 --- a/Realm/Realm/Schema/ObjectSchema.cs +++ b/Realm/Realm/Schema/ObjectSchema.cs @@ -50,11 +50,6 @@ public enum ObjectType : byte /// The value represents a schema type. /// EmbeddedObject = 1, - - /// - /// The value represents a schema type. - /// - AsymmetricObject = 2, } private static readonly ConcurrentDictionary _cache = new(); @@ -209,8 +204,8 @@ public Builder(string name, ObjectType schemaType = ObjectType.RealmObject) /// provided . /// /// - /// The that will be used to populate the builder. It must be a , - /// an , or an inheritor. + /// The that will be used to populate the builder. It must be a or + /// an inheritor. /// /// /// If you want to use strongly typed API, such as Realm.Add<T> or @@ -253,8 +248,8 @@ public Builder(string name, ObjectType schemaType = ObjectType.RealmObject) public Builder(Type type) { Argument.NotNull(type, nameof(type)); - Argument.Ensure(type.IsRealmObject() || type.IsEmbeddedObject() || type.IsAsymmetricObject(), - $"The class {type.FullName} must descend directly from either RealmObject, EmbeddedObject, or AsymmetricObject", nameof(type)); + Argument.Ensure(type.IsRealmObject() || type.IsEmbeddedObject(), + $"The class {type.FullName} must descend directly from either RealmObject or EmbeddedObject", nameof(type)); RealmSchemaType = type.GetRealmSchemaType(); diff --git a/Realm/Realm/Schema/RealmSchema.cs b/Realm/Realm/Schema/RealmSchema.cs index 4aa37d4362..d5cba540ba 100644 --- a/Realm/Realm/Schema/RealmSchema.cs +++ b/Realm/Realm/Schema/RealmSchema.cs @@ -32,7 +32,7 @@ namespace Realms.Schema /// dynamically, by evaluating a Realm from disk. To construct a new instance, use the /// RealmSchema.Builder API. ///
- /// By default this will be all the s, s and s + /// By default, this will be all the s and s /// in all your assemblies. Unless you restrict with . Just because a given class may /// be stored in a Realm doesn't imply much overhead. There will be a small amount of metadata but objects only start to /// take up space once written. @@ -77,9 +77,9 @@ public static void AddDefaultTypes(IEnumerable types) foreach (var type in types) { - if (!type.IsRealmObject() && !type.IsEmbeddedObject() && !type.IsAsymmetricObject()) + if (!type.IsRealmObject() && !type.IsEmbeddedObject()) { - throw new ArgumentException($"The type {type.FullName} must inherit directly from RealmObject, AsymmetricObject or EmbeddedObject to be used in the Realm schema."); + throw new ArgumentException($"The type {type.FullName} must inherit directly from RealmObject or EmbeddedObject to be used in the Realm schema."); } if (_defaultTypes.Add(type) && diff --git a/Tests/Benchmarks/Benchmarks/FodyWeavers.xml b/Tests/Benchmarks/Benchmarks/FodyWeavers.xml index 3e1a999a33..8a24e482bf 100644 --- a/Tests/Benchmarks/Benchmarks/FodyWeavers.xml +++ b/Tests/Benchmarks/Benchmarks/FodyWeavers.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/Tests/Benchmarks/PerformanceTests/FodyWeavers.xml b/Tests/Benchmarks/PerformanceTests/FodyWeavers.xml index 3e1a999a33..8a24e482bf 100644 --- a/Tests/Benchmarks/PerformanceTests/FodyWeavers.xml +++ b/Tests/Benchmarks/PerformanceTests/FodyWeavers.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/Tests/Realm.Tests/Database/DateTimeTests.cs b/Tests/Realm.Tests/Database/DateTimeTests.cs index c27bb8698d..1e5540f2e3 100644 --- a/Tests/Realm.Tests/Database/DateTimeTests.cs +++ b/Tests/Realm.Tests/Database/DateTimeTests.cs @@ -20,12 +20,8 @@ using System.Linq; using NUnit.Framework; #if TEST_WEAVER -using TestAsymmetricObject = Realms.AsymmetricObject; -using TestEmbeddedObject = Realms.EmbeddedObject; using TestRealmObject = Realms.RealmObject; #else -using TestAsymmetricObject = Realms.IAsymmetricObject; -using TestEmbeddedObject = Realms.IEmbeddedObject; using TestRealmObject = Realms.IRealmObject; #endif diff --git a/Tests/Realm.Tests/Database/DynamicEmbeddedTests.cs b/Tests/Realm.Tests/Database/DynamicEmbeddedTests.cs index fc4f843254..b822e7f1d0 100644 --- a/Tests/Realm.Tests/Database/DynamicEmbeddedTests.cs +++ b/Tests/Realm.Tests/Database/DynamicEmbeddedTests.cs @@ -17,7 +17,6 @@ //////////////////////////////////////////////////////////////////////////// #if TEST_WEAVER -using TestAsymmetricObject = Realms.AsymmetricObject; using TestEmbeddedObject = Realms.EmbeddedObject; using TestRealmObject = Realms.RealmObject; #else diff --git a/Tests/Realm.Tests/Database/DynamicRelationshipTests.cs b/Tests/Realm.Tests/Database/DynamicRelationshipTests.cs index 129074e716..b7f9c93565 100644 --- a/Tests/Realm.Tests/Database/DynamicRelationshipTests.cs +++ b/Tests/Realm.Tests/Database/DynamicRelationshipTests.cs @@ -21,8 +21,6 @@ using System.Linq; using NUnit.Framework; #if TEST_WEAVER -using TestAsymmetricObject = Realms.AsymmetricObject; -using TestEmbeddedObject = Realms.EmbeddedObject; using TestRealmObject = Realms.RealmObject; #else using TestRealmObject = Realms.IRealmObject; diff --git a/Tests/Realm.Tests/Database/GuidRepresentationMigrationTests.cs b/Tests/Realm.Tests/Database/GuidRepresentationMigrationTests.cs index add1944708..39ae4852f7 100644 --- a/Tests/Realm.Tests/Database/GuidRepresentationMigrationTests.cs +++ b/Tests/Realm.Tests/Database/GuidRepresentationMigrationTests.cs @@ -23,7 +23,6 @@ using NUnit.Framework; using Realms.Logging; #if TEST_WEAVER -using TestAsymmetricObject = Realms.AsymmetricObject; using TestEmbeddedObject = Realms.EmbeddedObject; using TestRealmObject = Realms.RealmObject; #else diff --git a/Tests/Realm.Tests/Database/InstanceTests.cs b/Tests/Realm.Tests/Database/InstanceTests.cs index 0383366379..ac4ba96d29 100644 --- a/Tests/Realm.Tests/Database/InstanceTests.cs +++ b/Tests/Realm.Tests/Database/InstanceTests.cs @@ -318,7 +318,7 @@ public void RealmObjectClassesOnlyAllowRealmObjects() })!; Assert.That(ex.Message, Does.Contain("System.Object")); - Assert.That(ex.Message, Does.Contain("must descend directly from either RealmObject, EmbeddedObject, or AsymmetricObject")); + Assert.That(ex.Message, Does.Contain("must descend directly from either RealmObject or EmbeddedObject")); } [TestCase(false, true)] @@ -733,7 +733,6 @@ public void GetInstance_WhenDynamic_ReadsSchemaFromDisk() Assert.That(dynamicRealm.Schema.TryFindObjectSchema(nameof(AllTypesObject), out var allTypesSchema), Is.True); Assert.That(allTypesSchema, Is.Not.Null); Assert.That(allTypesSchema!.BaseType, Is.Not.EqualTo(ObjectSchema.ObjectType.EmbeddedObject)); - Assert.That(allTypesSchema.BaseType, Is.Not.EqualTo(ObjectSchema.ObjectType.AsymmetricObject)); var hasExpectedProp = allTypesSchema.TryFindProperty(nameof(AllTypesObject.RequiredStringProperty), out var requiredStringProp); Assert.That(hasExpectedProp); diff --git a/Tests/Realm.Tests/Database/ObjectSchemaTests.cs b/Tests/Realm.Tests/Database/ObjectSchemaTests.cs index 79b989bec2..fab066890e 100644 --- a/Tests/Realm.Tests/Database/ObjectSchemaTests.cs +++ b/Tests/Realm.Tests/Database/ObjectSchemaTests.cs @@ -530,7 +530,6 @@ public void ObjectSchemaBuilder_FromType_AddsCorrectProperties() var builder = new ObjectSchema.Builder(typeof(ClassWithUnqueryableMembers)); Assert.That(builder.Name, Is.EqualTo(nameof(ClassWithUnqueryableMembers))); Assert.That(builder.RealmSchemaType, Is.Not.EqualTo(ObjectSchema.ObjectType.EmbeddedObject)); - Assert.That(builder.RealmSchemaType, Is.Not.EqualTo(ObjectSchema.ObjectType.AsymmetricObject)); Assert.That(builder.Contains(nameof(ClassWithUnqueryableMembers.PublicField)), Is.False); Assert.That(builder.Contains(nameof(ClassWithUnqueryableMembers.PublicMethod)), Is.False); @@ -554,7 +553,6 @@ public void ObjectSchemaBuilder_FromType_CanAddProperties() var builder = new ObjectSchema.Builder(typeof(PrimaryKeyGuidObject)); Assert.That(builder.Name, Is.EqualTo(nameof(PrimaryKeyGuidObject))); Assert.That(builder.RealmSchemaType, Is.Not.EqualTo(ObjectSchema.ObjectType.EmbeddedObject)); - Assert.That(builder.RealmSchemaType, Is.Not.EqualTo(ObjectSchema.ObjectType.AsymmetricObject)); Assert.That(builder.Count, Is.EqualTo(1)); @@ -885,7 +883,6 @@ public void ObjectSchema_FromType_RemappedType() Assert.That(schema.Name, Is.EqualTo("__RemappedTypeObject")); Assert.That(schema.BaseType, Is.Not.EqualTo(ObjectSchema.ObjectType.EmbeddedObject)); - Assert.That(schema.BaseType, Is.Not.EqualTo(ObjectSchema.ObjectType.AsymmetricObject)); Assert.That(schema.TryFindProperty("__mappedLink", out var remappedProp), Is.True); Assert.That(remappedProp.Type, Is.EqualTo(PropertyType.Object | PropertyType.Nullable)); Assert.That(remappedProp.ObjectType, Is.EqualTo("__RemappedTypeObject")); @@ -898,7 +895,6 @@ public void ObjectSchema_FromType_ResolvesPrimaryKey() Assert.That(schema.Name, Is.EqualTo(nameof(PrimaryKeyStringObject))); Assert.That(schema.BaseType, Is.Not.EqualTo(ObjectSchema.ObjectType.EmbeddedObject)); - Assert.That(schema.BaseType, Is.Not.EqualTo(ObjectSchema.ObjectType.AsymmetricObject)); Assert.That(schema.PrimaryKeyProperty, Is.Not.Null); Assert.That(schema.PrimaryKeyProperty!.Value.IsPrimaryKey, Is.True); Assert.That(schema.PrimaryKeyProperty.Value.Type, Is.EqualTo(PropertyType.NullableString)); @@ -1564,7 +1560,6 @@ private static void ValidateBuiltSchema(ObjectSchema.Builder builder, params Pro var schema = builder.Build(); Assert.That(schema.Name, Is.EqualTo("MyClass")); Assert.That(schema.BaseType, Is.Not.EqualTo(ObjectSchema.ObjectType.EmbeddedObject)); - Assert.That(schema.BaseType, Is.Not.EqualTo(ObjectSchema.ObjectType.AsymmetricObject)); Assert.That(schema.Count, Is.EqualTo(expectedProperties.Length)); foreach (var prop in expectedProperties) diff --git a/Tests/Realm.Tests/Database/PropertyChangedTests.cs b/Tests/Realm.Tests/Database/PropertyChangedTests.cs index 764471a8a1..1addb472ce 100644 --- a/Tests/Realm.Tests/Database/PropertyChangedTests.cs +++ b/Tests/Realm.Tests/Database/PropertyChangedTests.cs @@ -23,8 +23,6 @@ using System.Threading.Tasks; using NUnit.Framework; #if TEST_WEAVER -using TestAsymmetricObject = Realms.AsymmetricObject; -using TestEmbeddedObject = Realms.EmbeddedObject; using TestRealmObject = Realms.RealmObject; #else using TestRealmObject = Realms.IRealmObject; diff --git a/Tests/Realm.Tests/Database/RelationshipTests.cs b/Tests/Realm.Tests/Database/RelationshipTests.cs index f1f70d304c..d12e4bb554 100644 --- a/Tests/Realm.Tests/Database/RelationshipTests.cs +++ b/Tests/Realm.Tests/Database/RelationshipTests.cs @@ -21,8 +21,6 @@ using System.Linq; using NUnit.Framework; #if TEST_WEAVER -using TestAsymmetricObject = Realms.AsymmetricObject; -using TestEmbeddedObject = Realms.EmbeddedObject; using TestRealmObject = Realms.RealmObject; #else using TestRealmObject = Realms.IRealmObject; diff --git a/Tests/Realm.Tests/FodyWeavers.xml b/Tests/Realm.Tests/FodyWeavers.xml index 6769f96097..ef20bfeea8 100644 --- a/Tests/Realm.Tests/FodyWeavers.xml +++ b/Tests/Realm.Tests/FodyWeavers.xml @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/AsymmetricObjectWithAllTypes_generated.cs b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/AsymmetricObjectWithAllTypes_generated.cs index 33b7c863d1..95f27265b8 100644 --- a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/AsymmetricObjectWithAllTypes_generated.cs +++ b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/AsymmetricObjectWithAllTypes_generated.cs @@ -21,7 +21,6 @@ using System.Runtime.Serialization; using System.Threading.Tasks; using System.Xml.Serialization; -using TestAsymmetricObject = Realms.IAsymmetricObject; namespace Realms.Tests.Sync { diff --git a/Tests/Realm.Tests/Realm.Tests.csproj b/Tests/Realm.Tests/Realm.Tests.csproj index e470ddd88c..32f5ca3239 100644 --- a/Tests/Realm.Tests/Realm.Tests.csproj +++ b/Tests/Realm.Tests/Realm.Tests.csproj @@ -56,10 +56,6 @@ $(RealmDllsPath)\Realm.dll - - $(RealmDllsPath)\Realm.PlatformHelpers.dll - -