From 7ea8be1f74034bae860120d58d3491c4fcedff5b Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 30 Aug 2024 12:45:29 -0700 Subject: [PATCH] Remove all Atlas App Services/Device Sync functionality --- .github/workflows/publish-release.yml | 16 +- CHANGELOG.md | 56 +- Configuration/Debug.xcconfig | 2 +- Configuration/ObjectServerTests.xcconfig | 5 - Configuration/Release.xcconfig | 2 +- Configuration/Static.xcconfig | 2 +- Configuration/SwiftUISyncTestHost.xcconfig | 4 - Configuration/SwiftUISyncTests.xcconfig | 11 - Package.swift | 194 +- README.md | 5 +- Realm.podspec | 38 +- Realm.xcodeproj/project.pbxproj | 1030 +------- .../xcschemes/Download BaaS.xcscheme | 67 - .../xcschemes/Object Server Tests.xcscheme | 142 -- .../xcschemes/SwiftUISyncTestHost.xcscheme | 119 - .../xcschemes/SwiftUISyncTests.xcscheme | 82 - Realm/NSError+RLMSync.h | 46 - Realm/NSError+RLMSync.m | 43 - Realm/ObjectServerTests/AsyncSyncTests.swift | 1349 ----------- .../ObjectServerTests/ClientResetTests.swift | 736 ------ .../ObjectServerTests/CombineSyncTests.swift | 731 ------ Realm/ObjectServerTests/EventTests.swift | 571 ----- .../Object-Server-Tests-Bridging-Header.h | 23 - .../ObjectServerTests-Info.plist | 22 - .../RLMAsymmetricSyncServerTests.mm | 245 -- Realm/ObjectServerTests/RLMBSONTests.mm | 159 -- .../RLMCollectionSyncTests.mm | 473 ---- .../RLMFlexibleSyncServerTests.mm | 586 ----- .../ObjectServerTests/RLMMongoClientTests.mm | 801 ------- .../RLMObjectServerPartitionTests.mm | 83 - .../ObjectServerTests/RLMObjectServerTests.mm | 2121 ----------------- .../ObjectServerTests/RLMServerTestObjects.h | 142 -- .../ObjectServerTests/RLMServerTestObjects.m | 338 --- .../ObjectServerTests/RLMSubscriptionTests.mm | 668 ------ Realm/ObjectServerTests/RLMSyncTestCase.h | 265 -- Realm/ObjectServerTests/RLMSyncTestCase.mm | 699 ------ .../RLMUser+ObjectServerTests.h | 31 - .../RLMUser+ObjectServerTests.mm | 42 - Realm/ObjectServerTests/RLMWatchTestUtility.h | 47 - Realm/ObjectServerTests/RLMWatchTestUtility.m | 84 - Realm/ObjectServerTests/RealmServer.swift | 1243 ---------- .../SwiftAsymmetricSyncServerTests.swift | 300 --- .../SwiftCollectionSyncTests.swift | 929 -------- .../SwiftFlexibleSyncServerTests.swift | 1253 ---------- .../SwiftMongoClientTests.swift | 1257 ---------- .../SwiftObjectServerPartitionTests.swift | 84 - .../SwiftObjectServerTests.swift | 1390 ----------- .../SwiftServerObjects.swift | 227 -- .../ObjectServerTests/SwiftSyncTestCase.swift | 346 --- .../SwiftUIServerTests.swift | 635 ----- .../TimeoutProxyServer.swift | 126 - .../ObjectServerTests/WatchTestUtility.swift | 89 - .../ObjectServerTests/certificates/ca-key.pem | 27 - Realm/ObjectServerTests/certificates/ca.pem | 19 - .../certificates/localhost-cert-key.pem | 27 - .../certificates/localhost-cert.pem | 19 - .../certificates/localhost-other-cert.pem | 19 - .../certificates/localhost-other.cer | Bin 785 -> 0 bytes .../certificates/localhost.cer | Bin 785 -> 0 bytes .../certificates/not-localhost-cert.pem | 19 - .../certificates/not-localhost.cer | Bin 789 -> 0 bytes Realm/ObjectServerTests/config_overrides.json | 34 - .../include/RLMSyncTestCase.h | 1 - .../include/RLMUser+ObjectServerTests.h | 1 - Realm/ObjectServerTests/setup_baas.rb | 225 -- Realm/RLMAPIKeyAuth.h | 95 - Realm/RLMAPIKeyAuth.mm | 90 - Realm/RLMAnalytics.hpp | 61 - Realm/RLMAnalytics.mm | 427 ---- Realm/RLMApp.h | 239 -- Realm/RLMApp.mm | 502 ---- Realm/RLMApp_Private.h | 60 - Realm/RLMApp_Private.hpp | 39 - Realm/RLMAsymmetricObject.h | 98 - Realm/RLMAsymmetricObject.mm | 101 - Realm/RLMAsyncTask.h | 46 +- Realm/RLMAsyncTask.mm | 285 +-- Realm/RLMAsyncTask_Private.h | 28 +- Realm/RLMBSON.h | 174 -- Realm/RLMBSON.mm | 423 ---- Realm/RLMBSON_Private.hpp | 31 - Realm/RLMClassInfo.hpp | 2 - Realm/RLMClassInfo.mm | 2 +- Realm/RLMCollection.h | 4 +- Realm/RLMCollection.mm | 3 +- Realm/RLMCollection_Private.h | 2 +- Realm/RLMConstants.h | 15 +- Realm/RLMCredentials.h | 126 - Realm/RLMCredentials.mm | 87 - Realm/RLMCredentials_Private.hpp | 25 - Realm/RLMDecimal128.h | 7 +- Realm/RLMDecimal128.mm | 4 +- Realm/RLMEmailPasswordAuth.h | 120 - Realm/RLMEmailPasswordAuth.mm | 78 - Realm/RLMError.h | 296 --- Realm/RLMError.mm | 216 +- Realm/RLMError_Private.hpp | 11 - Realm/RLMEvent.h | 64 - Realm/RLMEvent.mm | 242 -- Realm/RLMFindOneAndModifyOptions.h | 80 - Realm/RLMFindOneAndModifyOptions.mm | 110 - Realm/RLMFindOneAndModifyOptions_Private.hpp | 25 - Realm/RLMFindOptions.h | 77 - Realm/RLMFindOptions.mm | 116 - Realm/RLMFindOptions_Private.hpp | 29 - Realm/RLMGeospatial.h | 10 +- Realm/RLMInitialSubscriptionsConfiguration.h | 70 - Realm/RLMInitialSubscriptionsConfiguration.m | 35 - Realm/RLMLogger.h | 4 +- Realm/RLMMongoClient.h | 47 - Realm/RLMMongoClient.mm | 67 - Realm/RLMMongoClient_Private.hpp | 34 - Realm/RLMMongoCollection.h | 330 --- Realm/RLMMongoCollection.mm | 445 ---- Realm/RLMMongoCollection_Private.h | 37 - Realm/RLMMongoDatabase.h | 51 - Realm/RLMMongoDatabase_Private.hpp | 36 - Realm/RLMNetworkTransport.h | 132 - Realm/RLMNetworkTransport.mm | 240 -- Realm/RLMNetworkTransport_Private.hpp | 25 - Realm/RLMObjectBase.mm | 9 +- Realm/RLMObjectId.h | 2 +- Realm/RLMObjectSchema.h | 2 +- Realm/RLMProperty.h | 4 +- Realm/RLMProviderClient.h | 32 - Realm/RLMProviderClient.mm | 48 - Realm/RLMProviderClient_Private.hpp | 34 - Realm/RLMPushClient.h | 47 - Realm/RLMPushClient.mm | 57 - Realm/RLMPushClient_Private.hpp | 28 - Realm/RLMQueryUtil.mm | 34 +- Realm/RLMRealm+Sync.h | 38 - Realm/RLMRealm+Sync.mm | 40 - Realm/RLMRealm.h | 25 +- Realm/RLMRealm.mm | 91 +- Realm/RLMRealmConfiguration.h | 15 +- Realm/RLMRealmConfiguration.mm | 93 +- Realm/RLMRealm_Private.h | 5 +- Realm/RLMResults.h | 173 -- Realm/RLMResults.mm | 133 -- Realm/RLMResults_Private.h | 7 - Realm/RLMScheduler.h | 2 +- Realm/RLMSchema.h | 2 +- Realm/RLMSyncConfiguration.h | 203 -- Realm/RLMSyncConfiguration.mm | 327 --- Realm/RLMSyncConfiguration_Private.h | 47 - Realm/RLMSyncConfiguration_Private.hpp | 43 - Realm/RLMSyncManager.h | 223 -- Realm/RLMSyncManager.mm | 269 --- Realm/RLMSyncManager_Private.hpp | 61 - Realm/RLMSyncSession.h | 326 --- Realm/RLMSyncSession.mm | 289 --- Realm/RLMSyncSession_Private.hpp | 49 - Realm/RLMSyncSubscription.h | 369 --- Realm/RLMSyncSubscription.mm | 600 ----- Realm/RLMSyncSubscription_Private.h | 72 - Realm/RLMSyncSubscription_Private.hpp | 66 - Realm/RLMSyncUtil.mm | 49 - Realm/RLMSyncUtil_Private.hpp | 28 - Realm/RLMThreadSafeReference.h | 2 +- Realm/RLMUpdateChecker.hpp | 20 - Realm/RLMUpdateChecker.mm | 60 - Realm/RLMUpdateResult.h | 45 - Realm/RLMUpdateResult.mm | 38 - Realm/RLMUpdateResult_Private.hpp | 27 - Realm/RLMUser.h | 447 ---- Realm/RLMUser.mm | 448 ---- Realm/RLMUserAPIKey.h | 43 - Realm/RLMUserAPIKey.mm | 68 - Realm/RLMUserAPIKey_Private.hpp | 26 - Realm/RLMUser_Private.h | 42 - Realm/RLMUser_Private.hpp | 45 - Realm/Realm-Info.plist | 4 +- Realm/Realm.h | 25 - Realm/Realm.modulemap | 32 - Realm/TestUtils/RLMTestCase.m | 4 +- Realm/TestUtils/TestUtils.mm | 78 - Realm/TestUtils/include/RLMTestCase.h | 4 +- Realm/TestUtils/include/TestUtils.h | 8 +- Realm/Tests/ArrayPropertyTests.m | 7 +- Realm/Tests/Decimal128Tests.m | 30 +- Realm/Tests/DictionaryPropertyTests.m | 9 +- Realm/Tests/ObjectTests.m | 23 +- Realm/Tests/QueryTests.m | 71 +- Realm/Tests/RealmConfigurationTests.mm | 19 - Realm/Tests/RealmTests.mm | 51 +- Realm/Tests/SchemaTests.mm | 22 - Realm/Tests/SetPropertyTests.m | 7 +- .../SwiftUISyncTestHost/ContentView.swift | 558 ----- Realm/Tests/SwiftUISyncTestHost/Info.plist | 37 - .../SwiftUISyncTestHostApp.swift | 29 - .../Tests/SwiftUISyncTestHost/TestType.swift | 30 - .../SwiftUISyncTestHostUITests/Info.plist | 22 - ...iftUISyncTestHostUITests-Bridging-Header.h | 23 - .../SwiftUISyncTestHostUITests.entitlements | 8 - .../SwiftUISyncTestHostUITests.swift | 396 --- RealmSwift.podspec | 3 +- RealmSwift/App.swift | 638 ----- RealmSwift/AsymmetricObject.swift | 132 - RealmSwift/BSON.swift | 492 ---- RealmSwift/Combine.swift | 64 - RealmSwift/Decimal128.swift | 13 +- RealmSwift/Error.swift | 7 - RealmSwift/Events.swift | 308 --- RealmSwift/Impl/ObjcBridgeable.swift | 2 +- RealmSwift/Map.swift | 49 +- RealmSwift/MongoClient.swift | 1297 ---------- RealmSwift/Nonsync.swift | 46 - RealmSwift/ObjectiveCSupport+BSON.swift | 184 -- RealmSwift/ObjectiveCSupport+Sync.swift | 60 - RealmSwift/ObjectiveCSupport.swift | 71 - RealmSwift/PersistedProperty.swift | 3 +- RealmSwift/Projection.swift | 7 +- RealmSwift/Realm.swift | 147 +- RealmSwift/RealmConfiguration.swift | 80 +- RealmSwift/Results.swift | 139 -- RealmSwift/SwiftUI.swift | 440 ---- RealmSwift/Sync.swift | 1266 ---------- RealmSwift/SyncSubscription.swift | 538 ----- RealmSwift/Tests/MapTests.swift | 34 +- RealmSwift/Tests/MixedCollectionTest.swift | 2 +- .../Tests/ModernObjectCreationTests.swift | 13 +- RealmSwift/Tests/ObjectCreationTests.swift | 105 +- RealmSwift/Tests/ObjectTests.swift | 5 +- RealmSwift/Tests/ObjectiveCSupportTests.swift | 6 - RealmSwift/Tests/PerformanceTests.swift | 201 -- RealmSwift/Tests/RealmTests.swift | 62 +- RealmSwift/Tests/SwiftBSONTests.swift | 203 -- RealmSwift/Tests/TestCase.swift | 17 +- build.sh | 56 +- ci_scripts/ci_post_clone.sh | 7 - ci_scripts/ci_pre_xcodebuild.sh | 11 - contrib/Development.md | 12 - dependencies.list | 5 +- examples/README.md | 8 - examples/ios/objc/Draw/AppDelegate.h | 25 - examples/ios/objc/Draw/AppDelegate.m | 105 - examples/ios/objc/Draw/Draw-Info.plist | 49 - examples/ios/objc/Draw/Draw.entitlements | 10 - .../AppIcon.appiconset/Contents.json | 98 - .../AppIcon.appiconset/RealmDraw-60@2x.png | Bin 23141 -> 0 bytes .../AppIcon.appiconset/RealmDraw-60@3x.png | Bin 28029 -> 0 bytes .../AppIcon.appiconset/RealmDraw-76.png | Bin 7008 -> 0 bytes .../AppIcon.appiconset/RealmDraw-76@2x.png | Bin 14533 -> 0 bytes .../AppIcon.appiconset/RealmDraw-83.5@2x.png | Bin 26860 -> 0 bytes .../Pencils/Charcoal.imageset/Charcoal.png | Bin 17327 -> 0 bytes .../Pencils/Charcoal.imageset/Charcoal@2x.png | Bin 21047 -> 0 bytes .../Pencils/Charcoal.imageset/Contents.json | 22 - .../Images.xcassets/Pencils/Contents.json | 6 - .../Pencils/Dove.imageset/Contents.json | 22 - .../Pencils/Dove.imageset/Dove.png | Bin 17278 -> 0 bytes .../Pencils/Dove.imageset/Dove@2x.png | Bin 20953 -> 0 bytes .../Pencils/Elephant.imageset/Contents.json | 22 - .../Pencils/Elephant.imageset/Elephant.png | Bin 17316 -> 0 bytes .../Pencils/Elephant.imageset/Elephant@2x.png | Bin 21035 -> 0 bytes .../Pencils/Flamingo.imageset/Contents.json | 22 - .../Pencils/Flamingo.imageset/Flamingo.png | Bin 17335 -> 0 bytes .../Pencils/Flamingo.imageset/Flamingo@2x.png | Bin 21050 -> 0 bytes .../Pencils/GrapeJelly.imageset/Contents.json | 22 - .../GrapeJelly.imageset/GrapeJelly.png | Bin 17326 -> 0 bytes .../GrapeJelly.imageset/GrapeJelly@2x.png | Bin 21035 -> 0 bytes .../Pencils/Indigo.imageset/Contents.json | 22 - .../Pencils/Indigo.imageset/Indigo.png | Bin 17332 -> 0 bytes .../Pencils/Indigo.imageset/Indigo@2x.png | Bin 21043 -> 0 bytes .../Pencils/Melon.imageset/Contents.json | 22 - .../Pencils/Melon.imageset/Melon.png | Bin 17304 -> 0 bytes .../Pencils/Melon.imageset/Melon@2x.png | Bin 21037 -> 0 bytes .../Pencils/Mulberry.imageset/Contents.json | 22 - .../Pencils/Mulberry.imageset/Mulberry.png | Bin 17312 -> 0 bytes .../Pencils/Mulberry.imageset/Mulberry@2x.png | Bin 21018 -> 0 bytes .../Pencils/Peach.imageset/Contents.json | 22 - .../Pencils/Peach.imageset/Peach.png | Bin 17303 -> 0 bytes .../Pencils/Peach.imageset/Peach@2x.png | Bin 21017 -> 0 bytes .../Pencils/SexySalmon.imageset/Contents.json | 22 - .../SexySalmon.imageset/SexySalmon.png | Bin 17301 -> 0 bytes .../SexySalmon.imageset/SexySalmon@2x.png | Bin 21036 -> 0 bytes .../Ultramarine.imageset/Contents.json | 22 - .../Ultramarine.imageset/Ultramarine.png | Bin 17321 -> 0 bytes .../Ultramarine.imageset/Ultramarine@2x.png | Bin 21023 -> 0 bytes examples/ios/objc/Draw/Models/DrawPath.h | 31 - examples/ios/objc/Draw/Models/DrawPath.m | 40 - examples/ios/objc/Draw/Models/DrawPoint.h | 31 - examples/ios/objc/Draw/Models/DrawPoint.m | 27 - examples/ios/objc/Draw/Models/UIColor+Realm.h | 24 - examples/ios/objc/Draw/Models/UIColor+Realm.m | 43 - examples/ios/objc/Draw/Views/CanvasView.h | 30 - examples/ios/objc/Draw/Views/CanvasView.m | 52 - examples/ios/objc/Draw/Views/DrawView.h | 22 - examples/ios/objc/Draw/Views/DrawView.m | 181 -- examples/ios/objc/Draw/Views/SwatchesView.h | 27 - examples/ios/objc/Draw/Views/SwatchesView.m | 159 -- examples/ios/objc/Draw/main.m | 28 - .../RealmExamples.xcodeproj/project.pbxproj | 395 --- .../xcshareddata/xcschemes/Draw.xcscheme | 102 - .../xcschemes/RACTableView.xcscheme | 116 - .../contents.xcworkspacedata | 3 - .../AppleAuthentication/AppDelegate.swift | 30 - .../AppleAuthentication.entitlements | 10 - .../AppIcon.appiconset/Contents.json | 98 - .../Assets.xcassets/Contents.json | 6 - .../Base.lproj/LaunchScreen.storyboard | 25 - .../AppleAuthentication/ContentView.swift | 92 - .../ios/swift/AppleAuthentication/Info.plist | 60 - .../Preview Assets.xcassets/Contents.json | 6 - .../ios/swift/AppleAuthentication/README.md | 8 - .../AppleAuthentication/SceneDelegate.swift | 35 - .../AccentColor.colorset/Contents.json | 11 - .../AppIcon.appiconset/Contents.json | 148 -- .../Assets.xcassets/Contents.json | 6 - .../AsyncOpenSwiftUI--iOS--Info.plist | 13 - .../AsyncOpenSwiftUI--macOS--Info.plist | 8 - .../AsyncOpenSwiftUIApp.swift | 28 - .../swift/AsyncOpenSwiftUI/ContentView.swift | 301 --- .../RealmExamples.xcodeproj/project.pbxproj | 523 ---- include/Realm/NSError+RLMSync.h | 1 - include/Realm/RLMAPIKeyAuth.h | 1 - include/Realm/RLMApp.h | 1 - include/Realm/RLMApp_Private.h | 1 - include/Realm/RLMAsymmetricObject.h | 1 - include/Realm/RLMBSON.h | 1 - include/Realm/RLMCredentials.h | 1 - include/Realm/RLMEmailPasswordAuth.h | 1 - include/Realm/RLMEvent.h | 1 - include/Realm/RLMFindOneAndModifyOptions.h | 1 - include/Realm/RLMFindOptions.h | 1 - .../RLMInitialSubscriptionsConfiguration.h | 1 - include/Realm/RLMMongoClient.h | 1 - include/Realm/RLMMongoCollection.h | 1 - include/Realm/RLMMongoCollection_Private.h | 1 - include/Realm/RLMMongoDatabase.h | 1 - include/Realm/RLMNetworkTransport.h | 1 - include/Realm/RLMProviderClient.h | 1 - include/Realm/RLMPushClient.h | 1 - include/Realm/RLMRealm+Sync.h | 1 - include/Realm/RLMSyncConfiguration.h | 1 - include/Realm/RLMSyncConfiguration_Private.h | 1 - include/Realm/RLMSyncManager.h | 1 - include/Realm/RLMSyncSession.h | 1 - include/Realm/RLMSyncSubscription.h | 1 - include/Realm/RLMSyncSubscription_Private.h | 1 - include/Realm/RLMUpdateResult.h | 1 - include/Realm/RLMUser.h | 1 - include/Realm/RLMUserAPIKey.h | 1 - include/Realm/RLMUser_Private.h | 1 - include/module.modulemap | 6 - scripts/setup-cocoapods.sh | 1 - 346 files changed, 352 insertions(+), 42775 deletions(-) delete mode 100644 Configuration/ObjectServerTests.xcconfig delete mode 100644 Configuration/SwiftUISyncTestHost.xcconfig delete mode 100644 Configuration/SwiftUISyncTests.xcconfig delete mode 100644 Realm.xcodeproj/xcshareddata/xcschemes/Download BaaS.xcscheme delete mode 100644 Realm.xcodeproj/xcshareddata/xcschemes/Object Server Tests.xcscheme delete mode 100644 Realm.xcodeproj/xcshareddata/xcschemes/SwiftUISyncTestHost.xcscheme delete mode 100644 Realm.xcodeproj/xcshareddata/xcschemes/SwiftUISyncTests.xcscheme delete mode 100644 Realm/NSError+RLMSync.h delete mode 100644 Realm/NSError+RLMSync.m delete mode 100644 Realm/ObjectServerTests/AsyncSyncTests.swift delete mode 100644 Realm/ObjectServerTests/ClientResetTests.swift delete mode 100644 Realm/ObjectServerTests/CombineSyncTests.swift delete mode 100644 Realm/ObjectServerTests/EventTests.swift delete mode 100644 Realm/ObjectServerTests/Object-Server-Tests-Bridging-Header.h delete mode 100644 Realm/ObjectServerTests/ObjectServerTests-Info.plist delete mode 100644 Realm/ObjectServerTests/RLMAsymmetricSyncServerTests.mm delete mode 100644 Realm/ObjectServerTests/RLMBSONTests.mm delete mode 100644 Realm/ObjectServerTests/RLMCollectionSyncTests.mm delete mode 100644 Realm/ObjectServerTests/RLMFlexibleSyncServerTests.mm delete mode 100644 Realm/ObjectServerTests/RLMMongoClientTests.mm delete mode 100644 Realm/ObjectServerTests/RLMObjectServerPartitionTests.mm delete mode 100644 Realm/ObjectServerTests/RLMObjectServerTests.mm delete mode 100644 Realm/ObjectServerTests/RLMServerTestObjects.h delete mode 100644 Realm/ObjectServerTests/RLMServerTestObjects.m delete mode 100644 Realm/ObjectServerTests/RLMSubscriptionTests.mm delete mode 100644 Realm/ObjectServerTests/RLMSyncTestCase.h delete mode 100644 Realm/ObjectServerTests/RLMSyncTestCase.mm delete mode 100644 Realm/ObjectServerTests/RLMUser+ObjectServerTests.h delete mode 100644 Realm/ObjectServerTests/RLMUser+ObjectServerTests.mm delete mode 100644 Realm/ObjectServerTests/RLMWatchTestUtility.h delete mode 100644 Realm/ObjectServerTests/RLMWatchTestUtility.m delete mode 100644 Realm/ObjectServerTests/RealmServer.swift delete mode 100644 Realm/ObjectServerTests/SwiftAsymmetricSyncServerTests.swift delete mode 100644 Realm/ObjectServerTests/SwiftCollectionSyncTests.swift delete mode 100644 Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift delete mode 100644 Realm/ObjectServerTests/SwiftMongoClientTests.swift delete mode 100644 Realm/ObjectServerTests/SwiftObjectServerPartitionTests.swift delete mode 100644 Realm/ObjectServerTests/SwiftObjectServerTests.swift delete mode 100644 Realm/ObjectServerTests/SwiftServerObjects.swift delete mode 100644 Realm/ObjectServerTests/SwiftSyncTestCase.swift delete mode 100644 Realm/ObjectServerTests/SwiftUIServerTests.swift delete mode 100644 Realm/ObjectServerTests/TimeoutProxyServer.swift delete mode 100644 Realm/ObjectServerTests/WatchTestUtility.swift delete mode 100644 Realm/ObjectServerTests/certificates/ca-key.pem delete mode 100644 Realm/ObjectServerTests/certificates/ca.pem delete mode 100644 Realm/ObjectServerTests/certificates/localhost-cert-key.pem delete mode 100644 Realm/ObjectServerTests/certificates/localhost-cert.pem delete mode 100644 Realm/ObjectServerTests/certificates/localhost-other-cert.pem delete mode 100644 Realm/ObjectServerTests/certificates/localhost-other.cer delete mode 100644 Realm/ObjectServerTests/certificates/localhost.cer delete mode 100644 Realm/ObjectServerTests/certificates/not-localhost-cert.pem delete mode 100644 Realm/ObjectServerTests/certificates/not-localhost.cer delete mode 100644 Realm/ObjectServerTests/config_overrides.json delete mode 120000 Realm/ObjectServerTests/include/RLMSyncTestCase.h delete mode 120000 Realm/ObjectServerTests/include/RLMUser+ObjectServerTests.h delete mode 100644 Realm/ObjectServerTests/setup_baas.rb delete mode 100644 Realm/RLMAPIKeyAuth.h delete mode 100644 Realm/RLMAPIKeyAuth.mm delete mode 100644 Realm/RLMAnalytics.hpp delete mode 100644 Realm/RLMAnalytics.mm delete mode 100644 Realm/RLMApp.h delete mode 100644 Realm/RLMApp.mm delete mode 100644 Realm/RLMApp_Private.h delete mode 100644 Realm/RLMApp_Private.hpp delete mode 100644 Realm/RLMAsymmetricObject.h delete mode 100644 Realm/RLMAsymmetricObject.mm delete mode 100644 Realm/RLMBSON.h delete mode 100644 Realm/RLMBSON.mm delete mode 100644 Realm/RLMBSON_Private.hpp delete mode 100644 Realm/RLMCredentials.h delete mode 100644 Realm/RLMCredentials.mm delete mode 100644 Realm/RLMCredentials_Private.hpp delete mode 100644 Realm/RLMEmailPasswordAuth.h delete mode 100644 Realm/RLMEmailPasswordAuth.mm delete mode 100644 Realm/RLMEvent.h delete mode 100644 Realm/RLMEvent.mm delete mode 100644 Realm/RLMFindOneAndModifyOptions.h delete mode 100644 Realm/RLMFindOneAndModifyOptions.mm delete mode 100644 Realm/RLMFindOneAndModifyOptions_Private.hpp delete mode 100644 Realm/RLMFindOptions.h delete mode 100644 Realm/RLMFindOptions.mm delete mode 100644 Realm/RLMFindOptions_Private.hpp delete mode 100644 Realm/RLMInitialSubscriptionsConfiguration.h delete mode 100644 Realm/RLMInitialSubscriptionsConfiguration.m delete mode 100644 Realm/RLMMongoClient.h delete mode 100644 Realm/RLMMongoClient.mm delete mode 100644 Realm/RLMMongoClient_Private.hpp delete mode 100644 Realm/RLMMongoCollection.h delete mode 100644 Realm/RLMMongoCollection.mm delete mode 100644 Realm/RLMMongoCollection_Private.h delete mode 100644 Realm/RLMMongoDatabase.h delete mode 100644 Realm/RLMMongoDatabase_Private.hpp delete mode 100644 Realm/RLMNetworkTransport.h delete mode 100644 Realm/RLMNetworkTransport.mm delete mode 100644 Realm/RLMNetworkTransport_Private.hpp delete mode 100644 Realm/RLMProviderClient.h delete mode 100644 Realm/RLMProviderClient.mm delete mode 100644 Realm/RLMProviderClient_Private.hpp delete mode 100644 Realm/RLMPushClient.h delete mode 100644 Realm/RLMPushClient.mm delete mode 100644 Realm/RLMPushClient_Private.hpp delete mode 100644 Realm/RLMRealm+Sync.h delete mode 100644 Realm/RLMRealm+Sync.mm delete mode 100644 Realm/RLMSyncConfiguration.h delete mode 100644 Realm/RLMSyncConfiguration.mm delete mode 100644 Realm/RLMSyncConfiguration_Private.h delete mode 100644 Realm/RLMSyncConfiguration_Private.hpp delete mode 100644 Realm/RLMSyncManager.h delete mode 100644 Realm/RLMSyncManager.mm delete mode 100644 Realm/RLMSyncManager_Private.hpp delete mode 100644 Realm/RLMSyncSession.h delete mode 100644 Realm/RLMSyncSession.mm delete mode 100644 Realm/RLMSyncSession_Private.hpp delete mode 100644 Realm/RLMSyncSubscription.h delete mode 100644 Realm/RLMSyncSubscription.mm delete mode 100644 Realm/RLMSyncSubscription_Private.h delete mode 100644 Realm/RLMSyncSubscription_Private.hpp delete mode 100644 Realm/RLMSyncUtil.mm delete mode 100644 Realm/RLMSyncUtil_Private.hpp delete mode 100644 Realm/RLMUpdateChecker.hpp delete mode 100644 Realm/RLMUpdateChecker.mm delete mode 100644 Realm/RLMUpdateResult.h delete mode 100644 Realm/RLMUpdateResult.mm delete mode 100644 Realm/RLMUpdateResult_Private.hpp delete mode 100644 Realm/RLMUser.h delete mode 100644 Realm/RLMUser.mm delete mode 100644 Realm/RLMUserAPIKey.h delete mode 100644 Realm/RLMUserAPIKey.mm delete mode 100644 Realm/RLMUserAPIKey_Private.hpp delete mode 100644 Realm/RLMUser_Private.h delete mode 100644 Realm/RLMUser_Private.hpp delete mode 100644 Realm/Tests/SwiftUISyncTestHost/ContentView.swift delete mode 100644 Realm/Tests/SwiftUISyncTestHost/Info.plist delete mode 100644 Realm/Tests/SwiftUISyncTestHost/SwiftUISyncTestHostApp.swift delete mode 100644 Realm/Tests/SwiftUISyncTestHost/TestType.swift delete mode 100644 Realm/Tests/SwiftUISyncTestHostUITests/Info.plist delete mode 100644 Realm/Tests/SwiftUISyncTestHostUITests/SwiftUISyncTestHostUITests-Bridging-Header.h delete mode 100644 Realm/Tests/SwiftUISyncTestHostUITests/SwiftUISyncTestHostUITests.entitlements delete mode 100644 Realm/Tests/SwiftUISyncTestHostUITests/SwiftUISyncTestHostUITests.swift delete mode 100644 RealmSwift/App.swift delete mode 100644 RealmSwift/AsymmetricObject.swift delete mode 100644 RealmSwift/BSON.swift delete mode 100644 RealmSwift/Events.swift delete mode 100644 RealmSwift/MongoClient.swift delete mode 100644 RealmSwift/Nonsync.swift delete mode 100644 RealmSwift/ObjectiveCSupport+BSON.swift delete mode 100644 RealmSwift/ObjectiveCSupport+Sync.swift delete mode 100644 RealmSwift/Sync.swift delete mode 100644 RealmSwift/SyncSubscription.swift delete mode 100644 RealmSwift/Tests/SwiftBSONTests.swift delete mode 100755 ci_scripts/ci_pre_xcodebuild.sh delete mode 100644 examples/ios/objc/Draw/AppDelegate.h delete mode 100644 examples/ios/objc/Draw/AppDelegate.m delete mode 100644 examples/ios/objc/Draw/Draw-Info.plist delete mode 100644 examples/ios/objc/Draw/Draw.entitlements delete mode 100644 examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/RealmDraw-60@2x.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/RealmDraw-60@3x.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/RealmDraw-76.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/RealmDraw-76@2x.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/RealmDraw-83.5@2x.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Charcoal.imageset/Charcoal.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Charcoal.imageset/Charcoal@2x.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Charcoal.imageset/Contents.json delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Contents.json delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Dove.imageset/Contents.json delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Dove.imageset/Dove.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Dove.imageset/Dove@2x.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Elephant.imageset/Contents.json delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Elephant.imageset/Elephant.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Elephant.imageset/Elephant@2x.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Flamingo.imageset/Contents.json delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Flamingo.imageset/Flamingo.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Flamingo.imageset/Flamingo@2x.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/GrapeJelly.imageset/Contents.json delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/GrapeJelly.imageset/GrapeJelly.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/GrapeJelly.imageset/GrapeJelly@2x.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Indigo.imageset/Contents.json delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Indigo.imageset/Indigo.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Indigo.imageset/Indigo@2x.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Melon.imageset/Contents.json delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Melon.imageset/Melon.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Melon.imageset/Melon@2x.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Mulberry.imageset/Contents.json delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Mulberry.imageset/Mulberry.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Mulberry.imageset/Mulberry@2x.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Peach.imageset/Contents.json delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Peach.imageset/Peach.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Peach.imageset/Peach@2x.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/SexySalmon.imageset/Contents.json delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/SexySalmon.imageset/SexySalmon.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/SexySalmon.imageset/SexySalmon@2x.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Ultramarine.imageset/Contents.json delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Ultramarine.imageset/Ultramarine.png delete mode 100644 examples/ios/objc/Draw/Images.xcassets/Pencils/Ultramarine.imageset/Ultramarine@2x.png delete mode 100644 examples/ios/objc/Draw/Models/DrawPath.h delete mode 100644 examples/ios/objc/Draw/Models/DrawPath.m delete mode 100644 examples/ios/objc/Draw/Models/DrawPoint.h delete mode 100644 examples/ios/objc/Draw/Models/DrawPoint.m delete mode 100644 examples/ios/objc/Draw/Models/UIColor+Realm.h delete mode 100644 examples/ios/objc/Draw/Models/UIColor+Realm.m delete mode 100644 examples/ios/objc/Draw/Views/CanvasView.h delete mode 100644 examples/ios/objc/Draw/Views/CanvasView.m delete mode 100644 examples/ios/objc/Draw/Views/DrawView.h delete mode 100644 examples/ios/objc/Draw/Views/DrawView.m delete mode 100644 examples/ios/objc/Draw/Views/SwatchesView.h delete mode 100644 examples/ios/objc/Draw/Views/SwatchesView.m delete mode 100644 examples/ios/objc/Draw/main.m delete mode 100644 examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/Draw.xcscheme delete mode 100644 examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/RACTableView.xcscheme delete mode 100644 examples/ios/swift/AppleAuthentication/AppDelegate.swift delete mode 100644 examples/ios/swift/AppleAuthentication/AppleAuthentication.entitlements delete mode 100644 examples/ios/swift/AppleAuthentication/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 examples/ios/swift/AppleAuthentication/Assets.xcassets/Contents.json delete mode 100644 examples/ios/swift/AppleAuthentication/Base.lproj/LaunchScreen.storyboard delete mode 100644 examples/ios/swift/AppleAuthentication/ContentView.swift delete mode 100644 examples/ios/swift/AppleAuthentication/Info.plist delete mode 100644 examples/ios/swift/AppleAuthentication/Preview Content/Preview Assets.xcassets/Contents.json delete mode 100644 examples/ios/swift/AppleAuthentication/README.md delete mode 100644 examples/ios/swift/AppleAuthentication/SceneDelegate.swift delete mode 100644 examples/ios/swift/AsyncOpenSwiftUI/Assets.xcassets/AccentColor.colorset/Contents.json delete mode 100644 examples/ios/swift/AsyncOpenSwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 examples/ios/swift/AsyncOpenSwiftUI/Assets.xcassets/Contents.json delete mode 100644 examples/ios/swift/AsyncOpenSwiftUI/AsyncOpenSwiftUI--iOS--Info.plist delete mode 100644 examples/ios/swift/AsyncOpenSwiftUI/AsyncOpenSwiftUI--macOS--Info.plist delete mode 100644 examples/ios/swift/AsyncOpenSwiftUI/AsyncOpenSwiftUIApp.swift delete mode 100644 examples/ios/swift/AsyncOpenSwiftUI/ContentView.swift delete mode 120000 include/Realm/NSError+RLMSync.h delete mode 120000 include/Realm/RLMAPIKeyAuth.h delete mode 120000 include/Realm/RLMApp.h delete mode 120000 include/Realm/RLMApp_Private.h delete mode 120000 include/Realm/RLMAsymmetricObject.h delete mode 120000 include/Realm/RLMBSON.h delete mode 120000 include/Realm/RLMCredentials.h delete mode 120000 include/Realm/RLMEmailPasswordAuth.h delete mode 120000 include/Realm/RLMEvent.h delete mode 120000 include/Realm/RLMFindOneAndModifyOptions.h delete mode 120000 include/Realm/RLMFindOptions.h delete mode 120000 include/Realm/RLMInitialSubscriptionsConfiguration.h delete mode 120000 include/Realm/RLMMongoClient.h delete mode 120000 include/Realm/RLMMongoCollection.h delete mode 120000 include/Realm/RLMMongoCollection_Private.h delete mode 120000 include/Realm/RLMMongoDatabase.h delete mode 120000 include/Realm/RLMNetworkTransport.h delete mode 120000 include/Realm/RLMProviderClient.h delete mode 120000 include/Realm/RLMPushClient.h delete mode 120000 include/Realm/RLMRealm+Sync.h delete mode 120000 include/Realm/RLMSyncConfiguration.h delete mode 120000 include/Realm/RLMSyncConfiguration_Private.h delete mode 120000 include/Realm/RLMSyncManager.h delete mode 120000 include/Realm/RLMSyncSession.h delete mode 120000 include/Realm/RLMSyncSubscription.h delete mode 120000 include/Realm/RLMSyncSubscription_Private.h delete mode 120000 include/Realm/RLMUpdateResult.h delete mode 120000 include/Realm/RLMUser.h delete mode 120000 include/Realm/RLMUserAPIKey.h delete mode 120000 include/Realm/RLMUser_Private.h diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index abe0bb4c97..9ab1a8bb95 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -74,20 +74,6 @@ jobs: bundler-cache: true - name: Publish run: bundle exec ./build.sh publish-cocoapods v${{ needs.prepare.outputs.VERSION }} - update-checker: - runs-on: macos-latest - name: Update to latest version update checker file - needs: tag-release - env: - AWS_ACCESS_KEY_ID: ${{ secrets.UPDATE_CHECKER_ACCESS_KEY }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.UPDATE_CHECKER_SECRET_KEY }} - steps: - - uses: actions/checkout@v4 - - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - run: brew install s3cmd - - run: bundle exec ./build.sh publish-update-checker test-installation: runs-on: macos-14 name: Run installation test for ${{ matrix.platform }}, ${{ matrix.installation }} and ${{ matrix.linkage }} @@ -148,7 +134,7 @@ jobs: post-slack-release: runs-on: macos-latest name: Publish to release Slack channel - needs: [create-release, prepare, publish-cocoapods, update-checker, publish-docs] + needs: [create-release, prepare, publish-cocoapods, publish-docs] env: WEBHOOK_URL: ${{ secrets.SLACK_RELEASES_WEBHOOK }} steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8569ec6fd6..b309cf8ce4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ -x.y.z Release notes (yyyy-MM-dd) +20.0.0 Release notes (2024-09-09) ============================================================= The minimum supported version of Xcode is now 15.3. ### Enhancements + * Build in Swift 6 language mode when using Xcode 16. Libraries build in Swift 6 mode can be consumed by apps built in Swift 5 mode, so this should not have any immediate effects beyond eliminating some warnings and ensuring that all @@ -19,9 +20,6 @@ The minimum supported version of Xcode is now 15.3. of macros. It can still be used as a property wrapper for class properties and as a manual wrapper locally, but note that it does not combine well with actor-isolated Realms. - - In Swift 6 mode a few mongo client functions have changed from returning - `[AnyHashable: Any]` to `Document`. These should have been `Document` all - along, and the old return type no longer compiles due to not being Sendable. * Some SwiftUI components are now explicitly marked as `@MainActor`. These types were implicitly `@MainActor` in Swift 5, but became nonisolated when using Xcode 16 in Swift 5 mode due to the removal of implicit isolation when @@ -30,21 +28,59 @@ The minimum supported version of Xcode is now 15.3. * Add Xcode 16 and 16.1 binaries to the release packages (currently built with beta 6 and beta 1 respectively). -### Fixed -* ([#????](https://github.com/realm/realm-swift/issues/????), since v?.?.?) -* None. +### Breaking Changes - +* All Atlas App Services and Atlas Device Sync functionality has been removed. + Users of Atlas Device Sync should pin to v10. +* Queries on AnyRealmValue properties previously considered strings to be + equivalent to Data containing the UTF-8 encoded string. Strings and Data are + now considered different types and queries for one of them will not match the + other. +* Realms are no longer autoreleased when initialized. This means that code + along the lines of the following will no longer work: + + ```Swift + try! Realm().beginWrite() + try! Realm().create(MyObject.self, value: ...) + try! Realm().commitWrite() + ``` + + This was a pattern which was somewhat natural with the original version of + the objective-c API, but only worked in debug builds and would fail in + release builds. We decided to make it consistently work by forcing the Realm + to be autoreleased rather than let users write code which appeared to work + but was actually broken. In modern Swift this code is very strange, and + autoreleasing the Realm made it much more difficult to ensure that the + file is actually closed at predictable times. + + Realms are now returned retained in both debug and release modes, so this + will always break rather than appearing to work. Note that there is still a + weak cache of Realms and `Realm()` will still return a reference to the + existing Realm if there is one open on the current thread. +* Iterating a Map now produces the tuple `(key: KeyType, value: ValueType)` + rather than a very similar struct, and `.asKeyValueSequence()` has been + removed. This aligns `Map` with `Dictionary` and makes many operations + defined by `Sequence` work on `Map`. +* Passing an empty array for notification keypaths to filter on (e.g. + `obj.observe(keyPaths: [])`) was treated the same as passing `nil`, i.e. no + filtering was done. It now instead observes no keypaths. For objects this + means it will only report the object being deleted, and for collections it + will only report collection-level changes and not changes to the objects + inside the collection. +* `Decimal128(string:)` was marked as `throws`, but it never actually threw an + error and instead returned `NaN` if the string could not be parsed as a + decimal128. That behavior was kept and it is no longer marked as `throws`. ### Compatibility + * Realm Studio: 15.0.0 or later. -* APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 15.4.0. * CocoaPods: 1.10 or later. * Xcode: 15.3.0-16.1 beta. ### Internal -* Upgraded realm-core from ? to ? + +* Upgraded realm-core from v14.12.1 to v20.0.0. 10.53.1 Release notes (2024-09-05) ============================================================= diff --git a/Configuration/Debug.xcconfig b/Configuration/Debug.xcconfig index f0b835a4c8..44f54ca19f 100644 --- a/Configuration/Debug.xcconfig +++ b/Configuration/Debug.xcconfig @@ -7,4 +7,4 @@ ONLY_ACTIVE_ARCH = YES; SWIFT_OPTIMIZATION_LEVEL = -Onone; OTHER_SWIFT_FLAGS = -Xfrontend -enable-actor-data-race-checks; -GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 REALM_DEBUG REALM_HAVE_CONFIG REALM_ENABLE_SYNC __ASSERTMACROS__; +GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 REALM_DEBUG REALM_HAVE_CONFIG __ASSERTMACROS__; diff --git a/Configuration/ObjectServerTests.xcconfig b/Configuration/ObjectServerTests.xcconfig deleted file mode 100644 index 550c590e38..0000000000 --- a/Configuration/ObjectServerTests.xcconfig +++ /dev/null @@ -1,5 +0,0 @@ -#include "Realm/Tests.xcconfig" - -OTHER_LDFLAGS = -weak_framework Combine; -SDKROOT = macosx; -SUPPORTED_PLATFORMS = macosx; diff --git a/Configuration/Release.xcconfig b/Configuration/Release.xcconfig index ea9ecd7718..af9c3051fa 100644 --- a/Configuration/Release.xcconfig +++ b/Configuration/Release.xcconfig @@ -7,7 +7,7 @@ DEBUG_INFORMATION_FORMAT_1500 = dwarf; DEBUG_INFORMATION_FORMAT = $(DEBUG_INFORMATION_FORMAT_$(XCODE_VERSION_MAJOR)); ENABLE_NS_ASSERTIONS = NO; -GCC_PREPROCESSOR_DEFINITIONS = REALM_HAVE_CONFIG REALM_ENABLE_SYNC __ASSERTMACROS__; +GCC_PREPROCESSOR_DEFINITIONS = REALM_HAVE_CONFIG __ASSERTMACROS__; LLVM_LTO = YES_THIN; VALIDATE_PRODUCT = YES; diff --git a/Configuration/Static.xcconfig b/Configuration/Static.xcconfig index 5578205ce6..0f923eb861 100644 --- a/Configuration/Static.xcconfig +++ b/Configuration/Static.xcconfig @@ -2,4 +2,4 @@ REALM_MACH_O_TYPE = staticlib; LLVM_LTO = NO; -GCC_PREPROCESSOR_DEFINITIONS = REALM_STATIC_FRAMEWORK REALM_HAVE_CONFIG REALM_ENABLE_SYNC __ASSERTMACROS__; +GCC_PREPROCESSOR_DEFINITIONS = REALM_STATIC_FRAMEWORK REALM_HAVE_CONFIG __ASSERTMACROS__; diff --git a/Configuration/SwiftUISyncTestHost.xcconfig b/Configuration/SwiftUISyncTestHost.xcconfig deleted file mode 100644 index 4ae57f6734..0000000000 --- a/Configuration/SwiftUISyncTestHost.xcconfig +++ /dev/null @@ -1,4 +0,0 @@ -#include "TestHost.xcconfig" - -INFOPLIST_FILE = Realm/Tests/SwiftUISyncTestHost/Info.plist; -SUPPORTED_PLATFORMS = macosx; diff --git a/Configuration/SwiftUISyncTests.xcconfig b/Configuration/SwiftUISyncTests.xcconfig deleted file mode 100644 index e976f788b0..0000000000 --- a/Configuration/SwiftUISyncTests.xcconfig +++ /dev/null @@ -1,11 +0,0 @@ -#include "SwiftUISyncTestHost.xcconfig" - -TEST_TARGET_NAME = SwiftUISyncTestHost; -INFOPLIST_FILE = Realm/Tests/SwiftUISyncTestHostUITests/Info.plist; -SDKROOT = macosx; -SUPPORTED_PLATFORMS = macosx; -CODE_SIGN_ENTITLEMENTS = Realm/Tests/SwiftUISyncTestHostUITests/SwiftUISyncTestHostUITests.entitlements; -CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; -SWIFT_OBJC_BRIDGING_HEADER = Realm/Tests/SwiftUISyncTestHostUITests/SwiftUISyncTestHostUITests-Bridging-Header.h -TEST_HOST[sdk=appletv*] = ; -TEST_HOST[sdk=iphone*] = ; diff --git a/Package.swift b/Package.swift index 5321bbd27f..d54319d7c8 100644 --- a/Package.swift +++ b/Package.swift @@ -3,8 +3,8 @@ import PackageDescription import Foundation -let coreVersion = Version("14.12.1") -let cocoaVersion = Version("10.53.1") +let coreVersion = Version("20.0.0") +let cocoaVersion = Version("20.0.0") #if compiler(>=6) let swiftVersion = [SwiftVersion.version("6")] @@ -16,10 +16,8 @@ let cxxSettings: [CXXSetting] = [ .headerSearchPath("."), .headerSearchPath("include"), .define("REALM_SPM", to: "1"), - .define("REALM_ENABLE_SYNC", to: "1"), .define("REALM_COCOA_VERSION", to: "@\"\(cocoaVersion)\""), .define("REALM_VERSION", to: "\"\(coreVersion)\""), - .define("REALM_IOPLATFORMUUID", to: "@\"\(runCommand())\""), .define("REALM_DEBUG", .when(configuration: .debug)), .define("REALM_NO_CONFIG"), @@ -42,106 +40,6 @@ let testCxxSettings: [CXXSetting] = cxxSettings + [ .headerSearchPath(".."), ] -// SPM requires all targets to explicitly include or exclude every file, which -// gets very awkward when we have four targets building from a single directory -let objectServerTestSources = [ - "AsyncSyncTests.swift", - "ClientResetTests.swift", - "CombineSyncTests.swift", - "EventTests.swift", - "Object-Server-Tests-Bridging-Header.h", - "ObjectServerTests-Info.plist", - "RLMAsymmetricSyncServerTests.mm", - "RLMBSONTests.mm", - "RLMCollectionSyncTests.mm", - "RLMFlexibleSyncServerTests.mm", - "RLMMongoClientTests.mm", - "RLMObjectServerPartitionTests.mm", - "RLMObjectServerTests.mm", - "RLMServerTestObjects.h", - "RLMServerTestObjects.m", - "RLMSubscriptionTests.mm", - "RLMSyncTestCase.h", - "RLMSyncTestCase.mm", - "RLMUser+ObjectServerTests.h", - "RLMUser+ObjectServerTests.mm", - "RLMWatchTestUtility.h", - "RLMWatchTestUtility.m", - "RealmServer.swift", - "SwiftAsymmetricSyncServerTests.swift", - "SwiftCollectionSyncTests.swift", - "SwiftFlexibleSyncServerTests.swift", - "SwiftMongoClientTests.swift", - "SwiftObjectServerPartitionTests.swift", - "SwiftObjectServerTests.swift", - "SwiftServerObjects.swift", - "SwiftSyncTestCase.swift", - "SwiftUIServerTests.swift", - "TimeoutProxyServer.swift", - "WatchTestUtility.swift", - "certificates", - "config_overrides.json", - "include", - "setup_baas.rb", -] - -func objectServerTestSupportTarget(name: String, dependencies: [Target.Dependency], sources: [String]) -> Target { - .target( - name: name, - dependencies: dependencies, - path: "Realm/ObjectServerTests", - exclude: objectServerTestSources.filter { !sources.contains($0) }, - sources: sources, - cxxSettings: testCxxSettings - ) -} - -func objectServerTestTarget(name: String, sources: [String]) -> Target { - .testTarget( - name: name, - dependencies: ["RealmSwift", "RealmTestSupport", "RealmSyncTestSupport", "RealmSwiftSyncTestSupport"], - path: "Realm/ObjectServerTests", - exclude: objectServerTestSources.filter { !sources.contains($0) }, - sources: sources, - cxxSettings: testCxxSettings - ) -} - -func runCommand() -> String { - let task = Process() - let pipe = Pipe() - - task.executableURL = URL(fileURLWithPath: "/usr/sbin/ioregg") - task.arguments = ["-rd1", "-c", "IOPlatformExpertDevice"] - task.standardInput = nil - task.standardError = nil - task.standardOutput = pipe - do { - try task.run() - } catch { - return "" - } - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - let output = String(data: data, encoding: .utf8) ?? "" - let range = NSRange(output.startIndex..., in: output) - guard let regex = try? NSRegularExpression(pattern: ".*\\\"IOPlatformUUID\\\"\\s=\\s\\\"(.+)\\\"", options: .caseInsensitive), - let firstMatch = regex.matches(in: output, range: range).first else { - return "" - } - - let matches = (0.. String? in - let matchRange = firstMatch.range(at: ind) - if matchRange != range, - let substringRange = Range(matchRange, in: output) { - let capture = String(output[substringRange]) - return capture - } - return nil - } - return matches.last ?? "" -} - let package = Package( name: "Realm", platforms: [ @@ -178,7 +76,6 @@ let package = Package( "README.md", "Realm.podspec", "Realm.xcodeproj", - "Realm/ObjectServerTests", "Realm/Realm-Info.plist", "Realm/Swift/RLMSupport.swift", "Realm/TestUtils", @@ -199,9 +96,7 @@ let package = Package( ], sources: [ "Realm/RLMAccessor.mm", - "Realm/RLMAnalytics.mm", "Realm/RLMArray.mm", - "Realm/RLMAsymmetricObject.mm", "Realm/RLMAsyncTask.mm", "Realm/RLMClassInfo.mm", "Realm/RLMCollection.mm", @@ -210,7 +105,6 @@ let package = Package( "Realm/RLMDictionary.mm", "Realm/RLMEmbeddedObject.mm", "Realm/RLMError.mm", - "Realm/RLMEvent.mm", "Realm/RLMGeospatial.mm", "Realm/RLMLogger.mm", "Realm/RLMManagedArray.mm", @@ -239,34 +133,8 @@ let package = Package( "Realm/RLMSwiftValueStorage.mm", "Realm/RLMThreadSafeReference.mm", "Realm/RLMUUID.mm", - "Realm/RLMUpdateChecker.mm", "Realm/RLMUtil.mm", "Realm/RLMValue.mm", - - // Sync source files - "Realm/NSError+RLMSync.m", - "Realm/RLMApp.mm", - "Realm/RLMAPIKeyAuth.mm", - "Realm/RLMBSON.mm", - "Realm/RLMCredentials.mm", - "Realm/RLMEmailPasswordAuth.mm", - "Realm/RLMFindOneAndModifyOptions.mm", - "Realm/RLMFindOptions.mm", - "Realm/RLMInitialSubscriptionsConfiguration.m", - "Realm/RLMMongoClient.mm", - "Realm/RLMMongoCollection.mm", - "Realm/RLMNetworkTransport.mm", - "Realm/RLMProviderClient.mm", - "Realm/RLMPushClient.mm", - "Realm/RLMRealm+Sync.mm", - "Realm/RLMSyncConfiguration.mm", - "Realm/RLMSyncManager.mm", - "Realm/RLMSyncSession.mm", - "Realm/RLMSyncSubscription.mm", - "Realm/RLMSyncUtil.mm", - "Realm/RLMUpdateResult.mm", - "Realm/RLMUser.mm", - "Realm/RLMUserAPIKey.mm" ], resources: [ .copy("Realm/PrivacyInfo.xcprivacy") @@ -282,7 +150,6 @@ let package = Package( dependencies: ["Realm"], path: "RealmSwift", exclude: [ - "Nonsync.swift", "RealmSwift-Info.plist", "Tests", ], @@ -321,8 +188,6 @@ let package = Package( "fileformat-pre-null.realm", "mixed_tests.py", "set_tests.py", - "SwiftUISyncTestHost", - "SwiftUISyncTestHostUITests" ], cxxSettings: testCxxSettings ), @@ -342,61 +207,6 @@ let package = Package( "TestUtils.swift" ] ), - - // Object server tests have support code written in both obj-c and - // Swift which is used by both the obj-c and swift test code. SPM - // doesn't support mixed targets, so this ends up requiring four - // different targets. - objectServerTestSupportTarget( - name: "RealmSyncTestSupport", - dependencies: ["Realm", "RealmSwift", "RealmTestSupport"], - sources: [ - "RLMServerTestObjects.m", - "RLMSyncTestCase.mm", - "RLMUser+ObjectServerTests.mm", - "RLMWatchTestUtility.m", - ] - ), - objectServerTestSupportTarget( - name: "RealmSwiftSyncTestSupport", - dependencies: ["RealmSwift", "RealmTestSupport", "RealmSyncTestSupport", "RealmSwiftTestSupport"], - sources: [ - "RealmServer.swift", - "SwiftServerObjects.swift", - "SwiftSyncTestCase.swift", - "TimeoutProxyServer.swift", - "WatchTestUtility.swift", - ] - ), - objectServerTestTarget( - name: "SwiftObjectServerTests", - sources: [ - "AsyncSyncTests.swift", - "ClientResetTests.swift", - "CombineSyncTests.swift", - "EventTests.swift", - "SwiftAsymmetricSyncServerTests.swift", - "SwiftCollectionSyncTests.swift", - "SwiftFlexibleSyncServerTests.swift", - "SwiftMongoClientTests.swift", - "SwiftObjectServerPartitionTests.swift", - "SwiftObjectServerTests.swift", - "SwiftUIServerTests.swift", - ] - ), - objectServerTestTarget( - name: "ObjcObjectServerTests", - sources: [ - "RLMAsymmetricSyncServerTests.mm", - "RLMBSONTests.mm", - "RLMCollectionSyncTests.mm", - "RLMFlexibleSyncServerTests.mm", - "RLMMongoClientTests.mm", - "RLMObjectServerPartitionTests.mm", - "RLMObjectServerTests.mm", - "RLMSubscriptionTests.mm", - ] - ) ], swiftLanguageVersions: swiftVersion, cxxLanguageStandard: .cxx20 diff --git a/README.md b/README.md index a9d117eddc..fc720f870a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ This repository holds the source code for the iOS, macOS, tvOS & watchOS version * **Intuitive to Developers:** Realm’s object-oriented data model is simple to learn, doesn’t need an ORM, and lets you write less code. * **Built for Mobile:** Realm is fully-featured, lightweight, and efficiently uses memory, disk space, and battery life. * **Designed for Offline Use:** Realm’s local database persists data on-disk, so apps work as well offline as they do online. -* **[MongoDB Atlas Device Sync](https://www.mongodb.com/docs/atlas/app-services/sync/)**: Makes it simple to keep data in sync across users, devices, and your backend in real-time. Get started for free with [a template application](https://github.com/mongodb/template-app-swiftui-todo) and [create the cloud backend](http://mongodb.com/realm/register?utm_medium=github_atlas_CTA&utm_source=realm_swift_github). ## Object-Oriented: Streamline Your Code @@ -128,8 +127,6 @@ We support installing Realm via Swift Package Manager, CocoaPods, Carthage, or b For more information, see the detailed instructions in our [docs](https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/install/). -Interested in getting started for free with [a template application](https://github.com/mongodb/template-app-swiftui-todo) that includes a cloud backend and Sync? [Create a MongoDB Atlas Account](http://mongodb.com/realm/register?utm_medium=github_atlas_CTA&utm_source=realm_swift_github). - ## Documentation The documentation can be found at [mongodb.com/docs/atlas/device-sdks/sdk/swift/](https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/). @@ -147,7 +144,7 @@ In case you don't want to use the precompiled version, you can build Realm yours Prerequisites: -* Building Realm requires Xcode 14.1 or newer. +* Building Realm requires Xcode 15.3 or newer. * Building Realm documentation requires [jazzy](https://github.com/realm/jazzy) Once you have all the necessary prerequisites, building Realm just takes a single command: `sh build.sh build`. diff --git a/Realm.podspec b/Realm.podspec index 9257063ca4..9135a03cad 100644 --- a/Realm.podspec +++ b/Realm.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.description = <<-DESC The Realm Database, for Objective-C. (If you want to use Realm from Swift, see the “RealmSwift” pod.) - Realm is a fast, easy-to-use replacement for Core Data & SQLite. Use it with Atlas Device Sync for realtime, automatic data sync. Works on iOS, macOS, tvOS & watchOS. Learn more and get help at https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/. + Realm is a fast, easy-to-use replacement for Core Data & SQLite. Works on iOS, macOS, tvOS & watchOS. Learn more and get help at https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/. DESC s.homepage = "https://realm.io" s.source = { :git => 'https://github.com/realm/realm-swift.git', :tag => "v#{s.version}" } @@ -50,32 +50,6 @@ Pod::Spec.new do |s| 'include/RLMThreadSafeReference.h', 'include/RLMValue.h', - # Sync - 'include/NSError+RLMSync.h', - 'include/RLMApp.h', - 'include/RLMAppCredentials.h', - 'include/RLMBSON.h', - 'include/RLMInitialSubscriptionsConfiguration.h', - 'include/RLMNetworkTransport.h', - 'include/RLMPushClient.h', - 'include/RLMProviderClient.h', - 'include/RLMRealm+Sync.h', - 'include/RLMSyncConfiguration.h', - 'include/RLMCredentials.h', - 'include/RLMSyncManager.h', - 'include/RLMSyncSession.h', - 'include/RLMUser.h', - 'include/RLMUserAPIKey.h', - 'include/RLMAPIKeyAuth.h', - 'include/RLMEmailPasswordAuth.h', - 'include/RLMFindOneAndModifyOptions.h', - 'include/RLMFindOptions.h', - 'include/RLMMongoClient.h', - 'include/RLMMongoCollection.h', - 'include/RLMMongoDatabase.h', - 'include/RLMUpdateResult.h', - 'include/RLMSyncSubscription.h', - # Realm.Dynamic module 'include/RLMRealm_Dynamic.h', 'include/RLMObjectBase_Dynamic.h', @@ -85,15 +59,12 @@ Pod::Spec.new do |s| # Realm.Private module private_header_files = 'include/RLMAccessor.h', - 'include/RLMApp_Private.h', 'include/RLMArray_Private.h', 'include/RLMAsyncTask_Private.h', - 'include/RLMBSON_Private.h', 'include/RLMCollection_Private.h', 'include/RLMDictionary_Private.h', 'include/RLMEvent.h', 'include/RLMLogger_Private.h', - 'include/RLMMongoCollection_Private.h', 'include/RLMObjectBase_Private.h', 'include/RLMObjectSchema_Private.h', 'include/RLMObjectStore.h', @@ -101,7 +72,6 @@ Pod::Spec.new do |s| 'include/RLMOptionalBase.h', 'include/RLMPropertyBase.h', 'include/RLMProperty_Private.h', - 'include/RLMProviderClient_Private.h', 'include/RLMRealmConfiguration_Private.h', 'include/RLMRealm_Private.h', 'include/RLMResults_Private.h', @@ -109,17 +79,13 @@ Pod::Spec.new do |s| 'include/RLMSchema_Private.h', 'include/RLMSet_Private.h', 'include/RLMSwiftProperty.h', - 'include/RLMSyncConfiguration_Private.h', - 'include/RLMSyncSubscription_Private.h', - 'include/RLMUpdateResult_Private.h', - 'include/RLMUser_Private.h', s.ios.frameworks = 'Security' s.ios.weak_framework = 'UIKit' s.tvos.weak_framework = 'UIKit' s.watchos.weak_framework = 'UIKit' s.module_map = 'Realm/Realm.modulemap' - s.compiler_flags = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"#{s.version}\"' -D__ASSERTMACROS__ -DREALM_ENABLE_SYNC" + s.compiler_flags = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"#{s.version}\"' -D__ASSERTMACROS__" s.prepare_command = 'sh scripts/setup-cocoapods.sh' s.source_files = private_header_files + ['Realm/*.{m,mm}'] s.private_header_files = private_header_files diff --git a/Realm.xcodeproj/project.pbxproj b/Realm.xcodeproj/project.pbxproj index 82b6715fd5..8fcb0ab3b7 100644 --- a/Realm.xcodeproj/project.pbxproj +++ b/Realm.xcodeproj/project.pbxproj @@ -7,17 +7,6 @@ objects = { /* Begin PBXAggregateTarget section */ - 49E57A16245C3F31004AF428 /* Download BaaS */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 49E57A17245C3F31004AF428 /* Build configuration list for PBXAggregateTarget "Download BaaS" */; - buildPhases = ( - 49E57A1A245C3F36004AF428 /* ShellScript */, - ); - dependencies = ( - ); - name = "Download BaaS"; - productName = "Download BaaS"; - }; ACB76B772AA9D9A600C59983 /* CI */ = { isa = PBXAggregateTarget; buildConfigurationList = ACB76B7B2AA9D9A600C59983 /* Build configuration list for PBXAggregateTarget "CI" */; @@ -53,33 +42,13 @@ 0C3BD4B325C1BDF1007CFDD3 /* RLMDictionary.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0C3BD4B125C1BDF1007CFDD3 /* RLMDictionary.mm */; }; 0C3BD4D325C1C5AB007CFDD3 /* Map.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3BD4D225C1C5AB007CFDD3 /* Map.swift */; }; 0C5796A225643D7500744CAE /* RLMUUID.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0C57969F25643D7500744CAE /* RLMUUID.mm */; }; - 0C63BB902BCD787300E25C3A /* RLMInitialSubscriptionsConfiguration.h in Sources */ = {isa = PBXBuildFile; fileRef = 0CC270AB2BCD665800788EE1 /* RLMInitialSubscriptionsConfiguration.h */; }; 0C86B33925E15B6000775FED /* PrimitiveDictionaryPropertyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C86B33825E15B6000775FED /* PrimitiveDictionaryPropertyTests.m */; }; 0C9758BF264974660097B48D /* SwiftRLMDictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9758BE264974660097B48D /* SwiftRLMDictionaryTests.swift */; }; 0CBF2DB927286FFD00635902 /* ProjectedCollectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBF2DB827286FFD00635902 /* ProjectedCollectTests.swift */; }; - 0CC270AA2BCD664200788EE1 /* RLMInitialSubscriptionsConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CC270A92BCD664200788EE1 /* RLMInitialSubscriptionsConfiguration.m */; }; - 0CC270AC2BCD665800788EE1 /* RLMInitialSubscriptionsConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 0CC270AB2BCD665800788EE1 /* RLMInitialSubscriptionsConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0CD1632826D3DF1D0027C49B /* ProjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD1632726D3DF1C0027C49B /* ProjectionTests.swift */; }; 0CED6DB82655087200B80277 /* RLMDictionary_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C3BD50125C1DE6F007CFDD3 /* RLMDictionary_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 1A0512771D8746CD00806AEC /* RLMSyncConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A3623651D8384BA00945A54 /* RLMSyncConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1A1536481DB0408A00C0EC93 /* RLMUser+ObjectServerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AF64DD11DA304A90081EB15 /* RLMUser+ObjectServerTests.mm */; }; - 1A3623681D8384BA00945A54 /* RLMSyncConfiguration.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3623661D8384BA00945A54 /* RLMSyncConfiguration.mm */; }; 1A7B823A1D51259F00750296 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A7B82391D51259F00750296 /* libz.tbd */; }; - 1A84132F1D4BCCE600C5326F /* RLMSyncUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A84132E1D4BCCE600C5326F /* RLMSyncUtil.mm */; }; - 1AA5AE981D989BE400ED8C27 /* SwiftSyncTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA5AE961D989BE000ED8C27 /* SwiftSyncTestCase.swift */; }; - 1AA5AE9C1D98A68E00ED8C27 /* RLMSyncTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AA5AE9B1D98A68E00ED8C27 /* RLMSyncTestCase.mm */; }; - 1AA5AEA11D98C99800ED8C27 /* SwiftObjectServerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA5AE9F1D98C99500ED8C27 /* SwiftObjectServerTests.swift */; }; - 1AA5AEA31D98DF1000ED8C27 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D660FCC1BE98C560021E04F /* RealmSwift.framework */; }; - 1AA5AEA41D98DF1500ED8C27 /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5D660FCC1BE98C560021E04F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 1AB605D31D495927007F53DE /* RealmCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB605D21D495927007F53DE /* RealmCollection.swift */; }; - 1ABDCDAE1D792FEB003489E3 /* RLMUser.h in Headers */ = {isa = PBXBuildFile; fileRef = 1ABDCDAD1D792FEB003489E3 /* RLMUser.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1ABDCDB01D793008003489E3 /* RLMUser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1ABDCDAF1D793008003489E3 /* RLMUser.mm */; }; - 1AD3870C1D4A7FBB00479110 /* RLMSyncSession.h in Headers */ = {isa = PBXBuildFile; fileRef = 1AD3870A1D4A7FBB00479110 /* RLMSyncSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1AD397CE1F72FFC7002AA897 /* RLMRealm+Sync.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AD397CC1F72FFC5002AA897 /* RLMRealm+Sync.mm */; }; - 1AD397CF1F72FFC7002AA897 /* RLMRealm+Sync.h in Headers */ = {isa = PBXBuildFile; fileRef = 1AD397CD1F72FFC6002AA897 /* RLMRealm+Sync.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1AF7EA961D340AF70001A9B5 /* RLMSyncManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 1AF7EA941D340AF70001A9B5 /* RLMSyncManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1AF7EA971D340AF70001A9B5 /* RLMSyncManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AF7EA951D340AF70001A9B5 /* RLMSyncManager.mm */; }; - 1AFEF8431D52D2C900495005 /* Sync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7DE7021D38460B0029F0AE /* Sync.swift */; }; 2973CCF91C175AB400FEA0FA /* fileformat-pre-null.realm in Resources */ = {isa = PBXBuildFile; fileRef = 29B7FDF71C0DE76B0023224E /* fileformat-pre-null.realm */; }; 297FBEFB1C19F696009D1118 /* RLMTestCaseUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297FBEFA1C19F696009D1118 /* RLMTestCaseUtils.swift */; }; 29B7FDF61C0DA6560023224E /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B7FDF51C0DA6560023224E /* Error.swift */; }; @@ -101,13 +70,7 @@ 3F1F47821B9612B300CD99A3 /* KVOTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F0F029D1B6FFE610046A4D5 /* KVOTests.mm */; }; 3F222C4E1E26F51300CA0713 /* ThreadSafeReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F222C4D1E26F51300CA0713 /* ThreadSafeReference.swift */; }; 3F2633C31E9D630000B32D30 /* PrimitiveListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F2633C21E9D630000B32D30 /* PrimitiveListTests.swift */; }; - 3F2B40CA2B2900DA00E30319 /* CombineSyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F2B40C52B2900DA00E30319 /* CombineSyncTests.swift */; }; - 3F2B40CB2B2900DA00E30319 /* AsyncSyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F2B40C62B2900DA00E30319 /* AsyncSyncTests.swift */; }; - 3F2B40CC2B2900DA00E30319 /* RLMSubscriptionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F2B40C72B2900DA00E30319 /* RLMSubscriptionTests.mm */; }; - 3F2B40CD2B2900DA00E30319 /* RLMMongoClientTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F2B40C82B2900DA00E30319 /* RLMMongoClientTests.mm */; }; - 3F2B40CE2B2900DA00E30319 /* ClientResetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F2B40C92B2900DA00E30319 /* ClientResetTests.swift */; }; 3F2E66641CA0BA11004761D5 /* NotificationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F2E66611CA0B9D5004761D5 /* NotificationTests.m */; }; - 3F336E8B1DA2FA15006CB5A0 /* RLMSyncConfiguration_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A36236A1D83868F00945A54 /* RLMSyncConfiguration_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 3F3411A6273433B300EC9D25 /* ObjcBridgeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F3411A5273433B300EC9D25 /* ObjcBridgeable.swift */; }; 3F40C613276D1B05007FEF1A /* CustomObjectCreationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F40C612276D1B05007FEF1A /* CustomObjectCreationTests.swift */; }; 3F4E0FF92654765C008B8C0B /* ModernKVOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F4E0FF82654765C008B8C0B /* ModernKVOTests.swift */; }; @@ -117,27 +80,19 @@ 3F4F3AD723F71C790048DB43 /* RLMObjectId.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F4F3AD023F71C790048DB43 /* RLMObjectId.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3F4F3ADB23F71C790048DB43 /* RLMObjectId.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F4F3AD223F71C790048DB43 /* RLMObjectId.mm */; }; 3F4F3ADD23F71C790048DB43 /* RLMDecimal128.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F4F3AD323F71C790048DB43 /* RLMDecimal128.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3F4FD2FB2B2A389A003E3DFD /* TestType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F4FD2FA2B2A389A003E3DFD /* TestType.swift */; }; - 3F4FD2FC2B2A389A003E3DFD /* TestType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F4FD2FA2B2A389A003E3DFD /* TestType.swift */; }; 3F558C8722C29A03002F0F30 /* TestUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C7E22C29A02002F0F30 /* TestUtils.mm */; }; - 3F558C8822C29A03002F0F30 /* TestUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C7E22C29A02002F0F30 /* TestUtils.mm */; }; 3F558C8A22C29A03002F0F30 /* TestUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C7E22C29A02002F0F30 /* TestUtils.mm */; }; 3F558C8B22C29A03002F0F30 /* RLMTestObjects.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8222C29A02002F0F30 /* RLMTestObjects.m */; }; 3F558C8E22C29A03002F0F30 /* RLMTestObjects.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8222C29A02002F0F30 /* RLMTestObjects.m */; }; 3F558C8F22C29A03002F0F30 /* RLMTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8322C29A02002F0F30 /* RLMTestCase.m */; }; - 3F558C9022C29A03002F0F30 /* RLMTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8322C29A02002F0F30 /* RLMTestCase.m */; }; 3F558C9222C29A03002F0F30 /* RLMTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8322C29A02002F0F30 /* RLMTestCase.m */; }; 3F558C9322C29A03002F0F30 /* RLMMultiProcessTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8522C29A03002F0F30 /* RLMMultiProcessTestCase.m */; }; - 3F558C9422C29A03002F0F30 /* RLMMultiProcessTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8522C29A03002F0F30 /* RLMMultiProcessTestCase.m */; }; 3F558C9622C29A03002F0F30 /* RLMMultiProcessTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8522C29A03002F0F30 /* RLMMultiProcessTestCase.m */; }; 3F572C941F2BDAAB00F6C9AB /* ThreadSafeReferenceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F572C911F2BDA9F00F6C9AB /* ThreadSafeReferenceTests.m */; }; 3F572C971F2BDAB100F6C9AB /* PrimitiveArrayPropertyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F572C901F2BDA9F00F6C9AB /* PrimitiveArrayPropertyTests.m */; }; 3F67DB3C1E26D69C0024533D /* RLMThreadSafeReference.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F67DB391E26D69C0024533D /* RLMThreadSafeReference.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3F67DB3E1E26D69C0024533D /* RLMThreadSafeReference.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F67DB3B1E26D69C0024533D /* RLMThreadSafeReference.mm */; }; - 3F73BC951E3A878500FE80B6 /* NSError+RLMSync.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F73BC931E3A878500FE80B6 /* NSError+RLMSync.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3F73BC961E3A878500FE80B6 /* NSError+RLMSync.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F73BC941E3A878500FE80B6 /* NSError+RLMSync.m */; }; 3F7556751BE95A0C0058BC7E /* AsyncTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F7556731BE95A050058BC7E /* AsyncTests.mm */; }; - 3F7BCEB52834377E00EB6E16 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F7BCEB42834377E00EB6E16 /* Events.swift */; }; 3F83E9A42630A14800FC9623 /* RLMSwiftProperty.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F83E9A22630A14800FC9623 /* RLMSwiftProperty.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3F857A482769291800F9B9B1 /* KeyPathStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F857A472769291800F9B9B1 /* KeyPathStrings.swift */; }; 3F857A49276A507200F9B9B1 /* Projection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD1632526D3DD7B0027C49B /* Projection.swift */; }; @@ -159,17 +114,14 @@ 3F88250C1E5E335000586B35 /* SwiftUnicodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6610111BE98D880021E04F /* SwiftUnicodeTests.swift */; }; 3F88250D1E5E335000586B35 /* ThreadSafeReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F73BC841E3A870F00FE80B6 /* ThreadSafeReferenceTests.swift */; }; 3F90C1B22716169C0029000E /* TestValueFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F90C1B12716169C0029000E /* TestValueFactory.swift */; }; - 3F95F4A2295B8488006BC287 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAF2D4029577100002EAC93 /* TestUtils.swift */; }; 3F98162A2317763000C3543D /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F9816292317763000C3543D /* libc++.tbd */; }; 3F98162B2317763600C3543D /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A7B82391D51259F00750296 /* libz.tbd */; }; 3F9863BB1D36876B00641C98 /* RLMClassInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F9863B91D36876B00641C98 /* RLMClassInfo.mm */; }; 3F997773273E23D300AA9E13 /* RealmCollectionImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F997772273E23D300AA9E13 /* RealmCollectionImpl.swift */; }; - 3F9ADA9426E7E87B007349A5 /* SwiftCollectionSyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9ADA9326E7E87B007349A5 /* SwiftCollectionSyncTests.swift */; }; 3F9F53D32718B5DA000EEB4A /* CustomPersistable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9F53D22718B5DA000EEB4A /* CustomPersistable.swift */; }; 3F9F53D52718E8E6000EEB4A /* CustomPersistableTestObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9F53D42718E8E6000EEB4A /* CustomPersistableTestObjects.swift */; }; 3FA5E94D266064C4008F1345 /* ModernObjectCreationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA5E94C266064C4008F1345 /* ModernObjectCreationTests.swift */; }; 3FAF2D4129577100002EAC93 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAF2D4029577100002EAC93 /* TestUtils.swift */; }; - 3FAF2D4229577100002EAC93 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAF2D4029577100002EAC93 /* TestUtils.swift */; }; 3FB19069265ECF0C00DA7C76 /* ModernObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB19068265ECF0C00DA7C76 /* ModernObjectTests.swift */; }; 3FB1906B265ED23300DA7C76 /* ModernTestObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB1906A265ED23300DA7C76 /* ModernTestObjects.swift */; }; 3FB4FA1719F5D2740020D53B /* SwiftTestObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F8D90B196CB8DD00475368 /* SwiftTestObjects.swift */; }; @@ -191,7 +143,6 @@ 3FCC56E429A55607004C5057 /* RLMSwiftObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FCC56E329A55607004C5057 /* RLMSwiftObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3FD0D7C729675A2E0031C196 /* RLMAsyncTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FD0D7C529675A2E0031C196 /* RLMAsyncTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3FD0D7C929675A2E0031C196 /* RLMAsyncTask.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3FD0D7C629675A2E0031C196 /* RLMAsyncTask.mm */; }; - 3FD0D7CE2967CA1E0031C196 /* RLMMongoCollection_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CF76F7D224816AAA00890DD2 /* RLMMongoCollection_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 3FD6D1A92B4C9EFB00A4FEBE /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3FD6D1A82B4C9EFB00A4FEBE /* PrivacyInfo.xcprivacy */; }; 3FD6D1AC2B4CA56D00A4FEBE /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3FD6D1AB2B4CA56C00A4FEBE /* PrivacyInfo.xcprivacy */; }; 3FDAB841290B4CCB00168F24 /* RLMError.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FDAB83E290B4CCB00168F24 /* RLMError.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -209,7 +160,6 @@ 3FE267D9264308680030F83C /* PropertyAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE267D3264308680030F83C /* PropertyAccessors.swift */; }; 3FE267DA264308680030F83C /* SchemaDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE267D4264308680030F83C /* SchemaDiscovery.swift */; }; 3FE2BE0323D8CAD1002860E9 /* CombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE2BE0223D8CAD1002860E9 /* CombineTests.swift */; }; - 3FE5818622C2B4B900BA10E7 /* ObjectiveCSupport+Sync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE5818422C2B4B900BA10E7 /* ObjectiveCSupport+Sync.swift */; }; 3FE5B4D724CF6909004D4EF3 /* realm-monorepo.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FE5B4D424CF3F06004D4EF3 /* realm-monorepo.xcframework */; }; 3FEB383F1E70AC8800F22712 /* ObjectCreationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3FEB383C1E70AC6900F22712 /* ObjectCreationTests.mm */; }; 3FEC4A3F1BBB18D400F009C3 /* SwiftSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEC4A3D1BBB188B00F009C3 /* SwiftSchemaTests.swift */; }; @@ -218,43 +168,17 @@ 3FEC91592A4B5DE90044BFF5 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FEC91552A4B5D520044BFF5 /* libcompression.tbd */; }; 3FEC915A2A4B5DEF0044BFF5 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FEC91572A4B5D600044BFF5 /* libz.tbd */; }; 3FEC915B2A4B65B30044BFF5 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D659ED91BE04556006515A0 /* Realm.framework */; }; - 3FEE4F40281C4370009194C7 /* RLMEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FEE4F3E281C4370009194C7 /* RLMEvent.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 3FEE4F42281C4370009194C7 /* RLMEvent.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3FEE4F3F281C4370009194C7 /* RLMEvent.mm */; }; - 3FEE4F45281C439D009194C7 /* EventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEE4F44281C439D009194C7 /* EventTests.swift */; }; 3FF3FFAF1F0D6D6400B84599 /* KVOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FFF1BE98D880021E04F /* KVOTests.swift */; }; - 3FFB5AF6266ECFC7008EF2E9 /* SwiftBSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4996EA9F2465C44E003A1F51 /* SwiftBSONTests.swift */; }; 3FFC68702B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFC686F2B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift */; }; - 494566A9246E8C59000FD07F /* ObjectiveCSupport+BSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494566A8246E8C59000FD07F /* ObjectiveCSupport+BSON.swift */; }; - 4993220A24129DCE00A0EC8E /* RLMCredentials.h in Headers */ = {isa = PBXBuildFile; fileRef = 4993220324129DCD00A0EC8E /* RLMCredentials.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 4993220C24129DCE00A0EC8E /* RLMCredentials.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4993220424129DCD00A0EC8E /* RLMCredentials.mm */; }; - 4993220E24129DCE00A0EC8E /* RLMApp.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4993220524129DCD00A0EC8E /* RLMApp.mm */; }; - 4993221224129DCE00A0EC8E /* RLMApp.h in Headers */ = {isa = PBXBuildFile; fileRef = 4993220724129DCE00A0EC8E /* RLMApp.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 4993221624129E6600A0EC8E /* RLMNetworkTransport.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4993221424129E6500A0EC8E /* RLMNetworkTransport.mm */; }; - 4993221824129E6600A0EC8E /* RLMNetworkTransport.h in Headers */ = {isa = PBXBuildFile; fileRef = 4993221524129E6600A0EC8E /* RLMNetworkTransport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 4996EA9E2465BB8A003A1F51 /* BSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4996EA9D2465BB8A003A1F51 /* BSON.swift */; }; - 49E12CF0245DB7CC00359DF1 /* RLMBSON.mm in Sources */ = {isa = PBXBuildFile; fileRef = 49E12CEF245DB7CC00359DF1 /* RLMBSON.mm */; }; - 49E12CF2245DB7E800359DF1 /* RLMBSON.h in Headers */ = {isa = PBXBuildFile; fileRef = 49E12CF1245DB7E800359DF1 /* RLMBSON.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 49E12CF5245DBF8A00359DF1 /* RLMBSON_Private.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 49E12CF4245DBF8A00359DF1 /* RLMBSON_Private.hpp */; settings = {ATTRIBUTES = (Private, ); }; }; 530BA61426DFA1CB008FC550 /* RLMChildProcessEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 530BA61326DFA1CB008FC550 /* RLMChildProcessEnvironment.m */; }; 530BA61526DFA1CB008FC550 /* RLMChildProcessEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 530BA61326DFA1CB008FC550 /* RLMChildProcessEnvironment.m */; }; - 530BA61626DFA1CB008FC550 /* RLMChildProcessEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 530BA61326DFA1CB008FC550 /* RLMChildProcessEnvironment.m */; }; 53124AD925B71AF700771CE4 /* SwiftUITestHostUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53124AD825B71AF700771CE4 /* SwiftUITestHostUITests.swift */; }; - 531F956727906DBC00E497F1 /* RLMSyncSubscription_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 53E308C927905794002A8D91 /* RLMSyncSubscription_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 531F956827906EB200E497F1 /* RLMSyncSubscription.h in Headers */ = {isa = PBXBuildFile; fileRef = 53E308CA27905794002A8D91 /* RLMSyncSubscription.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 531F956A27906EF300E497F1 /* RLMSyncSession.mm in Sources */ = {isa = PBXBuildFile; fileRef = 531F956927906EF300E497F1 /* RLMSyncSession.mm */; }; - 531F956C27906F7600E497F1 /* RLMSyncSubscription.mm in Sources */ = {isa = PBXBuildFile; fileRef = 531F956B27906F7600E497F1 /* RLMSyncSubscription.mm */; }; - 531F9570279070A900E497F1 /* RLMServerTestObjects.m in Sources */ = {isa = PBXBuildFile; fileRef = 531F956E279070A800E497F1 /* RLMServerTestObjects.m */; }; - 532E916F24AA533A003FD9DB /* TimeoutProxyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 532E916E24AA533A003FD9DB /* TimeoutProxyServer.swift */; }; - 5346E7322487AC9D00595C68 /* RLMBSONTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5346E7312487AC9D00595C68 /* RLMBSONTests.mm */; }; 535EA9E225B0919800DBF3CD /* SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535EA9E125B0919800DBF3CD /* SwiftUI.swift */; }; 535EAA7525B0B02B00DBF3CD /* SwiftUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535EAA7425B0B02B00DBF3CD /* SwiftUITests.swift */; }; 53626AAF25D31CAC00D9515D /* Objects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53626AAE25D31CAC00D9515D /* Objects.swift */; }; 53626AB025D31CAC00D9515D /* Objects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53626AAE25D31CAC00D9515D /* Objects.swift */; }; - 537130C824A9E417001FDBBC /* RealmServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537130C724A9E417001FDBBC /* RealmServer.swift */; }; 53A34E3625CDA0AC00698930 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53A34E3325CDA0AC00698930 /* LaunchScreen.storyboard */; }; 53A34E3725CDA0AC00698930 /* SwiftUITestHostApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A34E3425CDA0AC00698930 /* SwiftUITestHostApp.swift */; }; - 53CCC6C4257EC8A300A8FC50 /* RLMApp_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 53CCC6C3257EC8A300A8FC50 /* RLMApp_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 53CCC6E8257EC8C400A8FC50 /* RLMUser_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 53CCC6E7257EC8C300A8FC50 /* RLMUser_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5B77EACE1DCC5614006AB51D /* ObjectiveCSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B77EACD1DCC5614006AB51D /* ObjectiveCSupport.swift */; }; 5D03FB1F1E0DAFBA007D53EA /* PredicateUtilTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5D03FB1E1E0DAFBA007D53EA /* PredicateUtilTests.mm */; }; 5D128F2A1BE984E5001F4FBF /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5D659ED91BE04556006515A0 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -264,7 +188,6 @@ 5D432B8D1CC0713F00A610A9 /* LinkingObjectsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5D432B8C1CC0713F00A610A9 /* LinkingObjectsTests.mm */; }; 5D6156EE1BE0689200A4BD3F /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D659ED91BE04556006515A0 /* Realm.framework */; }; 5D659E851BE04556006515A0 /* RLMAccessor.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F641955FC9300FDED82 /* RLMAccessor.mm */; }; - 5D659E861BE04556006515A0 /* RLMAnalytics.mm in Sources */ = {isa = PBXBuildFile; fileRef = E83591931B3DF05C0035F2F3 /* RLMAnalytics.mm */; }; 5D659E871BE04556006515A0 /* RLMArray.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F671955FC9300FDED82 /* RLMArray.mm */; }; 5D659E881BE04556006515A0 /* RLMManagedArray.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F691955FC9300FDED82 /* RLMManagedArray.mm */; }; 5D659E891BE04556006515A0 /* RLMConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F6C1955FC9300FDED82 /* RLMConstants.m */; }; @@ -283,7 +206,6 @@ 5D659E971BE04556006515A0 /* RLMResults.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F6A1955FC9300FDED82 /* RLMResults.mm */; }; 5D659E981BE04556006515A0 /* RLMSchema.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F7F1955FC9300FDED82 /* RLMSchema.mm */; }; 5D659E991BE04556006515A0 /* RLMSwiftSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F452EC519C2279800AFC154 /* RLMSwiftSupport.m */; }; - 5D659E9A1BE04556006515A0 /* RLMUpdateChecker.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F20DA2119BE1EA6007DE308 /* RLMUpdateChecker.mm */; }; 5D659E9B1BE04556006515A0 /* RLMUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F821955FC9300FDED82 /* RLMUtil.mm */; }; 5D659EA51BE04556006515A0 /* Realm.h in Headers */ = {isa = PBXBuildFile; fileRef = E8D89B9D1955FC6D00CF2B9A /* Realm.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EA71BE04556006515A0 /* RLMAccessor.h in Headers */ = {isa = PBXBuildFile; fileRef = E81A1F631955FC9300FDED82 /* RLMAccessor.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -336,17 +258,9 @@ 5D66102E1BE98E500021E04F /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5D659ED91BE04556006515A0 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5D66102F1BE98E540021E04F /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5D660FCC1BE98C560021E04F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5DBEC9B11F719A9D001233EC /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FF01BE98D670021E04F /* Util.swift */; }; - 6807E64A2487E8660096066F /* RLMPushClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 6807E6492487E8660096066F /* RLMPushClient.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 6807E64C2487F7220096066F /* RLMPushClient.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6807E64B2487F7220096066F /* RLMPushClient.mm */; }; - 6807E64F2487F9210096066F /* RLMPushClient_Private.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 6807E64E2487F9210096066F /* RLMPushClient_Private.hpp */; settings = {ATTRIBUTES = (Private, ); }; }; 681EE33B25EE8E1400A9DEC5 /* AnyRealmValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681EE33A25EE8E1400A9DEC5 /* AnyRealmValue.swift */; }; 681EE34725EE8E5600A9DEC5 /* ObjectiveCSupport+AnyRealmValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681EE34625EE8E5600A9DEC5 /* ObjectiveCSupport+AnyRealmValue.swift */; }; 68A7B91D2543538B00C703BC /* RLMSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88C36FF19745E5500C9963D /* RLMSupport.swift */; }; - AC05380B2885B23E00CE27C4 /* RLMAsymmetricSyncServerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC0538052885B1DF00CE27C4 /* RLMAsymmetricSyncServerTests.mm */; }; - AC05380C2885B25A00CE27C4 /* SwiftAsymmetricSyncServerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC0538062885B1DF00CE27C4 /* SwiftAsymmetricSyncServerTests.swift */; }; - AC23487E26FC8619009129F2 /* RLMUser+ObjectServerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AF64DD11DA304A90081EB15 /* RLMUser+ObjectServerTests.mm */; }; - AC2C2A40268E1B0200B4DA33 /* SwiftServerObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC2C2A3E268E1ACE00B4DA33 /* SwiftServerObjects.swift */; }; - AC320BAE268E1F2D0043D484 /* SwiftServerObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC2C2A3E268E1ACE00B4DA33 /* SwiftServerObjects.swift */; }; AC3B33AE29DC6CEE0042F3A0 /* RLMLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3B33AB29DC6CEE0042F3A0 /* RLMLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC3B33AF29DC6CEE0042F3A0 /* RLMLogger.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC3B33AC29DC6CEE0042F3A0 /* RLMLogger.mm */; }; AC3B33B029DC6CEE0042F3A0 /* RLMLogger_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3B33AD29DC6CEE0042F3A0 /* RLMLogger_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -355,55 +269,16 @@ AC7825BD2ACD90DA007ABA4B /* RLMGeospatial.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC7825BA2ACD90DA007ABA4B /* RLMGeospatial.mm */; }; AC7825BF2ACD90DA007ABA4B /* RLMGeospatial.h in Headers */ = {isa = PBXBuildFile; fileRef = AC7825BC2ACD90DA007ABA4B /* RLMGeospatial.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC7825C22ACD917B007ABA4B /* GeospatialTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC7825C02ACD916C007ABA4B /* GeospatialTests.swift */; }; - AC7D182D261F2F560080E1D2 /* RLMObjectServerPartitionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC7D182B261F2F560080E1D2 /* RLMObjectServerPartitionTests.mm */; }; - AC7D182E261F2F560080E1D2 /* SwiftObjectServerPartitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC7D182C261F2F560080E1D2 /* SwiftObjectServerPartitionTests.swift */; }; - AC81360F287F21350029F15E /* AsymmetricObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC81360E287F21350029F15E /* AsymmetricObject.swift */; }; - AC813612287F21700029F15E /* RLMAsymmetricObject.h in Headers */ = {isa = PBXBuildFile; fileRef = AC813610287F21700029F15E /* RLMAsymmetricObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AC813613287F21700029F15E /* RLMAsymmetricObject.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC813611287F21700029F15E /* RLMAsymmetricObject.mm */; }; - AC8846762686573B00DF4A65 /* SwiftUISyncTestHostApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC8846752686573B00DF4A65 /* SwiftUISyncTestHostApp.swift */; }; - AC8846782686573B00DF4A65 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC8846772686573B00DF4A65 /* ContentView.swift */; }; - AC8846B72687BC4100DF4A65 /* SwiftUIServerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC8846B62687BC4100DF4A65 /* SwiftUIServerTests.swift */; }; - AC88478626888CEE00DF4A65 /* SwiftUISyncTestHostUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC8846902686573D00DF4A65 /* SwiftUISyncTestHostUITests.swift */; }; - AC8847A9268926B500DF4A65 /* RealmServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537130C724A9E417001FDBBC /* RealmServer.swift */; }; - AC8AE64B26BAD4B00037D4E5 /* SwiftMongoClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC8AE64A26BAD4B00037D4E5 /* SwiftMongoClientTests.swift */; }; - ACB6FD35273C60CC0009712F /* SyncSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB6FD34273C60CC0009712F /* SyncSubscription.swift */; }; - ACB6FD36273C60FD0009712F /* SwiftFlexibleSyncServerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB6FD31273C60920009712F /* SwiftFlexibleSyncServerTests.swift */; }; - ACB6FD37273C61040009712F /* RLMFlexibleSyncServerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACB6FD30273C60920009712F /* RLMFlexibleSyncServerTests.mm */; }; - ACBD595328364DB0009A664E /* SwiftSyncTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA5AE961D989BE000ED8C27 /* SwiftSyncTestCase.swift */; }; - ACBD595428364DCB009A664E /* RLMSyncTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AA5AE9B1D98A68E00ED8C27 /* RLMSyncTestCase.mm */; }; - ACBD595528364DFF009A664E /* RLMChildProcessEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 530BA61326DFA1CB008FC550 /* RLMChildProcessEnvironment.m */; }; - ACBD5957283652D7009A664E /* RLMTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8322C29A02002F0F30 /* RLMTestCase.m */; }; - ACBD59592836539B009A664E /* RLMServerTestObjects.m in Sources */ = {isa = PBXBuildFile; fileRef = 531F956E279070A800E497F1 /* RLMServerTestObjects.m */; }; - ACBD595A283653CF009A664E /* RLMMultiProcessTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8522C29A03002F0F30 /* RLMMultiProcessTestCase.m */; }; - ACBD595B2836541F009A664E /* TestUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C7E22C29A02002F0F30 /* TestUtils.mm */; }; ACF08B6726DD936200686CBC /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF08B6626DD936200686CBC /* Query.swift */; }; ACFF0EC728EC5ADB0097AEE0 /* CustomColumnNameTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACFF0EC628EC5ADB0097AEE0 /* CustomColumnNameTests.swift */; }; C042A48D1B7522A900771ED2 /* RealmConfigurationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = C042A48C1B7522A900771ED2 /* RealmConfigurationTests.mm */; }; C0CDC0821B38DABA00C5716D /* UtilTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 021A88311AAFB5BE00EEAC84 /* UtilTests.mm */; }; CF040494263DF0AA00F9AEE0 /* PrimitiveMapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF040493263DF0A900F9AEE0 /* PrimitiveMapTests.swift */; }; CF052EFB25DEB671008EEF86 /* DictionaryPropertyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CF052EFA25DEB671008EEF86 /* DictionaryPropertyTests.m */; }; - CF08757D260B98E100B9BE60 /* RLMCollectionSyncTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF08757C260B98E100B9BE60 /* RLMCollectionSyncTests.mm */; }; CF0D04F1269365300038A058 /* KeyPathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF0D04F02693652E0038A058 /* KeyPathTests.swift */; }; CF25080E283B90F8007D66FE /* SectionedResultsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CF25080D283B90F8007D66FE /* SectionedResultsTests.m */; }; - CF330BBE24E57D5F00F07EE2 /* RLMWatchTestUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = CF330BBD24E57D5F00F07EE2 /* RLMWatchTestUtility.m */; }; CF44461E26121C6800BAFDB4 /* RealmProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF44461D26121C6800BAFDB4 /* RealmProperty.swift */; }; CF46CC0226D931BA00DE450C /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF46CC0026D931BA00DE450C /* QueryTests.swift */; }; - CF6E0482242A141200DB7F14 /* RLMEmailPasswordAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = CF6E0480242A141200DB7F14 /* RLMEmailPasswordAuth.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CF6E0483242A141200DB7F14 /* RLMEmailPasswordAuth.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF6E0481242A141200DB7F14 /* RLMEmailPasswordAuth.mm */; }; - CF6E0486242A321200DB7F14 /* RLMProviderClient.h in Headers */ = {isa = PBXBuildFile; fileRef = CF6E0484242A321200DB7F14 /* RLMProviderClient.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CF6E0487242A321200DB7F14 /* RLMProviderClient.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF6E0485242A321200DB7F14 /* RLMProviderClient.mm */; }; - CF76F7DD24816AAB00890DD2 /* RLMUpdateResult.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF76F7CB24816AA800890DD2 /* RLMUpdateResult.mm */; }; - CF76F7DF24816AAB00890DD2 /* RLMUpdateResult.h in Headers */ = {isa = PBXBuildFile; fileRef = CF76F7CC24816AA800890DD2 /* RLMUpdateResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CF76F7E524816AAB00890DD2 /* RLMMongoCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = CF76F7CF24816AA900890DD2 /* RLMMongoCollection.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CF76F7E724816AAB00890DD2 /* RLMFindOptions.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF76F7D024816AAA00890DD2 /* RLMFindOptions.mm */; }; - CF76F7E924816AAB00890DD2 /* RLMMongoCollection.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF76F7D124816AAA00890DD2 /* RLMMongoCollection.mm */; }; - CF76F7F124816AAB00890DD2 /* RLMMongoClient.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF76F7D524816AAA00890DD2 /* RLMMongoClient.mm */; }; - CF76F7F324816AAB00890DD2 /* RLMFindOneAndModifyOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = CF76F7D624816AAA00890DD2 /* RLMFindOneAndModifyOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CF76F7F524816AAB00890DD2 /* RLMFindOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = CF76F7D724816AAA00890DD2 /* RLMFindOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CF76F7F924816AAB00890DD2 /* RLMMongoDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = CF76F7D924816AAB00890DD2 /* RLMMongoDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CF76F7FB24816AAB00890DD2 /* RLMMongoClient.h in Headers */ = {isa = PBXBuildFile; fileRef = CF76F7DA24816AAB00890DD2 /* RLMMongoClient.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CF76F7FD24816AAB00890DD2 /* RLMFindOneAndModifyOptions.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF76F7DB24816AAB00890DD2 /* RLMFindOneAndModifyOptions.mm */; }; - CF76F80224816B3800890DD2 /* MongoClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF76F80124816B3800890DD2 /* MongoClient.swift */; }; CF84D4F0281823B300005E27 /* SectionedResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF84D4EF281823B300005E27 /* SectionedResults.swift */; }; CF986D1E25AE3B090039D287 /* RLMSet_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CF986D1A25AE3B080039D287 /* RLMSet_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; CF986D2025AE3B090039D287 /* RLMSet.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF986D1B25AE3B080039D287 /* RLMSet.mm */; }; @@ -415,12 +290,6 @@ CF986DE225AE3EC70039D287 /* MutableSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF986DE125AE3EC70039D287 /* MutableSetTests.swift */; }; CF986DF625AE3EDF0039D287 /* PrimitiveMutableSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF986DF525AE3EDF0039D287 /* PrimitiveMutableSetTests.swift */; }; CF9881C125DABC6500BD7E4F /* RLMManagedDictionary.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF9881C025DABC6500BD7E4F /* RLMManagedDictionary.mm */; }; - CFAEF761242B5F9A00EAF721 /* RLMAPIKeyAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = CFAEF75F242B5F9A00EAF721 /* RLMAPIKeyAuth.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CFAEF762242B5F9A00EAF721 /* RLMAPIKeyAuth.mm in Sources */ = {isa = PBXBuildFile; fileRef = CFAEF760242B5F9A00EAF721 /* RLMAPIKeyAuth.mm */; }; - CFAEF765242B672700EAF721 /* RLMUserAPIKey.h in Headers */ = {isa = PBXBuildFile; fileRef = CFAEF763242B672700EAF721 /* RLMUserAPIKey.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CFAEF766242B672700EAF721 /* RLMUserAPIKey.mm in Sources */ = {isa = PBXBuildFile; fileRef = CFAEF764242B672700EAF721 /* RLMUserAPIKey.mm */; }; - CFB4313A243DF87100471C18 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFB43139243DF87100471C18 /* App.swift */; }; - CFB674A324EEE9CB00FBF0B8 /* WatchTestUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFB674A224EEE9CB00FBF0B8 /* WatchTestUtility.swift */; }; CFD8D12025BB0B8B0037FE4D /* RLMManagedSet.mm in Sources */ = {isa = PBXBuildFile; fileRef = CFD8D11F25BB0B8B0037FE4D /* RLMManagedSet.mm */; }; CFDBC4B628803C7200EE80E6 /* RLMSectionedResults.mm in Sources */ = {isa = PBXBuildFile; fileRef = CFDBC4B328803C7200EE80E6 /* RLMSectionedResults.mm */; }; CFDBC4B728803C7200EE80E6 /* RLMSectionedResults.h in Headers */ = {isa = PBXBuildFile; fileRef = CFDBC4B428803C7200EE80E6 /* RLMSectionedResults.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -440,9 +309,6 @@ E81A1FE71955FE0100FDED82 /* QueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E81A1FC11955FE0100FDED82 /* QueryTests.m */; }; E81A1FEB1955FE0100FDED82 /* RealmTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1FC31955FE0100FDED82 /* RealmTests.mm */; }; E81A20021955FE0100FDED82 /* TransactionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E81A1FD11955FE0100FDED82 /* TransactionTests.m */; }; - E8267FE31D90B79000E001C7 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D659ED91BE04556006515A0 /* Realm.framework */; }; - E8267FE51D90B79000E001C7 /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5D659ED91BE04556006515A0 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - E8267FF11D90B8E700E001C7 /* RLMObjectServerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = E8267FF01D90B8E700E001C7 /* RLMObjectServerTests.mm */; }; E8917598197A1B350068ACC6 /* UnicodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E8917597197A1B350068ACC6 /* UnicodeTests.m */; }; E8AE7C261EA436F800CDFF9A /* CompactionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AE7C251EA436F800CDFF9A /* CompactionTests.swift */; }; E8DA16F81E81210D0055141C /* CompactionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E8DA16F71E81210D0055141C /* CompactionTests.m */; }; @@ -464,13 +330,6 @@ remoteGlobalIDString = 5D660FCB1BE98C560021E04F; remoteInfo = RealmSwift; }; - 49E57A1B245C5B0E004AF428 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; - proxyType = 1; - remoteGlobalIDString = 49E57A16245C3F31004AF428; - remoteInfo = "Download BaaS"; - }; 534DF4CF25B86F3A00655AE2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; @@ -478,13 +337,6 @@ remoteGlobalIDString = 53124AB725B71AF600771CE4; remoteInfo = SwiftUITestHost; }; - 537689AB24A7DD060008E57B /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; - proxyType = 1; - remoteGlobalIDString = 5D660FCB1BE98C560021E04F; - remoteInfo = RealmSwift; - }; 53BBF08C25B7436F00D225AD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; @@ -520,34 +372,6 @@ remoteGlobalIDString = 5D659E7D1BE04556006515A0; remoteInfo = Realm; }; - AC88469F2686577B00DF4A65 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; - proxyType = 1; - remoteGlobalIDString = 5D660FCB1BE98C560021E04F; - remoteInfo = RealmSwift; - }; - AC88475B26888C6300DF4A65 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; - proxyType = 1; - remoteGlobalIDString = 49E57A16245C3F31004AF428; - remoteInfo = "Download BaaS"; - }; - AC88479D26891D4500DF4A65 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; - proxyType = 1; - remoteGlobalIDString = AC8846722686573B00DF4A65; - remoteInfo = SwifttUISyncTestHost; - }; - E8267FB51D90B79000E001C7 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; - proxyType = 1; - remoteGlobalIDString = 5D659E7D1BE04556006515A0; - remoteInfo = Realm; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -574,28 +398,6 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; - AC88477A26888C6300DF4A65 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; - E8267FE41D90B79000E001C7 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - E8267FE51D90B79000E001C7 /* Realm.framework in Embed Frameworks */, - 1AA5AEA41D98DF1500ED8C27 /* RealmSwift.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -632,37 +434,11 @@ 0C86B33825E15B6000775FED /* PrimitiveDictionaryPropertyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrimitiveDictionaryPropertyTests.m; sourceTree = ""; }; 0C9758BE264974660097B48D /* SwiftRLMDictionaryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftRLMDictionaryTests.swift; sourceTree = ""; }; 0CBF2DB827286FFD00635902 /* ProjectedCollectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectedCollectTests.swift; sourceTree = ""; }; - 0CC270A92BCD664200788EE1 /* RLMInitialSubscriptionsConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RLMInitialSubscriptionsConfiguration.m; sourceTree = ""; }; - 0CC270AB2BCD665800788EE1 /* RLMInitialSubscriptionsConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMInitialSubscriptionsConfiguration.h; sourceTree = ""; }; 0CD1632526D3DD7B0027C49B /* Projection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Projection.swift; sourceTree = ""; }; 0CD1632726D3DF1C0027C49B /* ProjectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProjectionTests.swift; sourceTree = ""; }; - 1A0512731D87413000806AEC /* RLMSyncUtil_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMSyncUtil_Private.hpp; sourceTree = ""; }; 1A1EBF861F269E8E00F47698 /* RLMResults_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMResults_Private.hpp; sourceTree = ""; }; - 1A33C42A1DAEB9C4001E87AA /* RLMUser_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMUser_Private.hpp; sourceTree = ""; }; - 1A3623651D8384BA00945A54 /* RLMSyncConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSyncConfiguration.h; sourceTree = ""; }; - 1A3623661D8384BA00945A54 /* RLMSyncConfiguration.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMSyncConfiguration.mm; sourceTree = ""; }; - 1A36236A1D83868F00945A54 /* RLMSyncConfiguration_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMSyncConfiguration_Private.h; sourceTree = ""; }; - 1A4AC06D1D8BA86200DC9736 /* RLMSyncConfiguration_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMSyncConfiguration_Private.hpp; sourceTree = ""; }; 1A7B82391D51259F00750296 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; - 1A7DE7021D38460B0029F0AE /* Sync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sync.swift; sourceTree = ""; }; - 1A84132E1D4BCCE600C5326F /* RLMSyncUtil.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMSyncUtil.mm; sourceTree = ""; }; - 1AA5AE961D989BE000ED8C27 /* SwiftSyncTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = SwiftSyncTestCase.swift; path = Realm/ObjectServerTests/SwiftSyncTestCase.swift; sourceTree = ""; }; - 1AA5AE9A1D98A1B000ED8C27 /* Object-Server-Tests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Object-Server-Tests-Bridging-Header.h"; path = "Realm/ObjectServerTests/Object-Server-Tests-Bridging-Header.h"; sourceTree = ""; }; - 1AA5AE9B1D98A68E00ED8C27 /* RLMSyncTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RLMSyncTestCase.mm; path = Realm/ObjectServerTests/RLMSyncTestCase.mm; sourceTree = ""; }; - 1AA5AE9D1D98A6D800ED8C27 /* RLMSyncTestCase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RLMSyncTestCase.h; path = Realm/ObjectServerTests/RLMSyncTestCase.h; sourceTree = ""; }; - 1AA5AE9F1D98C99500ED8C27 /* SwiftObjectServerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftObjectServerTests.swift; path = Realm/ObjectServerTests/SwiftObjectServerTests.swift; sourceTree = ""; wrapsLines = 0; }; 1AB605D21D495927007F53DE /* RealmCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmCollection.swift; sourceTree = ""; }; - 1ABDCDAD1D792FEB003489E3 /* RLMUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMUser.h; sourceTree = ""; }; - 1ABDCDAF1D793008003489E3 /* RLMUser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMUser.mm; sourceTree = ""; }; - 1ABF256A1D528B9900BAC441 /* RLMSyncSession_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMSyncSession_Private.hpp; sourceTree = ""; }; - 1AD3870A1D4A7FBB00479110 /* RLMSyncSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSyncSession.h; sourceTree = ""; }; - 1AD397CC1F72FFC5002AA897 /* RLMRealm+Sync.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "RLMRealm+Sync.mm"; sourceTree = ""; }; - 1AD397CD1F72FFC6002AA897 /* RLMRealm+Sync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RLMRealm+Sync.h"; sourceTree = ""; }; - 1AF64DD01DA304A90081EB15 /* RLMUser+ObjectServerTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RLMUser+ObjectServerTests.h"; path = "Realm/ObjectServerTests/RLMUser+ObjectServerTests.h"; sourceTree = ""; }; - 1AF64DD11DA304A90081EB15 /* RLMUser+ObjectServerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "RLMUser+ObjectServerTests.mm"; path = "Realm/ObjectServerTests/RLMUser+ObjectServerTests.mm"; sourceTree = ""; }; - 1AF7EA941D340AF70001A9B5 /* RLMSyncManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSyncManager.h; sourceTree = ""; }; - 1AF7EA951D340AF70001A9B5 /* RLMSyncManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMSyncManager.mm; sourceTree = ""; }; - 1AF7EA981D340D1F0001A9B5 /* RLMSyncManager_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMSyncManager_Private.hpp; sourceTree = ""; }; 26F3CA681986CC86004623E1 /* SwiftPropertyTypeTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftPropertyTypeTest.swift; sourceTree = ""; }; 297FBEFA1C19F696009D1118 /* RLMTestCaseUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLMTestCaseUtils.swift; sourceTree = ""; }; 29B7FDF51C0DA6560023224E /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; @@ -692,16 +468,8 @@ 3F1D8D90265B076C00593ABA /* PrimitiveSetPropertyTests.tpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrimitiveSetPropertyTests.tpl.m; sourceTree = ""; }; 3F1D8D91265B076C00593ABA /* PrimitiveRLMValuePropertyTests.tpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrimitiveRLMValuePropertyTests.tpl.m; sourceTree = ""; }; 3F1D8D92265B076C00593ABA /* PrimitiveArrayPropertyTests.tpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrimitiveArrayPropertyTests.tpl.m; sourceTree = ""; }; - 3F20DA2019BE1EA6007DE308 /* RLMUpdateChecker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMUpdateChecker.hpp; sourceTree = ""; }; - 3F20DA2119BE1EA6007DE308 /* RLMUpdateChecker.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMUpdateChecker.mm; sourceTree = ""; }; 3F222C4D1E26F51300CA0713 /* ThreadSafeReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreadSafeReference.swift; sourceTree = ""; }; 3F2633C21E9D630000B32D30 /* PrimitiveListTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimitiveListTests.swift; sourceTree = ""; }; - 3F275EBD2433A5DA00161E7F /* RLMApp_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMApp_Private.hpp; sourceTree = ""; }; - 3F2B40C52B2900DA00E30319 /* CombineSyncTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CombineSyncTests.swift; path = Realm/ObjectServerTests/CombineSyncTests.swift; sourceTree = ""; }; - 3F2B40C62B2900DA00E30319 /* AsyncSyncTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AsyncSyncTests.swift; path = Realm/ObjectServerTests/AsyncSyncTests.swift; sourceTree = ""; }; - 3F2B40C72B2900DA00E30319 /* RLMSubscriptionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RLMSubscriptionTests.mm; path = Realm/ObjectServerTests/RLMSubscriptionTests.mm; sourceTree = ""; }; - 3F2B40C82B2900DA00E30319 /* RLMMongoClientTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RLMMongoClientTests.mm; path = Realm/ObjectServerTests/RLMMongoClientTests.mm; sourceTree = ""; }; - 3F2B40C92B2900DA00E30319 /* ClientResetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ClientResetTests.swift; path = Realm/ObjectServerTests/ClientResetTests.swift; sourceTree = ""; }; 3F2E66611CA0B9D5004761D5 /* NotificationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationTests.m; sourceTree = ""; }; 3F3411A5273433B300EC9D25 /* ObjcBridgeable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjcBridgeable.swift; sourceTree = ""; }; 3F4071342A57472F00D9C4A3 /* PrivateSymbols.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = PrivateSymbols.txt; sourceTree = ""; }; @@ -717,7 +485,6 @@ 3F4F3AD223F71C790048DB43 /* RLMObjectId.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMObjectId.mm; sourceTree = ""; }; 3F4F3AD323F71C790048DB43 /* RLMDecimal128.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMDecimal128.h; sourceTree = ""; }; 3F4F3AD423F71C790048DB43 /* RLMObjectId_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMObjectId_Private.hpp; sourceTree = ""; }; - 3F4FD2FA2B2A389A003E3DFD /* TestType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestType.swift; sourceTree = ""; }; 3F558C7E22C29A02002F0F30 /* TestUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = TestUtils.mm; path = Realm/TestUtils/TestUtils.mm; sourceTree = ""; }; 3F558C7F22C29A02002F0F30 /* RLMTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RLMTestCase.h; path = Realm/TestUtils/include/RLMTestCase.h; sourceTree = ""; }; 3F558C8022C29A02002F0F30 /* RLMMultiProcessTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RLMMultiProcessTestCase.h; path = Realm/TestUtils/include/RLMMultiProcessTestCase.h; sourceTree = ""; }; @@ -734,11 +501,7 @@ 3F67DB3B1E26D69C0024533D /* RLMThreadSafeReference.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMThreadSafeReference.mm; sourceTree = ""; }; 3F68BFCD1B558CA800D50FBD /* RLMPrefix.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMPrefix.h; sourceTree = ""; }; 3F73BC841E3A870F00FE80B6 /* ThreadSafeReferenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreadSafeReferenceTests.swift; sourceTree = ""; }; - 3F73BC871E3A876600FE80B6 /* ObjectServerTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "ObjectServerTests-Info.plist"; path = "Realm/ObjectServerTests/ObjectServerTests-Info.plist"; sourceTree = ""; }; - 3F73BC931E3A878500FE80B6 /* NSError+RLMSync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+RLMSync.h"; sourceTree = ""; }; - 3F73BC941E3A878500FE80B6 /* NSError+RLMSync.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+RLMSync.m"; sourceTree = ""; }; 3F7556731BE95A050058BC7E /* AsyncTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AsyncTests.mm; sourceTree = ""; }; - 3F7BCEB42834377E00EB6E16 /* Events.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = ""; }; 3F83E9A22630A14800FC9623 /* RLMSwiftProperty.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSwiftProperty.h; sourceTree = ""; }; 3F852BE9278E1F000009DF74 /* TestBase.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TestBase.xcconfig; sourceTree = ""; }; 3F857A472769291800F9B9B1 /* KeyPathStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPathStrings.swift; sourceTree = ""; }; @@ -747,14 +510,12 @@ 3F9863B91D36876B00641C98 /* RLMClassInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMClassInfo.mm; sourceTree = ""; }; 3F9863BA1D36876B00641C98 /* RLMClassInfo.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMClassInfo.hpp; sourceTree = ""; }; 3F997772273E23D300AA9E13 /* RealmCollectionImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmCollectionImpl.swift; sourceTree = ""; }; - 3F9ADA9326E7E87B007349A5 /* SwiftCollectionSyncTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftCollectionSyncTests.swift; path = Realm/ObjectServerTests/SwiftCollectionSyncTests.swift; sourceTree = ""; }; 3F9F53D22718B5DA000EEB4A /* CustomPersistable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPersistable.swift; sourceTree = ""; }; 3F9F53D42718E8E6000EEB4A /* CustomPersistableTestObjects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPersistableTestObjects.swift; sourceTree = ""; }; 3FA5E94C266064C4008F1345 /* ModernObjectCreationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModernObjectCreationTests.swift; sourceTree = ""; }; 3FAF2D4029577100002EAC93 /* TestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = ""; }; 3FB19068265ECF0C00DA7C76 /* ModernObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModernObjectTests.swift; sourceTree = ""; }; 3FB1906A265ED23300DA7C76 /* ModernTestObjects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModernTestObjects.swift; sourceTree = ""; }; - 3FB56E7E250D457A00A6216B /* ObjectServerTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ObjectServerTests.xcconfig; sourceTree = ""; }; 3FB6ABD62416A26100E318C2 /* ObjectId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectId.swift; sourceTree = ""; }; 3FB6ABD82416A27000E318C2 /* Decimal128.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Decimal128.swift; sourceTree = ""; }; 3FBEF6781C63D66100F6935B /* RLMCollection_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMCollection_Private.hpp; sourceTree = ""; }; @@ -781,8 +542,6 @@ 3FE267D3264308680030F83C /* PropertyAccessors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyAccessors.swift; sourceTree = ""; }; 3FE267D4264308680030F83C /* SchemaDiscovery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaDiscovery.swift; sourceTree = ""; }; 3FE2BE0223D8CAD1002860E9 /* CombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineTests.swift; sourceTree = ""; }; - 3FE5818322C2B4B900BA10E7 /* Nonsync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Nonsync.swift; sourceTree = ""; }; - 3FE5818422C2B4B900BA10E7 /* ObjectiveCSupport+Sync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ObjectiveCSupport+Sync.swift"; sourceTree = ""; }; 3FE5B4D424CF3F06004D4EF3 /* realm-monorepo.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = "realm-monorepo.xcframework"; path = "core/realm-monorepo.xcframework"; sourceTree = ""; }; 3FE79FF719BA6A5900780C9A /* RLMSwiftSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSwiftSupport.h; sourceTree = ""; }; 3FEB383C1E70AC6900F22712 /* ObjectCreationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ObjectCreationTests.mm; sourceTree = ""; }; @@ -790,51 +549,21 @@ 3FEC91542A4B59A40044BFF5 /* Static.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Static.xcconfig; sourceTree = ""; }; 3FEC91552A4B5D520044BFF5 /* libcompression.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcompression.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS17.0.sdk/usr/lib/libcompression.tbd; sourceTree = DEVELOPER_DIR; }; 3FEC91572A4B5D600044BFF5 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS17.0.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; }; - 3FEE4F3E281C4370009194C7 /* RLMEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMEvent.h; sourceTree = ""; }; - 3FEE4F3F281C4370009194C7 /* RLMEvent.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMEvent.mm; sourceTree = ""; }; - 3FEE4F44281C439D009194C7 /* EventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EventTests.swift; path = Realm/ObjectServerTests/EventTests.swift; sourceTree = ""; }; 3FFC686F2B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCustomPropertiesTests.swift; sourceTree = ""; }; - 494566A8246E8C59000FD07F /* ObjectiveCSupport+BSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObjectiveCSupport+BSON.swift"; sourceTree = ""; }; - 4993220224129DCD00A0EC8E /* RLMCredentials_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMCredentials_Private.hpp; sourceTree = ""; }; - 4993220324129DCD00A0EC8E /* RLMCredentials.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMCredentials.h; sourceTree = ""; }; - 4993220424129DCD00A0EC8E /* RLMCredentials.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMCredentials.mm; sourceTree = ""; }; - 4993220524129DCD00A0EC8E /* RLMApp.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMApp.mm; sourceTree = ""; }; - 4993220724129DCE00A0EC8E /* RLMApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMApp.h; sourceTree = ""; }; - 4993221424129E6500A0EC8E /* RLMNetworkTransport.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMNetworkTransport.mm; sourceTree = ""; }; - 4993221524129E6600A0EC8E /* RLMNetworkTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMNetworkTransport.h; sourceTree = ""; }; - 4996EA9D2465BB8A003A1F51 /* BSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BSON.swift; sourceTree = ""; }; - 4996EA9F2465C44E003A1F51 /* SwiftBSONTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftBSONTests.swift; sourceTree = ""; }; - 49D9DFC4246C8E48003AD31D /* setup_baas.rb */ = {isa = PBXFileReference; lastKnownFileType = text.script.ruby; name = setup_baas.rb; path = Realm/ObjectServerTests/setup_baas.rb; sourceTree = ""; }; - 49E12CEF245DB7CC00359DF1 /* RLMBSON.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMBSON.mm; sourceTree = ""; }; - 49E12CF1245DB7E800359DF1 /* RLMBSON.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMBSON.h; sourceTree = ""; }; - 49E12CF4245DBF8A00359DF1 /* RLMBSON_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMBSON_Private.hpp; sourceTree = ""; }; 530BA61326DFA1CB008FC550 /* RLMChildProcessEnvironment.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RLMChildProcessEnvironment.m; path = Realm/TestUtils/RLMChildProcessEnvironment.m; sourceTree = ""; }; 53124A4F25B714EC00771CE4 /* SwiftUITestHost.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SwiftUITestHost.xcconfig; sourceTree = ""; }; 53124AB825B71AF600771CE4 /* SwiftUITestHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUITestHost.app; sourceTree = BUILT_PRODUCTS_DIR; }; 53124AD425B71AF700771CE4 /* SwiftUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 53124AD825B71AF700771CE4 /* SwiftUITestHostUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUITestHostUITests.swift; sourceTree = ""; }; 53124ADA25B71AF700771CE4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 531F956927906EF300E497F1 /* RLMSyncSession.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMSyncSession.mm; sourceTree = ""; }; - 531F956B27906F7600E497F1 /* RLMSyncSubscription.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMSyncSubscription.mm; sourceTree = ""; }; - 531F956E279070A800E497F1 /* RLMServerTestObjects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RLMServerTestObjects.m; path = Realm/ObjectServerTests/RLMServerTestObjects.m; sourceTree = ""; }; - 531F956F279070A800E497F1 /* RLMServerTestObjects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RLMServerTestObjects.h; path = Realm/ObjectServerTests/RLMServerTestObjects.h; sourceTree = ""; }; - 532E916E24AA533A003FD9DB /* TimeoutProxyServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TimeoutProxyServer.swift; path = Realm/ObjectServerTests/TimeoutProxyServer.swift; sourceTree = ""; }; 533489DD26E0F9510085EEE1 /* RLMChildProcessEnvironment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RLMChildProcessEnvironment.h; path = Realm/TestUtils/include/RLMChildProcessEnvironment.h; sourceTree = ""; }; - 5346E7312487AC9D00595C68 /* RLMBSONTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = RLMBSONTests.mm; path = Realm/ObjectServerTests/RLMBSONTests.mm; sourceTree = ""; }; 535EA9E125B0919800DBF3CD /* SwiftUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUI.swift; sourceTree = ""; }; 535EAA7425B0B02B00DBF3CD /* SwiftUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUITests.swift; sourceTree = ""; }; 53626A8C25D3172000D9515D /* SwiftUITests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SwiftUITests.xcconfig; sourceTree = ""; }; 53626AAE25D31CAC00D9515D /* Objects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Objects.swift; sourceTree = ""; }; - 536B7C0B24A4C223006B535D /* dependencies.list */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dependencies.list; sourceTree = ""; }; - 537130C724A9E417001FDBBC /* RealmServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RealmServer.swift; path = Realm/ObjectServerTests/RealmServer.swift; sourceTree = ""; }; 53A34E3325CDA0AC00698930 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 53A34E3425CDA0AC00698930 /* SwiftUITestHostApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUITestHostApp.swift; sourceTree = ""; }; 53A34E3525CDA0AC00698930 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 53CCC6C3257EC8A300A8FC50 /* RLMApp_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMApp_Private.h; sourceTree = ""; }; - 53CCC6E7257EC8C300A8FC50 /* RLMUser_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMUser_Private.h; sourceTree = ""; }; - 53E308C927905794002A8D91 /* RLMSyncSubscription_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSyncSubscription_Private.h; sourceTree = ""; }; - 53E308CA27905794002A8D91 /* RLMSyncSubscription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSyncSubscription.h; sourceTree = ""; }; - 53E308CB27905794002A8D91 /* RLMSyncSubscription_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMSyncSubscription_Private.hpp; sourceTree = ""; }; 5B77EACD1DCC5614006AB51D /* ObjectiveCSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectiveCSupport.swift; sourceTree = ""; }; 5BC537151DD5B8D70055C524 /* ObjectiveCSupportTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectiveCSupportTests.swift; sourceTree = ""; }; 5D03FB1E1E0DAFBA007D53EA /* PredicateUtilTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PredicateUtilTests.mm; sourceTree = ""; }; @@ -888,16 +617,9 @@ 5D6610111BE98D880021E04F /* SwiftUnicodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUnicodeTests.swift; sourceTree = ""; }; 5D6610121BE98D880021E04F /* TestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCase.swift; sourceTree = ""; }; 5DD755E01BE05C19002800DA /* Tests.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Tests.xcconfig; sourceTree = ""; }; - 6807E6492487E8660096066F /* RLMPushClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMPushClient.h; sourceTree = ""; }; - 6807E64B2487F7220096066F /* RLMPushClient.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMPushClient.mm; sourceTree = ""; }; - 6807E64E2487F9210096066F /* RLMPushClient_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMPushClient_Private.hpp; sourceTree = ""; }; 681EE33A25EE8E1400A9DEC5 /* AnyRealmValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyRealmValue.swift; sourceTree = ""; }; 681EE34625EE8E5600A9DEC5 /* ObjectiveCSupport+AnyRealmValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObjectiveCSupport+AnyRealmValue.swift"; sourceTree = ""; }; A05FA61E1B62C3900000C9B2 /* RLMObjectBase_Dynamic.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMObjectBase_Dynamic.h; sourceTree = ""; }; - AC0538052885B1DF00CE27C4 /* RLMAsymmetricSyncServerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RLMAsymmetricSyncServerTests.mm; path = Realm/ObjectServerTests/RLMAsymmetricSyncServerTests.mm; sourceTree = ""; }; - AC0538062885B1DF00CE27C4 /* SwiftAsymmetricSyncServerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftAsymmetricSyncServerTests.swift; path = Realm/ObjectServerTests/SwiftAsymmetricSyncServerTests.swift; sourceTree = ""; }; - AC2C2A3E268E1ACE00B4DA33 /* SwiftServerObjects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SwiftServerObjects.swift; path = Realm/ObjectServerTests/SwiftServerObjects.swift; sourceTree = ""; }; - AC2C2A43268F982D00B4DA33 /* SwiftUISyncTestHostUITests.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftUISyncTestHostUITests.entitlements; sourceTree = ""; }; AC3B33AB29DC6CEE0042F3A0 /* RLMLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMLogger.h; sourceTree = ""; }; AC3B33AC29DC6CEE0042F3A0 /* RLMLogger.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMLogger.mm; sourceTree = ""; }; AC3B33AD29DC6CEE0042F3A0 /* RLMLogger_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMLogger_Private.h; sourceTree = ""; }; @@ -907,26 +629,6 @@ AC7825BB2ACD90DA007ABA4B /* RLMGeospatial_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMGeospatial_Private.hpp; sourceTree = ""; }; AC7825BC2ACD90DA007ABA4B /* RLMGeospatial.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMGeospatial.h; sourceTree = ""; }; AC7825C02ACD916C007ABA4B /* GeospatialTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeospatialTests.swift; sourceTree = ""; }; - AC7D182B261F2F560080E1D2 /* RLMObjectServerPartitionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RLMObjectServerPartitionTests.mm; path = Realm/ObjectServerTests/RLMObjectServerPartitionTests.mm; sourceTree = ""; }; - AC7D182C261F2F560080E1D2 /* SwiftObjectServerPartitionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftObjectServerPartitionTests.swift; path = Realm/ObjectServerTests/SwiftObjectServerPartitionTests.swift; sourceTree = ""; }; - AC81360E287F21350029F15E /* AsymmetricObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsymmetricObject.swift; sourceTree = ""; }; - AC813610287F21700029F15E /* RLMAsymmetricObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMAsymmetricObject.h; sourceTree = ""; }; - AC813611287F21700029F15E /* RLMAsymmetricObject.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMAsymmetricObject.mm; sourceTree = ""; }; - AC8846732686573B00DF4A65 /* SwiftUISyncTestHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUISyncTestHost.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AC8846752686573B00DF4A65 /* SwiftUISyncTestHostApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUISyncTestHostApp.swift; sourceTree = ""; }; - AC8846772686573B00DF4A65 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - AC8846902686573D00DF4A65 /* SwiftUISyncTestHostUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUISyncTestHostUITests.swift; sourceTree = ""; }; - AC8846B62687BC4100DF4A65 /* SwiftUIServerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftUIServerTests.swift; path = Realm/ObjectServerTests/SwiftUIServerTests.swift; sourceTree = ""; }; - AC8846B82687BD4D00DF4A65 /* SwiftUISyncTestHostUITests-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SwiftUISyncTestHostUITests-Bridging-Header.h"; sourceTree = ""; }; - AC8847432687DFE700DF4A65 /* SwiftUISyncTestHost.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SwiftUISyncTestHost.xcconfig; sourceTree = ""; }; - AC8847442687DFE700DF4A65 /* SwiftUISyncTests.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SwiftUISyncTests.xcconfig; sourceTree = ""; }; - AC8847472687E0AA00DF4A65 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AC8847492687E0BD00DF4A65 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AC88478126888C6300DF4A65 /* SwiftUISyncTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftUISyncTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - AC8AE64A26BAD4B00037D4E5 /* SwiftMongoClientTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftMongoClientTests.swift; path = Realm/ObjectServerTests/SwiftMongoClientTests.swift; sourceTree = ""; }; - ACB6FD30273C60920009712F /* RLMFlexibleSyncServerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RLMFlexibleSyncServerTests.mm; path = Realm/ObjectServerTests/RLMFlexibleSyncServerTests.mm; sourceTree = ""; }; - ACB6FD31273C60920009712F /* SwiftFlexibleSyncServerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftFlexibleSyncServerTests.swift; path = Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift; sourceTree = ""; }; - ACB6FD34273C60CC0009712F /* SyncSubscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncSubscription.swift; sourceTree = ""; }; ACF08B6626DD936200686CBC /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; }; ACFF0EC628EC5ADB0097AEE0 /* CustomColumnNameTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomColumnNameTests.swift; sourceTree = ""; }; C042A48C1B7522A900771ED2 /* RealmConfigurationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RealmConfigurationTests.mm; sourceTree = ""; }; @@ -936,39 +638,13 @@ C0D2DD0F1B6BE0DD004E8919 /* RLMRealmConfiguration_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMRealmConfiguration_Private.h; sourceTree = ""; }; CF040493263DF0A900F9AEE0 /* PrimitiveMapTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimitiveMapTests.swift; sourceTree = ""; }; CF052EFA25DEB671008EEF86 /* DictionaryPropertyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DictionaryPropertyTests.m; sourceTree = ""; }; - CF08757C260B98E100B9BE60 /* RLMCollectionSyncTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RLMCollectionSyncTests.mm; path = Realm/ObjectServerTests/RLMCollectionSyncTests.mm; sourceTree = ""; }; CF0D04F02693652E0038A058 /* KeyPathTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPathTests.swift; sourceTree = ""; }; CF25080D283B90F8007D66FE /* SectionedResultsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SectionedResultsTests.m; sourceTree = ""; }; - CF330BBB24E56E3A00F07EE2 /* RLMNetworkTransport_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMNetworkTransport_Private.hpp; sourceTree = ""; }; - CF330BBC24E57D5F00F07EE2 /* RLMWatchTestUtility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RLMWatchTestUtility.h; path = Realm/ObjectServerTests/RLMWatchTestUtility.h; sourceTree = ""; }; - CF330BBD24E57D5F00F07EE2 /* RLMWatchTestUtility.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RLMWatchTestUtility.m; path = Realm/ObjectServerTests/RLMWatchTestUtility.m; sourceTree = ""; }; CF44460526121B2A00BAFDB4 /* RLMSwiftValueStorage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMSwiftValueStorage.mm; sourceTree = ""; }; CF44460626121B2A00BAFDB4 /* RLMSwiftValueStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSwiftValueStorage.h; sourceTree = ""; }; CF44461D26121C6800BAFDB4 /* RealmProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmProperty.swift; sourceTree = ""; }; CF46CBFF26D931BA00DE450C /* QueryTests.swift.gyb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = QueryTests.swift.gyb; sourceTree = ""; }; CF46CC0026D931BA00DE450C /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; - CF6E0480242A141200DB7F14 /* RLMEmailPasswordAuth.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMEmailPasswordAuth.h; sourceTree = ""; }; - CF6E0481242A141200DB7F14 /* RLMEmailPasswordAuth.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMEmailPasswordAuth.mm; sourceTree = ""; }; - CF6E0484242A321200DB7F14 /* RLMProviderClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMProviderClient.h; sourceTree = ""; }; - CF6E0485242A321200DB7F14 /* RLMProviderClient.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMProviderClient.mm; sourceTree = ""; }; - CF76F7CB24816AA800890DD2 /* RLMUpdateResult.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMUpdateResult.mm; sourceTree = ""; }; - CF76F7CC24816AA800890DD2 /* RLMUpdateResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMUpdateResult.h; sourceTree = ""; }; - CF76F7CE24816AA900890DD2 /* RLMUpdateResult_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMUpdateResult_Private.hpp; sourceTree = ""; }; - CF76F7CF24816AA900890DD2 /* RLMMongoCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMMongoCollection.h; sourceTree = ""; }; - CF76F7D024816AAA00890DD2 /* RLMFindOptions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMFindOptions.mm; sourceTree = ""; }; - CF76F7D124816AAA00890DD2 /* RLMMongoCollection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMMongoCollection.mm; sourceTree = ""; }; - CF76F7D224816AAA00890DD2 /* RLMMongoCollection_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMMongoCollection_Private.h; sourceTree = ""; }; - CF76F7D324816AAA00890DD2 /* RLMMongoClient_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMMongoClient_Private.hpp; sourceTree = ""; }; - CF76F7D424816AAA00890DD2 /* RLMFindOptions_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMFindOptions_Private.hpp; sourceTree = ""; }; - CF76F7D524816AAA00890DD2 /* RLMMongoClient.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMMongoClient.mm; sourceTree = ""; }; - CF76F7D624816AAA00890DD2 /* RLMFindOneAndModifyOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMFindOneAndModifyOptions.h; sourceTree = ""; }; - CF76F7D724816AAA00890DD2 /* RLMFindOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMFindOptions.h; sourceTree = ""; }; - CF76F7D824816AAB00890DD2 /* RLMFindOneAndModifyOptions_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMFindOneAndModifyOptions_Private.hpp; sourceTree = ""; }; - CF76F7D924816AAB00890DD2 /* RLMMongoDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMMongoDatabase.h; sourceTree = ""; }; - CF76F7DA24816AAB00890DD2 /* RLMMongoClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMMongoClient.h; sourceTree = ""; }; - CF76F7DB24816AAB00890DD2 /* RLMFindOneAndModifyOptions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMFindOneAndModifyOptions.mm; sourceTree = ""; }; - CF76F7DC24816AAB00890DD2 /* RLMMongoDatabase_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMMongoDatabase_Private.hpp; sourceTree = ""; }; - CF76F80124816B3800890DD2 /* MongoClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MongoClient.swift; sourceTree = ""; }; CF84D4EF281823B300005E27 /* SectionedResults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionedResults.swift; sourceTree = ""; }; CF986D1A25AE3B080039D287 /* RLMSet_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSet_Private.h; sourceTree = ""; }; CF986D1B25AE3B080039D287 /* RLMSet.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMSet.mm; sourceTree = ""; }; @@ -983,16 +659,7 @@ CF986DF525AE3EDF0039D287 /* PrimitiveMutableSetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimitiveMutableSetTests.swift; sourceTree = ""; }; CF9881C025DABC6500BD7E4F /* RLMManagedDictionary.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMManagedDictionary.mm; sourceTree = ""; }; CF9881CB25DABDE900BD7E4F /* RLMDictionary_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMDictionary_Private.hpp; sourceTree = ""; }; - CFA3A23D260B8427002C3266 /* RLMCollectionSyncTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = RLMCollectionSyncTests.mm; path = Realm/ObjectServerTests/RLMCollectionSyncTests.mm; sourceTree = ""; }; CFAE926A24A0A7F40033CB31 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; - CFAEF75F242B5F9A00EAF721 /* RLMAPIKeyAuth.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMAPIKeyAuth.h; sourceTree = ""; }; - CFAEF760242B5F9A00EAF721 /* RLMAPIKeyAuth.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMAPIKeyAuth.mm; sourceTree = ""; }; - CFAEF763242B672700EAF721 /* RLMUserAPIKey.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMUserAPIKey.h; sourceTree = ""; }; - CFAEF764242B672700EAF721 /* RLMUserAPIKey.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMUserAPIKey.mm; sourceTree = ""; }; - CFAEF767242B7F8900EAF721 /* RLMUserAPIKey_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMUserAPIKey_Private.hpp; sourceTree = ""; }; - CFB43139243DF87100471C18 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; - CFB5E1602497A45D009CABB3 /* RLMProviderClient_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMProviderClient_Private.hpp; sourceTree = ""; }; - CFB674A224EEE9CB00FBF0B8 /* WatchTestUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WatchTestUtility.swift; path = Realm/ObjectServerTests/WatchTestUtility.swift; sourceTree = ""; }; CFD8D11F25BB0B8B0037FE4D /* RLMManagedSet.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMManagedSet.mm; sourceTree = ""; }; CFDBC4B228803C7200EE80E6 /* RLMSectionedResults_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMSectionedResults_Private.hpp; sourceTree = ""; }; CFDBC4B328803C7200EE80E6 /* RLMSectionedResults.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMSectionedResults.mm; sourceTree = ""; }; @@ -1047,14 +714,10 @@ E81A1FC31955FE0100FDED82 /* RealmTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RealmTests.mm; sourceTree = ""; }; E81A1FD01955FE0100FDED82 /* SwiftRealmTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftRealmTests.swift; sourceTree = ""; }; E81A1FD11955FE0100FDED82 /* TransactionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TransactionTests.m; sourceTree = ""; }; - E8267FED1D90B79000E001C7 /* ObjectServerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ObjectServerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - E8267FF01D90B8E700E001C7 /* RLMObjectServerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = RLMObjectServerTests.mm; path = Realm/ObjectServerTests/RLMObjectServerTests.mm; sourceTree = ""; }; E82FA60A195632F20043A3C3 /* SwiftArrayPropertyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SwiftArrayPropertyTests.swift; sourceTree = ""; }; E82FA60B195632F20043A3C3 /* SwiftArrayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SwiftArrayTests.swift; sourceTree = ""; }; E82FA60D195632F20043A3C3 /* SwiftLinkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SwiftLinkTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E82FA60F195632F20043A3C3 /* SwiftObjectInterfaceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SwiftObjectInterfaceTests.swift; sourceTree = ""; }; - E83591931B3DF05C0035F2F3 /* RLMAnalytics.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMAnalytics.mm; sourceTree = ""; }; - E83591941B3DF05C0035F2F3 /* RLMAnalytics.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMAnalytics.hpp; sourceTree = ""; }; E83AF538196DDE58002275B2 /* SwiftDynamicTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftDynamicTests.swift; sourceTree = ""; }; E86900E11CC04F5B0008A8B6 /* RLMRealmConfiguration_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMRealmConfiguration_Private.hpp; sourceTree = ""; }; E8839B2C19E31FD90047B1A8 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Realm/Tests/TestHost/Info.plist; sourceTree = SOURCE_ROOT; }; @@ -1124,29 +787,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - AC8846702686573B00DF4A65 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AC88477726888C6300DF4A65 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - E8267FE21D90B79000E001C7 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - E8267FE31D90B79000E001C7 /* Realm.framework in Frameworks */, - 1AA5AEA31D98DF1000ED8C27 /* RealmSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; E8D89BA01955FC6D00CF2B9A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1174,73 +814,6 @@ name = Frameworks; sourceTree = ""; }; - 1AA5AEA21D98CA5300ED8C27 /* Utility */ = { - isa = PBXGroup; - children = ( - 1AA5AE9A1D98A1B000ED8C27 /* Object-Server-Tests-Bridging-Header.h */, - 3F73BC871E3A876600FE80B6 /* ObjectServerTests-Info.plist */, - 537130C724A9E417001FDBBC /* RealmServer.swift */, - 1AF64DD01DA304A90081EB15 /* RLMUser+ObjectServerTests.h */, - 1AF64DD11DA304A90081EB15 /* RLMUser+ObjectServerTests.mm */, - CF330BBC24E57D5F00F07EE2 /* RLMWatchTestUtility.h */, - CF330BBD24E57D5F00F07EE2 /* RLMWatchTestUtility.m */, - 532E916E24AA533A003FD9DB /* TimeoutProxyServer.swift */, - CFB674A224EEE9CB00FBF0B8 /* WatchTestUtility.swift */, - ); - name = Utility; - sourceTree = ""; - }; - 1AF6EA441D36B1220014EB85 /* Sync */ = { - isa = PBXGroup; - children = ( - CF76F7CA24816A5700890DD2 /* MongoClient */, - CF6E047D242A13CB00DB7F14 /* ProviderClients */, - 3F73BC931E3A878500FE80B6 /* NSError+RLMSync.h */, - 3F73BC941E3A878500FE80B6 /* NSError+RLMSync.m */, - 4993220724129DCE00A0EC8E /* RLMApp.h */, - 4993220524129DCD00A0EC8E /* RLMApp.mm */, - 53CCC6C3257EC8A300A8FC50 /* RLMApp_Private.h */, - 3F275EBD2433A5DA00161E7F /* RLMApp_Private.hpp */, - 49E12CF1245DB7E800359DF1 /* RLMBSON.h */, - 49E12CEF245DB7CC00359DF1 /* RLMBSON.mm */, - 49E12CF4245DBF8A00359DF1 /* RLMBSON_Private.hpp */, - 4993220324129DCD00A0EC8E /* RLMCredentials.h */, - 4993220424129DCD00A0EC8E /* RLMCredentials.mm */, - 4993220224129DCD00A0EC8E /* RLMCredentials_Private.hpp */, - 0CC270AB2BCD665800788EE1 /* RLMInitialSubscriptionsConfiguration.h */, - 0CC270A92BCD664200788EE1 /* RLMInitialSubscriptionsConfiguration.m */, - 4993221524129E6600A0EC8E /* RLMNetworkTransport.h */, - 4993221424129E6500A0EC8E /* RLMNetworkTransport.mm */, - CF330BBB24E56E3A00F07EE2 /* RLMNetworkTransport_Private.hpp */, - 6807E6492487E8660096066F /* RLMPushClient.h */, - 6807E64B2487F7220096066F /* RLMPushClient.mm */, - 6807E64E2487F9210096066F /* RLMPushClient_Private.hpp */, - 1AD397CD1F72FFC6002AA897 /* RLMRealm+Sync.h */, - 1AD397CC1F72FFC5002AA897 /* RLMRealm+Sync.mm */, - 1A3623651D8384BA00945A54 /* RLMSyncConfiguration.h */, - 1A3623661D8384BA00945A54 /* RLMSyncConfiguration.mm */, - 1A36236A1D83868F00945A54 /* RLMSyncConfiguration_Private.h */, - 1A4AC06D1D8BA86200DC9736 /* RLMSyncConfiguration_Private.hpp */, - 1AF7EA941D340AF70001A9B5 /* RLMSyncManager.h */, - 1AF7EA951D340AF70001A9B5 /* RLMSyncManager.mm */, - 1AF7EA981D340D1F0001A9B5 /* RLMSyncManager_Private.hpp */, - 1AD3870A1D4A7FBB00479110 /* RLMSyncSession.h */, - 531F956927906EF300E497F1 /* RLMSyncSession.mm */, - 1ABF256A1D528B9900BAC441 /* RLMSyncSession_Private.hpp */, - 53E308CA27905794002A8D91 /* RLMSyncSubscription.h */, - 531F956B27906F7600E497F1 /* RLMSyncSubscription.mm */, - 53E308C927905794002A8D91 /* RLMSyncSubscription_Private.h */, - 53E308CB27905794002A8D91 /* RLMSyncSubscription_Private.hpp */, - 1A84132E1D4BCCE600C5326F /* RLMSyncUtil.mm */, - 1A0512731D87413000806AEC /* RLMSyncUtil_Private.hpp */, - 1ABDCDAD1D792FEB003489E3 /* RLMUser.h */, - 1ABDCDAF1D793008003489E3 /* RLMUser.mm */, - 53CCC6E7257EC8C300A8FC50 /* RLMUser_Private.h */, - 1A33C42A1DAEB9C4001E87AA /* RLMUser_Private.hpp */, - ); - name = Sync; - sourceTree = ""; - }; 3F1A5E731992EB7400F45F4C /* TestHost */ = { isa = PBXGroup; children = ( @@ -1322,11 +895,8 @@ 5D660FBA1BE98BD80021E04F /* RealmSwift */, 5D659E6D1BE0398E006515A0 /* Base.xcconfig */, 5D659E6E1BE0398E006515A0 /* Debug.xcconfig */, - 3FB56E7E250D457A00A6216B /* ObjectServerTests.xcconfig */, 5D659E6F1BE0398E006515A0 /* Release.xcconfig */, 3FEC91542A4B59A40044BFF5 /* Static.xcconfig */, - AC8847432687DFE700DF4A65 /* SwiftUISyncTestHost.xcconfig */, - AC8847442687DFE700DF4A65 /* SwiftUISyncTests.xcconfig */, 53124A4F25B714EC00771CE4 /* SwiftUITestHost.xcconfig */, 53626A8C25D3172000D9515D /* SwiftUITests.xcconfig */, 3F852BE9278E1F000009DF74 /* TestBase.xcconfig */, @@ -1350,28 +920,20 @@ 3F48201C26307CE2005B40E8 /* Impl */, 5D660FE31BE98D670021E04F /* Aliases.swift */, 681EE33A25EE8E1400A9DEC5 /* AnyRealmValue.swift */, - CFB43139243DF87100471C18 /* App.swift */, - AC81360E287F21350029F15E /* AsymmetricObject.swift */, - 4996EA9D2465BB8A003A1F51 /* BSON.swift */, 3F102CBC23DBC68300108FD2 /* Combine.swift */, 3F9F53D22718B5DA000EEB4A /* CustomPersistable.swift */, 3FB6ABD82416A27000E318C2 /* Decimal128.swift */, 3FC3F9162419B63100E27322 /* EmbeddedObject.swift */, 29B7FDF51C0DA6560023224E /* Error.swift */, - 3F7BCEB42834377E00EB6E16 /* Events.swift */, AC7825B82ACD90BE007ABA4B /* Geospatial.swift */, 5D1534B71CCFF545008976D7 /* LinkingObjects.swift */, 5D660FE41BE98D670021E04F /* List.swift */, 0C3BD4D225C1C5AB007CFDD3 /* Map.swift */, 5D660FE51BE98D670021E04F /* Migration.swift */, - CF76F80124816B3800890DD2 /* MongoClient.swift */, CF986D8E25AE3C980039D287 /* MutableSet.swift */, - 3FE5818322C2B4B900BA10E7 /* Nonsync.swift */, 5D660FE61BE98D670021E04F /* Object.swift */, 3FB6ABD62416A26100E318C2 /* ObjectId.swift */, 681EE34625EE8E5600A9DEC5 /* ObjectiveCSupport+AnyRealmValue.swift */, - 494566A8246E8C59000FD07F /* ObjectiveCSupport+BSON.swift */, - 3FE5818422C2B4B900BA10E7 /* ObjectiveCSupport+Sync.swift */, 5B77EACD1DCC5614006AB51D /* ObjectiveCSupport.swift */, 5D660FE71BE98D670021E04F /* ObjectSchema.swift */, 5D660FE81BE98D670021E04F /* Optional.swift */, @@ -1390,8 +952,6 @@ CF84D4EF281823B300005E27 /* SectionedResults.swift */, 5D660FEF1BE98D670021E04F /* SortDescriptor.swift */, 535EA9E125B0919800DBF3CD /* SwiftUI.swift */, - 1A7DE7021D38460B0029F0AE /* Sync.swift */, - ACB6FD34273C60CC0009712F /* SyncSubscription.swift */, 3F222C4D1E26F51300CA0713 /* ThreadSafeReference.swift */, 5D660FF01BE98D670021E04F /* Util.swift */, ); @@ -1447,7 +1007,6 @@ 5D66100D1BE98D880021E04F /* SchemaTests.swift */, CFDBC4BE288040CD00EE80E6 /* SectionedResultsTests.swift */, 5D66100E1BE98D880021E04F /* SortDescriptorTests.swift */, - 4996EA9F2465C44E003A1F51 /* SwiftBSONTests.swift */, 5D66100F1BE98D880021E04F /* SwiftLinkTests.swift */, 5D6610101BE98D880021E04F /* SwiftTestObjects.swift */, 535EAA7425B0B02B00DBF3CD /* SwiftUITests.swift */, @@ -1469,69 +1028,6 @@ name = "Supporting Files"; sourceTree = ""; }; - AC8846742686573B00DF4A65 /* SwiftUISyncTestHost */ = { - isa = PBXGroup; - children = ( - AC8846772686573B00DF4A65 /* ContentView.swift */, - AC8847472687E0AA00DF4A65 /* Info.plist */, - AC8846752686573B00DF4A65 /* SwiftUISyncTestHostApp.swift */, - 3F4FD2FA2B2A389A003E3DFD /* TestType.swift */, - ); - path = SwiftUISyncTestHost; - sourceTree = ""; - }; - AC88468F2686573D00DF4A65 /* SwiftUISyncTestHostUITests */ = { - isa = PBXGroup; - children = ( - AC8847492687E0BD00DF4A65 /* Info.plist */, - AC8846B82687BD4D00DF4A65 /* SwiftUISyncTestHostUITests-Bridging-Header.h */, - AC2C2A43268F982D00B4DA33 /* SwiftUISyncTestHostUITests.entitlements */, - AC8846902686573D00DF4A65 /* SwiftUISyncTestHostUITests.swift */, - ); - path = SwiftUISyncTestHostUITests; - sourceTree = ""; - }; - CF6E047D242A13CB00DB7F14 /* ProviderClients */ = { - isa = PBXGroup; - children = ( - CFAEF75F242B5F9A00EAF721 /* RLMAPIKeyAuth.h */, - CFAEF760242B5F9A00EAF721 /* RLMAPIKeyAuth.mm */, - CF6E0480242A141200DB7F14 /* RLMEmailPasswordAuth.h */, - CF6E0481242A141200DB7F14 /* RLMEmailPasswordAuth.mm */, - CF6E0484242A321200DB7F14 /* RLMProviderClient.h */, - CF6E0485242A321200DB7F14 /* RLMProviderClient.mm */, - CFB5E1602497A45D009CABB3 /* RLMProviderClient_Private.hpp */, - CFAEF763242B672700EAF721 /* RLMUserAPIKey.h */, - CFAEF764242B672700EAF721 /* RLMUserAPIKey.mm */, - CFAEF767242B7F8900EAF721 /* RLMUserAPIKey_Private.hpp */, - ); - name = ProviderClients; - sourceTree = ""; - }; - CF76F7CA24816A5700890DD2 /* MongoClient */ = { - isa = PBXGroup; - children = ( - CF76F7D624816AAA00890DD2 /* RLMFindOneAndModifyOptions.h */, - CF76F7DB24816AAB00890DD2 /* RLMFindOneAndModifyOptions.mm */, - CF76F7D824816AAB00890DD2 /* RLMFindOneAndModifyOptions_Private.hpp */, - CF76F7D724816AAA00890DD2 /* RLMFindOptions.h */, - CF76F7D024816AAA00890DD2 /* RLMFindOptions.mm */, - CF76F7D424816AAA00890DD2 /* RLMFindOptions_Private.hpp */, - CF76F7DA24816AAB00890DD2 /* RLMMongoClient.h */, - CF76F7D524816AAA00890DD2 /* RLMMongoClient.mm */, - CF76F7D324816AAA00890DD2 /* RLMMongoClient_Private.hpp */, - CF76F7CF24816AA900890DD2 /* RLMMongoCollection.h */, - CF76F7D124816AAA00890DD2 /* RLMMongoCollection.mm */, - CF76F7D224816AAA00890DD2 /* RLMMongoCollection_Private.h */, - CF76F7D924816AAB00890DD2 /* RLMMongoDatabase.h */, - CF76F7DC24816AAB00890DD2 /* RLMMongoDatabase_Private.hpp */, - CF76F7CC24816AA800890DD2 /* RLMUpdateResult.h */, - CF76F7CB24816AA800890DD2 /* RLMUpdateResult.mm */, - CF76F7CE24816AA900890DD2 /* RLMUpdateResult_Private.hpp */, - ); - name = MongoClient; - sourceTree = ""; - }; E81A1FC81955FE0100FDED82 /* Swift */ = { isa = PBXGroup; children = ( @@ -1604,42 +1100,6 @@ name = "Objective-C"; sourceTree = ""; }; - E8267FEF1D90B7B100E001C7 /* Object Server Tests */ = { - isa = PBXGroup; - children = ( - 1AA5AEA21D98CA5300ED8C27 /* Utility */, - 3F2B40C62B2900DA00E30319 /* AsyncSyncTests.swift */, - 3F2B40C92B2900DA00E30319 /* ClientResetTests.swift */, - 3F2B40C52B2900DA00E30319 /* CombineSyncTests.swift */, - 536B7C0B24A4C223006B535D /* dependencies.list */, - 3FEE4F44281C439D009194C7 /* EventTests.swift */, - AC0538052885B1DF00CE27C4 /* RLMAsymmetricSyncServerTests.mm */, - 5346E7312487AC9D00595C68 /* RLMBSONTests.mm */, - CFA3A23D260B8427002C3266 /* RLMCollectionSyncTests.mm */, - CF08757C260B98E100B9BE60 /* RLMCollectionSyncTests.mm */, - ACB6FD30273C60920009712F /* RLMFlexibleSyncServerTests.mm */, - 3F2B40C82B2900DA00E30319 /* RLMMongoClientTests.mm */, - AC7D182B261F2F560080E1D2 /* RLMObjectServerPartitionTests.mm */, - E8267FF01D90B8E700E001C7 /* RLMObjectServerTests.mm */, - 531F956F279070A800E497F1 /* RLMServerTestObjects.h */, - 531F956E279070A800E497F1 /* RLMServerTestObjects.m */, - 3F2B40C72B2900DA00E30319 /* RLMSubscriptionTests.mm */, - 1AA5AE9D1D98A6D800ED8C27 /* RLMSyncTestCase.h */, - 1AA5AE9B1D98A68E00ED8C27 /* RLMSyncTestCase.mm */, - 49D9DFC4246C8E48003AD31D /* setup_baas.rb */, - AC0538062885B1DF00CE27C4 /* SwiftAsymmetricSyncServerTests.swift */, - 3F9ADA9326E7E87B007349A5 /* SwiftCollectionSyncTests.swift */, - ACB6FD31273C60920009712F /* SwiftFlexibleSyncServerTests.swift */, - AC8AE64A26BAD4B00037D4E5 /* SwiftMongoClientTests.swift */, - AC7D182C261F2F560080E1D2 /* SwiftObjectServerPartitionTests.swift */, - 1AA5AE9F1D98C99500ED8C27 /* SwiftObjectServerTests.swift */, - AC2C2A3E268E1ACE00B4DA33 /* SwiftServerObjects.swift */, - 1AA5AE961D989BE000ED8C27 /* SwiftSyncTestCase.swift */, - AC8846B62687BC4100DF4A65 /* SwiftUIServerTests.swift */, - ); - name = "Object Server Tests"; - sourceTree = ""; - }; E88C36FE19745E3200C9963D /* Swift */ = { isa = PBXGroup; children = ( @@ -1654,7 +1114,6 @@ 5D660FB71BE98B770021E04F /* Configuration */, 1A7B82361D51254600750296 /* Frameworks */, E81A1FB41955FCE000FDED82 /* LICENSE */, - E8267FEF1D90B7B100E001C7 /* Object Server Tests */, E8D89B991955FC6D00CF2B9A /* Products */, E8D89B9A1955FC6D00CF2B9A /* Realm */, E8D89BA71955FC6D00CF2B9A /* Realm Tests */, @@ -1670,12 +1129,9 @@ E8D89B991955FC6D00CF2B9A /* Products */ = { isa = PBXGroup; children = ( - E8267FED1D90B79000E001C7 /* ObjectServerTests.xctest */, 5D659ED91BE04556006515A0 /* Realm.framework */, 5D660FD81BE98C7C0021E04F /* RealmSwift Tests.xctest */, 5D660FCC1BE98C560021E04F /* RealmSwift.framework */, - AC8846732686573B00DF4A65 /* SwiftUISyncTestHost.app */, - AC88478126888C6300DF4A65 /* SwiftUISyncTests.xctest */, 53124AB825B71AF600771CE4 /* SwiftUITestHost.app */, 53124AD425B71AF700771CE4 /* SwiftUITests.xctest */, 3F1A5E721992EB7400F45F4C /* TestHost.app */, @@ -1689,19 +1145,14 @@ children = ( E8D89B9B1955FC6D00CF2B9A /* Supporting Files */, E88C36FE19745E3200C9963D /* Swift */, - 1AF6EA441D36B1220014EB85 /* Sync */, E8D89B9D1955FC6D00CF2B9A /* Realm.h */, E81A1F631955FC9300FDED82 /* RLMAccessor.h */, 3F0338491E6F466D00F9E288 /* RLMAccessor.hpp */, E81A1F641955FC9300FDED82 /* RLMAccessor.mm */, - E83591941B3DF05C0035F2F3 /* RLMAnalytics.hpp */, - E83591931B3DF05C0035F2F3 /* RLMAnalytics.mm */, E81A1F661955FC9300FDED82 /* RLMArray.h */, E81A1F671955FC9300FDED82 /* RLMArray.mm */, 0237B5421A856F06004ACD57 /* RLMArray_Private.h */, E81A1F651955FC9300FDED82 /* RLMArray_Private.hpp */, - AC813610287F21700029F15E /* RLMAsymmetricObject.h */, - AC813611287F21700029F15E /* RLMAsymmetricObject.mm */, 3FD0D7C529675A2E0031C196 /* RLMAsyncTask.h */, 3FD0D7C629675A2E0031C196 /* RLMAsyncTask.mm */, 3FD0D7CB29675AE10031C196 /* RLMAsyncTask_Private.h */, @@ -1725,8 +1176,6 @@ 3FDAB83E290B4CCB00168F24 /* RLMError.h */, 3FDAB840290B4CCB00168F24 /* RLMError.mm */, 3FDAB83F290B4CCB00168F24 /* RLMError_Private.hpp */, - 3FEE4F3E281C4370009194C7 /* RLMEvent.h */, - 3FEE4F3F281C4370009194C7 /* RLMEvent.mm */, AC7825BC2ACD90DA007ABA4B /* RLMGeospatial.h */, AC7825BA2ACD90DA007ABA4B /* RLMGeospatial.mm */, AC7825BB2ACD90DA007ABA4B /* RLMGeospatial_Private.hpp */, @@ -1807,8 +1256,6 @@ 3F67DB391E26D69C0024533D /* RLMThreadSafeReference.h */, 3F67DB3B1E26D69C0024533D /* RLMThreadSafeReference.mm */, 3F67DB3A1E26D69C0024533D /* RLMThreadSafeReference_Private.hpp */, - 3F20DA2019BE1EA6007DE308 /* RLMUpdateChecker.hpp */, - 3F20DA2119BE1EA6007DE308 /* RLMUpdateChecker.mm */, E81A1F811955FC9300FDED82 /* RLMUtil.hpp */, E81A1F821955FC9300FDED82 /* RLMUtil.mm */, 0C57969F25643D7500744CAE /* RLMUUID.mm */, @@ -1836,8 +1283,6 @@ E81A20061955FE1600FDED82 /* Objective-C */, E8D89BA81955FC6D00CF2B9A /* Supporting Files */, E81A1FC81955FE0100FDED82 /* Swift */, - AC8846742686573B00DF4A65 /* SwiftUISyncTestHost */, - AC88468F2686573D00DF4A65 /* SwiftUISyncTestHostUITests */, 53A34E3225CDA0AC00698930 /* SwiftUITestHost */, 53124AD725B71AF700771CE4 /* SwiftUITestHostUITests */, 3F1A5E731992EB7400F45F4C /* TestHost */, @@ -1864,42 +1309,25 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 3F73BC951E3A878500FE80B6 /* NSError+RLMSync.h in Headers */, 5D659EA51BE04556006515A0 /* Realm.h in Headers */, 5D659EA71BE04556006515A0 /* RLMAccessor.h in Headers */, - CFAEF761242B5F9A00EAF721 /* RLMAPIKeyAuth.h in Headers */, - 4993221224129DCE00A0EC8E /* RLMApp.h in Headers */, - 53CCC6C4257EC8A300A8FC50 /* RLMApp_Private.h in Headers */, 5D659EA91BE04556006515A0 /* RLMArray.h in Headers */, 5D659EAA1BE04556006515A0 /* RLMArray_Private.h in Headers */, - AC813612287F21700029F15E /* RLMAsymmetricObject.h in Headers */, 3FD0D7C729675A2E0031C196 /* RLMAsyncTask.h in Headers */, 3FDB67152970720E0052233B /* RLMAsyncTask_Private.h in Headers */, - 49E12CF2245DB7E800359DF1 /* RLMBSON.h in Headers */, - 49E12CF5245DBF8A00359DF1 /* RLMBSON_Private.hpp in Headers */, 5D659EAB1BE04556006515A0 /* RLMCollection.h in Headers */, 5D1BF1FF1EF987AD00B7DC87 /* RLMCollection_Private.h in Headers */, 5D659EAC1BE04556006515A0 /* RLMConstants.h in Headers */, - 4993220A24129DCE00A0EC8E /* RLMCredentials.h in Headers */, 3F4F3ADD23F71C790048DB43 /* RLMDecimal128.h in Headers */, 3F1D9068265C077C00593ABA /* RLMDictionary.h in Headers */, 0CED6DB82655087200B80277 /* RLMDictionary_Private.h in Headers */, - CF6E0482242A141200DB7F14 /* RLMEmailPasswordAuth.h in Headers */, 3FC3F912241808B400E27322 /* RLMEmbeddedObject.h in Headers */, 3FDAB841290B4CCB00168F24 /* RLMError.h in Headers */, - 3FEE4F40281C4370009194C7 /* RLMEvent.h in Headers */, - CF76F7F324816AAB00890DD2 /* RLMFindOneAndModifyOptions.h in Headers */, - CF76F7F524816AAB00890DD2 /* RLMFindOptions.h in Headers */, AC7825BF2ACD90DA007ABA4B /* RLMGeospatial.h in Headers */, AC3B33AE29DC6CEE0042F3A0 /* RLMLogger.h in Headers */, AC3B33B029DC6CEE0042F3A0 /* RLMLogger_Private.h in Headers */, 5D659EAF1BE04556006515A0 /* RLMMigration.h in Headers */, 5D659EB01BE04556006515A0 /* RLMMigration_Private.h in Headers */, - CF76F7FB24816AAB00890DD2 /* RLMMongoClient.h in Headers */, - CF76F7E524816AAB00890DD2 /* RLMMongoCollection.h in Headers */, - 3FD0D7CE2967CA1E0031C196 /* RLMMongoCollection_Private.h in Headers */, - CF76F7F924816AAB00890DD2 /* RLMMongoDatabase.h in Headers */, - 4993221824129E6600A0EC8E /* RLMNetworkTransport.h in Headers */, 5D659EB11BE04556006515A0 /* RLMObject.h in Headers */, 5D659EB21BE04556006515A0 /* RLMObject_Private.h in Headers */, 5D659EB31BE04556006515A0 /* RLMObjectBase.h in Headers */, @@ -1911,12 +1339,7 @@ 5D659EB81BE04556006515A0 /* RLMObjectStore.h in Headers */, 5D659EBC1BE04556006515A0 /* RLMProperty.h in Headers */, 5D659EBD1BE04556006515A0 /* RLMProperty_Private.h in Headers */, - CF6E0486242A321200DB7F14 /* RLMProviderClient.h in Headers */, - 6807E64A2487E8660096066F /* RLMPushClient.h in Headers */, - 6807E64F2487F9210096066F /* RLMPushClient_Private.hpp in Headers */, - 1AD397CF1F72FFC7002AA897 /* RLMRealm+Sync.h in Headers */, 5D659EBF1BE04556006515A0 /* RLMRealm.h in Headers */, - 0CC270AC2BCD665800788EE1 /* RLMInitialSubscriptionsConfiguration.h in Headers */, 5D659EC01BE04556006515A0 /* RLMRealm_Dynamic.h in Headers */, 5D659EC11BE04556006515A0 /* RLMRealm_Private.h in Headers */, 5D659EC21BE04556006515A0 /* RLMRealmConfiguration.h in Headers */, @@ -1934,17 +1357,7 @@ 3F83E9A42630A14800FC9623 /* RLMSwiftProperty.h in Headers */, 5D659EC91BE04556006515A0 /* RLMSwiftSupport.h in Headers */, 3F1D9092265C07A600593ABA /* RLMSwiftValueStorage.h in Headers */, - 1A0512771D8746CD00806AEC /* RLMSyncConfiguration.h in Headers */, - 3F336E8B1DA2FA15006CB5A0 /* RLMSyncConfiguration_Private.h in Headers */, - 1AF7EA961D340AF70001A9B5 /* RLMSyncManager.h in Headers */, - 1AD3870C1D4A7FBB00479110 /* RLMSyncSession.h in Headers */, - 531F956827906EB200E497F1 /* RLMSyncSubscription.h in Headers */, - 531F956727906DBC00E497F1 /* RLMSyncSubscription_Private.h in Headers */, 3F67DB3C1E26D69C0024533D /* RLMThreadSafeReference.h in Headers */, - CF76F7DF24816AAB00890DD2 /* RLMUpdateResult.h in Headers */, - 1ABDCDAE1D792FEB003489E3 /* RLMUser.h in Headers */, - 53CCC6E8257EC8C400A8FC50 /* RLMUser_Private.h in Headers */, - CFAEF765242B672700EAF721 /* RLMUserAPIKey.h in Headers */, 3F1D8D33265B071000593ABA /* RLMValue.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2071,66 +1484,6 @@ productReference = 5D660FD81BE98C7C0021E04F /* RealmSwift Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - AC8846722686573B00DF4A65 /* SwiftUISyncTestHost */ = { - isa = PBXNativeTarget; - buildConfigurationList = AC8846922686573D00DF4A65 /* Build configuration list for PBXNativeTarget "SwiftUISyncTestHost" */; - buildPhases = ( - AC88466F2686573B00DF4A65 /* Sources */, - AC8846702686573B00DF4A65 /* Frameworks */, - AC8846712686573B00DF4A65 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - AC8846A02686577B00DF4A65 /* PBXTargetDependency */, - ); - name = SwiftUISyncTestHost; - productName = SwifttUISyncTestHost; - productReference = AC8846732686573B00DF4A65 /* SwiftUISyncTestHost.app */; - productType = "com.apple.product-type.application"; - }; - AC88475926888C6300DF4A65 /* SwiftUISyncTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = AC88477E26888C6300DF4A65 /* Build configuration list for PBXNativeTarget "SwiftUISyncTests" */; - buildPhases = ( - AC88476326888C6300DF4A65 /* Sources */, - AC88477726888C6300DF4A65 /* Frameworks */, - AC88477A26888C6300DF4A65 /* Embed Frameworks */, - AC88477D26888C6300DF4A65 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - AC88475A26888C6300DF4A65 /* PBXTargetDependency */, - AC88479E26891D4500DF4A65 /* PBXTargetDependency */, - ); - name = SwiftUISyncTests; - productName = SwiftUISyncTestHostUITests; - productReference = AC88478126888C6300DF4A65 /* SwiftUISyncTests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; - E8267FB11D90B79000E001C7 /* ObjectServerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = E8267FEA1D90B79000E001C7 /* Build configuration list for PBXNativeTarget "ObjectServerTests" */; - buildPhases = ( - E8267FB81D90B79000E001C7 /* Get Version Number */, - E8267FB91D90B79000E001C7 /* Sources */, - E8267FE21D90B79000E001C7 /* Frameworks */, - E8267FE41D90B79000E001C7 /* Embed Frameworks */, - 5DAD37251F73221700EECA8E /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 49E57A1C245C5B0E004AF428 /* PBXTargetDependency */, - E8267FB41D90B79000E001C7 /* PBXTargetDependency */, - 537689AC24A7DD060008E57B /* PBXTargetDependency */, - ); - name = ObjectServerTests; - productName = RealmTests; - productReference = E8267FED1D90B79000E001C7 /* ObjectServerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; E8D89BA21955FC6D00CF2B9A /* Tests */ = { isa = PBXNativeTarget; buildConfigurationList = E8D89BB11955FC6D00CF2B9A /* Build configuration list for PBXNativeTarget "Tests" */; @@ -2170,11 +1523,6 @@ LastSwiftMigration = 0900; ProvisioningStyle = Automatic; }; - 49E57A16245C3F31004AF428 = { - CreatedOnToolsVersion = 11.4.1; - DevelopmentTeam = 2Z8M9MTEVV; - ProvisioningStyle = Automatic; - }; 53124AB725B71AF600771CE4 = { CreatedOnToolsVersion = 12.3; LastSwiftMigration = 1240; @@ -2197,18 +1545,9 @@ LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; - AC8846722686573B00DF4A65 = { - CreatedOnToolsVersion = 13.0; - }; - AC88475926888C6300DF4A65 = { - TestTargetID = AC8846722686573B00DF4A65; - }; ACB76B772AA9D9A600C59983 = { CreatedOnToolsVersion = 15.0; }; - E8267FB11D90B79000E001C7 = { - LastSwiftMigration = 1020; - }; E83EAC791BED3D880085CCD2 = { CreatedOnToolsVersion = 7.1; }; @@ -2236,14 +1575,10 @@ 5D660FCB1BE98C560021E04F /* RealmSwift */, E8D89BA21955FC6D00CF2B9A /* Tests */, 5D660FD71BE98C7C0021E04F /* RealmSwift Tests */, - E8267FB11D90B79000E001C7 /* ObjectServerTests */, E83EAC791BED3D880085CCD2 /* SwiftLint */, 3F1A5E711992EB7400F45F4C /* TestHost */, - 49E57A16245C3F31004AF428 /* Download BaaS */, 53124AB725B71AF600771CE4 /* SwiftUITestHost */, 53124AD325B71AF700771CE4 /* SwiftUITests */, - AC8846722686573B00DF4A65 /* SwiftUISyncTestHost */, - AC88475926888C6300DF4A65 /* SwiftUISyncTests */, ACB76B772AA9D9A600C59983 /* CI */, ); }; @@ -2303,27 +1638,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 5DAD37251F73221700EECA8E /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AC8846712686573B00DF4A65 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AC88477D26888C6300DF4A65 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -2347,28 +1661,6 @@ shellScript = "# Something that Carthage does results in Xcode skipping the ProcessXCFramework build step in some cases, which results in compilation failing. To work around this, ensure that the modified date on the XCFramework files are always newer than the built files, so that it happens on every build. This breaks incremental compilation, so we only want to do it when building for Carthage.\nif [ -n \"$CARTHAGE\" ]; then\n find \"${SRCROOT}/core\" -type f -exec touch {} \\;\nfi\n"; showEnvVarsInLog = 0; }; - 49E57A1A245C3F36004AF428 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "$(SRCROOT)/Realm/ObjectServerTests/setup_baas.rb", - ); - outputFileListPaths = ( - ); - outputPaths = ( - "$(SRCROOT)/.baas/bin/assisted_agg", - "$(SRCROOT)/.baas/bin/create_user", - "$(SRCROOT)/.baas/bin/stitch_server", - "$(SRCROOT)/.baas/bin/update_doc", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "ruby Realm/ObjectServerTests/setup_baas.rb\n"; - }; 5D304A2E1BE9AEFB0082C1A6 /* Get Version Number */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -2402,22 +1694,6 @@ shellScript = "exec \"${SRCROOT}/scripts/generate-rlmversion.sh\"\n"; showEnvVarsInLog = 0; }; - E8267FB81D90B79000E001C7 /* Get Version Number */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(SRCROOT)/Realm/Realm-Info.plist", - ); - name = "Get Version Number"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/RLMVersion.h", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "exec \"${SRCROOT}/scripts/generate-rlmversion.sh\"\n"; - }; E83EAC7D1BED3D8F0085CCD2 /* Run SwiftLint */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -2465,37 +1741,22 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0C63BB902BCD787300E25C3A /* RLMInitialSubscriptionsConfiguration.h in Sources */, - 3F73BC961E3A878500FE80B6 /* NSError+RLMSync.m in Sources */, 5D659E851BE04556006515A0 /* RLMAccessor.mm in Sources */, - 5D659E861BE04556006515A0 /* RLMAnalytics.mm in Sources */, - CFAEF762242B5F9A00EAF721 /* RLMAPIKeyAuth.mm in Sources */, - 4993220E24129DCE00A0EC8E /* RLMApp.mm in Sources */, 5D659E871BE04556006515A0 /* RLMArray.mm in Sources */, - AC813613287F21700029F15E /* RLMAsymmetricObject.mm in Sources */, 3FD0D7C929675A2E0031C196 /* RLMAsyncTask.mm in Sources */, - 49E12CF0245DB7CC00359DF1 /* RLMBSON.mm in Sources */, 3F9863BB1D36876B00641C98 /* RLMClassInfo.mm in Sources */, 3FBEF67B1C63D66100F6935B /* RLMCollection.mm in Sources */, 5D659E891BE04556006515A0 /* RLMConstants.m in Sources */, - 4993220C24129DCE00A0EC8E /* RLMCredentials.mm in Sources */, 3F4F3AD523F71C790048DB43 /* RLMDecimal128.mm in Sources */, 0C3BD4B325C1BDF1007CFDD3 /* RLMDictionary.mm in Sources */, - CF6E0483242A141200DB7F14 /* RLMEmailPasswordAuth.mm in Sources */, 3FC3F914241808B400E27322 /* RLMEmbeddedObject.mm in Sources */, 3FDAB845290B4CCB00168F24 /* RLMError.mm in Sources */, - 3FEE4F42281C4370009194C7 /* RLMEvent.mm in Sources */, - CF76F7FD24816AAB00890DD2 /* RLMFindOneAndModifyOptions.mm in Sources */, - CF76F7E724816AAB00890DD2 /* RLMFindOptions.mm in Sources */, AC7825BD2ACD90DA007ABA4B /* RLMGeospatial.mm in Sources */, AC3B33AF29DC6CEE0042F3A0 /* RLMLogger.mm in Sources */, 5D659E881BE04556006515A0 /* RLMManagedArray.mm in Sources */, CF9881C125DABC6500BD7E4F /* RLMManagedDictionary.mm in Sources */, CFD8D12025BB0B8B0037FE4D /* RLMManagedSet.mm in Sources */, 5D659E8B1BE04556006515A0 /* RLMMigration.mm in Sources */, - CF76F7F124816AAB00890DD2 /* RLMMongoClient.mm in Sources */, - CF76F7E924816AAB00890DD2 /* RLMMongoCollection.mm in Sources */, - 4993221624129E6600A0EC8E /* RLMNetworkTransport.mm in Sources */, 5D659E8C1BE04556006515A0 /* RLMObject.mm in Sources */, 5D659E8D1BE04556006515A0 /* RLMObjectBase.mm in Sources */, 3F4F3ADB23F71C790048DB43 /* RLMObjectId.mm in Sources */, @@ -2504,10 +1765,7 @@ 5D659E901BE04556006515A0 /* RLMObservation.mm in Sources */, 5D3E1A2F1C1FC6D5002913BA /* RLMPredicateUtil.mm in Sources */, 5D659E921BE04556006515A0 /* RLMProperty.mm in Sources */, - CF6E0487242A321200DB7F14 /* RLMProviderClient.mm in Sources */, - 6807E64C2487F7220096066F /* RLMPushClient.mm in Sources */, 5D659E931BE04556006515A0 /* RLMQueryUtil.mm in Sources */, - 1AD397CE1F72FFC7002AA897 /* RLMRealm+Sync.mm in Sources */, 5D659E941BE04556006515A0 /* RLMRealm.mm in Sources */, 5D659E951BE04556006515A0 /* RLMRealmConfiguration.mm in Sources */, 5D659E961BE04556006515A0 /* RLMRealmUtil.mm in Sources */, @@ -2519,17 +1777,7 @@ 5D659E8A1BE04556006515A0 /* RLMSwiftCollectionBase.mm in Sources */, 5D659E991BE04556006515A0 /* RLMSwiftSupport.m in Sources */, 3F1D90BC265C08F800593ABA /* RLMSwiftValueStorage.mm in Sources */, - 1A3623681D8384BA00945A54 /* RLMSyncConfiguration.mm in Sources */, - 1AF7EA971D340AF70001A9B5 /* RLMSyncManager.mm in Sources */, - 531F956A27906EF300E497F1 /* RLMSyncSession.mm in Sources */, - 531F956C27906F7600E497F1 /* RLMSyncSubscription.mm in Sources */, - 1A84132F1D4BCCE600C5326F /* RLMSyncUtil.mm in Sources */, 3F67DB3E1E26D69C0024533D /* RLMThreadSafeReference.mm in Sources */, - 0CC270AA2BCD664200788EE1 /* RLMInitialSubscriptionsConfiguration.m in Sources */, - 5D659E9A1BE04556006515A0 /* RLMUpdateChecker.mm in Sources */, - CF76F7DD24816AAB00890DD2 /* RLMUpdateResult.mm in Sources */, - 1ABDCDB01D793008003489E3 /* RLMUser.mm in Sources */, - CFAEF766242B672700EAF721 /* RLMUserAPIKey.mm in Sources */, 5D659E9B1BE04556006515A0 /* RLMUtil.mm in Sources */, 0C5796A225643D7500744CAE /* RLMUUID.mm in Sources */, 3F1D8D35265B071000593ABA /* RLMValue.mm in Sources */, @@ -2542,10 +1790,7 @@ files = ( 5D660FF11BE98D670021E04F /* Aliases.swift in Sources */, 681EE33B25EE8E1400A9DEC5 /* AnyRealmValue.swift in Sources */, - CFB4313A243DF87100471C18 /* App.swift in Sources */, - AC81360F287F21350029F15E /* AsymmetricObject.swift in Sources */, 3FE267D7264308680030F83C /* BasicTypes.swift in Sources */, - 4996EA9E2465BB8A003A1F51 /* BSON.swift in Sources */, 3FE267D5264308680030F83C /* CollectionAccess.swift in Sources */, 3F102CBD23DBC68300108FD2 /* Combine.swift in Sources */, 3FE267D6264308680030F83C /* ComplexTypes.swift in Sources */, @@ -2553,21 +1798,17 @@ 3FB6ABD92416A27000E318C2 /* Decimal128.swift in Sources */, 3FC3F9172419B63200E27322 /* EmbeddedObject.swift in Sources */, 29B7FDF61C0DA6560023224E /* Error.swift in Sources */, - 3F7BCEB52834377E00EB6E16 /* Events.swift in Sources */, AC7825B92ACD90BE007ABA4B /* Geospatial.swift in Sources */, 3F857A482769291800F9B9B1 /* KeyPathStrings.swift in Sources */, 5D1534B81CCFF545008976D7 /* LinkingObjects.swift in Sources */, 5D660FF21BE98D670021E04F /* List.swift in Sources */, 0C3BD4D325C1C5AB007CFDD3 /* Map.swift in Sources */, 5D660FF31BE98D670021E04F /* Migration.swift in Sources */, - CF76F80224816B3800890DD2 /* MongoClient.swift in Sources */, CFE9CE31265554FB00BF96D6 /* MutableSet.swift in Sources */, 3F3411A6273433B300EC9D25 /* ObjcBridgeable.swift in Sources */, 5D660FF41BE98D670021E04F /* Object.swift in Sources */, 3FB6ABD72416A26100E318C2 /* ObjectId.swift in Sources */, 681EE34725EE8E5600A9DEC5 /* ObjectiveCSupport+AnyRealmValue.swift in Sources */, - 494566A9246E8C59000FD07F /* ObjectiveCSupport+BSON.swift in Sources */, - 3FE5818622C2B4B900BA10E7 /* ObjectiveCSupport+Sync.swift in Sources */, 5B77EACE1DCC5614006AB51D /* ObjectiveCSupport.swift in Sources */, 5D660FF51BE98D670021E04F /* ObjectSchema.swift in Sources */, 5D660FF61BE98D670021E04F /* Optional.swift in Sources */, @@ -2590,8 +1831,6 @@ CF84D4F0281823B300005E27 /* SectionedResults.swift in Sources */, 5D660FFD1BE98D670021E04F /* SortDescriptor.swift in Sources */, 535EA9E225B0919800DBF3CD /* SwiftUI.swift in Sources */, - 1AFEF8431D52D2C900495005 /* Sync.swift in Sources */, - ACB6FD35273C60CC0009712F /* SyncSubscription.swift in Sources */, 3F222C4E1E26F51300CA0713 /* ThreadSafeReference.swift in Sources */, 5D660FFE1BE98D670021E04F /* Util.swift in Sources */, ); @@ -2624,6 +1863,7 @@ CF986DE225AE3EC70039D287 /* MutableSetTests.swift in Sources */, 3F8824FE1E5E335000586B35 /* ObjectAccessorTests.swift in Sources */, 3F8824FF1E5E335000586B35 /* ObjectCreationTests.swift in Sources */, + 3FFC68702B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift in Sources */, CFFECBB525078EC40010F585 /* ObjectIdTests.swift in Sources */, 3F8825001E5E335000586B35 /* ObjectiveCSupportTests.swift in Sources */, 3F8825011E5E335000586B35 /* ObjectSchemaInitializationTests.swift in Sources */, @@ -2647,9 +1887,7 @@ 3F558C8E22C29A03002F0F30 /* RLMTestObjects.m in Sources */, 3F8825091E5E335000586B35 /* SchemaTests.swift in Sources */, CFDBC4C0288040E700EE80E6 /* SectionedResultsTests.swift in Sources */, - 3FFC68702B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift in Sources */, 3F88250A1E5E335000586B35 /* SortDescriptorTests.swift in Sources */, - 3FFB5AF6266ECFC7008EF2E9 /* SwiftBSONTests.swift in Sources */, 3F88250B1E5E335000586B35 /* SwiftLinkTests.swift in Sources */, 5D6610251BE98D880021E04F /* SwiftTestObjects.swift in Sources */, 535EAA7525B0B02B00DBF3CD /* SwiftUITests.swift in Sources */, @@ -2663,76 +1901,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - AC88466F2686573B00DF4A65 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AC8846782686573B00DF4A65 /* ContentView.swift in Sources */, - AC8846762686573B00DF4A65 /* SwiftUISyncTestHostApp.swift in Sources */, - 3F4FD2FB2B2A389A003E3DFD /* TestType.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AC88476326888C6300DF4A65 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AC8847A9268926B500DF4A65 /* RealmServer.swift in Sources */, - ACBD595528364DFF009A664E /* RLMChildProcessEnvironment.m in Sources */, - ACBD595A283653CF009A664E /* RLMMultiProcessTestCase.m in Sources */, - ACBD59592836539B009A664E /* RLMServerTestObjects.m in Sources */, - ACBD595428364DCB009A664E /* RLMSyncTestCase.mm in Sources */, - ACBD5957283652D7009A664E /* RLMTestCase.m in Sources */, - AC23487E26FC8619009129F2 /* RLMUser+ObjectServerTests.mm in Sources */, - AC320BAE268E1F2D0043D484 /* SwiftServerObjects.swift in Sources */, - ACBD595328364DB0009A664E /* SwiftSyncTestCase.swift in Sources */, - AC88478626888CEE00DF4A65 /* SwiftUISyncTestHostUITests.swift in Sources */, - 3F4FD2FC2B2A389A003E3DFD /* TestType.swift in Sources */, - ACBD595B2836541F009A664E /* TestUtils.mm in Sources */, - 3F95F4A2295B8488006BC287 /* TestUtils.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - E8267FB91D90B79000E001C7 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3F2B40CB2B2900DA00E30319 /* AsyncSyncTests.swift in Sources */, - 3F2B40CE2B2900DA00E30319 /* ClientResetTests.swift in Sources */, - 3F2B40CA2B2900DA00E30319 /* CombineSyncTests.swift in Sources */, - 3FEE4F45281C439D009194C7 /* EventTests.swift in Sources */, - 537130C824A9E417001FDBBC /* RealmServer.swift in Sources */, - AC05380B2885B23E00CE27C4 /* RLMAsymmetricSyncServerTests.mm in Sources */, - 5346E7322487AC9D00595C68 /* RLMBSONTests.mm in Sources */, - 530BA61626DFA1CB008FC550 /* RLMChildProcessEnvironment.m in Sources */, - CF08757D260B98E100B9BE60 /* RLMCollectionSyncTests.mm in Sources */, - ACB6FD37273C61040009712F /* RLMFlexibleSyncServerTests.mm in Sources */, - 3F2B40CD2B2900DA00E30319 /* RLMMongoClientTests.mm in Sources */, - 3F558C9422C29A03002F0F30 /* RLMMultiProcessTestCase.m in Sources */, - AC7D182D261F2F560080E1D2 /* RLMObjectServerPartitionTests.mm in Sources */, - E8267FF11D90B8E700E001C7 /* RLMObjectServerTests.mm in Sources */, - 531F9570279070A900E497F1 /* RLMServerTestObjects.m in Sources */, - 3F2B40CC2B2900DA00E30319 /* RLMSubscriptionTests.mm in Sources */, - 1AA5AE9C1D98A68E00ED8C27 /* RLMSyncTestCase.mm in Sources */, - 3F558C9022C29A03002F0F30 /* RLMTestCase.m in Sources */, - 1A1536481DB0408A00C0EC93 /* RLMUser+ObjectServerTests.mm in Sources */, - CF330BBE24E57D5F00F07EE2 /* RLMWatchTestUtility.m in Sources */, - AC05380C2885B25A00CE27C4 /* SwiftAsymmetricSyncServerTests.swift in Sources */, - 3F9ADA9426E7E87B007349A5 /* SwiftCollectionSyncTests.swift in Sources */, - ACB6FD36273C60FD0009712F /* SwiftFlexibleSyncServerTests.swift in Sources */, - AC8AE64B26BAD4B00037D4E5 /* SwiftMongoClientTests.swift in Sources */, - AC7D182E261F2F560080E1D2 /* SwiftObjectServerPartitionTests.swift in Sources */, - 1AA5AEA11D98C99800ED8C27 /* SwiftObjectServerTests.swift in Sources */, - AC2C2A40268E1B0200B4DA33 /* SwiftServerObjects.swift in Sources */, - 1AA5AE981D989BE400ED8C27 /* SwiftSyncTestCase.swift in Sources */, - AC8846B72687BC4100DF4A65 /* SwiftUIServerTests.swift in Sources */, - 3F558C8822C29A03002F0F30 /* TestUtils.mm in Sources */, - 3FAF2D4229577100002EAC93 /* TestUtils.swift in Sources */, - 532E916F24AA533A003FD9DB /* TimeoutProxyServer.swift in Sources */, - CFB674A324EEE9CB00FBF0B8 /* WatchTestUtility.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; E8D89B9F1955FC6D00CF2B9A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2810,21 +1978,11 @@ target = 5D660FCB1BE98C560021E04F /* RealmSwift */; targetProxy = 3FF5165126E96D2B00618280 /* PBXContainerItemProxy */; }; - 49E57A1C245C5B0E004AF428 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 49E57A16245C3F31004AF428 /* Download BaaS */; - targetProxy = 49E57A1B245C5B0E004AF428 /* PBXContainerItemProxy */; - }; 534DF4D025B86F3A00655AE2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 53124AB725B71AF600771CE4 /* SwiftUITestHost */; targetProxy = 534DF4CF25B86F3A00655AE2 /* PBXContainerItemProxy */; }; - 537689AC24A7DD060008E57B /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 5D660FCB1BE98C560021E04F /* RealmSwift */; - targetProxy = 537689AB24A7DD060008E57B /* PBXContainerItemProxy */; - }; 53BBF08D25B7436F00D225AD /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 5D660FCB1BE98C560021E04F /* RealmSwift */; @@ -2850,26 +2008,6 @@ target = 5D659E7D1BE04556006515A0 /* Realm */; targetProxy = 5DD755D11BE05828002800DA /* PBXContainerItemProxy */; }; - AC8846A02686577B00DF4A65 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 5D660FCB1BE98C560021E04F /* RealmSwift */; - targetProxy = AC88469F2686577B00DF4A65 /* PBXContainerItemProxy */; - }; - AC88475A26888C6300DF4A65 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 49E57A16245C3F31004AF428 /* Download BaaS */; - targetProxy = AC88475B26888C6300DF4A65 /* PBXContainerItemProxy */; - }; - AC88479E26891D4500DF4A65 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = AC8846722686573B00DF4A65 /* SwiftUISyncTestHost */; - targetProxy = AC88479D26891D4500DF4A65 /* PBXContainerItemProxy */; - }; - E8267FB41D90B79000E001C7 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 5D659E7D1BE04556006515A0 /* Realm */; - targetProxy = E8267FB51D90B79000E001C7 /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -2934,17 +2072,6 @@ }; name = Static; }; - 3FEC914C2A4B58BD0044BFF5 /* Static */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 3FB56E7E250D457A00A6216B /* ObjectServerTests.xcconfig */; - buildSettings = { - COPY_PHASE_STRIP = NO; - INFOPLIST_FILE = "Realm/ObjectServerTests/ObjectServerTests-Info.plist"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Realm/ObjectServerTests/Object-Server-Tests-Bridging-Header.h"; - }; - name = Static; - }; 3FEC914D2A4B58BD0044BFF5 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2961,15 +2088,6 @@ }; name = Static; }; - 3FEC914F2A4B58BD0044BFF5 /* Static */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 2Z8M9MTEVV; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Static; - }; 3FEC91502A4B58BD0044BFF5 /* Static */ = { isa = XCBuildConfiguration; baseConfigurationReference = 53124A4F25B714EC00771CE4 /* SwiftUITestHost.xcconfig */; @@ -2989,46 +2107,6 @@ }; name = Static; }; - 3FEC91522A4B58BD0044BFF5 /* Static */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = AC8847432687DFE700DF4A65 /* SwiftUISyncTestHost.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.SwiftUISyncTestHost; - }; - name = Static; - }; - 3FEC91532A4B58BD0044BFF5 /* Static */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = AC8847442687DFE700DF4A65 /* SwiftUISyncTests.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.SwiftUISyncTests; - PROVISIONING_PROFILE_SPECIFIER = ""; - TEST_TARGET_NAME = SwiftUISyncTestHost; - }; - name = Static; - }; - 49E57A18245C3F31004AF428 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 2Z8M9MTEVV; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 49E57A19245C3F31004AF428 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 2Z8M9MTEVV; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; 53124ADC25B71AF700771CE4 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 53124A4F25B714EC00771CE4 /* SwiftUITestHost.xcconfig */; @@ -3125,50 +2203,6 @@ }; name = Release; }; - AC8846932686573D00DF4A65 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = AC8847432687DFE700DF4A65 /* SwiftUISyncTestHost.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.SwiftUISyncTestHost; - }; - name = Debug; - }; - AC8846942686573D00DF4A65 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = AC8847432687DFE700DF4A65 /* SwiftUISyncTestHost.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.SwiftUISyncTestHost; - }; - name = Release; - }; - AC88477F26888C6300DF4A65 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = AC8847442687DFE700DF4A65 /* SwiftUISyncTests.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.SwiftUISyncTests; - PROVISIONING_PROFILE_SPECIFIER = ""; - TEST_TARGET_NAME = SwiftUISyncTestHost; - }; - name = Debug; - }; - AC88478026888C6300DF4A65 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = AC8847442687DFE700DF4A65 /* SwiftUISyncTests.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.SwiftUISyncTests; - PROVISIONING_PROFILE_SPECIFIER = ""; - TEST_TARGET_NAME = SwiftUISyncTestHost; - }; - name = Release; - }; ACB76B782AA9D9A600C59983 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3193,28 +2227,6 @@ }; name = Static; }; - E8267FEB1D90B79000E001C7 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 3FB56E7E250D457A00A6216B /* ObjectServerTests.xcconfig */; - buildSettings = { - COPY_PHASE_STRIP = NO; - INFOPLIST_FILE = "Realm/ObjectServerTests/ObjectServerTests-Info.plist"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Realm/ObjectServerTests/Object-Server-Tests-Bridging-Header.h"; - }; - name = Debug; - }; - E8267FEC1D90B79000E001C7 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 3FB56E7E250D457A00A6216B /* ObjectServerTests.xcconfig */; - buildSettings = { - COPY_PHASE_STRIP = NO; - INFOPLIST_FILE = "Realm/ObjectServerTests/ObjectServerTests-Info.plist"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Realm/ObjectServerTests/Object-Server-Tests-Bridging-Header.h"; - }; - name = Release; - }; E83EAC7A1BED3D880085CCD2 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3272,16 +2284,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 49E57A17245C3F31004AF428 /* Build configuration list for PBXAggregateTarget "Download BaaS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 49E57A18245C3F31004AF428 /* Debug */, - 49E57A19245C3F31004AF428 /* Release */, - 3FEC914F2A4B58BD0044BFF5 /* Static */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 53124ADB25B71AF700771CE4 /* Build configuration list for PBXNativeTarget "SwiftUITestHost" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -3332,26 +2334,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - AC8846922686573D00DF4A65 /* Build configuration list for PBXNativeTarget "SwiftUISyncTestHost" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AC8846932686573D00DF4A65 /* Debug */, - AC8846942686573D00DF4A65 /* Release */, - 3FEC91522A4B58BD0044BFF5 /* Static */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AC88477E26888C6300DF4A65 /* Build configuration list for PBXNativeTarget "SwiftUISyncTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AC88477F26888C6300DF4A65 /* Debug */, - AC88478026888C6300DF4A65 /* Release */, - 3FEC91532A4B58BD0044BFF5 /* Static */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; ACB76B7B2AA9D9A600C59983 /* Build configuration list for PBXAggregateTarget "CI" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -3362,16 +2344,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - E8267FEA1D90B79000E001C7 /* Build configuration list for PBXNativeTarget "ObjectServerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - E8267FEB1D90B79000E001C7 /* Debug */, - E8267FEC1D90B79000E001C7 /* Release */, - 3FEC914C2A4B58BD0044BFF5 /* Static */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; E83EAC7C1BED3D880085CCD2 /* Build configuration list for PBXAggregateTarget "SwiftLint" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Realm.xcodeproj/xcshareddata/xcschemes/Download BaaS.xcscheme b/Realm.xcodeproj/xcshareddata/xcschemes/Download BaaS.xcscheme deleted file mode 100644 index 6464a60fee..0000000000 --- a/Realm.xcodeproj/xcshareddata/xcschemes/Download BaaS.xcscheme +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Realm.xcodeproj/xcshareddata/xcschemes/Object Server Tests.xcscheme b/Realm.xcodeproj/xcshareddata/xcschemes/Object Server Tests.xcscheme deleted file mode 100644 index 3933ee1c8e..0000000000 --- a/Realm.xcodeproj/xcshareddata/xcschemes/Object Server Tests.xcscheme +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Realm.xcodeproj/xcshareddata/xcschemes/SwiftUISyncTestHost.xcscheme b/Realm.xcodeproj/xcshareddata/xcschemes/SwiftUISyncTestHost.xcscheme deleted file mode 100644 index 3e032af65e..0000000000 --- a/Realm.xcodeproj/xcshareddata/xcschemes/SwiftUISyncTestHost.xcscheme +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Realm.xcodeproj/xcshareddata/xcschemes/SwiftUISyncTests.xcscheme b/Realm.xcodeproj/xcshareddata/xcschemes/SwiftUISyncTests.xcscheme deleted file mode 100644 index 907547589c..0000000000 --- a/Realm.xcodeproj/xcshareddata/xcschemes/SwiftUISyncTests.xcscheme +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Realm/NSError+RLMSync.h b/Realm/NSError+RLMSync.h deleted file mode 100644 index 5ef323a247..0000000000 --- a/Realm/NSError+RLMSync.h +++ /dev/null @@ -1,46 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability) - -@class RLMSyncErrorActionToken; - -/// NSError category extension providing methods to get data out of Realm's -/// "client reset" error. -@interface NSError (RLMSync) - -/** - Given an appropriate Atlas App Services error, return the token that - can be passed into `+[RLMSyncSession immediatelyHandleError:]` to - immediately perform error clean-up work, or nil if the error isn't of - a type that provides a token. - */ -- (nullable RLMSyncErrorActionToken *)rlmSync_errorActionToken NS_REFINED_FOR_SWIFT; - -/** - Given an Atlas App Services client reset error, return the path where the - backup copy of the Realm will be placed once the client reset process is - complete. - */ -- (nullable NSString *)rlmSync_clientResetBackedUpRealmPath NS_SWIFT_UNAVAILABLE(""); - -@end - -RLM_HEADER_AUDIT_END(nullability) diff --git a/Realm/NSError+RLMSync.m b/Realm/NSError+RLMSync.m deleted file mode 100644 index 8a707aaca5..0000000000 --- a/Realm/NSError+RLMSync.m +++ /dev/null @@ -1,43 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#import "RLMError.h" - -@implementation NSError (RLMSync) - -- (RLMSyncErrorActionToken *)rlmSync_errorActionToken { - if (self.domain != RLMSyncErrorDomain) { - return nil; - } - if (self.code == RLMSyncErrorClientResetError - || self.code == RLMSyncErrorPermissionDeniedError) { - return (RLMSyncErrorActionToken *)self.userInfo[kRLMSyncErrorActionTokenKey]; - } - return nil; -} - -- (NSString *)rlmSync_clientResetBackedUpRealmPath { - if (self.domain == RLMSyncErrorDomain && self.code == RLMSyncErrorClientResetError) { - return self.userInfo[kRLMSyncPathOfRealmBackupCopyKey]; - } - return nil; -} - -@end diff --git a/Realm/ObjectServerTests/AsyncSyncTests.swift b/Realm/ObjectServerTests/AsyncSyncTests.swift deleted file mode 100644 index 07ec125c4a..0000000000 --- a/Realm/ObjectServerTests/AsyncSyncTests.swift +++ /dev/null @@ -1,1349 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#if os(macOS) && swift(>=5.8) - -import Realm -import Realm.Private -@_spi(RealmSwiftExperimental) import RealmSwift -import XCTest - -#if canImport(RealmTestSupport) -import RealmSwiftSyncTestSupport -import RealmSyncTestSupport -import RealmTestSupport -import RealmSwiftTestSupport -#endif - -// SE-0392 exposes this functionality directly, but for now we have to call the -// internal standard library function -@_silgen_name("swift_job_run") -private func _swiftJobRun(_ job: UnownedJob, _ executor: UnownedSerialExecutor) - -@available(macOS 13, *) -class AsyncAwaitSyncTests: SwiftSyncTestCase { - override class var defaultTestSuite: XCTestSuite { - // async/await is currently incompatible with thread sanitizer and will - // produce many false positives - // https://bugs.swift.org/browse/SR-15444 - if RLMThreadSanitizerEnabled() { - return XCTestSuite(name: "\(type(of: self))") - } - return super.defaultTestSuite - } - - override var objectTypes: [ObjectBase.Type] { - [ - SwiftCustomColumnObject.self, - SwiftHugeSyncObject.self, - SwiftPerson.self, - SwiftTypesSyncObject.self, - ] - } - - @MainActor func populateRealm() async throws { - try await write { realm in - realm.add(SwiftHugeSyncObject.create()) - realm.add(SwiftHugeSyncObject.create()) - } - } - - func assertThrowsError( - _ expression: @autoclosure () async throws -> T, - file: StaticString = #filePath, line: UInt = #line, - _ errorHandler: (_ error: E) -> Void - ) async { - do { - _ = try await expression() - XCTFail("Expression should have thrown an error", file: file, line: line) - } catch let error as E { - errorHandler(error) - } catch { - XCTFail("Expected error of type \(E.self) but got \(error)") - } - } - - func testUpdateBaseUrl() async throws { - let app = App(id: appId) - XCTAssertEqual(app.baseURL, "https://services.cloud.mongodb.com") - - try await app.updateBaseUrl(to: "http://localhost:9090") - XCTAssertEqual(app.baseURL, "http://localhost:9090") - - try await app.updateBaseUrl(to: "http://127.0.0.1:9090") - XCTAssertEqual(app.baseURL, "http://127.0.0.1:9090") - - // Fails as this appId doesn't exist in prod - await assertThrowsError(try await app.updateBaseUrl(to: nil)) { (error: AppError) in - XCTAssertEqual(error.code, .unknown) - } - } - - @MainActor func testAsyncOpenStandalone() async throws { - try autoreleasepool { - let configuration = Realm.Configuration(objectTypes: [SwiftPerson.self]) - let realm = try Realm(configuration: configuration) - try realm.write { - (0..<10).forEach { _ in realm.add(SwiftPerson(firstName: "Charlie", lastName: "Bucket")) } - } - } - let configuration = Realm.Configuration(objectTypes: [SwiftPerson.self]) - let realm = try await Realm(configuration: configuration) - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 10) - } - - @MainActor func testAsyncOpenSync() async throws { - try await populateRealm() - let realm = try await openRealm() - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, 2) - } - - @MainActor func testAsyncOpenDownloadBehaviorNever() async throws { - try await populateRealm() - let config = try configuration() - - // Should not have any objects as it just opens immediately without waiting - let realm = try await Realm(configuration: config, downloadBeforeOpen: .never) - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, 0) - waitForDownloads(for: realm) - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, 2) - } - - @MainActor func testAsyncOpenDownloadBehaviorOnce() async throws { - try await populateRealm() - let config = try configuration() - - // Should have the objects - try await Task { - let realm = try await Realm(configuration: config, downloadBeforeOpen: .once) - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, 2) - }.value - - // Add some more objects - try await write { realm in - realm.add(SwiftHugeSyncObject.create()) - realm.add(SwiftHugeSyncObject.create()) - } - - // Will not wait for the new objects to download - try await Task { - let realm = try await Realm(configuration: config, downloadBeforeOpen: .once) - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, 2) - try XCTUnwrap(realm.syncSession).suspend() - }.value - } - - @MainActor func testAsyncOpenDownloadBehaviorAlwaysWithCachedRealm() async throws { - try await populateRealm() - let config = try configuration() - - // Should have the objects - let realm = try await Realm(configuration: config, downloadBeforeOpen: .always) - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, 2) - try XCTUnwrap(realm.syncSession).suspend() - - - // Add some more objects - try await populateRealm() - - // Should resume the session and wait for the new objects to download - _ = try await Realm(configuration: config, downloadBeforeOpen: .always) - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, 4) - } - - @MainActor func testAsyncOpenDownloadBehaviorAlwaysWithFreshRealm() async throws { - try await populateRealm() - let config = try configuration() - - // Open in a Task so that the Realm is closed and re-opened later - _ = try await Task { - let realm = try await Realm(configuration: config, downloadBeforeOpen: .always) - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, 2) - }.value - - // Add some more objects - try await populateRealm() - - // Should wait for the new objects to download - let realm = try await Realm(configuration: config, downloadBeforeOpen: .always) - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, 4) - } - - @MainActor func testDownloadPBSRealmCustomColumnNames() async throws { - let objectId = ObjectId.generate() - let linkedObjectId = ObjectId.generate() - - try await write { realm in - let object = SwiftCustomColumnObject() - object.id = objectId - object.binaryCol = Data("string".utf8) - let linkedObject = SwiftCustomColumnObject() - linkedObject.id = linkedObjectId - object.objectCol = linkedObject - realm.add(object) - } - - // Should have the objects - let realm = try await openRealm() - XCTAssertEqual(realm.objects(SwiftCustomColumnObject.self).count, 2) - - let object = try XCTUnwrap(realm.object(ofType: SwiftCustomColumnObject.self, forPrimaryKey: objectId)) - XCTAssertEqual(object.id, objectId) - XCTAssertEqual(object.boolCol, true) - XCTAssertEqual(object.intCol, 1) - XCTAssertEqual(object.doubleCol, 1.1) - XCTAssertEqual(object.stringCol, "string") - XCTAssertEqual(object.binaryCol, Data("string".utf8)) - XCTAssertEqual(object.dateCol, Date(timeIntervalSince1970: -1)) - XCTAssertEqual(object.longCol, 1) - XCTAssertEqual(object.decimalCol, Decimal128(1)) - XCTAssertEqual(object.uuidCol, UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")!) - XCTAssertNil(object.objectIdCol) - XCTAssertEqual(object.objectCol!.id, linkedObjectId) - } - - // A custom executor which cancels the task after the requested number of - // invocations. This is a very naive executor which just synchronously - // invokes jobs, which generally is not a legal thing to do - final class CancellingExecutor: SerialExecutor, @unchecked Sendable { - private var remaining: Locked - private var pendingJob: UnownedJob? - var task: Task? { - didSet { - if let pendingJob = pendingJob { - self.pendingJob = nil - enqueue(pendingJob) - } - } - } - - init(cancelAfter: Int) { - remaining = Locked(cancelAfter) - } - - func enqueue(_ job: UnownedJob) { - // The body of the task is enqueued before the task variable is - // set, so we need to defer invoking the very first job - guard let task = task else { - precondition(pendingJob == nil) - pendingJob = job - return - } - - remaining.withLock { remaining in - if remaining == 0 { - task.cancel() - } - remaining -= 1 - - // S#-0392 exposes all the stuff we need for this in the public - // API (Which hopefully will arrive in Swift 5.9), but for now - // invoking a job requires some private things. - _swiftJobRun(job, self.asUnownedSerialExecutor()) - } - } - - func asUnownedSerialExecutor() -> UnownedSerialExecutor { - UnownedSerialExecutor(ordinary: self) - } - } - - // An actor that does nothing other than have a custom executor - actor CustomExecutorActor { - nonisolated let executor: UnownedSerialExecutor - init(_ executor: UnownedSerialExecutor) { - self.executor = executor - } - nonisolated var unownedExecutor: UnownedSerialExecutor { - executor - } - } - - @MainActor func testAsyncOpenTaskCancellation() async throws { - try await populateRealm() - - let configuration = try configuration() - func isolatedOpen(_ actor: isolated CustomExecutorActor) async throws { -#if compiler(<6) - _ = try await Realm(configuration: configuration, actor: actor, downloadBeforeOpen: .always) -#else - _ = try await Realm.open(configuration: configuration, downloadBeforeOpen: .always) -#endif - } - - - // Try opening the Realm with the Task being cancelled at every possible - // point between executor invocations. This doesn't really test that - // cancellation is *correct*; just that cancellation never results in - // a hang or crash. - for i in 0 ..< .max { - RLMWaitForRealmToClose(configuration.fileURL!.path) - _ = try Realm.deleteFiles(for: configuration) - - let executor = CancellingExecutor(cancelAfter: i) - executor.task = Task { - try await isolatedOpen(.init(executor.asUnownedSerialExecutor())) - } - do { - try await executor.task!.value - break - } catch is CancellationError { - // pass - } catch { - XCTFail("Expected CancellationError but got \(error)") - } - } - - // Repeat the above, but with a cached Realm so that we hit that code path instead - let cachedRealm = try await Realm(configuration: configuration, downloadBeforeOpen: .always) - for i in 0 ..< .max { - let executor = CancellingExecutor(cancelAfter: i) - executor.task = Task { - try await isolatedOpen(.init(executor.asUnownedSerialExecutor())) - } - do { - try await executor.task!.value - break - } catch is CancellationError { - // pass - } catch { - XCTFail("Expected CancellationError but got \(error)") - } - } - cachedRealm.invalidate() - } - - func testCallResetPasswordAsyncAwait() async throws { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - try await app.emailPasswordAuth.registerUser(email: email, password: password) - let auth = app.emailPasswordAuth - await assertThrowsError(try await auth.callResetPasswordFunction(email: email, - password: randomString(10), - args: [[:]])) { - assertAppError($0, .unknown, "failed to reset password for user \"\(email)\"") - } - } - - func testAppLinkUserAsyncAwait() async throws { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - try await app.emailPasswordAuth.registerUser(email: email, password: password) - - let syncUser = try await self.app.login(credentials: Credentials.anonymous) - - let credentials = Credentials.emailPassword(email: email, password: password) - let linkedUser = try await syncUser.linkUser(credentials: credentials) - XCTAssertEqual(linkedUser.id, app.currentUser?.id) - XCTAssertEqual(linkedUser.identities.count, 2) - } - - func testUserCallFunctionAsyncAwait() async throws { - let user = try await self.app.login(credentials: basicCredentials()) - guard case let .int32(sum) = try await user.functions.sum(1, 2, 3, 4, 5) else { - return XCTFail("Should be int32") - } - XCTAssertEqual(sum, 15) - } - - // MARK: - Objective-C async await - func testPushRegistrationAsyncAwait() async throws { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - try await app.emailPasswordAuth.registerUser(email: email, password: password) - - _ = try await app.login(credentials: Credentials.emailPassword(email: email, password: password)) - - let client = app.pushClient(serviceName: "gcm") - try await client.registerDevice(token: "some-token", user: app.currentUser!) - try await client.deregisterDevice(user: app.currentUser!) - } - - func testEmailPasswordProviderClientAsyncAwait() async throws { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - let auth = app.emailPasswordAuth - try await auth.registerUser(email: email, password: password) - - await assertThrowsError(try await auth.confirmUser("atoken", tokenId: "atokenid")) { - assertAppError($0, .badRequest, "invalid token data") - } - await assertThrowsError(try await auth.resendConfirmationEmail(email)) { - assertAppError($0, .userAlreadyConfirmed, "already confirmed") - } - await assertThrowsError(try await auth.retryCustomConfirmation(email)) { - assertAppError($0, .unknown, - "cannot run confirmation for \(email): automatic confirmation is enabled") - } - await assertThrowsError(try await auth.sendResetPasswordEmail("atoken")) { - assertAppError($0, .userNotFound, "user not found") - } - } - - func testUserAPIKeyProviderClientAsyncAwait() async throws { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - try await app.emailPasswordAuth.registerUser(email: email, password: password) - - let credentials = Credentials.emailPassword(email: email, password: password) - let syncUser = try await self.app.login(credentials: credentials) - let apiKey = try await syncUser.apiKeysAuth.createAPIKey(named: "my-api-key") - XCTAssertNotNil(apiKey) - - let fetchedApiKey = try await syncUser.apiKeysAuth.fetchAPIKey(apiKey.objectId) - XCTAssertNotNil(fetchedApiKey) - - let fetchedApiKeys = try await syncUser.apiKeysAuth.fetchAPIKeys() - XCTAssertNotNil(fetchedApiKeys) - XCTAssertEqual(fetchedApiKeys.count, 1) - - try await syncUser.apiKeysAuth.disableAPIKey(apiKey.objectId) - try await syncUser.apiKeysAuth.enableAPIKey(apiKey.objectId) - try await syncUser.apiKeysAuth.deleteAPIKey(apiKey.objectId) - - let newFetchedApiKeys = try await syncUser.apiKeysAuth.fetchAPIKeys() - XCTAssertNotNil(newFetchedApiKeys) - XCTAssertEqual(newFetchedApiKeys.count, 0) - } - - func testCustomUserDataAsyncAwait() async throws { - let user = try await createUser() - _ = try await user.functions.updateUserData(["favourite_colour": "green", "apples": 10]) - - try await user.refreshCustomData() - XCTAssertEqual(user.customData["favourite_colour"], .string("green")) - XCTAssertEqual(user.customData["apples"], .int64(10)) - } - - func testDeleteUserAsyncAwait() async throws { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - let credentials: Credentials = .emailPassword(email: email, password: password) - try await app.emailPasswordAuth.registerUser(email: email, password: password) - - let user = try await self.app.login(credentials: credentials) - - XCTAssertNotNil(app.currentUser) - try await user.delete() - - XCTAssertNil(app.currentUser) - XCTAssertEqual(app.allUsers.count, 0) - } - - @MainActor - func testSwiftAddObjectsAsync() async throws { - let realm = try await openRealm() - checkCount(expected: 0, realm, SwiftPerson.self) - checkCount(expected: 0, realm, SwiftTypesSyncObject.self) - - try await write { realm in - realm.add(SwiftPerson(firstName: "Ringo", lastName: "Starr")) - realm.add(SwiftPerson(firstName: "John", lastName: "Lennon")) - realm.add(SwiftPerson(firstName: "Paul", lastName: "McCartney")) - realm.add(SwiftTypesSyncObject(person: SwiftPerson(firstName: "George", lastName: "Harrison"))) - } - - try await realm.syncSession?.wait(for: .download) - checkCount(expected: 4, realm, SwiftPerson.self) - checkCount(expected: 1, realm, SwiftTypesSyncObject.self) - - let obj = realm.objects(SwiftTypesSyncObject.self).first! - XCTAssertEqual(obj.boolCol, true) - XCTAssertEqual(obj.intCol, 1) - XCTAssertEqual(obj.doubleCol, 1.1) - XCTAssertEqual(obj.stringCol, "string") - XCTAssertEqual(obj.binaryCol, Data("string".utf8)) - XCTAssertEqual(obj.decimalCol, Decimal128(1)) - XCTAssertEqual(obj.dateCol, Date(timeIntervalSince1970: -1)) - XCTAssertEqual(obj.longCol, Int64(1)) - XCTAssertEqual(obj.uuidCol, UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")!) - XCTAssertEqual(obj.anyCol.intValue, 1) - XCTAssertEqual(obj.objectCol!.firstName, "George") - } -} - -@available(macOS 13, *) -class AsyncFlexibleSyncTests: SwiftSyncTestCase { - override class var defaultTestSuite: XCTestSuite { - // async/await is currently incompatible with thread sanitizer and will - // produce many false positives - // https://bugs.swift.org/browse/SR-15444 - if RLMThreadSanitizerEnabled() || true { - return XCTestSuite(name: "\(type(of: self))") - } - return super.defaultTestSuite - } - - override var objectTypes: [ObjectBase.Type] { - [SwiftCustomColumnObject.self, SwiftPerson.self, SwiftTypesSyncObject.self] - } - - override func configuration(user: User) -> Realm.Configuration { - user.flexibleSyncConfiguration() - } - - override func createApp() throws -> String { - try createFlexibleSyncApp() - } - - @MainActor - func populateSwiftPerson(_ count: Int = 10) async throws { - try await write { realm in - for i in 1...count { - let person = SwiftPerson(firstName: "\(self.name)", - lastName: "lastname_\(i)", - age: i) - realm.add(person) - } - } - } - - @MainActor - func testFlexibleSyncAppAddQueryAsyncAwait() async throws { - try await populateSwiftPerson(25) - - let realm = try await openRealm() - checkCount(expected: 0, realm, SwiftPerson.self) - - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 0) - - try await subscriptions.update { - subscriptions.append(QuerySubscription(name: "person_age_15") { - $0.age > 15 && $0.firstName == "\(name)" - }) - } - XCTAssertEqual(subscriptions.state, .complete) - XCTAssertEqual(subscriptions.count, 1) - - checkCount(expected: 10, realm, SwiftPerson.self) - } - - @MainActor - func testFlexibleSyncInitInMemory() async throws { - try await populateSwiftPerson(5) - - let user = try await createUser() - let name = self.name - try await Task { - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subs in - subs.append(QuerySubscription { - $0.age > 0 && $0.firstName == name - }) - }) - config.objectTypes = [SwiftPerson.self] - config.inMemoryIdentifier = "identifier" - let inMemoryRealm = try await Realm(configuration: config, downloadBeforeOpen: .always) - XCTAssertEqual(inMemoryRealm.objects(SwiftPerson.self).count, 5) - try! inMemoryRealm.write { - let person = SwiftPerson(firstName: self.name, - lastName: "lastname_10", - age: 10) - inMemoryRealm.add(person) - } - XCTAssertEqual(inMemoryRealm.objects(SwiftPerson.self).count, 6) - try await inMemoryRealm.syncSession?.wait(for: .upload) - }.value - - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subs in - subs.append(QuerySubscription { - $0.age > 5 && $0.firstName == name - }) - }) - config.objectTypes = [SwiftPerson.self] - config.inMemoryIdentifier = "identifier" - let inMemoryRealm = try await Realm(configuration: config, downloadBeforeOpen: .always) - XCTAssertEqual(inMemoryRealm.objects(SwiftPerson.self).count, 1) - - var config2 = user.flexibleSyncConfiguration(initialSubscriptions: { subs in - subs.append(QuerySubscription { - $0.age > 0 && $0.firstName == name - }) - }) - config2.objectTypes = [SwiftPerson.self] - config2.inMemoryIdentifier = "identifier2" - let inMemoryRealm2 = try await Realm(configuration: config2, downloadBeforeOpen: .always) - XCTAssertEqual(inMemoryRealm2.objects(SwiftPerson.self).count, 6) - } - - @MainActor - func testStates() async throws { - let realm = try await openRealm() - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 0) - - // should complete - try await subscriptions.update { - subscriptions.append(QuerySubscription(name: "person_age_15") { - $0.age > 15 && $0.firstName == "\(name)" - }) - } - XCTAssertEqual(subscriptions.state, .complete) - // should error - do { - try await subscriptions.update { - subscriptions.append(QuerySubscription(name: "swiftObject_longCol") { - $0.longCol == Int64(1) - }) - } - XCTFail("Invalid query should have failed") - } catch Realm.Error.subscriptionFailed { - guard case .error = subscriptions.state else { - return XCTFail("Adding a query for a not queryable field should change the subscription set state to error") - } - } - } - - @MainActor - func testFlexibleSyncNotInitialSubscriptions() async throws { - let realm = try await openRealm() - XCTAssertEqual(realm.subscriptions.count, 0) - } - - @MainActor - func testFlexibleSyncInitialSubscriptionsAsync() async throws { - try await write { realm in - for i in 1...20 { - realm.add(SwiftPerson(firstName: "\(self.name)", - lastName: "lastname_\(i)", - age: i)) - } - } - - let user = try await createUser() - let name = self.name - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subscriptions in - subscriptions.append(QuerySubscription(name: "person_age_10") { - $0.age > 10 && $0.firstName == "\(name)" - }) - }) - config.objectTypes = [SwiftPerson.self] - - XCTAssertNotNil(config.syncConfiguration?.initialSubscriptions) - XCTAssertNotNil(config.syncConfiguration?.initialSubscriptions?.callback) - XCTAssertEqual(config.syncConfiguration?.initialSubscriptions?.rerunOnOpen, false) - - let realm = try await Realm(configuration: config, downloadBeforeOpen: .once) - XCTAssertEqual(realm.subscriptions.count, 1) - checkCount(expected: 10, realm, SwiftPerson.self) - } - - @MainActor - func testFlexibleSyncInitialSubscriptionsNotRerunOnOpen() async throws { - let name = self.name - let user = try await createUser() - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subscriptions in - subscriptions.append(QuerySubscription(name: "person_age_10") { - $0.age > 10 && $0.firstName == "\(name)" - }) - }) - config.objectTypes = [SwiftPerson.self] - let realm = try await Realm(configuration: config, downloadBeforeOpen: .once) - XCTAssertEqual(realm.subscriptions.count, 1) - - let realm2 = try await Realm(configuration: config, downloadBeforeOpen: .once) - XCTAssertNotNil(realm2) - XCTAssertEqual(realm.subscriptions.count, 1) - } - - @MainActor - func testFlexibleSyncInitialSubscriptionsRerunOnOpenNamedQuery() async throws { - let user = try await createUser() - let name = self.name - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subscriptions in - if subscriptions.first(named: "person_age_10") == nil { - subscriptions.append(QuerySubscription(name: "person_age_10") { - $0.age > 20 && $0.firstName == "\(name)" - }) - } - }, rerunOnOpen: true) - config.objectTypes = [SwiftPerson.self] - - XCTAssertNotNil(config.syncConfiguration?.initialSubscriptions) - XCTAssertNotNil(config.syncConfiguration?.initialSubscriptions?.callback) - XCTAssertEqual(config.syncConfiguration?.initialSubscriptions?.rerunOnOpen, false) - - try await Task { - let realm = try await Realm(configuration: config, downloadBeforeOpen: .once) - XCTAssertEqual(realm.subscriptions.count, 1) - }.value - - try await Task { - let realm2 = try await Realm(configuration: config, downloadBeforeOpen: .once) - XCTAssertNotNil(realm2) - XCTAssertEqual(realm2.subscriptions.count, 1) - }.value - } - - @MainActor - func testFlexibleSyncInitialSubscriptionsRerunOnOpenUnnamedQuery() async throws { - try await write { realm in - for i in 1...30 { - let object = SwiftTypesSyncObject() - object.stringCol = self.name - object.dateCol = Calendar.current.date( - byAdding: .hour, - value: -i, - to: Date())! - realm.add(object) - } - } - - let user = try await createUser() - let isFirstOpen = Locked(true) - let name = self.name - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subscriptions in - subscriptions.append(QuerySubscription(query: { - let date = isFirstOpen.wrappedValue ? Calendar.current.date( - byAdding: .hour, - value: -10, - to: Date()) : Calendar.current.date( - byAdding: .hour, - value: -20, - to: Date()) - isFirstOpen.wrappedValue = false - return $0.stringCol == name && $0.dateCol < Date() && $0.dateCol > date! - })) - }, rerunOnOpen: true) - config.objectTypes = [SwiftTypesSyncObject.self, SwiftPerson.self] - let c = config - try await Task { - let realm = try await Realm(configuration: c, downloadBeforeOpen: .always) - XCTAssertEqual(realm.subscriptions.count, 1) - checkCount(expected: 9, realm, SwiftTypesSyncObject.self) - }.value - - try await Task { - let realm = try await Realm(configuration: c, downloadBeforeOpen: .always) - XCTAssertEqual(realm.subscriptions.count, 2) - checkCount(expected: 19, realm, SwiftTypesSyncObject.self) - }.value - } - - @MainActor - func testFlexibleSyncInitialSubscriptionsThrows() async throws { - let user = try await createUser() - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subscriptions in - subscriptions.append(QuerySubscription(query: { - $0.uuidCol == UUID() - })) - }) - config.objectTypes = [SwiftTypesSyncObject.self, SwiftPerson.self] - do { - _ = try await Realm(configuration: config, downloadBeforeOpen: .once) - } catch let error as Realm.Error { - XCTAssertEqual(error.code, .subscriptionFailed) - } - } - - @MainActor - func testFlexibleSyncInitialSubscriptionsDefaultConfiguration() async throws { - let user = try await createUser() - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subscriptions in - subscriptions.append(QuerySubscription()) - }) - config.objectTypes = [SwiftTypesSyncObject.self, SwiftPerson.self] - Realm.Configuration.defaultConfiguration = config - - let realm = try await Realm(downloadBeforeOpen: .once) - XCTAssertEqual(realm.subscriptions.count, 1) - } - - // MARK: Subscribe - - @MainActor - func testSubscribe() async throws { - try await populateSwiftPerson() - - let realm = try await openRealm() - let results0 = try await realm.objects(SwiftPerson.self).where { $0.age >= 6 }.subscribe() - XCTAssertEqual(results0.count, 5) - XCTAssertEqual(realm.subscriptions.count, 1) - let results1 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == self.name && $0.lastName == "lastname_3" } - .subscribe() - XCTAssertEqual(results1.count, 1) - XCTAssertEqual(results0.count, 5) - XCTAssertEqual(realm.subscriptions.count, 2) - let results2 = realm.objects(SwiftPerson.self) - XCTAssertEqual(results2.count, 6) - } - - @MainActor - func testSubscribeReassign() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - var results0 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == self.name && $0.age >= 8 } - .subscribe() - XCTAssertEqual(results0.count, 3) - XCTAssertEqual(realm.subscriptions.count, 1) - // results0 local query is { $0.age >= 8 AND $0.age < 8 } - results0 = try await results0.where { $0.age < 8 }.subscribe() - XCTAssertEqual(results0.count, 0) // no matches because local query is impossible - // two subscriptions: "$0.age >= 8 AND $0.age < 8" and "$0.age >= 8" - XCTAssertEqual(realm.subscriptions.count, 2) - let results1 = realm.objects(SwiftPerson.self) - XCTAssertEqual(results1.count, 3) // three objects from "$0.age >= 8". None "$0.age >= 8 AND $0.age < 8". - } - - @MainActor - func testSubscribeSameQueryNoName() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - let results0 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 } - .subscribe() - let ex = XCTestExpectation(description: "no attempt to re-create subscription, returns immediately") - realm.syncSession!.suspend() - let task = Task { - _ = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe() - _ = try await results0.subscribe() - ex.fulfill() - } - await fulfillment(of: [ex], timeout: 1.0) - try await task.value - XCTAssertEqual(realm.subscriptions.count, 1) - } - - @MainActor - func testSubscribeSameQuerySameName() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - let results0 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 } - .subscribe(name: "8 or older") - realm.syncSession!.suspend() - let ex = XCTestExpectation(description: "no attempt to re-create subscription, returns immediately") - Task { @MainActor in - _ = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 } - .subscribe(name: "8 or older") - _ = try await results0.subscribe(name: "8 or older") - XCTAssertEqual(realm.subscriptions.count, 1) - ex.fulfill() - } - await fulfillment(of: [ex], timeout: 5.0) - XCTAssertEqual(realm.subscriptions.count, 1) - } - - @MainActor - func testSubscribeSameQueryDifferentName() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - let results0 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe() - _ = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe(name: "8 or older") - _ = try await results0.subscribe(name: "older than 7") - XCTAssertEqual(realm.subscriptions.count, 3) - let subscriptions = realm.subscriptions - XCTAssertNil(subscriptions[0]!.name) - XCTAssertEqual(subscriptions[1]!.name, "8 or older") - XCTAssertEqual(subscriptions[2]!.name, "older than 7") - } - - @MainActor - func testSubscribeDifferentQuerySameName() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - _ = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age > 8 }.subscribe(name: "group1") - _ = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age > 5 }.subscribe(name: "group1") - XCTAssertEqual(realm.subscriptions.count, 1) - XCTAssertNotNil(realm.subscriptions.first(ofType: SwiftPerson.self) { $0.firstName == name && $0.age > 5 }) - } - - @MainActor - func testSubscribeOnRealmConfinedActor() async throws { - try await populateSwiftPerson() - try await populateSwiftPerson() - - let user = try await createUser() - var config = user.flexibleSyncConfiguration() - config.objectTypes = [SwiftPerson.self] - let realm = try await Realm(configuration: config) - let results1 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age > 8 }.subscribe(waitForSync: .onCreation) - XCTAssertEqual(results1.count, 2) - let results2 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age > 6 }.subscribe(waitForSync: .always) - XCTAssertEqual(results2.count, 4) - let results3 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age > 4 }.subscribe(waitForSync: .never) - XCTAssertEqual(results3.count, 4) - XCTAssertEqual(realm.subscriptions.count, 3) - } - - @CustomGlobalActor - func testSubscribeOnRealmConfinedCustomActor() async throws { - nonisolated(unsafe) let unsafeSelf = self - try await unsafeSelf.populateSwiftPerson() - - let user = try await createUser() - var config = user.flexibleSyncConfiguration() - config.objectTypes = [SwiftPerson.self] -#if compiler(<6) - let realm = try await Realm(configuration: config, actor: CustomGlobalActor.shared) -#else - let realm = try await Realm.open(configuration: config) -#endif - let name = self.name - let results1 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age > 8 }.subscribe(waitForSync: .onCreation) - XCTAssertEqual(results1.count, 2) - let results2 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age > 6 }.subscribe(waitForSync: .always) - XCTAssertEqual(results2.count, 4) - let results3 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age > 4 }.subscribe(waitForSync: .never) - XCTAssertEqual(results3.count, 4) - XCTAssertEqual(realm.subscriptions.count, 3) - } - - @MainActor - func testUnsubscribe() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - let results1 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.lastName == "lastname_3" }.subscribe() - XCTAssertEqual(realm.subscriptions.count, 1) - results1.unsubscribe() - XCTAssertEqual(realm.subscriptions.count, 0) - } - - @MainActor - func testUnsubscribeAfterReassign() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - var results0 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe() - XCTAssertEqual(results0.count, 3) - XCTAssertEqual(realm.subscriptions.count, 1) - results0 = try await results0 - .where { $0.firstName == name && $0.age < 8 }.subscribe() // subscribes to "age >= 8 && age < 8" because that's the local query - XCTAssertEqual(results0.count, 0) - XCTAssertEqual(realm.subscriptions.count, 2) // Two subs present:1) "age >= 8" 2) "age >= 8 && age < 8" - let results1 = realm.objects(SwiftPerson.self) - XCTAssertEqual(results1.count, 3) - results0.unsubscribe() // unsubscribes from "age >= 8 && age < 8" - XCTAssertEqual(realm.subscriptions.count, 1) - XCTAssertNotNil(realm.subscriptions.first(ofType: SwiftPerson.self) { $0.firstName == name && $0.age >= 8 }) - XCTAssertEqual(results0.count, 0) // local query is still "age >= 8 && age < 8". - XCTAssertEqual(results1.count, 3) - } - - @MainActor - func testUnsubscribeWithoutSubscriptionExistingNamed() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - _ = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe(name: "sub1") - XCTAssertEqual(realm.subscriptions.count, 1) - let results = realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 } - results.unsubscribe() - XCTAssertEqual(realm.subscriptions.count, 1) - XCTAssertEqual(realm.subscriptions.first!.name, "sub1") - } - - @MainActor - func testUnsubscribeNoExistingMatch() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - XCTAssertEqual(realm.subscriptions.count, 0) - _ = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe(name: "age_older_8") - let results0 = realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 } - XCTAssertEqual(realm.subscriptions.count, 1) - XCTAssertEqual(results0.count, 3) - results0.unsubscribe() - XCTAssertEqual(realm.subscriptions.count, 1) - XCTAssertEqual(results0.count, 3) // Results are not modified because there is no subscription associated to the unsubscribed result - } - - @MainActor - func testUnsubscribeNamed() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - _ = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe() - _ = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe(name: "first_named") - let results = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe(name: "second_named") - XCTAssertEqual(realm.subscriptions.count, 3) - - results.unsubscribe() - XCTAssertEqual(realm.subscriptions.count, 2) - XCTAssertEqual(realm.subscriptions[0]!.name, nil) - XCTAssertEqual(realm.subscriptions[1]!.name, "first_named") - results.unsubscribe() // check again for case when subscription doesn't exist - XCTAssertEqual(realm.subscriptions.count, 2) - XCTAssertEqual(realm.subscriptions[0]!.name, nil) - XCTAssertEqual(realm.subscriptions[1]!.name, "first_named") - } - - @MainActor - func testUnsubscribeReassign() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - _ = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe(name: "first_named") - var results = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe(name: "second_named") - // expect `results` associated subscription to be reassigned to the id which matches the unnamed subscription - results = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe() - XCTAssertEqual(realm.subscriptions.count, 3) - - results.unsubscribe() - // so the two named subscriptions remain. - XCTAssertEqual(realm.subscriptions.count, 2) - XCTAssertEqual(realm.subscriptions[0]!.name, "first_named") - XCTAssertEqual(realm.subscriptions[1]!.name, "second_named") - } - - @MainActor - func testUnsubscribeSameQueryDifferentName() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - _ = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe() - let results2 = realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 } - XCTAssertEqual(realm.subscriptions.count, 1) - results2.unsubscribe() - XCTAssertEqual(realm.subscriptions.count, 0) - } - - @MainActor - func testSubscribeNameAcrossTypes() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - let results = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe(name: "sameName") - XCTAssertEqual(realm.subscriptions.count, 1) - XCTAssertEqual(results.count, 3) - _ = try await realm.objects(SwiftTypesSyncObject.self).subscribe(name: "sameName") - XCTAssertEqual(realm.subscriptions.count, 1) - XCTAssertEqual(results.count, 0) - } - - @MainActor - func testSubscribeOnCreation() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - var results = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe(waitForSync: .onCreation) - XCTAssertEqual(results.count, 3) - let expectation = XCTestExpectation(description: "method doesn't hang") - realm.syncSession!.suspend() - let task = Task { - results = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 } - .subscribe(waitForSync: .onCreation) - XCTAssertEqual(results.count, 3) // expect method to return immediately, and not hang while no connection - XCTAssertEqual(realm.subscriptions.count, 1) - expectation.fulfill() - } - await fulfillment(of: [expectation], timeout: 2.0) - try await task.value - } - - @MainActor - func testSubscribeAlways() async throws { - let collection = anonymousUser.collection(for: SwiftPerson.self, app: app) - try await populateSwiftPerson() - let realm = try await openRealm() - - var results = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 9 }.subscribe(waitForSync: .always) - XCTAssertEqual(results.count, 2) - - // suspend session on client. Add a document that isn't on the client. - realm.syncSession!.suspend() - let serverObject: Document = [ - "_id": .objectId(ObjectId.generate()), - "firstName": .string(name), - "lastName": .string("M"), - "age": .int32(30) - ] - collection.insertOne(serverObject).await(self, timeout: 10.0) - - // Resume the client session. - realm.syncSession!.resume() - XCTAssertEqual(results.count, 2) - results = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 9 }.subscribe(waitForSync: .always) - // Expect this subscribe call to wait for sync downloads, even though the subscription already existed - XCTAssertEqual(results.count, 3) // Count is 3 because it includes the object/document that was created while offline. - XCTAssertEqual(realm.subscriptions.count, 1) - } - - @MainActor - func testSubscribeNever() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - let expectation = XCTestExpectation(description: "test doesn't hang") - Task { - let results = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 }.subscribe(waitForSync: .never) - XCTAssertEqual(results.count, 0) // expect no objects to be able to sync because of immediate return - XCTAssertEqual(realm.subscriptions.count, 1) - expectation.fulfill() - } - await fulfillment(of: [expectation], timeout: 1) - } - - @MainActor - func testSubscribeTimeout() async throws { - try await populateSwiftPerson() - let realm = try await openRealm() - - realm.syncSession!.suspend() - let timeout = 1.0 - do { - _ = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 8 } - .subscribe(waitForSync: .always, timeout: timeout) - XCTFail("subscribe did not time out") - } catch let error as NSError { - XCTAssertEqual(error.code, Int(ETIMEDOUT)) - XCTAssertEqual(error.domain, NSPOSIXErrorDomain) - XCTAssertEqual(error.localizedDescription, "Waiting for update timed out after \(timeout) seconds.") - } - } - - @MainActor - func testSubscribeTimeoutSucceeds() async throws { - try await populateSwiftPerson() - - let realm = try await openRealm() - let results0 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.age >= 6 }.subscribe(timeout: 2.0) - XCTAssertEqual(results0.count, 5) - XCTAssertEqual(realm.subscriptions.count, 1) - let results1 = try await realm.objects(SwiftPerson.self) - .where { $0.firstName == name && $0.lastName == "lastname_3" }.subscribe(timeout: 2.0) - XCTAssertEqual(results1.count, 1) - XCTAssertEqual(results0.count, 5) - XCTAssertEqual(realm.subscriptions.count, 2) - - let results2 = realm.objects(SwiftPerson.self) - XCTAssertEqual(results2.count, 6) - } - - // MARK: - Custom Column - - @MainActor - func testCustomColumnFlexibleSyncSchema() throws { - let realm = try openRealm() - for property in realm.schema.objectSchema.first(where: { $0.className == "SwiftCustomColumnObject" })!.properties { - XCTAssertEqual(customColumnPropertiesMapping[property.name], property.columnName) - } - } - - @MainActor - func testCreateCustomColumnFlexibleSyncSubscription() async throws { - let objectId = ObjectId.generate() - try await write { realm in - let valuesDictionary: [String: Any] = ["id": objectId, - "boolCol": true, - "intCol": 365, - "doubleCol": 365.365, - "stringCol": "@#¢∞¬÷÷", - "binaryCol": Data("string".utf8), - "dateCol": Date(timeIntervalSince1970: -365), - "longCol": 365, - "decimalCol": Decimal128(365), - "uuidCol": UUID(uuidString: "629bba42-97dc-4fee-97ff-78af054952ec")!, - "objectIdCol": ObjectId.generate()] - - realm.create(SwiftCustomColumnObject.self, value: valuesDictionary) - } - - let user = try await createUser() - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subscriptions in - subscriptions.append(QuerySubscription()) - }) - config.objectTypes = [SwiftCustomColumnObject.self] - let realm = try await Realm(configuration: config, downloadBeforeOpen: .once) - XCTAssertEqual(realm.subscriptions.count, 1) - - let foundObject = realm.object(ofType: SwiftCustomColumnObject.self, forPrimaryKey: objectId) - XCTAssertNotNil(foundObject) - XCTAssertEqual(foundObject!.id, objectId) - XCTAssertEqual(foundObject!.boolCol, true) - XCTAssertEqual(foundObject!.intCol, 365) - XCTAssertEqual(foundObject!.doubleCol, 365.365) - XCTAssertEqual(foundObject!.stringCol, "@#¢∞¬÷÷") - XCTAssertEqual(foundObject!.binaryCol, Data("string".utf8)) - XCTAssertEqual(foundObject!.dateCol, Date(timeIntervalSince1970: -365)) - XCTAssertEqual(foundObject!.longCol, 365) - XCTAssertEqual(foundObject!.decimalCol, Decimal128(365)) - XCTAssertEqual(foundObject!.uuidCol, UUID(uuidString: "629bba42-97dc-4fee-97ff-78af054952ec")!) - XCTAssertNotNil(foundObject?.objectIdCol) - XCTAssertNil(foundObject?.objectCol) - } - - @MainActor - func testCustomColumnFlexibleSyncSubscriptionNSPredicate() async throws { - let objectId = ObjectId.generate() - let linkedObjectId = ObjectId.generate() - try await write { realm in - let object = SwiftCustomColumnObject() - object.id = objectId - object.binaryCol = Data("string".utf8) - let linkedObject = SwiftCustomColumnObject() - linkedObject.id = linkedObjectId - object.objectCol = linkedObject - realm.add(object) - } - let user = try await createUser() - - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subscriptions in - subscriptions.append(QuerySubscription(where: NSPredicate(format: "id == %@ || id == %@", objectId, linkedObjectId))) - }) - config.objectTypes = [SwiftCustomColumnObject.self] - let realm = try await Realm(configuration: config, downloadBeforeOpen: .once) - XCTAssertEqual(realm.subscriptions.count, 1) - checkCount(expected: 2, realm, SwiftCustomColumnObject.self) - - let foundObject = realm.objects(SwiftCustomColumnObject.self).where { $0.id == objectId }.first - XCTAssertNotNil(foundObject) - XCTAssertEqual(foundObject!.id, objectId) - XCTAssertEqual(foundObject!.boolCol, true) - XCTAssertEqual(foundObject!.intCol, 1) - XCTAssertEqual(foundObject!.doubleCol, 1.1) - XCTAssertEqual(foundObject!.stringCol, "string") - XCTAssertEqual(foundObject!.binaryCol, Data("string".utf8)) - XCTAssertEqual(foundObject!.dateCol, Date(timeIntervalSince1970: -1)) - XCTAssertEqual(foundObject!.longCol, 1) - XCTAssertEqual(foundObject!.decimalCol, Decimal128(1)) - XCTAssertEqual(foundObject!.uuidCol, UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")!) - XCTAssertNil(foundObject?.objectIdCol) - XCTAssertEqual(foundObject!.objectCol!.id, linkedObjectId) - } - - @MainActor - func testCustomColumnFlexibleSyncSubscriptionFilter() async throws { - let objectId = ObjectId.generate() - let linkedObjectId = ObjectId.generate() - try await write { realm in - let object = SwiftCustomColumnObject() - object.id = objectId - object.binaryCol = Data("string".utf8) - let linkedObject = SwiftCustomColumnObject() - linkedObject.id = linkedObjectId - object.objectCol = linkedObject - realm.add(object) - } - let user = try await createUser() - - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subscriptions in - subscriptions.append(QuerySubscription(where: "id == %@ || id == %@", objectId, linkedObjectId)) - }) - config.objectTypes = [SwiftCustomColumnObject.self] - let realm = try await Realm(configuration: config, downloadBeforeOpen: .once) - XCTAssertEqual(realm.subscriptions.count, 1) - checkCount(expected: 2, realm, SwiftCustomColumnObject.self) - - let foundObject = realm.objects(SwiftCustomColumnObject.self).where { $0.id == objectId }.first - XCTAssertNotNil(foundObject) - XCTAssertEqual(foundObject!.id, objectId) - XCTAssertEqual(foundObject!.boolCol, true) - XCTAssertEqual(foundObject!.intCol, 1) - XCTAssertEqual(foundObject!.doubleCol, 1.1) - XCTAssertEqual(foundObject!.stringCol, "string") - XCTAssertEqual(foundObject!.binaryCol, Data("string".utf8)) - XCTAssertEqual(foundObject!.dateCol, Date(timeIntervalSince1970: -1)) - XCTAssertEqual(foundObject!.longCol, 1) - XCTAssertEqual(foundObject!.decimalCol, Decimal128(1)) - XCTAssertEqual(foundObject!.uuidCol, UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")!) - XCTAssertNil(foundObject?.objectIdCol) - XCTAssertEqual(foundObject!.objectCol!.id, linkedObjectId) - } - - @MainActor - func testCustomColumnFlexibleSyncSubscriptionQuery() async throws { - let objectId = ObjectId.generate() - let linkedObjectId = ObjectId.generate() - try await write { realm in - let object = SwiftCustomColumnObject() - object.id = objectId - object.binaryCol = Data("string".utf8) - let linkedObject = SwiftCustomColumnObject() - linkedObject.id = linkedObjectId - object.objectCol = linkedObject - realm.add(object) - } - let user = try await createUser() - - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subscriptions in - subscriptions.append(QuerySubscription { - $0.id == objectId || $0.id == linkedObjectId - }) - }) - config.objectTypes = [SwiftCustomColumnObject.self] - let realm = try await Realm(configuration: config, downloadBeforeOpen: .once) - XCTAssertEqual(realm.subscriptions.count, 1) - checkCount(expected: 2, realm, SwiftCustomColumnObject.self) - - let foundObject = realm.objects(SwiftCustomColumnObject.self).where { $0.id == objectId }.first - - XCTAssertNotNil(foundObject) - XCTAssertEqual(foundObject!.id, objectId) - XCTAssertEqual(foundObject!.boolCol, true) - XCTAssertEqual(foundObject!.intCol, 1) - XCTAssertEqual(foundObject!.doubleCol, 1.1) - XCTAssertEqual(foundObject!.stringCol, "string") - XCTAssertEqual(foundObject!.binaryCol, Data("string".utf8)) - XCTAssertEqual(foundObject!.dateCol, Date(timeIntervalSince1970: -1)) - XCTAssertEqual(foundObject!.longCol, 1) - XCTAssertEqual(foundObject!.decimalCol, Decimal128(1)) - XCTAssertEqual(foundObject!.uuidCol, UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")!) - XCTAssertNil(foundObject?.objectIdCol) - XCTAssertEqual(foundObject!.objectCol!.id, linkedObjectId) - } -} - -@available(macOS 13, *) -@globalActor actor CustomGlobalActor: GlobalActor { - static let shared = CustomGlobalActor() -} - -#endif // os(macOS) && swift(>=5.8) diff --git a/Realm/ObjectServerTests/ClientResetTests.swift b/Realm/ObjectServerTests/ClientResetTests.swift deleted file mode 100644 index fb2ff0d815..0000000000 --- a/Realm/ObjectServerTests/ClientResetTests.swift +++ /dev/null @@ -1,736 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#if os(macOS) - -import Realm -import Realm.Private -import RealmSwift -import XCTest - -#if canImport(RealmTestSupport) -import RealmSwiftSyncTestSupport -import RealmSyncTestSupport -import RealmTestSupport -import RealmSwiftTestSupport -#endif - -// Uses admin API to toggle recovery mode on the baas server -func waitForEditRecoveryMode(flexibleSync: Bool = false, appId: String, disable: Bool) throws { - // Retrieve server IDs - let appServerId = try RealmServer.shared.retrieveAppServerId(appId) - let syncServiceId = try RealmServer.shared.retrieveSyncServiceId(appServerId: appServerId) - guard let syncServiceConfig = try RealmServer.shared.getSyncServiceConfiguration(appServerId: appServerId, syncServiceId: syncServiceId) else { fatalError("precondition failure: no sync service configuration found") } - - _ = try RealmServer.shared.patchRecoveryMode( - flexibleSync: flexibleSync, disable: disable, appServerId, - syncServiceId, syncServiceConfig).get() -} - -@available(macOS 13, *) -class ClientResetTests: SwiftSyncTestCase { - @MainActor - func prepareClientReset(app: App? = nil) throws -> User { - let app = app ?? self.app - let config = try configuration(app: app) - let user = config.syncConfiguration!.user - try autoreleasepool { - let realm = try openRealm(configuration: config) - realm.syncSession!.suspend() - - try RealmServer.shared.triggerClientReset(app.appId, realm) - - // Add an object to the local realm that won't be synced due to the suspend - try realm.write { - realm.add(SwiftPerson(firstName: "John", lastName: name)) - } - } - - // Add an object which should be present post-reset - try write(app: app) { realm in - realm.add(SwiftPerson(firstName: "Paul", lastName: self.name)) - } - - return user - } - - @MainActor - func expectSyncError(_ fn: () -> Void) -> SyncError? { - let error = Locked(SyncError?.none) - let ex = expectation(description: "Waiting for error handler to be called...") - app.syncManager.errorHandler = { @Sendable (e, _) in - if let e = e as? SyncError { - error.value = e - } else { - XCTFail("Error \(e) was not a sync error. Something is wrong.") - } - ex.fulfill() - } - - fn() - - waitForExpectations(timeout: 10, handler: nil) - XCTAssertNotNil(error.value) - return error.value - } - - func assertManualClientReset(_ user: User, app: App) -> ErrorReportingBlock { - let ex = self.expectation(description: "get client reset error") - return { error, session in - guard let error = error as? SyncError else { - return XCTFail("Bad error type: \(error)") - } - XCTAssertEqual(error.code, .clientResetError) - XCTAssertEqual(session?.state, .inactive) - XCTAssertEqual(session?.connectionState, .disconnected) - XCTAssertEqual(session?.parentUser()?.id, user.id) - guard let (path, token) = error.clientResetInfo() else { - return XCTAssertNotNil(error.clientResetInfo()) - } - XCTAssertTrue(path.contains("mongodb-realm/\(app.appId)/recovered-realms/recovered_realm")) - XCTAssertFalse(FileManager.default.fileExists(atPath: path)) - SyncSession.immediatelyHandleError(token) - XCTAssertTrue(FileManager.default.fileExists(atPath: path)) - ex.fulfill() - } - } - - func assertDiscardLocal() -> (@Sendable (Realm) -> Void, @Sendable (Realm, Realm) -> Void) { - let beforeCallbackEx = expectation(description: "before reset callback") - @Sendable func beforeClientReset(_ before: Realm) { - let results = before.objects(SwiftPerson.self) - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.filter("firstName == 'John'").count, 1) - - beforeCallbackEx.fulfill() - } - let afterCallbackEx = expectation(description: "before reset callback") - @Sendable func afterClientReset(_ before: Realm, _ after: Realm) { - let results = before.objects(SwiftPerson.self) - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.filter("firstName == 'John'").count, 1) - - let results2 = after.objects(SwiftPerson.self) - XCTAssertEqual(results2.count, 1) - XCTAssertEqual(results2.filter("firstName == 'Paul'").count, 1) - - // Fulfill on the main thread to make it harder to hit a race - // condition where the test completes before the client reset finishes - // unwinding. This does not fully fix the problem. - DispatchQueue.main.async { - afterCallbackEx.fulfill() - } - } - return (beforeClientReset, afterClientReset) - } - - func assertRecover() -> (@Sendable (Realm) -> Void, @Sendable (Realm, Realm) -> Void) { - let beforeCallbackEx = expectation(description: "before reset callback") - @Sendable func beforeClientReset(_ before: Realm) { - let results = before.objects(SwiftPerson.self) - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.filter("firstName == 'John'").count, 1) - beforeCallbackEx.fulfill() - } - let afterCallbackEx = expectation(description: "after reset callback") - @Sendable func afterClientReset(_ before: Realm, _ after: Realm) { - let results = before.objects(SwiftPerson.self) - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.filter("firstName == 'John'").count, 1) - - let results2 = after.objects(SwiftPerson.self) - XCTAssertEqual(results2.count, 2) - XCTAssertEqual(results2.filter("firstName == 'John'").count, 1) - XCTAssertEqual(results2.filter("firstName == 'Paul'").count, 1) - - // Fulfill on the main thread to make it harder to hit a race - // condition where the test completes before the client reset finishes - // unwinding. This does not fully fix the problem. - DispatchQueue.main.async { - afterCallbackEx.fulfill() - } - } - return (beforeClientReset, afterClientReset) - } - - func verifyClientResetDiscardedLocalChanges(_ user: User) throws { - try autoreleasepool { - var configuration = user.configuration(partitionValue: name) - configuration.objectTypes = [SwiftPerson.self] - - let realm = try Realm(configuration: configuration) - waitForDownloads(for: realm) - // After reopening, the old Realm file should have been moved aside - // and we should now have the data from the server - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 1) - XCTAssertEqual(realm.objects(SwiftPerson.self)[0].firstName, "Paul") - } - } -} - -@available(macOS 13, *) -class PBSClientResetTests: ClientResetTests { - @MainActor - func testClientResetManual() throws { - let user = try prepareClientReset() - try autoreleasepool { - var configuration = user.configuration(partitionValue: name, clientResetMode: .manual()) - configuration.objectTypes = [SwiftPerson.self] - - let syncManager = app.syncManager - syncManager.errorHandler = assertManualClientReset(user, app: app) - - try autoreleasepool { - let realm = try Realm(configuration: configuration) - waitForExpectations(timeout: 15.0) - realm.refresh() - // The locally created object should still be present as we didn't - // actually handle the client reset - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 1) - XCTAssertEqual(realm.objects(SwiftPerson.self)[0].firstName, "John") - } - } - app.syncManager.waitForSessionTermination() - try verifyClientResetDiscardedLocalChanges(user) - } - - @MainActor - func testClientResetManualWithEnumCallback() throws { - let user = try prepareClientReset() - try autoreleasepool { - var configuration = user.configuration(partitionValue: name, clientResetMode: .manual(errorHandler: assertManualClientReset(user, app: app))) - configuration.objectTypes = [SwiftPerson.self] - - switch configuration.syncConfiguration!.clientResetMode { - case .manual(let block): - XCTAssertNotNil(block) - default: - XCTFail("Should be set to manual") - } - - try autoreleasepool { - let realm = try Realm(configuration: configuration) - waitForExpectations(timeout: 15.0) - realm.refresh() - // The locally created object should still be present as we didn't - // actually handle the client reset - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 1) - XCTAssertEqual(realm.objects(SwiftPerson.self)[0].firstName, "John") - } - } - try verifyClientResetDiscardedLocalChanges(user) - } - - @MainActor - func testClientResetManualManagerFallback() throws { - let user = try prepareClientReset() - - try autoreleasepool { - // No callback is passed into enum `.manual`, but a syncManager.errorHandler exists, - // so expect that to be used instead. - var configuration = user.configuration(partitionValue: name, clientResetMode: .manual()) - configuration.objectTypes = [SwiftPerson.self] - - let syncManager = self.app.syncManager - syncManager.errorHandler = assertManualClientReset(user, app: app) - - try autoreleasepool { - let realm = try Realm(configuration: configuration) - waitForExpectations(timeout: 15.0) // Wait for expectations in asssertManualClientReset - // The locally created object should still be present as we didn't - // actually handle the client reset - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 1) - XCTAssertEqual(realm.objects(SwiftPerson.self)[0].firstName, "John") - } - } - - try verifyClientResetDiscardedLocalChanges(user) - } - - // If the syncManager.ErrorHandler and manual enum callback - // are both set, use the enum callback. - @MainActor - func testClientResetManualEnumCallbackNotManager() throws { - let user = try prepareClientReset() - - try autoreleasepool { - var configuration = user.configuration(partitionValue: name, clientResetMode: .manual(errorHandler: assertManualClientReset(user, app: app))) - configuration.objectTypes = [SwiftPerson.self] - - switch configuration.syncConfiguration!.clientResetMode { - case .manual(let block): - XCTAssertNotNil(block) - default: - XCTFail("Should be set to manual") - } - - let syncManager = self.app.syncManager - syncManager.errorHandler = { error, _ in - guard nil != error as? SyncError else { - return XCTFail("Bad error type: \(error)") - } - XCTFail("Expected the syncManager.ErrorHandler to not be called") - } - - try autoreleasepool { - let realm = try Realm(configuration: configuration) - waitForExpectations(timeout: 15.0) - // The locally created object should still be present as we didn't - // actually handle the client reset - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 1) - XCTAssertEqual(realm.objects(SwiftPerson.self)[0].firstName, "John") - } - } - - try verifyClientResetDiscardedLocalChanges(user) - } - - @MainActor - func testClientResetManualWithoutLiveRealmInstance() throws { - let user = try prepareClientReset() - - var configuration = user.configuration(partitionValue: name, clientResetMode: .manual()) - configuration.objectTypes = [SwiftPerson.self] - - let syncManager = app.syncManager - syncManager.errorHandler = assertManualClientReset(user, app: app) - - try autoreleasepool { - _ = try Realm(configuration: configuration) - // We have to wait for the error to arrive (or the session will just - // transition to inactive without calling the error handler), but we - // need to ensure the Realm is deallocated before the error handler - // is invoked on the main thread. - sleep(1) - } - waitForExpectations(timeout: 15.0) - syncManager.waitForSessionTermination() - resetSyncManager() - } - - @MainActor - @available(*, deprecated) // .discardLocal - func testClientResetDiscardLocal() throws { - let user = try prepareClientReset() - - let (assertBeforeBlock, assertAfterBlock) = assertDiscardLocal() - var configuration = user.configuration(partitionValue: name, - clientResetMode: .discardLocal(beforeReset: assertBeforeBlock, afterReset: assertAfterBlock)) - configuration.objectTypes = [SwiftPerson.self] - - let syncConfig = try XCTUnwrap(configuration.syncConfiguration) - switch syncConfig.clientResetMode { - case .discardUnsyncedChanges(let before, let after): - XCTAssertNotNil(before) - XCTAssertNotNil(after) - default: - XCTFail("Should be set to discardLocal") - } - - try autoreleasepool { - let realm = try Realm(configuration: configuration) - let results = realm.objects(SwiftPerson.self) - XCTAssertEqual(results.count, 1) - waitForExpectations(timeout: 15.0) - realm.refresh() // expectation is potentially fulfilled before autorefresh - // The Person created locally ("John") should have been discarded, - // while the one from the server ("Paul") should be present - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 1) - XCTAssertEqual(realm.objects(SwiftPerson.self)[0].firstName, "Paul") - } - } - - @MainActor - func testClientResetDiscardUnsyncedChanges() throws { - let user = try prepareClientReset() - - let (assertBeforeBlock, assertAfterBlock) = assertDiscardLocal() - var configuration = user.configuration(partitionValue: name, - clientResetMode: .discardUnsyncedChanges(beforeReset: assertBeforeBlock, afterReset: assertAfterBlock)) - configuration.objectTypes = [SwiftPerson.self] - - guard let syncConfig = configuration.syncConfiguration else { fatalError("Test condition failure. SyncConfiguration not set.") } - switch syncConfig.clientResetMode { - case .discardUnsyncedChanges(let before, let after): - XCTAssertNotNil(before) - XCTAssertNotNil(after) - default: - XCTFail("Should be set to discardUnsyncedChanges") - } - - try autoreleasepool { - let realm = try Realm(configuration: configuration) - waitForExpectations(timeout: 15.0) - realm.refresh() - // The Person created locally ("John") should have been discarded, - // while the one from the server ("Paul") should be present - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 1) - XCTAssertEqual(realm.objects(SwiftPerson.self)[0].firstName, "Paul") - } - } - - @MainActor - @available(*, deprecated) // .discardLocal - func testClientResetDiscardLocalAsyncOpen() throws { - let user = try prepareClientReset() - - let (assertBeforeBlock, assertAfterBlock) = assertDiscardLocal() - var configuration = user.configuration(partitionValue: name, clientResetMode: .discardLocal(beforeReset: assertBeforeBlock, afterReset: assertAfterBlock)) - configuration.objectTypes = [SwiftPerson.self] - - let asyncOpenEx = expectation(description: "async open") - Realm.asyncOpen(configuration: configuration) { result in - let realm = try! result.get() - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 1) - XCTAssertEqual(realm.objects(SwiftPerson.self)[0].firstName, "Paul") - asyncOpenEx.fulfill() - } - waitForExpectations(timeout: 15.0) - } - - @MainActor - func testClientResetRecover() throws { - let user = try prepareClientReset() - - let (assertBeforeBlock, assertAfterBlock) = assertRecover() - var configuration = user.configuration(partitionValue: name, clientResetMode: .recoverUnsyncedChanges(beforeReset: assertBeforeBlock, afterReset: assertAfterBlock)) - configuration.objectTypes = [SwiftPerson.self] - - let syncConfig = try XCTUnwrap(configuration.syncConfiguration) - switch syncConfig.clientResetMode { - case .recoverUnsyncedChanges(let before, let after): - XCTAssertNotNil(before) - XCTAssertNotNil(after) - default: - XCTFail("Should be set to recover") - } - try autoreleasepool { - let realm = try Realm(configuration: configuration) - waitForExpectations(timeout: 15.0) - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 2) - // The object created locally (John) and the object created on the server (Paul) - // should both be integrated into the new realm file. - XCTAssertEqual(realm.objects(SwiftPerson.self)[0].firstName, "John") - XCTAssertEqual(realm.objects(SwiftPerson.self)[1].firstName, "Paul") - } - } - - @MainActor - func testClientResetRecoverAsyncOpen() throws { - let user = try prepareClientReset() - - let (assertBeforeBlock, assertAfterBlock) = assertRecover() - var configuration = user.configuration(partitionValue: name, clientResetMode: .recoverUnsyncedChanges(beforeReset: assertBeforeBlock, afterReset: assertAfterBlock)) - configuration.objectTypes = [SwiftPerson.self] - - let syncConfig = try XCTUnwrap(configuration.syncConfiguration) - switch syncConfig.clientResetMode { - case .recoverUnsyncedChanges(let before, let after): - XCTAssertNotNil(before) - XCTAssertNotNil(after) - default: - XCTFail("Should be set to recover") - } - autoreleasepool { - let realm = Realm.asyncOpen(configuration: configuration).await(self) - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 2) - // The object created locally (John) and the object created on the server (Paul) - // should both be integrated into the new realm file. - XCTAssertEqual(realm.objects(SwiftPerson.self)[0].firstName, "John") - XCTAssertEqual(realm.objects(SwiftPerson.self)[1].firstName, "Paul") - waitForExpectations(timeout: 15.0) - } - } - - @MainActor - func testClientResetRecoverWithSchemaChanges() throws { - let user = try prepareClientReset() - - let beforeCallbackEx = expectation(description: "before reset callback") - @Sendable func beforeClientReset(_ before: Realm) { - let person = before.objects(SwiftPersonWithAdditionalProperty.self).first! - XCTAssertEqual(person.objectSchema.properties.map(\.name), - ["_id", "firstName", "lastName", "age", "newProperty"]) - XCTAssertEqual(person.newProperty, 0) - beforeCallbackEx.fulfill() - } - let afterCallbackEx = expectation(description: "after reset callback") - @Sendable func afterClientReset(_ before: Realm, _ after: Realm) { - let beforePerson = before.objects(SwiftPersonWithAdditionalProperty.self).first! - XCTAssertEqual(beforePerson.objectSchema.properties.map(\.name), - ["_id", "firstName", "lastName", "age", "newProperty"]) - XCTAssertEqual(beforePerson.newProperty, 0) - let afterPerson = after.objects(SwiftPersonWithAdditionalProperty.self).first! - XCTAssertEqual(afterPerson.objectSchema.properties.map(\.name), - ["_id", "firstName", "lastName", "age", "newProperty"]) - XCTAssertEqual(afterPerson.newProperty, 0) - - // Fulfill on the main thread to make it harder to hit a race - // condition where the test completes before the client reset finishes - // unwinding. This does not fully fix the problem. - DispatchQueue.main.async { - afterCallbackEx.fulfill() - } - } - - var configuration = user.configuration(partitionValue: name, clientResetMode: .recoverUnsyncedChanges(beforeReset: beforeClientReset, afterReset: afterClientReset)) - configuration.objectTypes = [SwiftPersonWithAdditionalProperty.self] - - autoreleasepool { - _ = Realm.asyncOpen(configuration: configuration).await(self) - waitForExpectations(timeout: 15.0) - } - } - - @MainActor - func testClientResetRecoverOrDiscardLocalFailedRecovery() throws { - let appId = try RealmServer.shared.createApp(types: [SwiftPerson.self]) - // Disable recovery mode on the server. - // This attempts to simulate a case where recovery mode fails when - // using RecoverOrDiscardLocal - try waitForEditRecoveryMode(appId: appId, disable: true) - - let user = try prepareClientReset(app: self.app(id: appId)) - // Expect the recovery to fail back to discardLocal logic - let (assertBeforeBlock, assertAfterBlock) = assertDiscardLocal() - var configuration = user.configuration(partitionValue: name, clientResetMode: .recoverOrDiscardUnsyncedChanges(beforeReset: assertBeforeBlock, afterReset: assertAfterBlock)) - configuration.objectTypes = [SwiftPerson.self] - - let syncConfig = try XCTUnwrap(configuration.syncConfiguration) - switch syncConfig.clientResetMode { - case .recoverOrDiscardUnsyncedChanges(let before, let after): - XCTAssertNotNil(before) - XCTAssertNotNil(after) - default: - XCTFail("Should be set to recoverOrDiscard") - } - - // Expect the recovery to fail back to discardLocal logic - try autoreleasepool { - let realm = try Realm(configuration: configuration) - waitForExpectations(timeout: 15.0) - realm.refresh() - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 1) - // The Person created locally ("John") should have been discarded, - // while the one from the server ("Paul") should be present. - XCTAssertEqual(realm.objects(SwiftPerson.self)[0].firstName, "Paul") - } - } -} - -@available(macOS 13, *) -class FLXClientResetTests: ClientResetTests { - override func createApp() throws -> String { - try createFlexibleSyncApp() - } - - override func configuration(user: User) -> Realm.Configuration { - let name = self.name - return user.flexibleSyncConfiguration { subscriptions in - subscriptions.append(QuerySubscription { $0.lastName == name }) - } - } - - @MainActor - @available(*, deprecated) // .discardLocal - func testFlexibleSyncDiscardLocalClientReset() throws { - let user = try prepareClientReset() - - let (assertBeforeBlock, assertAfterBlock) = assertDiscardLocal() - var config = user.flexibleSyncConfiguration(clientResetMode: .discardLocal(beforeReset: assertBeforeBlock, afterReset: assertAfterBlock)) - config.objectTypes = [SwiftPerson.self] - let syncConfig = try XCTUnwrap(config.syncConfiguration) - switch syncConfig.clientResetMode { - case .discardUnsyncedChanges(let before, let after): - XCTAssertNotNil(before) - XCTAssertNotNil(after) - default: - XCTFail("Should be set to discardUnsyncedChanges") - } - - try autoreleasepool { - XCTAssertEqual(user.flexibleSyncConfiguration().fileURL, config.fileURL) - let realm = try Realm(configuration: config) - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 1) // subscription created during prepareFlexibleSyncClientReset - - waitForExpectations(timeout: 15.0) - realm.refresh() - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 1) - XCTAssertEqual(realm.objects(SwiftPerson.self).first?.firstName, "Paul") - } - } - - @MainActor - func testFlexibleSyncDiscardUnsyncedChangesClientReset() throws { - let user = try prepareClientReset() - - let (assertBeforeBlock, assertAfterBlock) = assertDiscardLocal() - var config = user.flexibleSyncConfiguration(clientResetMode: .discardUnsyncedChanges(beforeReset: assertBeforeBlock, afterReset: assertAfterBlock)) - config.objectTypes = [SwiftPerson.self] - let syncConfig = try XCTUnwrap(config.syncConfiguration) - switch syncConfig.clientResetMode { - case .discardUnsyncedChanges(let before, let after): - XCTAssertNotNil(before) - XCTAssertNotNil(after) - default: - XCTFail("Should be set to discardUnsyncedChanges") - } - - try autoreleasepool { - XCTAssertEqual(user.flexibleSyncConfiguration().fileURL, config.fileURL) - let realm = try Realm(configuration: config) - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 1) // subscription created during prepareFlexibleSyncClientReset - - waitForExpectations(timeout: 15.0) - realm.refresh() - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 1) - XCTAssertEqual(realm.objects(SwiftPerson.self).first?.firstName, "Paul") - } - } - - @MainActor - func testFlexibleSyncClientResetRecover() throws { - let user = try prepareClientReset() - - let (assertBeforeBlock, assertAfterBlock) = assertRecover() - var config = user.flexibleSyncConfiguration(clientResetMode: .recoverUnsyncedChanges(beforeReset: assertBeforeBlock, afterReset: assertAfterBlock)) - config.objectTypes = [SwiftPerson.self] - let syncConfig = try XCTUnwrap(config.syncConfiguration) - switch syncConfig.clientResetMode { - case .recoverUnsyncedChanges(let before, let after): - XCTAssertNotNil(before) - XCTAssertNotNil(after) - default: - XCTFail("Should be set to recover") - } - - try autoreleasepool { - XCTAssertEqual(user.flexibleSyncConfiguration().fileURL, config.fileURL) - let realm = try Realm(configuration: config) - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 1) // subscription created during prepareFlexibleSyncClientReset - - waitForExpectations(timeout: 15.0) // wait for expectations in assertRecover - realm.refresh() - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 2) - // The object created locally (John) and the object created on the server (Paul) - // should both be integrated into the new realm file. - XCTAssertEqual(realm.objects(SwiftPerson.self).filter("firstName == 'John'").count, 1) - XCTAssertEqual(realm.objects(SwiftPerson.self).filter("firstName == 'Paul'").count, 1) - } - } - - @MainActor - func testFlexibleSyncClientResetRecoverOrDiscardLocalFailedRecovery() throws { - let appId = try RealmServer.shared.createApp(fields: ["lastName"], types: [SwiftPerson.self]) - try waitForEditRecoveryMode(flexibleSync: true, appId: appId, disable: true) - let user = try prepareClientReset(app: app(id: appId)) - - // Expect the client reset process to discard the local changes - let (assertBeforeBlock, assertAfterBlock) = assertDiscardLocal() - var config = user.flexibleSyncConfiguration(clientResetMode: .recoverOrDiscardUnsyncedChanges(beforeReset: assertBeforeBlock, afterReset: assertAfterBlock)) - config.objectTypes = [SwiftPerson.self] - guard let syncConfig = config.syncConfiguration else { - fatalError("Test condition failure. SyncConfiguration not set.") - } - switch syncConfig.clientResetMode { - case .recoverOrDiscardUnsyncedChanges(let before, let after): - XCTAssertNotNil(before) - XCTAssertNotNil(after) - default: - XCTFail("Should be set to recoverOrDiscard") - } - - try autoreleasepool { - XCTAssertEqual(user.flexibleSyncConfiguration().fileURL, config.fileURL) - let realm = try Realm(configuration: config) - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 1) // subscription created during prepareFlexibleSyncClientReset - - waitForExpectations(timeout: 15.0) - realm.refresh() - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 1) - // The Person created locally ("John") should have been discarded, - // while the one from the server ("Paul") should be present. - XCTAssertEqual(realm.objects(SwiftPerson.self)[0].firstName, "Paul") - } - } - - @MainActor - func testFlexibleClientResetManual() throws { - let user = try prepareClientReset() - try autoreleasepool { - var config = user.flexibleSyncConfiguration(clientResetMode: .manual(errorHandler: assertManualClientReset(user, app: app))) - config.objectTypes = [SwiftPerson.self] - - switch config.syncConfiguration!.clientResetMode { - case .manual(let block): - XCTAssertNotNil(block) - default: - XCTFail("Should be set to manual") - } - try autoreleasepool { - let realm = try Realm(configuration: config) - waitForExpectations(timeout: 15.0) - // The locally created object should still be present as we didn't - // actually handle the client reset - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 1) - XCTAssertEqual(realm.objects(SwiftPerson.self)[0].firstName, "John") - } - } - - let name = self.name - var config = user.flexibleSyncConfiguration { subscriptions in - subscriptions.append(QuerySubscription { $0.lastName == name }) - } - config.objectTypes = [SwiftPerson.self] - - try autoreleasepool { - let realm = try openRealm(configuration: config) - XCTAssertEqual(realm.subscriptions.count, 1) - - // After reopening, the old Realm file should have been moved aside - // and we should now have the data from the server - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 1) - XCTAssertEqual(realm.objects(SwiftPerson.self)[0].firstName, "Paul") - } - } - - func testDefaultClientResetMode() throws { - let user = createUser() - let fConfig = user.flexibleSyncConfiguration() - let pConfig = user.configuration(partitionValue: name) - - switch fConfig.syncConfiguration!.clientResetMode { - case .recoverUnsyncedChanges: - return - default: - XCTFail("expected recover mode") - } - switch pConfig.syncConfiguration!.clientResetMode { - case .recoverUnsyncedChanges: - return - default: - XCTFail("expected recover mode") - } - } -} - -#endif // os(macOS) diff --git a/Realm/ObjectServerTests/CombineSyncTests.swift b/Realm/ObjectServerTests/CombineSyncTests.swift deleted file mode 100644 index 5c611abae2..0000000000 --- a/Realm/ObjectServerTests/CombineSyncTests.swift +++ /dev/null @@ -1,731 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#if os(macOS) - -import Combine -import Realm -import Realm.Private -import RealmSwift -import XCTest - -#if canImport(RealmTestSupport) -import RealmSwiftSyncTestSupport -import RealmSyncTestSupport -import RealmTestSupport -import RealmSwiftTestSupport -#endif - -extension AnyCancellable { - func store(in lockedSet: Locked>) { - lockedSet.withLock { - self.store(in: &$0) - } - } -} - -@available(macOS 13, *) -@objc(CombineSyncTests) -class CombineSyncTests: SwiftSyncTestCase { - override var objectTypes: [ObjectBase.Type] { - [Dog.self, SwiftPerson.self, SwiftHugeSyncObject.self] - } - - nonisolated let subscriptions = Locked(Set()) - override func tearDown() { - subscriptions.withLock { - $0.forEach { $0.cancel() } - $0 = [] - } - super.tearDown() - } - - // swiftlint:disable multiple_closures_with_trailing_closure - func testWatchCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - - let watchEx1 = Locked(expectation(description: "Main thread watch")) - let watchEx2 = Locked(expectation(description: "Background thread watch")) - - collection.watch() - .onOpen { - watchEx1.wrappedValue.fulfill() - } - .subscribe(on: DispatchQueue.global()) - .receive(on: DispatchQueue.global()) - .sink(receiveCompletion: { @Sendable _ in }) { @Sendable _ in - XCTAssertFalse(Thread.isMainThread) - watchEx1.wrappedValue.fulfill() - }.store(in: subscriptions) - - collection.watch() - .onOpen { - watchEx2.wrappedValue.fulfill() - } - .subscribe(on: DispatchQueue.main) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in }) { _ in - XCTAssertTrue(Thread.isMainThread) - watchEx2.wrappedValue.fulfill() - }.store(in: subscriptions) - - for _ in 0..<3 { - wait(for: [watchEx1.wrappedValue, watchEx2.wrappedValue], timeout: 60.0) - watchEx1.wrappedValue = expectation(description: "Main thread watch") - watchEx2.wrappedValue = expectation(description: "Background thread watch") - collection.insertOne(document) { result in - if case .failure(let error) = result { - XCTFail("Failed to insert: \(error)") - } - } - } - wait(for: [watchEx1.wrappedValue, watchEx2.wrappedValue], timeout: 60.0) - } - - func testWatchCombineWithFilterIds() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - let document4: Document = ["name": "ted", "breed": "bullmastiff"] - - let objIds = collection.insertMany([document, document2, document3, document4]).await(self) - let objectIds = objIds.map { $0.objectIdValue! } - - let watchEx1 = Locked(expectation(description: "Main thread watch")) - let watchEx2 = Locked(expectation(description: "Background thread watch")) - collection.watch(filterIds: [objectIds[0]]) - .onOpen { - watchEx1.wrappedValue.fulfill() - } - .subscribe(on: DispatchQueue.main) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in }) { changeEvent in - XCTAssertTrue(Thread.isMainThread) - guard let doc = changeEvent.documentValue else { - return - } - - let objectId = doc["fullDocument"]??.documentValue!["_id"]??.objectIdValue! - if objectId == objectIds[0] { - watchEx1.wrappedValue.fulfill() - } - }.store(in: subscriptions) - - collection.watch(filterIds: [objectIds[1]]) - .onOpen { - watchEx2.wrappedValue.fulfill() - } - .subscribe(on: DispatchQueue.global()) - .receive(on: DispatchQueue.global()) - .sink(receiveCompletion: { _ in }) { @Sendable changeEvent in - XCTAssertFalse(Thread.isMainThread) - guard let doc = changeEvent.documentValue else { - return - } - - let objectId = doc["fullDocument"]??.documentValue!["_id"]??.objectIdValue! - if objectId == objectIds[1] { - watchEx2.wrappedValue.fulfill() - } - }.store(in: subscriptions) - - for i in 0..<3 { - wait(for: [watchEx1.wrappedValue, watchEx2.wrappedValue], timeout: 60.0) - watchEx1.wrappedValue = expectation(description: "Main thread watch") - watchEx2.wrappedValue = expectation(description: "Background thread watch") - - let name: AnyBSON = .string("fido-\(i)") - collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[0])], - update: ["name": name, "breed": "king charles"]) { result in - if case .failure(let error) = result { - XCTFail("Failed to update: \(error)") - } - } - collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[1])], - update: ["name": name, "breed": "king charles"]) { result in - if case .failure(let error) = result { - XCTFail("Failed to update: \(error)") - } - } - } - wait(for: [watchEx1.wrappedValue, watchEx2.wrappedValue], timeout: 60.0) - } - - func testWatchCombineWithMatchFilter() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - let document4: Document = ["name": "ted", "breed": "bullmastiff"] - - let objIds = collection.insertMany([document, document2, document3, document4]).await(self) - XCTAssertEqual(objIds.count, 4) - let objectIds = objIds.map { $0.objectIdValue! } - - let watchEx1 = Locked(expectation(description: "Main thread watch")) - let watchEx2 = Locked(expectation(description: "Background thread watch")) - collection.watch(matchFilter: ["fullDocument._id": AnyBSON.objectId(objectIds[0])]) - .onOpen { - watchEx1.wrappedValue.fulfill() - } - .subscribe(on: DispatchQueue.main) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in }) { changeEvent in - XCTAssertTrue(Thread.isMainThread) - guard let doc = changeEvent.documentValue else { - return - } - - let objectId = doc["fullDocument"]??.documentValue!["_id"]??.objectIdValue! - if objectId == objectIds[0] { - watchEx1.wrappedValue.fulfill() - } - }.store(in: subscriptions) - - collection.watch(matchFilter: ["fullDocument._id": AnyBSON.objectId(objectIds[1])]) - .onOpen { - watchEx2.wrappedValue.fulfill() - } - .subscribe(on: DispatchQueue.global()) - .receive(on: DispatchQueue.global()) - .sink(receiveCompletion: { _ in }) { @Sendable changeEvent in - XCTAssertFalse(Thread.isMainThread) - guard let doc = changeEvent.documentValue else { - return - } - - let objectId = doc["fullDocument"]??.documentValue!["_id"]??.objectIdValue! - if objectId == objectIds[1] { - watchEx2.wrappedValue.fulfill() - } - }.store(in: subscriptions) - - for i in 0..<3 { - wait(for: [watchEx1.wrappedValue, watchEx2.wrappedValue], timeout: 60.0) - watchEx1.wrappedValue = expectation(description: "Main thread watch") - watchEx2.wrappedValue = expectation(description: "Background thread watch") - - let name: AnyBSON = .string("fido-\(i)") - collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[0])], - update: ["name": name, "breed": "king charles"]) { result in - if case .failure(let error) = result { - XCTFail("Failed to update: \(error)") - } - } - collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[1])], - update: ["name": name, "breed": "king charles"]) { result in - if case .failure(let error) = result { - XCTFail("Failed to update: \(error)") - } - } - } - wait(for: [watchEx1.wrappedValue, watchEx2.wrappedValue], timeout: 60.0) - } - - // MARK: - Combine promises - - func testAppLoginCombine() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - let loginEx = expectation(description: "Login user") - let appEx = expectation(description: "App changes triggered") - var triggered = 0 - app.objectWillChange.sink { _ in - triggered += 1 - if triggered == 2 { - appEx.fulfill() - } - }.store(in: subscriptions) - - let app = self.app - app.emailPasswordAuth.registerUser(email: email, password: password) - .flatMap { @Sendable in app.login(credentials: .emailPassword(email: email, password: password)) } - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { result in - if case let .failure(error) = result { - XCTFail("Should have completed login chain: \(error.localizedDescription)") - } - }, receiveValue: { user in - user.objectWillChange.sink { @Sendable user in - XCTAssert(!user.isLoggedIn) - loginEx.fulfill() - }.store(in: self.subscriptions) - XCTAssertEqual(user.id, self.app.currentUser?.id) - user.logOut { _ in } // logout user and make sure it is observed - }) - .store(in: subscriptions) - wait(for: [loginEx, appEx], timeout: 30.0) - XCTAssertEqual(self.app.allUsers.count, 1) - XCTAssertEqual(triggered, 2) - } - - func testAsyncOpenCombine() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - let app = self.app - let name = self.name - app.emailPasswordAuth.registerUser(email: email, password: password) - .flatMap { @Sendable in app.login(credentials: .emailPassword(email: email, password: password)) } - .flatMap { @Sendable (user: User) in - var config = user.configuration(partitionValue: name) - config.objectTypes = [SwiftHugeSyncObject.self] - return Realm.asyncOpen(configuration: config) - } - .tryMap { realm in - try realm.write { - realm.add(SwiftHugeSyncObject.create()) - realm.add(SwiftHugeSyncObject.create()) - } - let progressEx = self.expectation(description: "Should upload") - let token = try XCTUnwrap(realm.syncSession).addProgressNotification(for: .upload, mode: .forCurrentlyOutstandingWork) { - if $0.isTransferComplete { - progressEx.fulfill() - } - } - self.wait(for: [progressEx], timeout: 30.0) - token?.invalidate() - } - .await(self, timeout: 30.0) - - let chainEx = expectation(description: "Should chain realm login => realm async open") - let progressEx = expectation(description: "Should receive progress notification") - app.login(credentials: .anonymous) - .flatMap { @Sendable user in - var config = user.configuration(partitionValue: name) - config.objectTypes = [SwiftHugeSyncObject.self] - return Realm.asyncOpen(configuration: config).onProgressNotification { - if $0.isTransferComplete { - progressEx.fulfill() - } - } - } - .expectValue(self, chainEx) { realm in - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, 2) - }.store(in: subscriptions) - wait(for: [chainEx, progressEx], timeout: 30.0) - } - - func testAsyncOpenStandaloneCombine() throws { - try autoreleasepool { - let realm = try Realm() - try realm.write { - (0..<10000).forEach { _ in realm.add(SwiftPerson(firstName: "Charlie", lastName: "Bucket")) } - } - } - - Realm.asyncOpen().await(self) { realm in - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 10000) - } - } - - func testDeleteUserCombine() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - let appEx = expectation(description: "App changes triggered") - var triggered = 0 - app.objectWillChange.sink { _ in - triggered += 1 - if triggered == 2 { - appEx.fulfill() - } - }.store(in: subscriptions) - - let app = self.app - app.emailPasswordAuth.registerUser(email: email, password: password) - .flatMap { @Sendable in app.login(credentials: .emailPassword(email: email, password: password)) } - .flatMap { @Sendable in $0.delete() } - .await(self) - wait(for: [appEx], timeout: 30.0) - XCTAssertEqual(self.app.allUsers.count, 0) - XCTAssertEqual(triggered, 2) - } - - func testMongoCollectionInsertCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "tibetan mastiff"] - - collection.insertOne(document).await(self) - collection.insertMany([document, document2]) - .await(self) { objectIds in - XCTAssertEqual(objectIds.count, 2) - } - collection.find(filter: [:]) - .await(self) { findResult in - XCTAssertEqual(findResult.map({ $0["name"]??.stringValue }), ["fido", "fido", "rex"]) - } - } - - func testMongoCollectionFindCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "tibetan mastiff"] - let document3: Document = ["name": "rex", "breed": "tibetan mastiff", "coat": ["fawn", "brown", "white"]] - let findOptions = FindOptions(1, nil) - - collection.find(filter: [:], options: findOptions) - .await(self) { findResult in - XCTAssertEqual(findResult.count, 0) - } - collection.insertMany([document, document2, document3]).await(self) - collection.find(filter: [:]) - .await(self) { findResult in - XCTAssertEqual(findResult.map({ $0["name"]??.stringValue }), ["fido", "rex", "rex"]) - } - collection.find(filter: [:], options: findOptions) - .await(self) { findResult in - XCTAssertEqual(findResult.count, 1) - XCTAssertEqual(findResult[0]["name"]??.stringValue, "fido") - } - collection.find(filter: document3, options: findOptions) - .await(self) { findResult in - XCTAssertEqual(findResult.count, 1) - } - collection.findOneDocument(filter: document).await(self) - - collection.findOneDocument(filter: document, options: findOptions).await(self) - } - - func testMongoCollectionCountAndAggregateCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - - collection.insertMany([document]).await(self) - collection.aggregate(pipeline: [["$match": ["name": "fido"]], ["$group": ["_id": "$name"]]]) - .await(self) - collection.count(filter: document).await(self) { count in - XCTAssertEqual(count, 1) - } - collection.count(filter: document, limit: 1).await(self) { count in - XCTAssertEqual(count, 1) - } - } - - func testMongoCollectionDeleteOneCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - - collection.deleteOneDocument(filter: document).await(self) { count in - XCTAssertEqual(count, 0) - } - collection.insertMany([document, document2]).await(self) - collection.deleteOneDocument(filter: document).await(self) { count in - XCTAssertEqual(count, 1) - } - } - - func testMongoCollectionDeleteManyCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - - collection.deleteManyDocuments(filter: document).await(self) { count in - XCTAssertEqual(count, 0) - } - collection.insertMany([document, document2]).await(self) - collection.deleteManyDocuments(filter: ["breed": "cane corso"]).await(self) { count in - XCTAssertEqual(count, 2) - } - } - - func testMongoCollectionUpdateOneCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - let document4: Document = ["name": "ted", "breed": "bullmastiff"] - let document5: Document = ["name": "bill", "breed": "great dane"] - - collection.insertMany([document, document2, document3, document4]).await(self) - collection.updateOneDocument(filter: document, update: document2).await(self) { updateResult in - XCTAssertEqual(updateResult.matchedCount, 1) - XCTAssertEqual(updateResult.modifiedCount, 1) - XCTAssertNil(updateResult.documentId) - } - - collection.updateOneDocument(filter: document5, update: document2, upsert: true).await(self) { updateResult in - XCTAssertEqual(updateResult.matchedCount, 0) - XCTAssertEqual(updateResult.modifiedCount, 0) - XCTAssertNotNil(updateResult.documentId) - } - } - - func testMongoCollectionUpdateManyCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - let document4: Document = ["name": "ted", "breed": "bullmastiff"] - let document5: Document = ["name": "bill", "breed": "great dane"] - - collection.insertMany([document, document2, document3, document4]).await(self) - collection.updateManyDocuments(filter: document, update: document2).await(self) { updateResult in - XCTAssertEqual(updateResult.matchedCount, 1) - XCTAssertEqual(updateResult.modifiedCount, 1) - XCTAssertNil(updateResult.documentId) - } - collection.updateManyDocuments(filter: document5, update: document2, upsert: true).await(self) { updateResult in - XCTAssertEqual(updateResult.matchedCount, 0) - XCTAssertEqual(updateResult.modifiedCount, 0) - XCTAssertNotNil(updateResult.documentId) - } - } - - func testMongoCollectionFindAndUpdateCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - - collection.findOneAndUpdate(filter: document, update: document2).await(self) - - let options1 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, true) - collection.findOneAndUpdate(filter: document2, update: document3, options: options1).await(self) { updateResult in - guard let updateResult = updateResult else { - XCTFail("Should find") - return - } - XCTAssertEqual(updateResult["name"]??.stringValue, "john") - } - - let options2 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, true) - collection.findOneAndUpdate(filter: document, update: document2, options: options2).await(self) { updateResult in - guard let updateResult = updateResult else { - XCTFail("Should find") - return - } - XCTAssertEqual(updateResult["name"]??.stringValue, "rex") - } - } - - func testMongoCollectionFindAndReplaceCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - - collection.findOneAndReplace(filter: document, replacement: document2).await(self) { updateResult in - XCTAssertNil(updateResult) - } - - let options1 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, true) - collection.findOneAndReplace(filter: document2, replacement: document3, options: options1).await(self) { updateResult in - guard let updateResult = updateResult else { - XCTFail("Should find") - return - } - XCTAssertEqual(updateResult["name"]??.stringValue, "john") - } - - let options2 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, false) - collection.findOneAndReplace(filter: document, replacement: document2, options: options2).await(self) { updateResult in - XCTAssertNil(updateResult) - } - } - - func testMongoCollectionFindAndDeleteCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - collection.insertMany([document]).await(self) - - collection.findOneAndDelete(filter: document).await(self) { updateResult in - XCTAssertNotNil(updateResult) - } - collection.findOneAndDelete(filter: document).await(self) { updateResult in - XCTAssertNil(updateResult) - } - - collection.insertMany([document]).await(self) - let options1 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], false, false) - collection.findOneAndDelete(filter: document, options: options1).await(self) { deleteResult in - XCTAssertNotNil(deleteResult) - } - collection.findOneAndDelete(filter: document, options: options1).await(self) { deleteResult in - XCTAssertNil(deleteResult) - } - - collection.insertMany([document]).await(self) - let options2 = FindOneAndModifyOptions(["name": 1], [["_id": 1]]) - collection.findOneAndDelete(filter: document, options: options2).await(self) { deleteResult in - XCTAssertNotNil(deleteResult) - } - collection.findOneAndDelete(filter: document, options: options2).await(self) { deleteResult in - XCTAssertNil(deleteResult) - } - - collection.insertMany([document]).await(self) - collection.find(filter: [:]).await(self) { updateResult in - XCTAssertEqual(updateResult.count, 1) - } - } - - func testCallFunctionCombine() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - - let credentials = Credentials.emailPassword(email: email, password: password) - app.login(credentials: credentials).await(self) { user in - XCTAssertNotNil(user) - } - - app.currentUser?.functions.sum(1, 2, 3, 4, 5).await(self) { bson in - guard case let .int32(sum) = bson else { - XCTFail("Should be int32") - return - } - XCTAssertEqual(sum, 15) - } - - app.currentUser?.functions.updateUserData(["favourite_colour": "green", "apples": 10]).await(self) { bson in - guard case let .bool(upd) = bson else { - XCTFail("Should be bool") - return - } - XCTAssertTrue(upd) - } - } - - func testAPIKeyAuthCombine() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - - let user = app.login(credentials: Credentials.emailPassword(email: email, password: password)).await(self) - - let apiKey = user.apiKeysAuth.createAPIKey(named: "my-api-key").await(self) - user.apiKeysAuth.fetchAPIKey(apiKey.objectId).await(self) - user.apiKeysAuth.fetchAPIKeys().await(self) { userApiKeys in - XCTAssertEqual(userApiKeys.count, 1) - } - - user.apiKeysAuth.disableAPIKey(apiKey.objectId).await(self) - user.apiKeysAuth.enableAPIKey(apiKey.objectId).await(self) - user.apiKeysAuth.deleteAPIKey(apiKey.objectId).await(self) - } - - func testPushRegistrationCombine() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - app.login(credentials: Credentials.emailPassword(email: email, password: password)).await(self) - - let client = app.pushClient(serviceName: "gcm") - client.registerDevice(token: "some-token", user: app.currentUser!).await(self) - client.deregisterDevice(user: app.currentUser!).await(self) - } -} - -@available(macOS 13, *) -class CombineFlexibleSyncTests: SwiftSyncTestCase { - override var objectTypes: [ObjectBase.Type] { - [SwiftPerson.self, SwiftTypesSyncObject.self] - } - - override func configuration(user: User) -> Realm.Configuration { - user.flexibleSyncConfiguration() - } - - override func createApp() throws -> String { - try createFlexibleSyncApp() - } - - nonisolated let cancellables = Locked(Set()) - override func tearDown() { - cancellables.withLock { - $0.forEach { $0.cancel() } - $0 = [] - } - super.tearDown() - } - - @MainActor - func testFlexibleSyncCombineWrite() throws { - try write { realm in - for i in 1...25 { - let person = SwiftPerson(firstName: "\(self.name)", - lastName: "lastname_\(i)", - age: i) - realm.add(person) - } - } - - let realm = try openRealm() - checkCount(expected: 0, realm, SwiftPerson.self) - - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 0) - - let ex = expectation(description: "state change complete") - subscriptions.updateSubscriptions { - subscriptions.append(QuerySubscription(name: "person_age_10") { - $0.age > 10 && $0.firstName == "\(self.name)" - }) - }.sink(receiveCompletion: { @Sendable _ in }, - receiveValue: { @Sendable _ in ex.fulfill() } - ).store(in: cancellables) - - waitForExpectations(timeout: 20.0, handler: nil) - - waitForDownloads(for: realm) - checkCount(expected: 15, realm, SwiftPerson.self) - } - - @MainActor - func testFlexibleSyncCombineWriteFails() throws { - let realm = try openRealm() - checkCount(expected: 0, realm, SwiftPerson.self) - - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 0) - - let ex = expectation(description: "state change error") - subscriptions.updateSubscriptions { - subscriptions.append(QuerySubscription(name: "swiftObject_longCol") { - $0.longCol == Int64(1) - }) - } - .sink(receiveCompletion: { @Sendable result in - if case .failure(let error as Realm.Error) = result { - XCTAssertEqual(error.code, .subscriptionFailed) - } else { - XCTFail("Expected an error but got \(result)") - } - ex.fulfill() - }, receiveValue: { _ in }) - .store(in: cancellables) - - waitForExpectations(timeout: 20.0, handler: nil) - guard case .error = subscriptions.state else { - return XCTFail("Adding a query for a not queryable field should change the subscription set state to error") - } - - waitForDownloads(for: realm) - checkCount(expected: 0, realm, SwiftPerson.self) - } -} - -#endif // os(macOS) diff --git a/Realm/ObjectServerTests/EventTests.swift b/Realm/ObjectServerTests/EventTests.swift deleted file mode 100644 index 927559deac..0000000000 --- a/Realm/ObjectServerTests/EventTests.swift +++ /dev/null @@ -1,571 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import Combine -import Foundation -import RealmSwift -import XCTest - -#if canImport(RealmTestSupport) -import RealmSwiftSyncTestSupport -import RealmSwiftTestSupport -import RealmSyncTestSupport -import RealmTestSupport -#endif - -class SwiftCustomEventRepresentation: Object, CustomEventRepresentable { - @Persisted(primaryKey: true) var _id: ObjectId - @Persisted var value: Int - - convenience init(value: Int) { - self.init() - self.value = value - } - - func customEventRepresentation() -> String { - if value == 0 { - return "invalid json" - } - if value == 1 { - _ = NSArray()[1] - return "" - } - return "{\"int\": \(value)}" - } -} - -class AuditEvent: Object { - @Persisted(primaryKey: true) var _id: ObjectId - @Persisted var activity: String - @Persisted var event: String? - @Persisted var data: String? - @Persisted var timestamp: Date - @Persisted var userId: String? - - var parsedData: NSDictionary? -} - -@available(macOS 13, *) -class SwiftEventTests: SwiftSyncTestCase { - var user: User! - var collection: MongoCollection! - var start: Date! - - override func setUp() async throws { - user = try await createUser() - collection = user.collection(for: AuditEvent.self, app: app) - try await _ = collection.deleteManyDocuments(filter: [:]) - - // The server truncates date values to lower precision than we support, - // so we need to set the start date to slightly in the past - start = Date(timeIntervalSinceNow: -1.0) - } - - override func tearDown() { - if let user { - while user.allSessions.count > 0 { - RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1)) - } - self.user = nil - } - super.tearDown() - } - - override func configuration(user: User) -> Realm.Configuration { - var config = user.configuration(partitionValue: name) - config.eventConfiguration = EventConfiguration() - return config - } - - override var objectTypes: [ObjectBase.Type] { - [AuditEvent.self, SwiftPerson.self, SwiftCustomEventRepresentation.self, LinkToSwiftPerson.self] - } - - @MainActor - func scope(_ events: Events, _ name: String, body: () throws -> T) rethrows -> T { - let scope = events.beginScope(activity: name) - XCTAssertTrue(scope.isActive) - let result = try body() - scope.commit().await(self) - XCTAssertFalse(scope.isActive) - return result - } - - @MainActor - func getEvents(expectedCount: Int) -> [AuditEvent] { - waitForCollectionCount(collection, expectedCount) - - let docs = collection.find(filter: [:]).await(self) - XCTAssertEqual(docs.count, expectedCount) - return docs.map { doc in - let event = AuditEvent() - event._id = doc["_id"]!!.objectIdValue! - event.activity = doc["activity"]!!.stringValue! - event.event = doc["event"]??.stringValue - event.data = doc["data"]??.stringValue - event.parsedData = event.data - .flatMap { try? JSONSerialization.jsonObject(with: $0.data(using: .utf8)!) } - .flatMap { $0 as? NSDictionary } - event.userId = doc["userId"]??.stringValue - - XCTAssertGreaterThan(doc["timestamp"]!!.dateValue!, start) - - return event - } - } - - func full(_ person: SwiftPerson) -> NSDictionary { - return [ - "_id": person._id.stringValue, - "firstName": person.firstName, - "lastName": person.lastName, - "age": person.age, - "realm_id": NSNull() - ] - } - - func idOnly(_ person: SwiftPerson) -> Any { - return person._id.stringValue - } - - func assertEvent(_ events: [AuditEvent], activity: String, event: String?, - _ data: NSDictionary?, line: UInt = #line) { - let matching = events.filter { $0.activity == activity && $0.event == event } - XCTAssertEqual(matching.count, 1, line: line) - guard let actual = matching.first else { return } - if let parsed = actual.parsedData { - XCTAssertEqual(parsed, data) - } else { - XCTAssertNil(actual.data) - } - } - - func assertEvent(_ events: [AuditEvent], activity: String, userId: String?, line: UInt = #line) { - let matching = events.filter { $0.activity == activity } - XCTAssertEqual(matching.count, 1, line: line) - XCTAssertEqual(matching[0].userId, userId, line: line) - } - - func assertEvent(_ events: [AuditEvent], activity: String, event: String?, - data: String?, line: UInt = #line) { - let matching = events.filter { $0.activity == activity && $0.event == event } - XCTAssertEqual(matching.count, 1, line: line) - XCTAssertEqual(matching[0].data, data, line: line) - } - - @MainActor - func testBasicEvents() throws { - let realm = try openRealm() - let events = realm.events! - - let personJson: NSDictionary = try scope(events, "create object") { - try realm.write { - let person = SwiftPerson(firstName: "Fred", lastName: "Q", age: 30) - realm.add(person) - return full(person) - } - } - - let person = scope(events, "read object") { - realm.objects(SwiftPerson.self).first! - } - - try scope(events, "mutate object") { - try realm.write { - person.age = 31 - } - } - - try scope(events, "delete object") { - try realm.write { - realm.delete(person) - } - } - - let mutatedPersonJson = personJson.mutableCopy() as! NSMutableDictionary - mutatedPersonJson["age"] = 31 - - let result = getEvents(expectedCount: 4) - assertEvent(result, activity: "create object", event: "write", - ["SwiftPerson": ["insertions": [personJson]]]) - assertEvent(result, activity: "read object", event: "read", - ["type": "SwiftPerson", "value": [personJson]]) - assertEvent(result, activity: "mutate object", event: "write", - ["SwiftPerson": ["modifications": [ - ["oldValue": personJson, "newValue": ["age": 31]]]]]) - assertEvent(result, activity: "delete object", event: "write", - ["SwiftPerson": ["deletions": [mutatedPersonJson]]]) - } - - @MainActor - func testBasicWithAsyncOpen() throws { - let realm = Realm.asyncOpen(configuration: try configuration()).await(self) - let events = try XCTUnwrap(realm.events) - - let personJson: NSDictionary = try scope(events, "create object") { - try realm.write { - let person = SwiftPerson(firstName: "Fred", lastName: "Q", age: 30) - realm.add(person) - return full(person) - } - } - - let result = getEvents(expectedCount: 1) - assertEvent(result, activity: "create object", event: "write", - ["SwiftPerson": ["insertions": [personJson]]]) - } - - @MainActor - func testCustomEventRepresentation() throws { - let realm = try openRealm() - let events = realm.events! - let scope1 = events.beginScope(activity: "bad json") - try realm.write { - realm.add(SwiftCustomEventRepresentation(value: 0)) - } - scope1.commit().awaitFailure(self) { error in - XCTAssert(error.localizedDescription.contains("json.exception.parse_error")) - } - - let scope2 = events.beginScope(activity: "exception thrown") - try realm.write { - realm.add(SwiftCustomEventRepresentation(value: 1)) - } - scope2.commit().awaitFailure(self) { error in - XCTAssertEqual((error as NSError).userInfo["ExceptionName"] as! String?, - NSExceptionName.rangeException.rawValue) - } - - try scope(events, "valid representation") { - try realm.write { - realm.add(SwiftCustomEventRepresentation(value: 2)) - } - } - - let result = getEvents(expectedCount: 1) - assertEvent(result, activity: "valid representation", event: "write", - ["SwiftCustomEventRepresentation": ["insertions": [["int": 2]]]]) - } - - @MainActor - func testReadEvents() throws { - let realm = try openRealm() - let events = realm.events! - - let a = SwiftPerson(firstName: "A", lastName: "B") - let b = SwiftPerson(firstName: "B", lastName: "C") - let c = SwiftPerson(firstName: "C", lastName: "D") - try realm.write { - realm.add([a, b, c]) - realm.create(LinkToSwiftPerson.self, value: [ - "person": a, - "people": [b, c], - "peopleByName": [b.firstName: b, c.firstName: c] - ] as [String: Any]) - } - - let objects = realm.objects(SwiftPerson.self) - let first = realm.objects(LinkToSwiftPerson.self).first! - scope(events, "link") { - _ = first.person - } - scope(events, "results") { - _ = objects.first - } - scope(events, "query") { - _ = objects.filter("firstName != 'B'").first - } - scope(events, "list") { - _ = first.people.first - } - scope(events, "dynamic list") { - _ = first.dynamicList("people").first - } - scope(events, "collection kvc") { - _ = first.people.value(forKey: "firstName") as [AnyObject] - } - scope(events, "dictionary") { - _ = first.peopleByName["B"] - } - scope(events, "dynamic dictionary") { - _ = first.dynamicMap("peopleByName")["B"] - } - scope(events, "lookup by primary key") { - _ = realm.object(ofType: SwiftPerson.self, forPrimaryKey: a._id) - } - - let result = getEvents(expectedCount: 10) - func assertEvent(_ activity: String, _ value: [NSDictionary]..., line: UInt = #line) { - let filtered = Array(result.filter { $0.activity == activity }.sorted { $0._id < $1._id }) - XCTAssertEqual(filtered.count, value.count, line: line) - for (expected, actual) in zip(value, filtered.map { $0.parsedData }) { - XCTAssertNotNil(actual, line: line) - guard let actual = actual else { continue } - XCTAssertEqual(actual["type"] as? String, "SwiftPerson", line: line) - XCTAssertEqual(actual["value"]! as! [NSDictionary], - expected, line: line) - } - } - - assertEvent("link", [full(a)]) - assertEvent("results", [full(a)]) - assertEvent("query", [full(a), full(c)]) - assertEvent("list", [full(b)]) - assertEvent("dynamic list", [full(b)]) - assertEvent("collection kvc", [full(b)], [full(c)]) - assertEvent("dictionary", [full(b)]) - assertEvent("dynamic dictionary", [full(b)]) - assertEvent("lookup by primary key", [full(a)]) - } - - @MainActor - func testLinkTracking() throws { - let realm = try openRealm() - let events = realm.events! - - let a = SwiftPerson(firstName: "A", lastName: "B") - let b = SwiftPerson(firstName: "B", lastName: "C") - let c = SwiftPerson(firstName: "C", lastName: "D") - var id: ObjectId? - try realm.write { - realm.add([a, b, c]) - id = realm.create(LinkToSwiftPerson.self, value: [ - "person": a, - "people": [b, c], - "peopleByName": [b.firstName: b, c.firstName: c] - ] as [String: Any])._id - } - - let objects = realm.objects(LinkToSwiftPerson.self) - let dynamicObjects = realm.dynamicObjects("LinkToSwiftPerson") - scope(events, "object read without link accesses") { - _ = objects.first - } - scope(events, "link property") { - _ = objects.first!.person - } - scope(events, "link via KVC") { - _ = objects.first!.value(forKey: "person") - } - scope(events, "link via subscript") { - _ = objects.first!["person"] - } - scope(events, "link via dynamic") { - _ = dynamicObjects.first!["person"] - } - - scope(events, "list property") { - _ = objects.first!.people.first - } - scope(events, "dynamic list property") { - _ = dynamicObjects.first!.dynamicList("people").first - } - scope(events, "dictionary property") { - _ = objects.first!.peopleByName["B"] - } - scope(events, "dynamic dictionary property") { - _ = dynamicObjects.first!.dynamicMap("peopleByName")["B"] - } - - let result = getEvents(expectedCount: 17) - - func assertEvent(_ activity: String, personCount: Int, _ value: NSDictionary, line: UInt = #line) { - XCTAssertEqual(result.filter { $0.activity == activity && - $0.parsedData!["type"] as! String == "SwiftPerson" }.count, - personCount, line: line) - let event = result.filter { $0.activity == activity && - $0.parsedData!["type"] as! String == "LinkToSwiftPerson" }.first - XCTAssertNotNil(event, line: line) - guard let event = event else { return } - let array = event.parsedData!["value"]! as! NSArray - XCTAssertEqual(array.count, 1, line: line) - XCTAssertEqual(array[0] as! NSDictionary, value, line: line) - } - - assertEvent("object read without link accesses", personCount: 0, [ - "_id": id!.stringValue, - "realm_id": NSNull(), - "person": idOnly(a), - "people": [idOnly(b), idOnly(c)], - "peopleByName": [b.firstName: idOnly(b), c.firstName: idOnly(c)] - ]) - - let linkAccessed: NSDictionary = [ - "_id": id!.stringValue, - "realm_id": NSNull(), - "person": full(a), - "people": [idOnly(b), idOnly(c)], - "peopleByName": [b.firstName: idOnly(b), c.firstName: idOnly(c)] - ] - assertEvent("link property", personCount: 1, linkAccessed) - assertEvent("link via KVC", personCount: 1, linkAccessed) - assertEvent("link via subscript", personCount: 1, linkAccessed) - assertEvent("link via dynamic", personCount: 1, linkAccessed) - - let listAccessed: NSDictionary = [ - "_id": id!.stringValue, - "realm_id": NSNull(), - "person": idOnly(a), - "people": [full(b), full(c)], - "peopleByName": [b.firstName: idOnly(b), c.firstName: idOnly(c)] - ] - assertEvent("list property", personCount: 1, listAccessed) - assertEvent("dynamic list property", personCount: 1, listAccessed) - - let dictionaryAccessed: NSDictionary = [ - "_id": id!.stringValue, - "realm_id": NSNull(), - "person": idOnly(a), - "people": [idOnly(b), idOnly(c)], - "peopleByName": [b.firstName: full(b), c.firstName: full(c)] - ] - assertEvent("dictionary property", personCount: 1, dictionaryAccessed) - assertEvent("dynamic dictionary property", personCount: 1, dictionaryAccessed) - } - - @MainActor - func testMetadata() throws { - let realm = try openRealm() - let events = realm.events! - - func writeEvent(_ name: String) throws { - try scope(events, name) { - try realm.write { - realm.add(SwiftPerson()) - } - } - } - - try writeEvent("no metadata") - events.updateMetadata(["userId": "a"]) - try writeEvent("userId a") - events.updateMetadata(["userId": "b"]) - try writeEvent("userId b") - events.updateMetadata([:]) - try writeEvent("metadata removed") - - let result = getEvents(expectedCount: 4) - assertEvent(result, activity: "no metadata", userId: nil) - assertEvent(result, activity: "userId a", userId: "a") - assertEvent(result, activity: "userId b", userId: "b") - assertEvent(result, activity: "metadata removed", userId: nil) - } - - @MainActor - func testCustomLogger() throws { - let ex = expectation(description: "saw message with scope name") - ex.assertForOverFulfill = false - var config = try configuration() - config.eventConfiguration!.logger = { _, message in - // Mostly just verify that the user-provided logger is wired up - // correctly and not that the log messages are sensible - if message.contains("a scope name") { - ex.fulfill() - } - } - let realm = try Realm(configuration: config) - let scope = realm.events!.beginScope(activity: "a scope name") - scope.commit().await(self) - waitForExpectations(timeout: 2.0) - } - - @MainActor - func testCustomEvent() throws { - let realm = try openRealm() - let events = realm.events! - - events.recordEvent(activity: "no event or data") - events.recordEvent(activity: "event", eventType: "custom event") - events.recordEvent(activity: "json data", data: "{\"foo\": \"bar\"}") - events.recordEvent(activity: "non-json data", data: "not valid json") - events.recordEvent(activity: "event and data", eventType: "custom json event", - data: "{\"bar\": \"foo\"}").await(self) - - let result = getEvents(expectedCount: 5) - assertEvent(result, activity: "no event or data", event: nil, nil) - assertEvent(result, activity: "event", event: "custom event", nil) - assertEvent(result, activity: "json data", event: nil, ["foo": "bar"]) - assertEvent(result, activity: "non-json data", event: nil, data: "not valid json") - assertEvent(result, activity: "event and data", event: "custom json event", ["bar": "foo"]) - } - - @MainActor - func testScopeLifetimes() throws { - let realm = try openRealm() - let events = realm.events! - - try autoreleasepool { () -> Future in - let scope1 = events.beginScope(activity: "scope 1") - let scope2 = events.beginScope(activity: "scope 2") - let scope3 = events.beginScope(activity: "scope 3") - - try realm.write { - realm.add(SwiftPerson()) - } - - scope1.cancel() - XCTAssertTrue(scope2.isActive) // ensure scope stays alive to here - return scope3.commit() - }.await(self) - - let result = getEvents(expectedCount: 1) - XCTAssertEqual(result[0].activity, "scope 3") - } - - @MainActor - func testScopeCanOutliveSourceRealm() throws { - var scope: Events.Scope? - try autoreleasepool { - let realm = try openRealm() - let events = realm.events! - scope = events.beginScope(activity: "scope") - try realm.write { - realm.add(SwiftPerson()) - } - } - - scope?.commit().await(self) - - let result = getEvents(expectedCount: 1) - XCTAssertEqual(result[0].activity, "scope") - } - - @MainActor - func testErrorHandler() throws { - var config = try configuration() - let blockCalled = Locked(false) - let ex = expectation(description: "Error callback called") - var eventConfiguration = config.eventConfiguration! - eventConfiguration.errorHandler = { error in - assertSyncError(error, .clientInternalError, - "Invalid schema change (UPLOAD): non-breaking schema change: adding \"String\" column at field \"invalid metadata field\" in schema \"AuditEvent\", schema changes from clients are restricted when developer mode is disabled") - blockCalled.value = true - ex.fulfill() - } - eventConfiguration.metadata = ["invalid metadata field": "value"] - config.eventConfiguration = eventConfiguration - let realm = try openRealm(configuration: config) - let events = realm.events! - - // Recording the audit event should succeed, but we should get a sync - // error when trying to actually upload it due to the user having - // an invalid access token - events.recordEvent(activity: "activity which should fail").await(self) - wait(for: [ex], timeout: 4.0) - } -} diff --git a/Realm/ObjectServerTests/Object-Server-Tests-Bridging-Header.h b/Realm/ObjectServerTests/Object-Server-Tests-Bridging-Header.h deleted file mode 100644 index 2c833d6345..0000000000 --- a/Realm/ObjectServerTests/Object-Server-Tests-Bridging-Header.h +++ /dev/null @@ -1,23 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMChildProcessEnvironment.h" -#import "RLMSyncTestCase.h" -#import "RLMUser+ObjectServerTests.h" -#import "RLMWatchTestUtility.h" -#import "TestUtils.h" diff --git a/Realm/ObjectServerTests/ObjectServerTests-Info.plist b/Realm/ObjectServerTests/ObjectServerTests-Info.plist deleted file mode 100644 index 169b6f710e..0000000000 --- a/Realm/ObjectServerTests/ObjectServerTests-Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/Realm/ObjectServerTests/RLMAsymmetricSyncServerTests.mm b/Realm/ObjectServerTests/RLMAsymmetricSyncServerTests.mm deleted file mode 100644 index 74afda786a..0000000000 --- a/Realm/ObjectServerTests/RLMAsymmetricSyncServerTests.mm +++ /dev/null @@ -1,245 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncTestCase.h" - -#import "RLMApp_Private.h" -#import "RLMObject_Private.h" -#import "RLMObjectSchema_Private.h" - -#pragma mark PersonAsymmetric - -@interface PersonAsymmetric : RLMAsymmetricObject -@property RLMObjectId *_id; -@property NSString *firstName; -@property NSString *lastName; -@property NSInteger age; - -- (instancetype)initWithPrimaryKey:(RLMObjectId *)primaryKey firstName:(NSString *)firstName lastName:(NSString *)lastName age:(NSInteger)age; -@end - -@implementation PersonAsymmetric - -+ (NSDictionary *)defaultPropertyValues { - return @{@"_id": [RLMObjectId objectId]}; -} - -+ (NSString *)primaryKey { - return @"_id"; -} - -+ (NSArray *)requiredProperties { - return @[@"firstName", @"lastName", @"age"]; -} - -+ (bool)_realmIgnoreClass { - return true; -} - -- (instancetype)initWithPrimaryKey:(RLMObjectId *)primaryKey firstName:(NSString *)firstName lastName:(NSString *)lastName age:(NSInteger)age { - self = [super init]; - if (self) { - self._id = primaryKey; - self.age = age; - self.firstName = firstName; - self.lastName = lastName; - } - return self; -} -@end - -#pragma mark UnsupportedLinkAsymmetric - -RLM_COLLECTION_TYPE(PersonAsymmetric); -@interface UnsupportedLinkAsymmetric : RLMAsymmetricObject -@property RLMObjectId *_id; -@property PersonAsymmetric *object; -@property RLM_GENERIC_ARRAY(PersonAsymmetric) *objectArray; -@end - -@implementation UnsupportedLinkAsymmetric - -+ (NSDictionary *)defaultPropertyValues { - return @{@"_id": [RLMObjectId objectId]}; -} - -+ (NSString *)primaryKey { - return @"_id"; -} - -+ (bool)_realmIgnoreClass { - return YES; -} -@end - -#pragma mark UnsupportedObjectLinkAsymmetric - -@interface UnsupportedObjectLinkAsymmetric : RLMObject -@property RLMObjectId *_id; -@property PersonAsymmetric *object; -@property RLM_GENERIC_ARRAY(PersonAsymmetric) *objectArray; -@end - -@implementation UnsupportedObjectLinkAsymmetric - -+ (NSDictionary *)defaultPropertyValues { - return @{@"_id": [RLMObjectId objectId]}; -} - -+ (NSString *)primaryKey { - return @"_id"; -} - -+ (bool)_realmIgnoreClass { - return true; -} -@end - -@interface RLMAsymmetricSyncServerTests : RLMSyncTestCase -@end - -@implementation RLMAsymmetricSyncServerTests - -#pragma mark Asymmetric Sync App - -- (NSString *)createAppWithError:(NSError **)error { - return [RealmServer.shared createAppWithFields:@[] - types:@[PersonAsymmetric.self] - persistent:true - error:error]; -} - -- (NSArray *)defaultObjectTypes { - return @[PersonAsymmetric.self]; -} - -- (RLMRealmConfiguration *)configurationForUser:(RLMUser *)user { - return [user flexibleSyncConfiguration]; -} - -- (void)tearDown { - [self cleanupRemoteDocuments:[self.anonymousUser collectionForType:PersonAsymmetric.class app:self.app]]; - [super tearDown]; -} - -- (void)checkCountInMongo:(unsigned long)expectedCount { - RLMMongoCollection *collection = [self.anonymousUser collectionForType:PersonAsymmetric.class app:self.app]; - - __block unsigned long count = 0; - NSDate *waitStart = [NSDate date]; - while (count < expectedCount && ([waitStart timeIntervalSinceNow] > -600.0)) { - auto ex = [self expectationWithDescription:@""]; - [collection countWhere:@{} - completion:^(NSInteger c, NSError *error) { - XCTAssertNil(error); - count = c; - [ex fulfill]; - }]; - [self waitForExpectations:@[ex] timeout:5.0]; - if (count < expectedCount) { - sleep(5); - } - } - XCTAssertEqual(count, expectedCount); -} - -- (void)testAsymmetricObjectSchema { - RLMRealm *realm = [self openRealm]; - XCTAssertTrue(realm.schema.objectSchema[0].isAsymmetric); -} - -- (void)testUnsupportedAsymmetricLinkAsymmetricThrowsError { - RLMUser *user = [self createUser]; - RLMRealmConfiguration *configuration = [user flexibleSyncConfiguration]; - configuration.objectClasses = @[UnsupportedLinkAsymmetric.self, PersonAsymmetric.self]; - NSError *error; - RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:&error]; - XCTAssertNil(realm); - XCTAssert([error.localizedDescription containsString:@"Property 'UnsupportedLinkAsymmetric.object' of type 'object' cannot be a link to an asymmetric object."]); - XCTAssert([error.localizedDescription containsString:@"Property 'UnsupportedLinkAsymmetric.objectArray' of type 'array' cannot be a link to an asymmetric object."]); -} - -- (void)testUnsupportedObjectLinksAsymmetricThrowsError { - RLMUser *user = [self createUser]; - RLMRealmConfiguration *configuration = [user flexibleSyncConfiguration]; - configuration.objectClasses = @[UnsupportedObjectLinkAsymmetric.self, PersonAsymmetric.self]; - NSError *error; - RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:&error]; - XCTAssertNil(realm); - // This error comes from core, we are not even adding this objects to the server schema. - XCTAssert([error.localizedDescription containsString:@"Property 'UnsupportedObjectLinkAsymmetric.object' of type 'object' cannot be a link to an asymmetric object."]); - XCTAssert([error.localizedDescription containsString:@"Property 'UnsupportedObjectLinkAsymmetric.objectArray' of type 'array' cannot be a link to an asymmetric object."]); -} - -- (void)testOpenLocalRealmWithAsymmetricObjectError { - RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; - configuration.objectClasses = @[PersonAsymmetric.self]; - NSError *error; - RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:&error]; - XCTAssertNil(realm); - RLMValidateErrorContains(error, RLMErrorDomain, RLMErrorFail, - @"Asymmetric table 'PersonAsymmetric' not allowed in a local Realm"); -} - -- (void)testOpenPBSConfigurationWithAsymmetricObjectError { - RLMUser *user = [self createUser]; - RLMRealmConfiguration *configuration = [user configurationWithPartitionValue:self.name]; - configuration.objectClasses = @[PersonAsymmetric.self]; - NSError *error; - RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:&error]; - XCTAssertNil(realm); - RLMValidateErrorContains(error, RLMErrorDomain, RLMErrorFail, - @"Asymmetric table 'PersonAsymmetric' not allowed in partition based sync"); -} - -- (void)testCreateAsymmetricObjects { - RLMRealm *realm = [self openRealm]; - - [realm beginWriteTransaction]; - for (int i = 1; i <= 12; ++i) { - RLMObjectId *oid = [RLMObjectId objectId]; - PersonAsymmetric *person = [PersonAsymmetric createInRealm:realm withValue:@[ - oid, [NSString stringWithFormat:@"firstname_%d", i], - [NSString stringWithFormat:@"lastname_%d", i] - ]]; - XCTAssertNil(person); - } - [realm commitWriteTransaction]; - [self waitForUploadsForRealm:realm]; - [self checkCountInMongo:12]; -} - -- (void)testCreateAsymmetricSameObjectNotDuplicates { - RLMRealm *realm = [self openRealm]; - - RLMObjectId *oid = [RLMObjectId objectId]; - [realm beginWriteTransaction]; - PersonAsymmetric *person = [PersonAsymmetric createInRealm:realm withValue:@[oid, @"firstname", @"lastname", @10]]; - XCTAssertNil(person); - [realm commitWriteTransaction]; - [self waitForUploadsForRealm:realm]; - [self checkCountInMongo:1]; - - [realm beginWriteTransaction]; - PersonAsymmetric *person2 = [PersonAsymmetric createInRealm:realm withValue:@[oid, @"firstname", @"lastname", @10]]; - XCTAssertNil(person2); - [realm commitWriteTransaction]; - [self waitForUploadsForRealm:realm]; - [self checkCountInMongo:1]; -} -@end diff --git a/Realm/ObjectServerTests/RLMBSONTests.mm b/Realm/ObjectServerTests/RLMBSONTests.mm deleted file mode 100644 index 30e45d7530..0000000000 --- a/Realm/ObjectServerTests/RLMBSONTests.mm +++ /dev/null @@ -1,159 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMBSON_Private.hpp" -#import "RLMUUID_Private.hpp" - -#import - -#import - -using namespace realm::bson; - -@interface RLMBSONTestCase : XCTestCase - -@end - -@implementation RLMBSONTestCase - -- (void)testNilRoundTrip { - auto bson = Bson(); - id rlm = RLMConvertBsonToRLMBSON(bson); - XCTAssertEqual(rlm, [NSNull null]); - XCTAssertEqual(RLMConvertRLMBSONToBson(rlm), bson); -} - -- (void)testIntRoundTrip { - auto bson = Bson(int64_t(42)); - NSNumber *rlm = (NSNumber *)RLMConvertBsonToRLMBSON(bson); - XCTAssertEqual(rlm.intValue, 42); - XCTAssertEqual(RLMConvertRLMBSONToBson(rlm), bson); -} - -- (void)testBoolRoundTrip { - auto bson = Bson(true); - NSNumber *rlm = (NSNumber *)RLMConvertBsonToRLMBSON(bson); - XCTAssertEqual(rlm.boolValue, true); - XCTAssertEqual(RLMConvertRLMBSONToBson(rlm), bson); -} - -- (void)testDoubleRoundTrip { - auto bson = Bson(42.42); - NSNumber *rlm = (NSNumber *)RLMConvertBsonToRLMBSON(bson); - XCTAssertEqual(rlm.doubleValue, 42.42); - XCTAssertEqual(RLMConvertRLMBSONToBson(rlm), bson); -} - -- (void)testStringRoundTrip { - auto bson = Bson("foo"); - NSString *rlm = (NSString *)RLMConvertBsonToRLMBSON(bson); - XCTAssertEqualObjects(rlm, @"foo"); - XCTAssertEqual(RLMConvertRLMBSONToBson(rlm), bson); -} - -- (void)testBinaryRoundTrip { - auto bson = Bson(std::vector{1, 2, 3}); - NSData *rlm = (NSData *)RLMConvertBsonToRLMBSON(bson); - NSData *d = [[NSData alloc] initWithBytes:(char[]){1, 2, 3} length:3]; - XCTAssert([rlm isEqualToData: d]); - XCTAssertEqual(RLMConvertRLMBSONToBson(rlm), bson); -} - -- (void)testDatetimeMongoTimestampRoundTrip { - auto bson = Bson(realm::Timestamp(42, 0)); - NSDate *rlm = (NSDate *)RLMConvertBsonToRLMBSON(bson); - NSDate *d = [[NSDate alloc] initWithTimeIntervalSince1970:42]; - XCTAssert([rlm isEqualToDate: d]); - XCTAssertEqual(RLMConvertRLMBSONToBson(rlm), bson); -} - -- (void)testDatetimeTimestampRoundTrip { - auto bson = Bson(realm::Timestamp(42, 0)); - NSDate *rlm = (NSDate *)RLMConvertBsonToRLMBSON(bson); - NSDate *d = [[NSDate alloc] initWithTimeIntervalSince1970:42]; - XCTAssert([rlm isEqualToDate: d]); - // Not an exact round trip since we ignore Timestamp Cocoa side - XCTAssertEqual(RLMConvertRLMBSONToBson(rlm), Bson(realm::Timestamp(42, 0))); -} - -- (void)testObjectIdRoundTrip { - auto bson = Bson(realm::ObjectId::gen()); - RLMObjectId *rlm = (RLMObjectId *)RLMConvertBsonToRLMBSON(bson); - RLMObjectId *d = [[RLMObjectId alloc] initWithString:rlm.stringValue error:nil]; - XCTAssertEqualObjects(rlm, d); - XCTAssertEqual(RLMConvertRLMBSONToBson(rlm), bson); -} - -- (void)testUUIDRoundTrip { - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:@"b1c11e54-e719-4275-b631-69ec3f2d616d"]; - auto bson = Bson(uuid.rlm_uuidValue); - NSUUID *rlm = (NSUUID *)RLMConvertBsonToRLMBSON(bson); - XCTAssertEqualObjects(rlm, uuid); - XCTAssertEqual(RLMConvertRLMBSONToBson(rlm), bson); -} - -- (void)testDocumentRoundTrip { - NSDictionary> *document = @{ - @"nil": [NSNull null], - @"string": @"test string", - @"true": @YES, - @"false": @NO, - @"int": @25, - @"int32": @5, - @"int64": @10, - @"double": @15.0, - @"decimal128": [[RLMDecimal128 alloc] initWithString:@"1.2E+10" error:nil], - @"minkey": [RLMMinKey new], - @"maxkey": [RLMMaxKey new], - @"date": [[NSDate alloc] initWithTimeIntervalSince1970: 500], - @"nestedarray": @[@[@1, @2], @[@3, @4]], - @"nesteddoc": @{@"a": @1, @"b": @2, @"c": @NO, @"d": @[@3, @4], @"e" : @{@"f": @"g"}}, - @"oid": [[RLMObjectId alloc] initWithString:@"507f1f77bcf86cd799439011" error:nil], - @"regex": [[NSRegularExpression alloc] initWithPattern:@"^abc" options:0 error:nil], - @"array1": @[@1, @2], - @"array2": @[@"string1", @"string2"], - @"uuid": [[NSUUID alloc] initWithUUIDString:@"00000000-0000-0000-0000-000000000000"], - }; - - auto bson = RLMConvertRLMBSONToBson(document); - - auto bsonDocument = static_cast(bson); - - XCTAssertEqual(document[@"nil"], [NSNull null]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["nil"]), document[@"nil"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["string"]), document[@"string"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["true"]), document[@"true"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["false"]), document[@"false"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["int"]), document[@"int"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["int32"]), document[@"int32"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["int64"]), document[@"int64"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["double"]), document[@"double"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["decimal128"]), document[@"decimal128"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["minkey"]), document[@"minkey"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["maxkey"]), document[@"maxkey"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["date"]), document[@"date"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["nestedarray"]), document[@"nestedarray"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["nesteddoc"]), document[@"nesteddoc"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["oid"]), document[@"oid"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["regex"]), document[@"regex"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["array1"]), document[@"array1"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["array2"]), document[@"array2"]); - XCTAssertEqualObjects(RLMConvertBsonToRLMBSON(bsonDocument["uuid"]), document[@"uuid"]); -} - -@end diff --git a/Realm/ObjectServerTests/RLMCollectionSyncTests.mm b/Realm/ObjectServerTests/RLMCollectionSyncTests.mm deleted file mode 100644 index ba16655064..0000000000 --- a/Realm/ObjectServerTests/RLMCollectionSyncTests.mm +++ /dev/null @@ -1,473 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncTestCase.h" - -#if TARGET_OS_OSX - -// Each of these test suites compares either Person or non-realm-object values -#define RLMAssertEqual(lft, rgt) do { \ - if (isObject) { \ - XCTAssertEqualObjects(lft.firstName, \ - ((Person *)rgt).firstName); \ - } else { \ - XCTAssertEqualObjects(lft, rgt); \ - } \ -} while (0) - -#pragma mark RLMSet Sync Tests - -@interface RLMSetObjectServerTests : RLMSyncTestCase -@end - -@implementation RLMSetObjectServerTests -- (NSArray *)defaultObjectTypes { - return @[RLMSetSyncObject.self, Person.self]; -} - -- (void)roundTripWithPropertyGetter:(RLMSet *(^)(id))propertyGetter - values:(NSArray *)values - otherPropertyGetter:(RLMSet *(^)(id))otherPropertyGetter - otherValues:(NSArray *)otherValues - isObject:(BOOL)isObject { - RLMRealm *readRealm = [self openRealm]; - RLMRealm *writeRealm = [self openRealm]; - auto write = [&](auto fn) { - [writeRealm transactionWithBlock:^{ - fn(); - }]; - [self waitForUploadsForRealm:writeRealm]; - [self waitForDownloadsForRealm:readRealm]; - }; - - CHECK_COUNT(0, RLMSetSyncObject, readRealm); - - __block RLMSetSyncObject *writeObj; - write(^{ - writeObj = [RLMSetSyncObject createInRealm:writeRealm - withValue:@{@"_id": [RLMObjectId objectId]}]; - }); - CHECK_COUNT(1, RLMSetSyncObject, readRealm); - - write(^{ - [propertyGetter(writeObj) addObjects:values]; - [otherPropertyGetter(writeObj) addObjects:otherValues]; - }); - CHECK_COUNT(1, RLMSetSyncObject, readRealm); - RLMResults *results = [RLMSetSyncObject allObjectsInRealm:readRealm]; - RLMSetSyncObject *obj = results.firstObject; - RLMSet *set = propertyGetter(obj); - RLMSet *otherSet = otherPropertyGetter(obj); - XCTAssertEqual(set.count, values.count); - XCTAssertEqual(otherSet.count, otherValues.count); - - write(^{ - if (isObject) { - [propertyGetter(writeObj) removeAllObjects]; - [propertyGetter(writeObj) addObject:values[0]]; - } else { - [propertyGetter(writeObj) intersectSet:otherPropertyGetter(writeObj)]; - } - }); - CHECK_COUNT(1, RLMSetSyncObject, readRealm); - if (!isObject) { - XCTAssertTrue([propertyGetter(obj) intersectsSet:propertyGetter(obj)]); - XCTAssertEqual(propertyGetter(obj).count, 1U); - } - - write(^{ - [propertyGetter(writeObj) removeAllObjects]; - [otherPropertyGetter(writeObj) removeAllObjects]; - }); - XCTAssertEqual(propertyGetter(obj).count, 0U); - XCTAssertEqual(otherPropertyGetter(obj).count, 0U); -} - -- (void)testIntSet { - [self roundTripWithPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.intSet; } - values:@[@123, @234, @345] - otherPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.otherIntSet; } - otherValues:@[@345, @567, @789] - isObject:NO]; -} - -- (void)testStringSet { - [self roundTripWithPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.stringSet; } - values:@[@"Who", @"What", @"When"] - otherPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.otherStringSet; } - otherValues:@[@"When", @"Strings", @"Collide"] - isObject:NO]; -} - -- (void)testDataSet { - NSData* (^createData)(size_t) = ^(size_t size) { - void *buffer = malloc(size); - arc4random_buf(buffer, size); - return [NSData dataWithBytesNoCopy:buffer length:size freeWhenDone:YES]; - }; - - NSData *duplicateData = createData(1024U); - [self roundTripWithPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.dataSet; } - values:@[duplicateData, createData(1024U), createData(1024U)] - otherPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.otherDataSet; } - otherValues:@[duplicateData, createData(1024U), createData(1024U)] - isObject:NO]; -} - -- (void)testDoubleSet { - [self roundTripWithPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.doubleSet; } - values:@[@123.456, @234.456, @345.567] - otherPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.otherDoubleSet; } - otherValues:@[@123.456, @434.456, @545.567] - isObject:NO]; -} - -- (void)testObjectIdSet { - [self roundTripWithPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.objectIdSet; } - values:@[[[RLMObjectId alloc] initWithString:@"6058f12b957ba06156586a7c" error:nil], - [[RLMObjectId alloc] initWithString:@"6058f12682b2fbb1f334ef1d" error:nil], - [[RLMObjectId alloc] initWithString:@"6058f12d42e5a393e67538d0" error:nil]] - otherPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.otherObjectIdSet; } - otherValues:@[[[RLMObjectId alloc] initWithString:@"6058f12b957ba06156586a7c" error:nil], - [[RLMObjectId alloc] initWithString:@"6058f12682b2fbb1f334ef1e" error:nil], - [[RLMObjectId alloc] initWithString:@"6058f12d42e5a393e67538df" error:nil]] - isObject:NO]; -} - -- (void)testDecimalSet { - [self roundTripWithPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.decimalSet; } - values:@[[[RLMDecimal128 alloc] initWithNumber:@123.456], - [[RLMDecimal128 alloc] initWithNumber:@223.456], - [[RLMDecimal128 alloc] initWithNumber:@323.456]] - otherPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.otherDecimalSet; } - otherValues:@[[[RLMDecimal128 alloc] initWithNumber:@123.456], - [[RLMDecimal128 alloc] initWithNumber:@423.456], - [[RLMDecimal128 alloc] initWithNumber:@523.456]] - isObject:NO]; -} - -- (void)testUUIDSet { - [self roundTripWithPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.uuidSet; } - values:@[[[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd"], - [[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90fe"], - [[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90ff"]] - otherPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.otherUuidSet; } - otherValues:@[[[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd"], - [[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90ae"], - [[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90bf"]] - isObject:NO]; -} - -- (void)testObjectSet { - [self roundTripWithPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.objectSet; } - values:@[[Person john], [Person paul], [Person ringo]] - otherPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.otherObjectSet; } - otherValues:@[[Person john], [Person paul], [Person ringo]] - isObject:YES]; -} - -- (void)testAnySet { - [self roundTripWithPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.anySet; } - values:@[@123, @"Hey", NSNull.null] - otherPropertyGetter:^RLMSet *(RLMSetSyncObject *obj) { return obj.otherAnySet; } - otherValues:@[[[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd"], - @123, - [[RLMObjectId alloc] initWithString:@"6058f12682b2fbb1f334ef1d" error:nil]] - isObject:NO]; -} - -@end - -#pragma mark RLMArray Sync Tests - -@interface RLMArrayObjectServerTests : RLMSyncTestCase -@end - -@implementation RLMArrayObjectServerTests -- (NSArray *)defaultObjectTypes { - return @[RLMArraySyncObject.self, Person.self]; -} - -- (void)roundTripWithPropertyGetter:(RLMArray *(^)(id))propertyGetter - values:(NSArray *)values - isObject:(BOOL)isObject { - RLMRealm *readRealm = [self openRealm]; - RLMRealm *writeRealm = [self openRealm]; - auto write = [&](auto fn) { - [writeRealm transactionWithBlock:^{ - fn(); - }]; - [self waitForUploadsForRealm:writeRealm]; - [self waitForDownloadsForRealm:readRealm]; - }; - - CHECK_COUNT(0, RLMArraySyncObject, readRealm); - __block RLMArraySyncObject *writeObj; - write(^{ - writeObj = [RLMArraySyncObject createInRealm:writeRealm - withValue:@{@"_id": [RLMObjectId objectId]}]; - }); - CHECK_COUNT(1, RLMArraySyncObject, readRealm); - - write(^{ - [propertyGetter(writeObj) addObjects:values]; - [propertyGetter(writeObj) addObjects:values]; - }); - CHECK_COUNT(1, RLMArraySyncObject, readRealm); - RLMResults *results = [RLMArraySyncObject allObjectsInRealm:readRealm]; - RLMArraySyncObject *obj = results.firstObject; - RLMArray *array = propertyGetter(obj); - XCTAssertEqual(array.count, values.count*2); - for (NSUInteger i = 0; i < values.count; i++) { - RLMAssertEqual(array[i], values[i]); - } - - write(^{ - [propertyGetter(writeObj) removeLastObject]; - [propertyGetter(writeObj) removeLastObject]; - [propertyGetter(writeObj) removeLastObject]; - }); - XCTAssertEqual(propertyGetter(obj).count, values.count); - - write(^{ - [propertyGetter(writeObj) replaceObjectAtIndex:0 - withObject:values[1]]; - }); - RLMAssertEqual(array[0], values[1]); -} - -- (void)testIntArray { - [self roundTripWithPropertyGetter:^RLMArray *(RLMArraySyncObject *obj) { return obj.intArray; } - values:@[@123, @234, @345] - isObject:NO]; -} - -- (void)testBoolArray { - [self roundTripWithPropertyGetter:^RLMArray *(RLMArraySyncObject *obj) { return obj.boolArray; } - values:@[@YES, @NO, @YES] - isObject:NO]; -} - -- (void)testStringArray { - [self roundTripWithPropertyGetter:^RLMArray *(RLMArraySyncObject *obj) { return obj.stringArray; } - values:@[@"Hello...", @"It's", @"Me"] - isObject:NO]; -} - -- (void)testDataArray { - [self roundTripWithPropertyGetter:^RLMArray *(RLMArraySyncObject *obj) { return obj.dataArray; } - values:@[[NSData dataWithBytes:(unsigned char[]){0x0a} - length:1], - [NSData dataWithBytes:(unsigned char[]){0x0b} - length:1], - [NSData dataWithBytes:(unsigned char[]){0x0c} - length:1]] - isObject:NO]; -} - -- (void)testDoubleArray { - [self roundTripWithPropertyGetter:^RLMArray *(RLMArraySyncObject *obj) { return obj.doubleArray; } - values:@[@123.456, @789.456, @987.344] - isObject:NO]; -} - -- (void)testObjectIdArray { - [self roundTripWithPropertyGetter:^RLMArray *(RLMArraySyncObject *obj) { return obj.objectIdArray; } - values:@[[[RLMObjectId alloc] initWithString:@"6058f12b957ba06156586a7c" error:nil], - [[RLMObjectId alloc] initWithString:@"6058f12682b2fbb1f334ef1d" error:nil], - [[RLMObjectId alloc] initWithString:@"6058f12d42e5a393e67538d0" error:nil]] - isObject:NO]; -} - -- (void)testDecimalArray { - [self roundTripWithPropertyGetter:^RLMArray *(RLMArraySyncObject *obj) { return obj.decimalArray; } - values:@[[[RLMDecimal128 alloc] initWithNumber:@123.456], - [[RLMDecimal128 alloc] initWithNumber:@456.456], - [[RLMDecimal128 alloc] initWithNumber:@789.456]] - isObject:NO]; -} - -- (void)testUUIDArray { - [self roundTripWithPropertyGetter:^RLMArray *(RLMArraySyncObject *obj) { return obj.uuidArray; } - values:@[[[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd"], - [[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90fe"], - [[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90ff"]] - isObject:NO]; -} - -- (void)testObjectArray { - [self roundTripWithPropertyGetter:^RLMArray *(RLMArraySyncObject *obj) { return obj.objectArray; } - values:@[[Person john], [Person paul], [Person ringo]] - isObject:YES]; -} - -- (void)testAnyArray { - [self roundTripWithPropertyGetter:^RLMArray *(RLMArraySyncObject *obj) { return obj.anyArray; } - values:@[@1234, @"I'm a String", NSNull.null] - isObject:NO]; -} - -@end - -#pragma mark RLMDictionary Sync Tests - -@interface RLMDictionaryObjectServerTests : RLMSyncTestCase -@end - -@implementation RLMDictionaryObjectServerTests -- (NSArray *)defaultObjectTypes { - return @[RLMDictionarySyncObject.self, Person.self]; -} - -- (void)roundTripWithPropertyGetter:(RLMDictionary *(^)(id))propertyGetter - values:(NSDictionary *)values - isObject:(BOOL)isObject { - RLMRealm *readRealm = [self openRealm]; - RLMRealm *writeRealm = [self openRealm]; - auto write = [&](auto fn) { - [writeRealm transactionWithBlock:^{ - fn(); - }]; - [self waitForUploadsForRealm:writeRealm]; - [self waitForDownloadsForRealm:readRealm]; - }; - - CHECK_COUNT(0, RLMDictionarySyncObject, readRealm); - - __block RLMDictionarySyncObject *writeObj; - write(^{ - writeObj = [RLMDictionarySyncObject createInRealm:writeRealm - withValue:@{@"_id": [RLMObjectId objectId]}]; - }); - CHECK_COUNT(1, RLMDictionarySyncObject, readRealm); - - write(^{ - [propertyGetter(writeObj) addEntriesFromDictionary:values]; - }); - CHECK_COUNT(1, RLMDictionarySyncObject, readRealm); - RLMResults *results = [RLMDictionarySyncObject allObjectsInRealm:readRealm]; - RLMDictionarySyncObject *obj = results.firstObject; - RLMDictionary *dict = propertyGetter(obj); - XCTAssertEqual(dict.count, values.count); - for (NSString *key in values) { - RLMAssertEqual(dict[key], values[key]); - } - - write(^{ - int i = 0; - RLMDictionary *dict = propertyGetter(writeObj); - for (NSString *key in dict) { - dict[key] = nil; - if (++i >= 3) { - break; - } - } - }); - CHECK_COUNT(1, RLMDictionarySyncObject, readRealm); - XCTAssertEqual(dict.count, 2U); - - write(^{ - RLMDictionary *dict = propertyGetter(writeObj); - NSArray *keys = dict.allKeys; - dict[keys[0]] = dict[keys[1]]; - }); - CHECK_COUNT(1, RLMDictionarySyncObject, readRealm); - XCTAssertEqual(dict.count, 2U); - NSArray *keys = dict.allKeys; - RLMAssertEqual(dict[keys[0]], dict[keys[1]]); -} - -- (void)testIntDictionary { - [self roundTripWithPropertyGetter:^RLMDictionary *(RLMDictionarySyncObject *obj) { return obj.intDictionary; } - values:@{@"0": @123, @"1": @234, @"2": @345, @"3": @567, @"4": @789} - isObject:NO]; -} -- (void)testStringDictionary { - [self roundTripWithPropertyGetter:^RLMDictionary *(RLMDictionarySyncObject *obj) { return obj.stringDictionary; } - values:@{@"0": @"Who", @"1": @"What", @"2": @"When", @"3": @"Strings", @"4": @"Collide"} - isObject:NO]; -} - -- (void)testDataDictionary { - [self roundTripWithPropertyGetter:^RLMDictionary *(RLMDictionarySyncObject *obj) { return obj.dataDictionary; } - values:@{@"0": [NSData dataWithBytes:(unsigned char[]){0x0a} length:1], - @"1": [NSData dataWithBytes:(unsigned char[]){0x0b} length:1], - @"2": [NSData dataWithBytes:(unsigned char[]){0x0c} length:1], - @"3": [NSData dataWithBytes:(unsigned char[]){0x0d} length:1], - @"4": [NSData dataWithBytes:(unsigned char[]){0x0e} length:1]} - isObject:NO]; -} - -- (void)testDoubleDictionary { - [self roundTripWithPropertyGetter:^RLMDictionary *(RLMDictionarySyncObject *obj) { return obj.doubleDictionary; } - values:@{@"0": @123.456, @"1": @234.456, @"2": @345.567, @"3": @434.456, @"4": @545.567} - isObject:NO]; -} - -- (void)testObjectIdDictionary { - [self roundTripWithPropertyGetter:^RLMDictionary *(RLMDictionarySyncObject *obj) { return obj.objectIdDictionary; } - values:@{@"0": [[RLMObjectId alloc] initWithString:@"6058f12b957ba06156586a7c" error:nil], - @"1": [[RLMObjectId alloc] initWithString:@"6058f12682b2fbb1f334ef1d" error:nil], - @"2": [[RLMObjectId alloc] initWithString:@"6058f12d42e5a393e67538d0" error:nil], - @"3": [[RLMObjectId alloc] initWithString:@"6058f12682b2fbb1f334ef1e" error:nil], - @"4": [[RLMObjectId alloc] initWithString:@"6058f12d42e5a393e67538df" error:nil]} - isObject:NO]; -} - -- (void)testDecimalDictionary { - [self roundTripWithPropertyGetter:^RLMDictionary *(RLMDictionarySyncObject *obj) { return obj.decimalDictionary; } - values:@{@"0": [[RLMDecimal128 alloc] initWithNumber:@123.456], - @"1": [[RLMDecimal128 alloc] initWithNumber:@223.456], - @"2": [[RLMDecimal128 alloc] initWithNumber:@323.456], - @"3": [[RLMDecimal128 alloc] initWithNumber:@423.456], - @"4": [[RLMDecimal128 alloc] initWithNumber:@523.456]} - isObject:NO]; -} - -- (void)testUUIDDictionary { - [self roundTripWithPropertyGetter:^RLMDictionary *(RLMDictionarySyncObject *obj) { return obj.uuidDictionary; } - values:@{@"0": [[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd"], - @"1": [[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90fe"], - @"2": [[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90ff"], - @"3": [[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90ae"], - @"4": [[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90bf"]} - isObject:NO]; -} - -- (void)testObjectDictionary { - [self roundTripWithPropertyGetter:^RLMDictionary *(RLMDictionarySyncObject *obj) { return obj.objectDictionary; } - values:@{@"0": [Person john], - @"1": [Person paul], - @"2": [Person ringo], - @"3": [Person george], - @"4": [Person stuart]} - isObject:YES]; -} - -- (void)testAnyDictionary { - [self roundTripWithPropertyGetter:^RLMDictionary *(RLMDictionarySyncObject *obj) { return obj.anyDictionary; } - values:@{@"0": @123, - @"1": @"Hey", - @"2": NSNull.null, - @"3": [[NSUUID alloc] initWithUUIDString:@"6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd"], - @"4": [[RLMObjectId alloc] initWithString:@"6058f12682b2fbb1f334ef1d" error:nil]} - isObject:NO]; -} -@end - -#endif // TARGET_OS_OSX diff --git a/Realm/ObjectServerTests/RLMFlexibleSyncServerTests.mm b/Realm/ObjectServerTests/RLMFlexibleSyncServerTests.mm deleted file mode 100644 index ca2d1fde26..0000000000 --- a/Realm/ObjectServerTests/RLMFlexibleSyncServerTests.mm +++ /dev/null @@ -1,586 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncTestCase.h" -#import "RLMSyncSubscription_Private.h" -#import "RLMApp_Private.hpp" - -@interface TimeoutProxyServer : NSObject -- (instancetype)initWithPort:(uint16_t)port targetPort:(uint16_t)targetPort; -- (void)startAndReturnError:(NSError **)error; -- (void)stop; -@property (nonatomic) double delay; -@end - -@interface RLMFlexibleSyncTests : RLMSyncTestCase -@end - -@implementation RLMFlexibleSyncTests -- (NSArray *)defaultObjectTypes { - return @[Dog.self, Person.self, UUIDPrimaryKeyObject.self]; -} - -- (NSString *)createAppWithError:(NSError **)error { - return [self createFlexibleSyncAppWithError:error]; -} - -- (RLMRealmConfiguration *)configurationForUser:(RLMUser *)user { - return [user flexibleSyncConfiguration]; -} - -- (void)createPeople:(RLMRealm *)realm { - const int numberOfSubs = 21; - for (int i = 1; i <= numberOfSubs; ++i) { - Person *person = [[Person alloc] initWithPrimaryKey:[RLMObjectId objectId] - age:i - firstName:[NSString stringWithFormat:@"firstname_%d", i] - lastName:[NSString stringWithFormat:@"lastname_%d", i]]; - person.partition = self.name; - [realm addObject:person]; - } -} -- (void)createDog:(RLMRealm *)realm { - Dog *dog = [[Dog alloc] initWithPrimaryKey:[RLMObjectId objectId] - breed:@"Labradoodle" - name:@"Tom"]; - dog.partition = self.name; - [realm addObject:dog]; -} - -- (void)testFlexibleSyncWithoutQuery { - [self populateData:^(RLMRealm *realm) { - [self createPeople:realm]; - }]; - - RLMRealm *realm = [self openRealm]; - CHECK_COUNT(0, Person, realm); - - RLMSyncSubscriptionSet *subs = realm.subscriptions; - XCTAssertNotNil(subs); - XCTAssertEqual(subs.version, 0UL); - XCTAssertEqual(subs.count, 0UL); - - [self waitForDownloadsForRealm:realm]; - CHECK_COUNT(0, Person, realm); - CHECK_COUNT(0, Dog, realm); -} - -- (void)testFlexibleSyncAddQuery { - [self populateData:^(RLMRealm *realm) { - [self createPeople:realm]; - }]; - - RLMRealm *realm = [self openRealm]; - CHECK_COUNT(0, Person, realm); - - [self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) { - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 15 and partition == %@", self.name]; - }]; - - CHECK_COUNT(6, Person, realm); - CHECK_COUNT(0, Dog, realm); -} - -- (void)testFlexibleSyncAddMultipleQuery { - [self populateData:^(RLMRealm *realm) { - [self createPeople:realm]; - [self createDog:realm]; - }]; - - RLMRealm *realm = [self openRealm]; - CHECK_COUNT(0, Person, realm); - - [self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) { - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 10 and partition == %@", self.name]; - [subs addSubscriptionWithClassName:Dog.className - subscriptionName:@"dog_breed_labradoodle" - where:@"breed == 'Labradoodle' and partition == %@", self.name]; - }]; - - CHECK_COUNT(11, Person, realm); - CHECK_COUNT(1, Dog, realm); -} - -- (void)testFlexibleSyncRemoveQuery { - [self populateData:^(RLMRealm *realm) { - [self createPeople:realm]; - [self createDog:realm]; - }]; - - RLMRealm *realm = [self openRealm]; - CHECK_COUNT(0, Person, realm); - - [self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) { - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 5 and partition == %@", self.name]; - [subs addSubscriptionWithClassName:Dog.className - subscriptionName:@"dog_breed_labradoodle" - where:@"breed == 'Labradoodle' and partition == %@", self.name]; - }]; - CHECK_COUNT(16, Person, realm); - CHECK_COUNT(1, Dog, realm); - - [self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) { - [subs removeSubscriptionWithName:@"person_age"]; - }]; - CHECK_COUNT(0, Person, realm); - CHECK_COUNT(1, Dog, realm); -} - -- (void)testFlexibleSyncRemoveAllQueries { - [self populateData:^(RLMRealm *realm) { - [self createPeople:realm]; - [self createDog:realm]; - }]; - - RLMRealm *realm = [self openRealm]; - CHECK_COUNT(0, Person, realm); - - [self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) { - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 5 and partition == %@", self.name]; - [subs addSubscriptionWithClassName:Dog.className - subscriptionName:@"dog_breed_labradoodle" - where:@"breed == 'Labradoodle' and partition == %@", self.name]; - }]; - CHECK_COUNT(16, Person, realm); - CHECK_COUNT(1, Dog, realm); - - [self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) { - [subs removeAllSubscriptions]; - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 0 and partition == %@", self.name]; - }]; - CHECK_COUNT(21, Person, realm); - CHECK_COUNT(0, Dog, realm); -} - -- (void)testFlexibleSyncRemoveAllQueriesForType { - [self populateData:^(RLMRealm *realm) { - [self createPeople:realm]; - [self createDog:realm]; - }]; - - RLMRealm *realm = [self openRealm]; - CHECK_COUNT(0, Person, realm); - - [self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) { - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 20 and partition == %@", self.name]; - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age_2" - where:@"firstName == 'firstname_1' and partition == %@", self.name]; - [subs addSubscriptionWithClassName:Dog.className - subscriptionName:@"dog_breed_labradoodle" - where:@"breed == 'Labradoodle' and partition == %@", self.name]; - }]; - CHECK_COUNT(2, Person, realm); - CHECK_COUNT(1, Dog, realm); - - [self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) { - [subs removeAllSubscriptionsWithClassName:Person.className]; - }]; - CHECK_COUNT(0, Person, realm); - CHECK_COUNT(1, Dog, realm); -} - -- (void)testRemoveAllUnnamedSubscriptions { - [self populateData:^(RLMRealm *realm) { - [self createPeople:realm]; - [self createDog:realm]; - }]; - - RLMRealm *realm = [self openRealm]; - CHECK_COUNT(0, Person, realm); - - [self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) { - [subs addSubscriptionWithClassName:Person.className - where:@"age > 20 and partition == %@", self.name]; - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age_2" - where:@"firstName == 'firstname_1' and partition == %@", self.name]; - [subs addSubscriptionWithClassName:Dog.className - where:@"breed == 'Labradoodle' and partition == %@", self.name]; - }]; - XCTAssertEqual(realm.subscriptions.count, 3U); - CHECK_COUNT(2, Person, realm); - CHECK_COUNT(1, Dog, realm); - - [self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) { - [subs removeAllUnnamedSubscriptions]; - }]; - XCTAssertEqual(realm.subscriptions.count, 1U); - CHECK_COUNT(1, Person, realm); - CHECK_COUNT(0, Dog, realm); -} - -- (void)testFlexibleSyncUpdateQuery { - [self populateData:^(RLMRealm *realm) { - [self createPeople:realm]; - }]; - - RLMRealm *realm = [self openRealm]; - CHECK_COUNT(0, Person, realm); - - [self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) { - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 0 and partition == %@", self.name]; - }]; - CHECK_COUNT(21, Person, realm); - - [self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) { - RLMSyncSubscription *foundSub = [subs subscriptionWithName:@"person_age"]; - [foundSub updateSubscriptionWhere:@"age > 20 and partition == %@", self.name]; - }]; - CHECK_COUNT(1, Person, realm); -} - -- (void)testFlexibleSyncAddObjectOutsideQuery { - [self populateData:^(RLMRealm *realm) { - [self createPeople:realm]; - }]; - - RLMRealm *realm = [self openRealm]; - CHECK_COUNT(0, Person, realm); - - [self writeQueryAndCompleteForRealm:realm block:^(RLMSyncSubscriptionSet *subs) { - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 18 and partition == %@", self.name]; - }]; - CHECK_COUNT(3, Person, realm); - - RLMObjectId *invalidObjectPK = [RLMObjectId objectId]; - auto ex = [self expectationWithDescription:@"should revert write"]; - self.app.syncManager.errorHandler = ^(NSError *error, RLMSyncSession *) { - RLMValidateError(error, RLMSyncErrorDomain, RLMSyncErrorWriteRejected, - @"Client attempted a write that is not allowed; it has been reverted"); - NSArray *info = error.userInfo[RLMCompensatingWriteInfoKey]; - XCTAssertEqual(info.count, 1U); - XCTAssertEqualObjects(info[0].objectType, @"Person"); - XCTAssertEqualObjects(info[0].primaryKey, invalidObjectPK); - XCTAssertEqualObjects(info[0].reason, - ([NSString stringWithFormat:@"write to ObjectID(\"%@\") in table \"Person\" not allowed; object is outside of the current query view", invalidObjectPK])); - [ex fulfill]; - }; - [realm transactionWithBlock:^{ - [realm addObject:[[Person alloc] initWithPrimaryKey:invalidObjectPK age:10 firstName:@"Nic" lastName:@"Cages"]]; - }]; - [self waitForExpectations:@[ex] timeout:20]; - [self waitForDownloadsForRealm:realm]; - CHECK_COUNT(3, Person, realm); -} - -- (void)testFlexibleSyncInitialSubscription { - RLMUser *user = [self createUser]; - RLMRealmConfiguration *config = [user flexibleSyncConfigurationWithInitialSubscriptions:^(RLMSyncSubscriptionSet *subscriptions) { - [subscriptions addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"TRUEPREDICATE"]; - } rerunOnOpen:false]; - config.objectClasses = @[Person.self]; - RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; - XCTAssertEqual(realm.subscriptions.count, 1UL); -} - -- (void)testFlexibleSyncInitialSubscriptionAwait { - [self populateData:^(RLMRealm *realm) { - [self createPeople:realm]; - }]; - - RLMUser *user = [self createUser]; - RLMRealmConfiguration *config = [user flexibleSyncConfigurationWithInitialSubscriptions:^(RLMSyncSubscriptionSet *subscriptions) { - [subscriptions addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 10 and partition == %@", self.name]; - } rerunOnOpen:false]; - config.objectClasses = @[Person.self]; - XCTestExpectation *ex = [self expectationWithDescription:@"download-realm"]; - [RLMRealm asyncOpenWithConfiguration:config - callbackQueue:dispatch_get_main_queue() - callback:^(RLMRealm *realm, NSError *error) { - XCTAssertNil(error); - XCTAssertEqual(realm.subscriptions.count, 1UL); - CHECK_COUNT(11, Person, realm); - [ex fulfill]; - }]; - [self waitForExpectationsWithTimeout:30.0 handler:nil]; -} - -- (void)testFlexibleSyncInitialSubscriptionDoNotRerunOnOpen { - RLMUser *user = [self createUser]; - RLMRealmConfiguration *config = [user flexibleSyncConfigurationWithInitialSubscriptions:^(RLMSyncSubscriptionSet *subscriptions) { - [subscriptions addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 10 and partition == %@", self.name]; - } rerunOnOpen:false]; - config.objectClasses = @[Person.self]; - - @autoreleasepool { - RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; - XCTAssertEqual(realm.subscriptions.count, 1UL); - } - - RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; - XCTAssertEqual(realm.subscriptions.count, 1UL); - - [self dispatchAsyncAndWait:^{ - RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; - XCTAssertEqual(realm.subscriptions.count, 1UL); - }]; -} - -- (void)testFlexibleSyncInitialSubscriptionRerunOnOpen { - [self populateData:^(RLMRealm *realm) { - [self createPeople:realm]; - }]; - - RLMUser *user = [self createUser]; - - __block int openCount = 0; - RLMRealmConfiguration *config = [user flexibleSyncConfigurationWithInitialSubscriptions:^(RLMSyncSubscriptionSet *subscriptions) { - XCTAssertLessThan(openCount, 2); - int age = openCount == 0 ? 10 : 5; - [subscriptions addSubscriptionWithClassName:Person.className - where:@"age > %i and partition == %@", age, self.name]; - ++openCount; - } rerunOnOpen:true]; - config.objectClasses = @[Person.self]; - XCTestExpectation *ex = [self expectationWithDescription:@"download-realm"]; - [RLMRealm asyncOpenWithConfiguration:config - callbackQueue:dispatch_get_main_queue() - callback:^(RLMRealm *realm, NSError *error) { - XCTAssertNotNil(realm); - XCTAssertNil(error); - XCTAssertEqual(realm.subscriptions.count, 1UL); - CHECK_COUNT(11, Person, realm); - [ex fulfill]; - }]; - [self waitForExpectationsWithTimeout:90.0 handler:nil]; - XCTAssertEqual(openCount, 1); - - __block RLMRealm *realm; - XCTestExpectation *ex2 = [self expectationWithDescription:@"download-realm-2"]; - [RLMRealm asyncOpenWithConfiguration:config - callbackQueue:dispatch_get_main_queue() - callback:^(RLMRealm *r, NSError *error) { - realm = r; - XCTAssertNotNil(realm); - XCTAssertNil(error); - XCTAssertEqual(realm.subscriptions.count, 2UL); - CHECK_COUNT(16, Person, realm); - [ex2 fulfill]; - }]; - [self waitForExpectationsWithTimeout:90.0 handler:nil]; - XCTAssertEqual(openCount, 2); - - [self dispatchAsyncAndWait:^{ - [RLMRealm realmWithConfiguration:config error:nil]; - // Should not have called initial subscriptions despite rerunOnOpen being - // set as the Realm was already open - XCTAssertEqual(openCount, 2); - }]; -} - -- (void)testFlexibleSyncInitialOnConnectionTimeout { - TimeoutProxyServer *proxy = [[TimeoutProxyServer alloc] initWithPort:5678 targetPort:9090]; - NSError *error; - [proxy startAndReturnError:&error]; - XCTAssertNil(error); - - RLMAppConfiguration *appConfig = [[RLMAppConfiguration alloc] initWithBaseURL:@"http://localhost:9090" - transport:[AsyncOpenConnectionTimeoutTransport new] - defaultRequestTimeoutMS:60]; - RLMSyncTimeoutOptions *timeoutOptions = [RLMSyncTimeoutOptions new]; - timeoutOptions.connectTimeout = 1000.0; - appConfig.syncTimeouts = timeoutOptions; - RLMApp *app = [RLMApp appWithId:self.appId configuration:appConfig]; - RLMUser *user = [self logInUserForCredentials:[RLMCredentials anonymousCredentials] app:app]; - - RLMRealmConfiguration *config = [user flexibleSyncConfigurationWithInitialSubscriptions:^(RLMSyncSubscriptionSet *subscriptions) { - [subscriptions addSubscriptionWithClassName:Person.className - where:@"age > 10 and partition == %@", self.name]; - } rerunOnOpen:true]; - config.objectClasses = @[Person.class]; - RLMSyncConfiguration *syncConfig = config.syncConfiguration; - syncConfig.cancelAsyncOpenOnNonFatalErrors = true; - config.syncConfiguration = syncConfig; - - // Set delay above the timeout so it should fail - proxy.delay = 2.0; - - XCTestExpectation *ex = [self expectationWithDescription:@"async open"]; - [RLMRealm asyncOpenWithConfiguration:config - callbackQueue:dispatch_get_main_queue() - callback:^(RLMRealm *realm, NSError *error) { - RLMValidateError(error, NSPOSIXErrorDomain, ETIMEDOUT, - @"Sync connection was not fully established in time"); - XCTAssertNil(realm); - [ex fulfill]; - }]; - [self waitForExpectationsWithTimeout:30.0 handler:nil]; - - [proxy stop]; -} - -- (void)testSubscribeWithName { - [self populateData:^(RLMRealm *realm) { - Person *person = [[Person alloc] initWithPrimaryKey:[RLMObjectId objectId] - age:30 - firstName:@"Brian" - lastName:@"Epstein"]; - person.partition = self.name; - [realm addObject:person]; - }]; - - RLMRealm *realm = [self openRealm]; - CHECK_COUNT(0, Person, realm); - - XCTestExpectation *ex = [self expectationWithDescription:@"wait for download"]; - [[[Person allObjectsInRealm:realm] objectsWhere:@"lastName == 'Epstein'"] subscribeWithName:@"5thBeatle" onQueue:dispatch_get_main_queue() completion:^(RLMResults *results, NSError *error) { - XCTAssertNil(error); - XCTAssertEqual(results.count, 1U); - [ex fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; -} - -- (void)testUnsubscribeWithinBlock { - [self populateData:^(RLMRealm *realm) { - Person *person = [[Person alloc] initWithPrimaryKey:[RLMObjectId objectId] - age:30 - firstName:@"Joe" - lastName:@"Doe"]; - person.partition = self.name; - [realm addObject:person]; - }]; - - RLMRealm *realm = [self openRealm]; - CHECK_COUNT(0, Person, realm); - - XCTestExpectation *ex = [self expectationWithDescription:@"wait for download"]; - [[Person objectsInRealm:realm where:@"lastName == 'Doe' AND partition == %@", self.name] - subscribeWithName:@"unknown" onQueue:dispatch_get_main_queue() - completion:^(RLMResults *results, NSError *error) { - XCTAssertNil(error); - XCTAssertEqual(results.count, 1U); - [results unsubscribe]; - [ex fulfill]; - }]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; - XCTAssertEqual(realm.subscriptions.count, 0U); -} - -- (void)testSubscribeOnQueue { - [self populateData:^(RLMRealm *realm) { - Person *person = [[Person alloc] initWithPrimaryKey:[RLMObjectId objectId] - age:30 - firstName:@"Sophia" - lastName:@"Loren"]; - person.partition = self.name; - [realm addObject:person]; - }]; - - RLMUser *user = [self createUser]; - RLMRealmConfiguration *config = [user flexibleSyncConfiguration]; - config.objectClasses = @[Person.self]; - - XCTestExpectation *ex = [self expectationWithDescription:@"wait for download"]; - [self dispatchAsyncAndWait:^{ - RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; - XCTAssertNotNil(realm); - CHECK_COUNT(0, Person, realm); - - [[[Person allObjectsInRealm:realm] objectsWhere:@"lastName == 'Loren'"] - subscribeWithCompletionOnQueue:dispatch_get_main_queue() - completion:^(RLMResults *results, NSError *error) { - XCTAssertNil(error); - XCTAssertEqual(results.realm.subscriptions.count, 1UL); - XCTAssertEqual(results.realm.subscriptions.state, RLMSyncSubscriptionStateComplete); - CHECK_COUNT(1, Person, results.realm); - [ex fulfill]; - }]; - }]; - [self waitForExpectationsWithTimeout:20.0 handler:nil]; - - RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; - XCTAssertEqual(realm.subscriptions.count, 1UL); - CHECK_COUNT(1, Person, realm); -} - -- (void)testSubscribeWithNameAndTimeout { - [self populateData:^(RLMRealm *realm) { - [self createPeople:realm]; - }]; - - RLMRealm *realm = [self openRealm]; - XCTAssertNotNil(realm); - CHECK_COUNT(0, Person, realm); - - [[realm syncSession] suspend]; - XCTestExpectation *ex = [self expectationWithDescription:@"expect timeout"]; - NSTimeInterval timeout = 2.0; - RLMResults *res = [[Person allObjectsInRealm:realm] objectsWhere:@"age >= 20"]; - [res subscribeWithName:@"20up" waitForSync:RLMWaitForSyncModeAlways onQueue:dispatch_get_main_queue() timeout:timeout completion:^(RLMResults *results, NSError *error) { - XCTAssert(error); - NSString *expectedDesc = [NSString stringWithFormat:@"Waiting for update timed out after %.01f seconds.", timeout]; - XCTAssert([error.localizedDescription isEqualToString:expectedDesc]); - XCTAssertNil(results); - [ex fulfill]; - }]; - XCTAssertEqual(realm.subscriptions.count, 1UL); - [self waitForExpectationsWithTimeout:5.0 handler:nil]; - - // resume session and wait for complete - // otherwise test will not tear down successfully - [[realm syncSession] resume]; - NSDate * start = [[NSDate alloc] init]; - while (realm.subscriptions.state != RLMSyncSubscriptionStateComplete && start.timeIntervalSinceNow > -10.0) { - sleep(1); - } - XCTAssertEqual(realm.subscriptions.state, RLMSyncSubscriptionStateComplete); -} - -- (void)testFlexibleSyncInitialSubscriptionThrowsError { - RLMUser *user = [self createUser]; - RLMRealmConfiguration *config = [user flexibleSyncConfigurationWithInitialSubscriptions:^(RLMSyncSubscriptionSet *subscriptions) { - [subscriptions addSubscriptionWithClassName:UUIDPrimaryKeyObject.className - where:@"strCol == %@", @"Tom"]; - } rerunOnOpen:false]; - config.objectClasses = @[UUIDPrimaryKeyObject.self]; - XCTestExpectation *ex = [self expectationWithDescription:@"download-realm"]; - [RLMRealm asyncOpenWithConfiguration:config - callbackQueue:dispatch_get_main_queue() - callback:^(RLMRealm *realm, NSError *error) { - RLMValidateError(error, RLMErrorDomain, RLMErrorSubscriptionFailed, - @"Invalid query: unsupported query for table \"UUIDPrimaryKeyObject\": key \"strCol\" is not a queryable field"); - XCTAssertNil(realm); - [ex fulfill]; - }]; - [self waitForExpectationsWithTimeout:30.0 handler:nil]; -} -@end diff --git a/Realm/ObjectServerTests/RLMMongoClientTests.mm b/Realm/ObjectServerTests/RLMMongoClientTests.mm deleted file mode 100644 index ddaaf94c69..0000000000 --- a/Realm/ObjectServerTests/RLMMongoClientTests.mm +++ /dev/null @@ -1,801 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncTestCase.h" -#import "RLMUser+ObjectServerTests.h" -#import "RLMWatchTestUtility.h" -#import "RLMBSON_Private.hpp" -#import "RLMUser_Private.hpp" - -#import -#import -#import - -#import - -#if TARGET_OS_OSX - -@interface RLMMongoClientTests : RLMSyncTestCase -@end - -@implementation RLMMongoClientTests -- (NSArray *)defaultObjectTypes { - return @[Dog.self]; -} - -- (void)tearDown { - [self cleanupRemoteDocuments:[self.anonymousUser collectionForType:Dog.class app:self.app]]; - [super tearDown]; -} - -- (void)testFindOneAndModifyOptions { - NSDictionary> *projection = @{@"name": @1, @"breed": @1}; - NSArray> *sorting = @[@{@"age": @1}, @{@"coat": @1}]; - - RLMFindOneAndModifyOptions *findOneAndModifyOptions1 = [[RLMFindOneAndModifyOptions alloc] init]; - XCTAssertNil(findOneAndModifyOptions1.projection); - XCTAssertEqual(findOneAndModifyOptions1.sorting.count, 0U); - XCTAssertFalse(findOneAndModifyOptions1.shouldReturnNewDocument); - XCTAssertFalse(findOneAndModifyOptions1.upsert); - - RLMFindOneAndModifyOptions *findOneAndModifyOptions2 = [[RLMFindOneAndModifyOptions alloc] init]; - findOneAndModifyOptions2.projection = projection; - findOneAndModifyOptions2.sorting = sorting; - findOneAndModifyOptions2.shouldReturnNewDocument = YES; - findOneAndModifyOptions2.upsert = YES; - XCTAssertNotNil(findOneAndModifyOptions2.projection); - XCTAssertEqual(findOneAndModifyOptions2.sorting.count, 2U); - XCTAssertTrue(findOneAndModifyOptions2.shouldReturnNewDocument); - XCTAssertTrue(findOneAndModifyOptions2.upsert); - XCTAssertFalse([findOneAndModifyOptions2.projection isEqual:@{}]); - XCTAssertTrue([findOneAndModifyOptions2.projection isEqual:projection]); - XCTAssertFalse([findOneAndModifyOptions2.sorting isEqual:@{}]); - XCTAssertTrue([findOneAndModifyOptions2.sorting isEqual:sorting]); - - RLMFindOneAndModifyOptions *findOneAndModifyOptions3 = [[RLMFindOneAndModifyOptions alloc] - initWithProjection:projection - sorting:sorting - upsert:YES - shouldReturnNewDocument:YES]; - XCTAssertNotNil(findOneAndModifyOptions3.projection); - XCTAssertEqual(findOneAndModifyOptions3.sorting.count, 2U); - XCTAssertTrue(findOneAndModifyOptions3.shouldReturnNewDocument); - XCTAssertTrue(findOneAndModifyOptions3.upsert); - XCTAssertFalse([findOneAndModifyOptions3.projection isEqual:@{}]); - XCTAssertTrue([findOneAndModifyOptions3.projection isEqual:projection]); - XCTAssertFalse([findOneAndModifyOptions3.sorting isEqual:@{}]); - XCTAssertTrue([findOneAndModifyOptions3.sorting isEqual:sorting]); - - findOneAndModifyOptions3.projection = nil; - findOneAndModifyOptions3.sorting = @[]; - XCTAssertNil(findOneAndModifyOptions3.projection); - XCTAssertEqual(findOneAndModifyOptions3.sorting.count, 0U); - XCTAssertTrue([findOneAndModifyOptions3.sorting isEqual:@[]]); - - RLMFindOneAndModifyOptions *findOneAndModifyOptions4 = [[RLMFindOneAndModifyOptions alloc] - initWithProjection:nil - sorting:@[] - upsert:NO - shouldReturnNewDocument:NO]; - XCTAssertNil(findOneAndModifyOptions4.projection); - XCTAssertEqual(findOneAndModifyOptions4.sorting.count, 0U); - XCTAssertFalse(findOneAndModifyOptions4.upsert); - XCTAssertFalse(findOneAndModifyOptions4.shouldReturnNewDocument); -} - -- (void)testFindOptions { - NSDictionary> *projection = @{@"name": @1, @"breed": @1}; - NSArray> *sorting = @[@{@"age": @1}, @{@"coat": @1}]; - - RLMFindOptions *findOptions1 = [[RLMFindOptions alloc] init]; - XCTAssertNil(findOptions1.projection); - XCTAssertEqual(findOptions1.sorting.count, 0U); - XCTAssertEqual(findOptions1.limit, 0); - - findOptions1.limit = 37; - findOptions1.projection = projection; - findOptions1.sorting = sorting; - XCTAssertEqual(findOptions1.limit, 37); - XCTAssertTrue([findOptions1.projection isEqual:projection]); - XCTAssertEqual(findOptions1.sorting.count, 2U); - XCTAssertTrue([findOptions1.sorting isEqual:sorting]); - - RLMFindOptions *findOptions2 = [[RLMFindOptions alloc] initWithProjection:projection - sorting:sorting]; - XCTAssertTrue([findOptions2.projection isEqual:projection]); - XCTAssertEqual(findOptions2.sorting.count, 2U); - XCTAssertEqual(findOptions2.limit, 0); - XCTAssertTrue([findOptions2.sorting isEqual:sorting]); - - RLMFindOptions *findOptions3 = [[RLMFindOptions alloc] initWithLimit:37 - projection:projection - sorting:sorting]; - XCTAssertTrue([findOptions3.projection isEqual:projection]); - XCTAssertEqual(findOptions3.sorting.count, 2U); - XCTAssertEqual(findOptions3.limit, 37); - XCTAssertTrue([findOptions3.sorting isEqual:sorting]); - - findOptions3.projection = nil; - findOptions3.sorting = @[]; - XCTAssertNil(findOptions3.projection); - XCTAssertEqual(findOptions3.sorting.count, 0U); - - RLMFindOptions *findOptions4 = [[RLMFindOptions alloc] initWithProjection:nil - sorting:@[]]; - XCTAssertNil(findOptions4.projection); - XCTAssertEqual(findOptions4.sorting.count, 0U); - XCTAssertEqual(findOptions4.limit, 0); -} - -- (void)testMongoInsert { - RLMMongoCollection *collection = [self.anonymousUser collectionForType:Dog.class app:self.app]; - - XCTestExpectation *insertOneExpectation = [self expectationWithDescription:@"should insert one document"]; - [collection insertOneDocument:@{@"name": @"fido", @"breed": @"cane corso"} completion:^(id objectId, NSError *error) { - XCTAssertEqual(objectId.bsonType, RLMBSONTypeObjectId); - XCTAssertNotEqualObjects(((RLMObjectId *)objectId).stringValue, @""); - XCTAssertNil(error); - [insertOneExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *insertManyExpectation = [self expectationWithDescription:@"should insert one document"]; - [collection insertManyDocuments:@[ - @{@"name": @"fido", @"breed": @"cane corso"}, - @{@"name": @"fido", @"breed": @"cane corso"}, - @{@"name": @"rex", @"breed": @"tibetan mastiff"}] - completion:^(NSArray> *objectIds, NSError *error) { - XCTAssertGreaterThan(objectIds.count, 0U); - XCTAssertNil(error); - [insertManyExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *findExpectation = [self expectationWithDescription:@"should find documents"]; - RLMFindOptions *options = [[RLMFindOptions alloc] initWithLimit:0 projection:nil sorting:@[]]; - [collection findWhere:@{@"name": @"fido", @"breed": @"cane corso"} - options:options - completion:^(NSArray *documents, NSError *error) { - XCTAssertEqual(documents.count, 3U); - XCTAssertNil(error); - [findExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -- (void)testMongoFind { - RLMMongoCollection *collection = [self.anonymousUser collectionForType:Dog.class app:self.app]; - - XCTestExpectation *insertManyExpectation = [self expectationWithDescription:@"should insert one document"]; - [collection insertManyDocuments:@[ - @{@"name": @"fido", @"breed": @"cane corso"}, - @{@"name": @"fido", @"breed": @"cane corso"}, - @{@"name": @"rex", @"breed": @"tibetan mastiff"}] - completion:^(NSArray> *objectIds, NSError *error) { - XCTAssertGreaterThan(objectIds.count, 0U); - XCTAssertNil(error); - [insertManyExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *findExpectation = [self expectationWithDescription:@"should find documents"]; - RLMFindOptions *options = [[RLMFindOptions alloc] initWithLimit:0 projection:nil sorting:@[]]; - [collection findWhere:@{@"name": @"fido", @"breed": @"cane corso"} - options:options - completion:^(NSArray *documents, NSError *error) { - XCTAssertEqual(documents.count, 2U); - XCTAssertNil(error); - [findExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *findExpectation2 = [self expectationWithDescription:@"should find documents"]; - [collection findWhere:@{@"name": @"fido", @"breed": @"cane corso"} - completion:^(NSArray *documents, NSError *error) { - XCTAssertEqual(documents.count, 2U); - XCTAssertNil(error); - [findExpectation2 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *findExpectation3 = [self expectationWithDescription:@"should not find documents"]; - [collection findWhere:@{@"name": @"should not exist", @"breed": @"should not exist"} - completion:^(NSArray *documents, NSError *error) { - XCTAssertEqual(documents.count, NSUInteger(0)); - XCTAssertNil(error); - [findExpectation3 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *findExpectation4 = [self expectationWithDescription:@"should not find documents"]; - [collection findWhere:@{} - completion:^(NSArray *documents, NSError *error) { - XCTAssertGreaterThan(documents.count, 0U); - XCTAssertNil(error); - [findExpectation4 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *findOneExpectation1 = [self expectationWithDescription:@"should find documents"]; - [collection findOneDocumentWhere:@{@"name": @"fido", @"breed": @"cane corso"} - completion:^(NSDictionary *document, NSError *error) { - XCTAssertTrue([document[@"name"] isEqualToString:@"fido"]); - XCTAssertTrue([document[@"breed"] isEqualToString:@"cane corso"]); - XCTAssertNil(error); - [findOneExpectation1 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *findOneExpectation2 = [self expectationWithDescription:@"should find documents"]; - [collection findOneDocumentWhere:@{@"name": @"fido", @"breed": @"cane corso"} - options:options - completion:^(NSDictionary *document, NSError *error) { - XCTAssertTrue([document[@"name"] isEqualToString:@"fido"]); - XCTAssertTrue([document[@"breed"] isEqualToString:@"cane corso"]); - XCTAssertNil(error); - [findOneExpectation2 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -- (void)testMongoAggregateAndCount { - RLMMongoCollection *collection = [self.anonymousUser collectionForType:Dog.class app:self.app]; - - XCTestExpectation *insertManyExpectation = [self expectationWithDescription:@"should insert one document"]; - [collection insertManyDocuments:@[ - @{@"name": @"fido", @"breed": @"cane corso"}, - @{@"name": @"fido", @"breed": @"cane corso"}, - @{@"name": @"rex", @"breed": @"tibetan mastiff"}] - completion:^(NSArray> *objectIds, NSError *error) { - XCTAssertEqual(objectIds.count, 3U); - XCTAssertNil(error); - [insertManyExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *aggregateExpectation1 = [self expectationWithDescription:@"should aggregate documents"]; - [collection aggregateWithPipeline:@[@{@"name" : @"fido"}] - completion:^(NSArray *documents, NSError *error) { - RLMValidateErrorContains(error, RLMAppErrorDomain, RLMAppErrorMongoDBError, - @"Unrecognized pipeline stage name: 'name'"); - XCTAssertNil(documents); - [aggregateExpectation1 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *aggregateExpectation2 = [self expectationWithDescription:@"should aggregate documents"]; - [collection aggregateWithPipeline:@[@{@"$match" : @{@"name" : @"fido"}}, @{@"$group" : @{@"_id" : @"$name"}}] - completion:^(NSArray *documents, NSError *error) { - XCTAssertNil(error); - XCTAssertNotNil(documents); - XCTAssertGreaterThan(documents.count, 0U); - [aggregateExpectation2 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *countExpectation1 = [self expectationWithDescription:@"should aggregate documents"]; - [collection countWhere:@{@"name" : @"fido"} - completion:^(NSInteger count, NSError *error) { - XCTAssertGreaterThan(count, 0); - XCTAssertNil(error); - [countExpectation1 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *countExpectation2 = [self expectationWithDescription:@"should aggregate documents"]; - [collection countWhere:@{@"name" : @"fido"} - limit:1 - completion:^(NSInteger count, NSError *error) { - XCTAssertEqual(count, 1); - XCTAssertNil(error); - [countExpectation2 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -- (void)testMongoUpdate { - RLMMongoCollection *collection = [self.anonymousUser collectionForType:Dog.class app:self.app]; - - XCTestExpectation *updateExpectation1 = [self expectationWithDescription:@"should update document"]; - [collection updateOneDocumentWhere:@{@"name" : @"scrabby doo"} - updateDocument:@{@"name" : @"scooby"} - upsert:YES - completion:^(RLMUpdateResult *result, NSError *error) { - XCTAssertNotNil(result); - XCTAssertNotNil(result.documentId); - XCTAssertEqual(result.modifiedCount, (NSUInteger)0); - XCTAssertEqual(result.matchedCount, (NSUInteger)0); - XCTAssertNil(error); - [updateExpectation1 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *updateExpectation2 = [self expectationWithDescription:@"should update document"]; - [collection updateOneDocumentWhere:@{@"name" : @"scooby"} - updateDocument:@{@"name" : @"fred"} - upsert:NO - completion:^(RLMUpdateResult *result, NSError *error) { - XCTAssertNotNil(result); - XCTAssertNil(result.documentId); - XCTAssertEqual(result.modifiedCount, (NSUInteger)1); - XCTAssertEqual(result.matchedCount, (NSUInteger)1); - XCTAssertNil(error); - [updateExpectation2 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *updateExpectation3 = [self expectationWithDescription:@"should update document"]; - [collection updateOneDocumentWhere:@{@"name" : @"fred"} - updateDocument:@{@"name" : @"scrabby"} - completion:^(RLMUpdateResult *result, NSError *error) { - XCTAssertNotNil(result); - XCTAssertNil(result.documentId); - XCTAssertEqual(result.modifiedCount, (NSUInteger)1); - XCTAssertEqual(result.matchedCount, (NSUInteger)1); - XCTAssertNil(error); - [updateExpectation3 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *updateManyExpectation1 = [self expectationWithDescription:@"should update many documents"]; - [collection updateManyDocumentsWhere:@{@"name" : @"scrabby"} - updateDocument:@{@"name" : @"fred"} - completion:^(RLMUpdateResult *result, NSError *error) { - XCTAssertNotNil(result); - XCTAssertNil(result.documentId); - XCTAssertEqual(result.modifiedCount, (NSUInteger)1); - XCTAssertEqual(result.matchedCount, (NSUInteger)1); - XCTAssertNil(error); - [updateManyExpectation1 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *updateManyExpectation2 = [self expectationWithDescription:@"should update many documents"]; - [collection updateManyDocumentsWhere:@{@"name" : @"john"} - updateDocument:@{@"name" : @"alex"} - upsert:YES - completion:^(RLMUpdateResult *result, NSError *error) { - XCTAssertNotNil(result); - XCTAssertNotNil(result.documentId); - XCTAssertEqual(result.modifiedCount, (NSUInteger)0); - XCTAssertEqual(result.matchedCount, (NSUInteger)0); - XCTAssertNil(error); - [updateManyExpectation2 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -- (void)testMongoFindAndModify { - RLMMongoCollection *collection = [self.anonymousUser collectionForType:Dog.class app:self.app]; - - NSArray> *sorting = @[@{@"name": @1}, @{@"breed": @1}]; - RLMFindOneAndModifyOptions *findAndModifyOptions = [[RLMFindOneAndModifyOptions alloc] - initWithProjection:@{@"name" : @1, @"breed" : @1} - sorting:sorting - upsert:YES - shouldReturnNewDocument:YES]; - - XCTestExpectation *findOneAndUpdateExpectation1 = [self expectationWithDescription:@"should find one document and update"]; - [collection findOneAndUpdateWhere:@{@"name" : @"alex"} - updateDocument:@{@"name" : @"max"} - options:findAndModifyOptions - completion:^(NSDictionary *document, NSError *error) { - XCTAssertTrue([document[@"name"] isEqualToString:@"max"]); - XCTAssertNil(error); - [findOneAndUpdateExpectation1 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *findOneAndUpdateExpectation2 = [self expectationWithDescription:@"should find one document and update"]; - [collection findOneAndUpdateWhere:@{@"name" : @"max"} - updateDocument:@{@"name" : @"john"} - completion:^(NSDictionary *document, NSError *error) { - XCTAssertTrue([document[@"name"] isEqualToString:@"max"]); - XCTAssertNil(error); - [findOneAndUpdateExpectation2 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *findOneAndReplaceExpectation1 = [self expectationWithDescription:@"should find one document and replace"]; - [collection findOneAndReplaceWhere:@{@"name" : @"alex"} - replacementDocument:@{@"name" : @"max"} - options:findAndModifyOptions - completion:^(NSDictionary *document, NSError *error) { - XCTAssertTrue([document[@"name"] isEqualToString:@"max"]); - XCTAssertNil(error); - [findOneAndReplaceExpectation1 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *findOneAndReplaceExpectation2 = [self expectationWithDescription:@"should find one document and replace"]; - [collection findOneAndReplaceWhere:@{@"name" : @"max"} - replacementDocument:@{@"name" : @"john"} - completion:^(NSDictionary *document, NSError *error) { - XCTAssertTrue([document[@"name"] isEqualToString:@"max"]); - XCTAssertNil(error); - [findOneAndReplaceExpectation2 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -- (void)testMongoDelete { - RLMMongoCollection *collection = [self.anonymousUser collectionForType:Dog.class app:self.app]; - - NSArray *objectIds = [self prepareDogDocumentsIn:collection]; - RLMObjectId *rexObjectId = objectIds[1]; - - XCTestExpectation *deleteOneExpectation1 = [self expectationWithDescription:@"should delete first document in collection"]; - [collection deleteOneDocumentWhere:@{@"_id" : rexObjectId} - completion:^(NSInteger count, NSError *error) { - XCTAssertEqual(count, 1); - XCTAssertNil(error); - [deleteOneExpectation1 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *findExpectation1 = [self expectationWithDescription:@"should find documents"]; - [collection findWhere:@{} - completion:^(NSArray *documents, NSError *error) { - XCTAssertEqual(documents.count, 2U); - XCTAssertTrue([documents[0][@"name"] isEqualToString:@"fido"]); - XCTAssertNil(error); - [findExpectation1 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *deleteManyExpectation1 = [self expectationWithDescription:@"should delete many documents"]; - [collection deleteManyDocumentsWhere:@{@"name" : @"rex"} - completion:^(NSInteger count, NSError *error) { - XCTAssertEqual(count, 0U); - XCTAssertNil(error); - [deleteManyExpectation1 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *deleteManyExpectation2 = [self expectationWithDescription:@"should delete many documents"]; - [collection deleteManyDocumentsWhere:@{@"breed" : @"cane corso"} - completion:^(NSInteger count, NSError *error) { - XCTAssertEqual(count, 1); - XCTAssertNil(error); - [deleteManyExpectation2 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *findOneAndDeleteExpectation1 = [self expectationWithDescription:@"should find one and delete"]; - [collection findOneAndDeleteWhere:@{@"name": @"john"} - completion:^(NSDictionary> *document, NSError *error) { - XCTAssertNotNil(document); - NSString *name = (NSString *)document[@"name"]; - XCTAssertTrue([name isEqualToString:@"john"]); - XCTAssertNil(error); - [findOneAndDeleteExpectation1 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; - - XCTestExpectation *findOneAndDeleteExpectation2 = [self expectationWithDescription:@"should find one and delete"]; - NSDictionary> *projection = @{@"name": @1, @"breed": @1}; - NSArray> *sortDescriptors = @[@{@"_id": @1}, @{@"breed": @1}]; - RLMFindOneAndModifyOptions *findOneAndModifyOptions = [[RLMFindOneAndModifyOptions alloc] - initWithProjection:projection - sorting:sortDescriptors - upsert:YES - shouldReturnNewDocument:YES]; - - [collection findOneAndDeleteWhere:@{@"name": @"john"} - options:findOneAndModifyOptions - completion:^(NSDictionary> *document, NSError *error) { - XCTAssertNil(document); - // FIXME: when a projection is used, the server reports the error - // "expected pre-image to match projection matcher" when there are no - // matches, rather than simply doing nothing like when there is no projection -// XCTAssertNil(error); - (void)error; - [findOneAndDeleteExpectation2 fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -#pragma mark - Watch - -- (void)testWatch { - [self performWatchTest:nil]; -} - -- (void)testWatchAsync { - auto asyncQueue = dispatch_queue_create("io.realm.watchQueue", DISPATCH_QUEUE_CONCURRENT); - [self performWatchTest:asyncQueue]; -} - -- (void)performWatchTest:(nullable dispatch_queue_t)delegateQueue { - XCTestExpectation *expectation = [self expectationWithDescription:@"watch collection and receive change event 3 times"]; - - RLMMongoCollection *collection = [self.anonymousUser collectionForType:Dog.class app:self.app]; - - RLMWatchTestUtility *testUtility = - [[RLMWatchTestUtility alloc] initWithChangeEventCount:3 - expectation:expectation]; - - RLMChangeStream *changeStream = [collection watchWithDelegate:testUtility delegateQueue:delegateQueue]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - WAIT_FOR_SEMAPHORE(testUtility.isOpenSemaphore, 30.0); - for (int i = 0; i < 3; i++) { - [collection insertOneDocument:@{@"name": @"fido"} completion:^(id objectId, NSError *error) { - XCTAssertNil(error); - XCTAssertNotNil(objectId); - }]; - WAIT_FOR_SEMAPHORE(testUtility.semaphore, 30.0); - } - [changeStream close]; - }); - - [self waitForExpectations:@[expectation] timeout:60.0]; -} - -- (void)testWatchWithMatchFilter { - [self performWatchWithMatchFilterTest:nil]; -} - -- (void)testWatchWithMatchFilterAsync { - auto asyncQueue = dispatch_queue_create("io.realm.watchQueue", DISPATCH_QUEUE_CONCURRENT); - [self performWatchWithMatchFilterTest:asyncQueue]; -} - -- (NSArray *)prepareDogDocumentsIn:(RLMMongoCollection *)collection { - __block NSArray *objectIds; - XCTestExpectation *ex = [self expectationWithDescription:@"delete existing documents"]; - [collection deleteManyDocumentsWhere:@{} completion:^(NSInteger, NSError *error) { - XCTAssertNil(error); - [ex fulfill]; - }]; - [self waitForExpectations:@[ex] timeout:60.0]; - - XCTestExpectation *insertManyExpectation = [self expectationWithDescription:@"should insert documents"]; - [collection insertManyDocuments:@[ - @{@"name": @"fido", @"breed": @"cane corso"}, - @{@"name": @"rex", @"breed": @"tibetan mastiff"}, - @{@"name": @"john", @"breed": @"tibetan mastiff"}] - completion:^(NSArray> *ids, NSError *error) { - XCTAssertEqual(ids.count, 3U); - for (id objectId in ids) { - XCTAssertEqual(objectId.bsonType, RLMBSONTypeObjectId); - } - XCTAssertNil(error); - objectIds = (NSArray *)ids; - [insertManyExpectation fulfill]; - }]; - [self waitForExpectations:@[insertManyExpectation] timeout:60.0]; - return objectIds; -} - -- (void)performWatchWithMatchFilterTest:(nullable dispatch_queue_t)delegateQueue { - RLMMongoCollection *collection = [self.anonymousUser collectionForType:Dog.class app:self.app]; - NSArray *objectIds = [self prepareDogDocumentsIn:collection]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"watch collection and receive change event 3 times"]; - - RLMWatchTestUtility *testUtility = - [[RLMWatchTestUtility alloc] initWithChangeEventCount:3 - matchingObjectId:objectIds[0] - expectation:expectation]; - - RLMChangeStream *changeStream = [collection watchWithMatchFilter:@{@"fullDocument._id": objectIds[0]} - delegate:testUtility - delegateQueue:delegateQueue]; - - dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - WAIT_FOR_SEMAPHORE(testUtility.isOpenSemaphore, 30.0); - for (int i = 0; i < 3; i++) { - [collection updateOneDocumentWhere:@{@"_id": objectIds[0]} - updateDocument:@{@"breed": @"king charles", @"name": [NSString stringWithFormat:@"fido-%d", i]} - completion:^(RLMUpdateResult *, NSError *error) { - XCTAssertNil(error); - }]; - - [collection updateOneDocumentWhere:@{@"_id": objectIds[1]} - updateDocument:@{@"breed": @"french bulldog", @"name": [NSString stringWithFormat:@"fido-%d", i]} - completion:^(RLMUpdateResult *, NSError *error) { - XCTAssertNil(error); - }]; - WAIT_FOR_SEMAPHORE(testUtility.semaphore, 30.0); - } - [changeStream close]; - }); - [self waitForExpectations:@[expectation] timeout:60.0]; -} - -- (void)testWatchWithFilterIds { - [self performWatchWithFilterIdsTest:nil]; -} - -- (void)testWatchWithFilterIdsAsync { - auto asyncQueue = dispatch_queue_create("io.realm.watchQueue", DISPATCH_QUEUE_CONCURRENT); - [self performWatchWithFilterIdsTest:asyncQueue]; -} - -- (void)performWatchWithFilterIdsTest:(nullable dispatch_queue_t)delegateQueue { - RLMMongoCollection *collection = [self.anonymousUser collectionForType:Dog.class app:self.app]; - NSArray *objectIds = [self prepareDogDocumentsIn:collection]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"watch collection and receive change event 3 times"]; - - RLMWatchTestUtility *testUtility = - [[RLMWatchTestUtility alloc] initWithChangeEventCount:3 - matchingObjectId:objectIds[0] - expectation:expectation]; - - RLMChangeStream *changeStream = [collection watchWithFilterIds:@[objectIds[0]] - delegate:testUtility - delegateQueue:delegateQueue]; - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - WAIT_FOR_SEMAPHORE(testUtility.isOpenSemaphore, 30.0); - for (int i = 0; i < 3; i++) { - [collection updateOneDocumentWhere:@{@"_id": objectIds[0]} - updateDocument:@{@"breed": @"king charles", @"name": [NSString stringWithFormat:@"fido-%d", i]} - completion:^(RLMUpdateResult *, NSError *error) { - XCTAssertNil(error); - }]; - - [collection updateOneDocumentWhere:@{@"_id": objectIds[1]} - updateDocument:@{@"breed": @"french bulldog", @"name": [NSString stringWithFormat:@"fido-%d", i]} - completion:^(RLMUpdateResult *, NSError *error) { - XCTAssertNil(error); - }]; - WAIT_FOR_SEMAPHORE(testUtility.semaphore, 30.0); - } - [changeStream close]; - }); - - [self waitForExpectations:@[expectation] timeout:60.0]; -} - -- (void)testMultipleWatchStreams { - auto asyncQueue = dispatch_queue_create("io.realm.watchQueue", DISPATCH_QUEUE_CONCURRENT); - [self performMultipleWatchStreamsTest:asyncQueue]; -} - -- (void)testMultipleWatchStreamsAsync { - [self performMultipleWatchStreamsTest:nil]; -} - -- (void)performMultipleWatchStreamsTest:(nullable dispatch_queue_t)delegateQueue { - RLMMongoCollection *collection = [self.anonymousUser collectionForType:Dog.class app:self.app]; - NSArray *objectIds = [self prepareDogDocumentsIn:collection]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"watch collection and receive change event 3 times"]; - expectation.expectedFulfillmentCount = 2; - - RLMWatchTestUtility *testUtility1 = - [[RLMWatchTestUtility alloc] initWithChangeEventCount:3 - matchingObjectId:objectIds[0] - expectation:expectation]; - - RLMWatchTestUtility *testUtility2 = - [[RLMWatchTestUtility alloc] initWithChangeEventCount:3 - matchingObjectId:objectIds[1] - expectation:expectation]; - - RLMChangeStream *changeStream1 = [collection watchWithFilterIds:@[objectIds[0]] - delegate:testUtility1 - delegateQueue:delegateQueue]; - - RLMChangeStream *changeStream2 = [collection watchWithFilterIds:@[objectIds[1]] - delegate:testUtility2 - delegateQueue:delegateQueue]; - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - WAIT_FOR_SEMAPHORE(testUtility1.isOpenSemaphore, 30.0); - WAIT_FOR_SEMAPHORE(testUtility2.isOpenSemaphore, 30.0); - for (int i = 0; i < 3; i++) { - [collection updateOneDocumentWhere:@{@"_id": objectIds[0]} - updateDocument:@{@"breed": @"king charles", @"name": [NSString stringWithFormat:@"fido-%d", i]} - completion:^(RLMUpdateResult *, NSError *error) { - XCTAssertNil(error); - }]; - - [collection updateOneDocumentWhere:@{@"_id": objectIds[1]} - updateDocument:@{@"breed": @"french bulldog", @"name": [NSString stringWithFormat:@"fido-%d", i]} - completion:^(RLMUpdateResult *, NSError *error) { - XCTAssertNil(error); - }]; - - [collection updateOneDocumentWhere:@{@"_id": objectIds[2]} - updateDocument:@{@"breed": @"german shepard", @"name": [NSString stringWithFormat:@"fido-%d", i]} - completion:^(RLMUpdateResult *, NSError *error) { - XCTAssertNil(error); - }]; - WAIT_FOR_SEMAPHORE(testUtility1.semaphore, 30.0); - WAIT_FOR_SEMAPHORE(testUtility2.semaphore, 30.0); - } - [changeStream1 close]; - [changeStream2 close]; - }); - - [self waitForExpectations:@[expectation] timeout:60.0]; -} - -#pragma mark - File paths - -static NSString *newPathForPartitionValue(RLMUser *user, id partitionValue) { - std::stringstream s; - s << RLMConvertRLMBSONToBson(partitionValue); - // Intentionally not passing the correct partition value here as we (accidentally?) - // don't use the filename generated from the partition value - realm::SyncConfig config(user.user, "null"); - return @(user.user->path_for_realm(config, s.str()).c_str()); -} - -- (void)testSyncFilePaths { - RLMUser *user = self.anonymousUser; - auto configuration = [user configurationWithPartitionValue:@"abc"]; - XCTAssertTrue([configuration.fileURL.path - hasSuffix:([NSString stringWithFormat:@"mongodb-realm/%@/%@/%%22abc%%22.realm", - self.appId, user.identifier])]); - configuration = [user configurationWithPartitionValue:@123]; - XCTAssertTrue([configuration.fileURL.path - hasSuffix:([NSString stringWithFormat:@"mongodb-realm/%@/%@/%@.realm", - self.appId, user.identifier, @"%7B%22%24numberInt%22%3A%22123%22%7D"])]); - configuration = [user configurationWithPartitionValue:nil]; - XCTAssertTrue([configuration.fileURL.path - hasSuffix:([NSString stringWithFormat:@"mongodb-realm/%@/%@/null.realm", - self.appId, user.identifier])]); - - XCTAssertEqualObjects([user configurationWithPartitionValue:@"abc"].fileURL.path, - newPathForPartitionValue(user, @"abc")); - XCTAssertEqualObjects([user configurationWithPartitionValue:@123].fileURL.path, - newPathForPartitionValue(user, @123)); - XCTAssertEqualObjects([user configurationWithPartitionValue:nil].fileURL.path, - newPathForPartitionValue(user, nil)); -} - -static NSString *oldPathForPartitionValue(RLMUser *user, NSString *oldName) { - realm::SyncConfig config(user.user, "null"); - return [NSString stringWithFormat:@"%@/%s%@.realm", - [@(user.user->path_for_realm(config).c_str()) stringByDeletingLastPathComponent], - user.user->user_id().c_str(), oldName]; -} - -- (void)testLegacyFilePathsAreUsedIfFilesArePresent { - RLMUser *user = self.anonymousUser; - - auto testPartitionValue = [&](id partitionValue, NSString *oldName) { - NSURL *url = [NSURL fileURLWithPath:oldPathForPartitionValue(user, oldName)]; - @autoreleasepool { - auto configuration = [user configurationWithPartitionValue:partitionValue]; - configuration.fileURL = url; - configuration.objectClasses = @[Person.class]; - RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; - [realm beginWriteTransaction]; - [Person createInRealm:realm withValue:[Person george]]; - [realm commitWriteTransaction]; - } - - auto configuration = [user configurationWithPartitionValue:partitionValue]; - configuration.objectClasses = @[Person.class]; - XCTAssertEqualObjects(configuration.fileURL, url); - RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; - XCTAssertEqual([Person allObjectsInRealm:realm].count, 1U); - }; - - testPartitionValue(@"abc", @"%2F%2522abc%2522"); - testPartitionValue(@123, @"%2F%257B%2522%24numberInt%2522%253A%2522123%2522%257D"); - testPartitionValue(nil, @"%2Fnull"); -} -@end - -#endif // TARGET_OS_OSX diff --git a/Realm/ObjectServerTests/RLMObjectServerPartitionTests.mm b/Realm/ObjectServerTests/RLMObjectServerPartitionTests.mm deleted file mode 100644 index 61978c2053..0000000000 --- a/Realm/ObjectServerTests/RLMObjectServerPartitionTests.mm +++ /dev/null @@ -1,83 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncTestCase.h" - -#import "RLMApp_Private.hpp" - -#if TARGET_OS_OSX - -#pragma mark ObjectServer Partition Tests - -@interface RLMObjectServerPartitionTests : RLMSyncTestCase -@end - -@implementation RLMObjectServerPartitionTests - -- (void)roundTripForPartitionValue:(id)value { - NSError *error; - NSString *appId = [RealmServer.shared - createAppWithPartitionKeyType:[self partitionBsonType:value] - types:@[Person.self] persistent:false error:&error]; - if (error) { - return XCTFail(@"Could not create app for partition value %@d", value); - } - - RLMApp *app = [self appWithId:appId]; - RLMUser *user = [self createUserForApp:app]; - RLMRealm *realm = [self openRealmForPartitionValue:value user:user]; - CHECK_COUNT(0, Person, realm); - - RLMUser *writeUser = [self createUserForApp:app]; - RLMRealm *writeRealm = [self openRealmForPartitionValue:value user:writeUser]; - - auto write = [&] { - [self addPersonsToRealm:writeRealm - persons:@[[Person john], [Person paul], [Person ringo]]]; - [self waitForUploadsForRealm:writeRealm]; - [self waitForDownloadsForRealm:realm]; - }; - - write(); - CHECK_COUNT(3, Person, realm); - XCTAssertEqual([Person objectsInRealm:realm where:@"firstName = 'John'"].count, 1UL); - - write(); - CHECK_COUNT(6, Person, realm); - XCTAssertEqual([Person objectsInRealm:realm where:@"firstName = 'John'"].count, 2UL); -} - -- (void)testRoundTripForObjectIdPartitionValue { - [self roundTripForPartitionValue:[[RLMObjectId alloc] initWithString:@"1234567890ab1234567890ab" error:nil]]; -} - -- (void)testRoundTripForUUIDPartitionValue { - [self roundTripForPartitionValue:[[NSUUID alloc] initWithUUIDString:@"85d4fbee-6ec6-47df-bfa1-615931903d7e"]]; -} - -- (void)testRoundTripForStringPartitionValue { - [self roundTripForPartitionValue:@"1234567890ab1234567890ab"]; -} - -- (void)testRoundTripForIntPartitionValue { - [self roundTripForPartitionValue:@1234567890]; -} - -@end - -#endif // TARGET_OS_OSX diff --git a/Realm/ObjectServerTests/RLMObjectServerTests.mm b/Realm/ObjectServerTests/RLMObjectServerTests.mm deleted file mode 100644 index 0eefd0939d..0000000000 --- a/Realm/ObjectServerTests/RLMObjectServerTests.mm +++ /dev/null @@ -1,2121 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncTestCase.h" -#import "RLMUser+ObjectServerTests.h" - -#if TARGET_OS_OSX - -#import "RLMApp_Private.hpp" -#import "RLMBSON_Private.hpp" -#import "RLMCredentials.h" -#import "RLMObjectSchema_Private.hpp" -#import "RLMRealm+Sync.h" -#import "RLMRealmConfiguration_Private.hpp" -#import "RLMRealmUtil.hpp" -#import "RLMRealm_Dynamic.h" -#import "RLMRealm_Private.hpp" -#import "RLMSchema_Private.h" -#import "RLMSyncConfiguration_Private.hpp" -#import "RLMSyncManager_Private.hpp" -#import "RLMUser_Private.hpp" -#import "RLMWatchTestUtility.h" - -#import -#import -#import -#import -#import - -#import - -#pragma mark - Helpers - -@interface TimeoutProxyServer : NSObject -- (instancetype)initWithPort:(uint16_t)port targetPort:(uint16_t)targetPort; -- (void)startAndReturnError:(NSError **)error; -- (void)stop; -@property (nonatomic) double delay; -@end - -@interface RLMObjectServerTests : RLMSyncTestCase -@end -@implementation RLMObjectServerTests - -- (NSArray *)defaultObjectTypes { - return @[ - AllTypesSyncObject.class, - HugeSyncObject.class, - IntPrimaryKeyObject.class, - Person.class, - RLMSetSyncObject.class, - StringPrimaryKeyObject.class, - UUIDPrimaryKeyObject.class, - ]; -} - -#pragma mark - App Tests - -static NSString *generateRandomString(int num) { - NSMutableString *string = [NSMutableString stringWithCapacity:num]; - for (int i = 0; i < num; i++) { - [string appendFormat:@"%c", (char)('a' + arc4random_uniform(26))]; - } - return string; -} - -#pragma mark - Authentication and Tokens - -- (void)testUpdateBaseUrl { - RLMApp *app = self.app; - XCTAssertEqualObjects(app.baseURL, @"http://localhost:9090"); - - XCTestExpectation *expectation = [self expectationWithDescription:@"should update base url"]; - [app updateBaseURL:@"http://127.0.0.1:9090" completion:^(NSError *error) { - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectations:@[expectation]]; - XCTAssertEqualObjects(app.baseURL, @"http://127.0.0.1:9090"); - - TimeoutProxyServer *proxy = [[TimeoutProxyServer alloc] initWithPort:7070 targetPort:9090]; - proxy.delay = 0; - [proxy startAndReturnError:nil]; - - expectation = [self expectationWithDescription:@"should update base url"]; - [app updateBaseURL:@"http://localhost:7070/" completion:^(NSError *error) { - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectations:@[expectation]]; - XCTAssertEqualObjects(app.baseURL, @"http://localhost:7070"); - [proxy stop]; - - expectation = [self expectationWithDescription:@"should fail to update base url to default value"]; - [app updateBaseURL:nil completion:^(NSError *error) { - // This fails because our local app doesn't exist in the prod env - XCTAssertNotNil(error); - XCTAssertEqual(error.code, RLMAppErrorUnknown); - [expectation fulfill]; - }]; - [self waitForExpectations:@[expectation]]; - // baseURL update failed, so it's left unchanged - XCTAssertEqualObjects(app.baseURL, @"http://localhost:7070"); -} - -- (void)testAnonymousAuthentication { - RLMUser *syncUser = self.anonymousUser; - RLMUser *currentUser = [self.app currentUser]; - XCTAssert([currentUser.identifier isEqualToString:syncUser.identifier]); - XCTAssert([currentUser.refreshToken isEqualToString:syncUser.refreshToken]); - XCTAssert([currentUser.accessToken isEqualToString:syncUser.accessToken]); -} - -- (void)testCustomTokenAuthentication { - RLMUser *user = [self logInUserForCredentials:[self jwtCredentialWithAppId:self.appId]]; - XCTAssertTrue([user.profile.metadata[@"anotherName"] isEqualToString:@"Bar Foo"]); - XCTAssertTrue([user.profile.metadata[@"name"] isEqualToString:@"Foo Bar"]); - XCTAssertTrue([user.profile.metadata[@"occupation"] isEqualToString:@"firefighter"]); -} - -- (void)testCallFunction { - XCTestExpectation *expectation = [self expectationWithDescription:@"should get sum of arguments from remote function"]; - [self.anonymousUser callFunctionNamed:@"sum" - arguments:@[@1, @2, @3, @4, @5] - completionBlock:^(id bson, NSError *error) { - XCTAssert(!error); - XCTAssertEqual([((NSNumber *)bson) intValue], 15); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:30.0 handler:nil]; -} - -- (void)testLogoutCurrentUser { - RLMUser *user = self.anonymousUser; - XCTestExpectation *expectation = [self expectationWithDescription:@"should log out current user"]; - [self.app.currentUser logOutWithCompletion:^(NSError *error) { - XCTAssertNil(error); - XCTAssertEqual(user.state, RLMUserStateRemoved); - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -- (void)testLogoutSpecificUser { - RLMUser *firstUser = [self createUser]; - RLMUser *secondUser = [self createUser]; - - XCTAssertEqualObjects(self.app.currentUser.identifier, secondUser.identifier); - // `[app currentUser]` will now be `secondUser`, so let's logout firstUser and ensure - // the state is correct - XCTestExpectation *expectation = [self expectationWithDescription:@"should log out current user"]; - [firstUser logOutWithCompletion:^(NSError *error) { - XCTAssertNil(error); - XCTAssertEqual(firstUser.state, RLMUserStateLoggedOut); - XCTAssertEqual(secondUser.state, RLMUserStateLoggedIn); - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:30.0 handler:nil]; -} - -- (void)testSwitchUser { - RLMUser *syncUserA = [self createUser]; - RLMUser *syncUserB = [self createUser]; - - XCTAssertNotEqualObjects(syncUserA.identifier, syncUserB.identifier); - XCTAssertEqualObjects(self.app.currentUser.identifier, syncUserB.identifier); - - XCTAssertEqualObjects([self.app switchToUser:syncUserA].identifier, syncUserA.identifier); -} - -- (void)testRemoveUser { - RLMUser *firstUser = [self createUser]; - RLMUser *secondUser = [self createUser]; - - XCTAssert([self.app.currentUser.identifier isEqualToString:secondUser.identifier]); - - XCTestExpectation *removeUserExpectation = [self expectationWithDescription:@"should remove user"]; - - [secondUser removeWithCompletion:^(NSError *error) { - XCTAssert(!error); - XCTAssert(self.app.allUsers.count == 1); - XCTAssert([self.app.currentUser.identifier isEqualToString:firstUser.identifier]); - [removeUserExpectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -- (void)testDeleteUser { - RLMUser *firstUser = [self createUser]; - RLMUser *secondUser = [self createUser]; - - XCTAssert([self.app.currentUser.identifier isEqualToString:secondUser.identifier]); - - XCTestExpectation *deleteUserExpectation = [self expectationWithDescription:@"should delete user"]; - - [secondUser deleteWithCompletion:^(NSError *error) { - XCTAssert(!error); - XCTAssert(self.app.allUsers.count == 1); - XCTAssertEqualObjects(self.app.currentUser, firstUser); - XCTAssertEqual(secondUser.state, RLMUserStateRemoved); - [deleteUserExpectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -- (void)testDeviceRegistration { - RLMPushClient *client = [self.app pushClientWithServiceName:@"gcm"]; - auto expectation = [self expectationWithDescription:@"should register device"]; - [client registerDeviceWithToken:@"token" user:self.anonymousUser completion:^(NSError *error) { - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:30.0 handler:nil]; - - expectation = [self expectationWithDescription:@"should deregister device"]; - [client deregisterDeviceForUser:self.app.currentUser completion:^(NSError *error) { - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:30.0 handler:nil]; -} - -// FIXME: Reenable once possible underlying race condition is understood -- (void)fixme_testMultipleRegisterDevice { - RLMApp *app = self.app; - XCTestExpectation *registerExpectation = [self expectationWithDescription:@"should register device"]; - XCTestExpectation *secondRegisterExpectation = [self expectationWithDescription:@"should not throw error when attempting to register again"]; - - RLMUser *user = self.anonymousUser; - RLMPushClient *client = [app pushClientWithServiceName:@"gcm"]; - [client registerDeviceWithToken:@"token" user:user completion:^(NSError *_Nullable error) { - XCTAssertNil(error); - [registerExpectation fulfill]; - }]; - [self waitForExpectations:@[registerExpectation] timeout:10.0]; - - [client registerDeviceWithToken:@"token" user:user completion:^(NSError *_Nullable error) { - XCTAssertNil(error); - [secondRegisterExpectation fulfill]; - }]; - [self waitForExpectations:@[secondRegisterExpectation] timeout:10.0]; -} - -#pragma mark - RLMEmailPasswordAuth - -static NSString *randomEmail() { - return [NSString stringWithFormat:@"%@@%@.com", generateRandomString(10), generateRandomString(10)]; -} - -- (void)testRegisterEmailAndPassword { - XCTestExpectation *expectation = [self expectationWithDescription:@"should register with email and password"]; - - NSString *randomPassword = generateRandomString(10); - [self.app.emailPasswordAuth registerUserWithEmail:randomEmail() password:randomPassword completion:^(NSError *error) { - XCTAssert(!error); - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -- (void)testConfirmUser { - XCTestExpectation *expectation = [self expectationWithDescription:@"should try confirm user and fail"]; - - [self.app.emailPasswordAuth confirmUser:randomEmail() tokenId:@"a_token" completion:^(NSError *error) { - XCTAssertEqual(error.code, RLMAppErrorBadRequest); - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -- (void)testRetryCustomConfirmation { - XCTestExpectation *expectation = [self expectationWithDescription:@"should try retry confirmation email and fail"]; - - [self.app.emailPasswordAuth retryCustomConfirmation:@"some-email@email.com" completion:^(NSError *error) { - XCTAssertTrue([error.userInfo[@"NSLocalizedDescription"] isEqualToString:@"cannot run confirmation for some-email@email.com: automatic confirmation is enabled"]); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -- (void)testResendConfirmationEmail { - XCTestExpectation *expectation = [self expectationWithDescription:@"should try resend confirmation email and fail"]; - - [self.app.emailPasswordAuth resendConfirmationEmail:randomEmail() completion:^(NSError *error) { - XCTAssertEqual(error.code, RLMAppErrorUserNotFound); - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -- (void)testResetPassword { - XCTestExpectation *expectation = [self expectationWithDescription:@"should try reset password and fail"]; - [self.app.emailPasswordAuth resetPasswordTo:@"APassword123" token:@"a_token" tokenId:@"a_token_id" completion:^(NSError *error) { - XCTAssertEqual(error.code, RLMAppErrorBadRequest); - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -- (void)testCallResetPasswordFunction { - XCTestExpectation *expectation = [self expectationWithDescription:@"should try call reset password function and fail"]; - [self.app.emailPasswordAuth callResetPasswordFunction:@"test@mongodb.com" - password:@"aPassword123" - args:@[@{}] - completion:^(NSError *error) { - XCTAssertEqual(error.code, RLMAppErrorUserNotFound); - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -#pragma mark - UserAPIKeyProviderClient - -- (void)testUserAPIKeyProviderClientFlow { - XCTestExpectation *registerExpectation = [self expectationWithDescription:@"should try register"]; - XCTestExpectation *loginExpectation = [self expectationWithDescription:@"should try login"]; - XCTestExpectation *createAPIKeyExpectationA = [self expectationWithDescription:@"should try create an api key"]; - XCTestExpectation *createAPIKeyExpectationB = [self expectationWithDescription:@"should try create an api key"]; - XCTestExpectation *fetchAPIKeysExpectation = [self expectationWithDescription:@"should try call fetch api keys"]; - XCTestExpectation *disableAPIKeyExpectation = [self expectationWithDescription:@"should try disable api key"]; - XCTestExpectation *enableAPIKeyExpectation = [self expectationWithDescription:@"should try enable api key"]; - XCTestExpectation *deleteAPIKeyExpectation = [self expectationWithDescription:@"should try delete api key"]; - - __block RLMUser *syncUser; - __block RLMUserAPIKey *userAPIKeyA; - __block RLMUserAPIKey *userAPIKeyB; - - NSString *randomPassword = generateRandomString(10); - NSString *email = randomEmail(); - [self.app.emailPasswordAuth registerUserWithEmail:email password:randomPassword completion:^(NSError *error) { - XCTAssert(!error); - [registerExpectation fulfill]; - }]; - - [self waitForExpectations:@[registerExpectation] timeout:60.0]; - - [self.app loginWithCredential:[RLMCredentials credentialsWithEmail:email password:randomPassword] - completion:^(RLMUser *user, NSError *error) { - XCTAssert(!error); - XCTAssert(user); - syncUser = user; - [loginExpectation fulfill]; - }]; - - [self waitForExpectations:@[loginExpectation] timeout:60.0]; - - [[syncUser apiKeysAuth] createAPIKeyWithName:@"apiKeyName1" completion:^(RLMUserAPIKey *userAPIKey, NSError *error) { - XCTAssert(!error); - XCTAssert([userAPIKey.name isEqualToString:@"apiKeyName1"]); - XCTAssert(![userAPIKey.key isEqualToString:@"apiKeyName1"] && userAPIKey.key.length > 0); - userAPIKeyA = userAPIKey; - [createAPIKeyExpectationA fulfill]; - }]; - - [[syncUser apiKeysAuth] createAPIKeyWithName:@"apiKeyName2" completion:^(RLMUserAPIKey *userAPIKey, NSError *error) { - XCTAssert(!error); - XCTAssert([userAPIKey.name isEqualToString:@"apiKeyName2"]); - userAPIKeyB = userAPIKey; - [createAPIKeyExpectationB fulfill]; - }]; - - [self waitForExpectations:@[createAPIKeyExpectationA, createAPIKeyExpectationB] timeout:60.0]; - - // sleep for 2 seconds as there seems to be an issue fetching the keys straight after they are created. - [NSThread sleepForTimeInterval:2]; - - [[syncUser apiKeysAuth] fetchAPIKeysWithCompletion:^(NSArray *_Nonnull apiKeys, NSError *error) { - XCTAssert(!error); - XCTAssert(apiKeys.count == 2); - [fetchAPIKeysExpectation fulfill]; - }]; - - [self waitForExpectations:@[fetchAPIKeysExpectation] timeout:60.0]; - - [[syncUser apiKeysAuth] disableAPIKey:userAPIKeyA.objectId completion:^(NSError *error) { - XCTAssert(!error); - [disableAPIKeyExpectation fulfill]; - }]; - - [self waitForExpectations:@[disableAPIKeyExpectation] timeout:60.0]; - - [[syncUser apiKeysAuth] enableAPIKey:userAPIKeyA.objectId completion:^(NSError *error) { - XCTAssert(!error); - [enableAPIKeyExpectation fulfill]; - }]; - - [self waitForExpectations:@[enableAPIKeyExpectation] timeout:60.0]; - - [[syncUser apiKeysAuth] deleteAPIKey:userAPIKeyA.objectId completion:^(NSError *error) { - XCTAssert(!error); - [deleteAPIKeyExpectation fulfill]; - }]; - - [self waitForExpectations:@[deleteAPIKeyExpectation] timeout:60.0]; -} - -#pragma mark - Link user - - -- (void)testLinkUser { - XCTestExpectation *registerExpectation = [self expectationWithDescription:@"should try register"]; - XCTestExpectation *loginExpectation = [self expectationWithDescription:@"should try login"]; - XCTestExpectation *linkExpectation = [self expectationWithDescription:@"should try link and fail"]; - - __block RLMUser *syncUser; - - NSString *email = randomEmail(); - NSString *randomPassword = generateRandomString(10); - - [self.app.emailPasswordAuth registerUserWithEmail:email password:randomPassword completion:^(NSError *error) { - XCTAssert(!error); - [registerExpectation fulfill]; - }]; - - [self waitForExpectations:@[registerExpectation] timeout:60.0]; - - [self.app loginWithCredential:[RLMCredentials credentialsWithEmail:email password:randomPassword] - completion:^(RLMUser *user, NSError *error) { - XCTAssert(!error); - XCTAssert(user); - syncUser = user; - [loginExpectation fulfill]; - }]; - - [self waitForExpectations:@[loginExpectation] timeout:60.0]; - - [syncUser linkUserWithCredentials:[RLMCredentials credentialsWithFacebookToken:@"a_token"] - completion:^(RLMUser *user, NSError *error) { - XCTAssert(!user); - XCTAssertEqual(error.code, RLMAppErrorInvalidSession); - [linkExpectation fulfill]; - }]; - - [self waitForExpectations:@[linkExpectation] timeout:60.0]; -} - -#pragma mark - Auth Credentials - - -- (void)testEmailPasswordCredential { - RLMCredentials *emailPasswordCredential = [RLMCredentials credentialsWithEmail:@"test@mongodb.com" password:@"apassword"]; - XCTAssertEqualObjects(emailPasswordCredential.provider, @"local-userpass"); -} - -- (void)testJWTCredential { - RLMCredentials *jwtCredential = [RLMCredentials credentialsWithJWT:@"sometoken"]; - XCTAssertEqualObjects(jwtCredential.provider, @"custom-token"); -} - -- (void)testAnonymousCredential { - RLMCredentials *anonymousCredential = [RLMCredentials anonymousCredentials]; - XCTAssertEqualObjects(anonymousCredential.provider, @"anon-user"); -} - -- (void)testUserAPIKeyCredential { - RLMCredentials *userAPICredential = [RLMCredentials credentialsWithUserAPIKey:@"apikey"]; - XCTAssertEqualObjects(userAPICredential.provider, @"api-key"); -} - -- (void)testServerAPIKeyCredential { - RLMCredentials *serverAPICredential = [RLMCredentials credentialsWithServerAPIKey:@"apikey"]; - XCTAssertEqualObjects(serverAPICredential.provider, @"api-key"); -} - -- (void)testFacebookCredential { - RLMCredentials *facebookCredential = [RLMCredentials credentialsWithFacebookToken:@"facebook token"]; - XCTAssertEqualObjects(facebookCredential.provider, @"oauth2-facebook"); -} - -- (void)testGoogleCredential { - RLMCredentials *googleCredential = [RLMCredentials credentialsWithGoogleAuthCode:@"google token"]; - XCTAssertEqualObjects(googleCredential.provider, @"oauth2-google"); -} - -- (void)testGoogleIdCredential { - RLMCredentials *googleCredential = [RLMCredentials credentialsWithGoogleIdToken:@"id token"]; - XCTAssertEqualObjects(googleCredential.provider, @"oauth2-google"); -} - -- (void)testAppleCredential { - RLMCredentials *appleCredential = [RLMCredentials credentialsWithAppleToken:@"apple token"]; - XCTAssertEqualObjects(appleCredential.provider, @"oauth2-apple"); -} - -- (void)testFunctionCredential { - NSError *error; - RLMCredentials *functionCredential = [RLMCredentials credentialsWithFunctionPayload:@{@"dog": @{@"name": @"fido"}}]; - XCTAssertEqualObjects(functionCredential.provider, @"custom-function"); - XCTAssertEqualObjects(error, nil); -} - -#pragma mark - Username Password - -/// Valid email/password credentials should be able to log in a user. Using the same credentials should return the -/// same user object. -- (void)testEmailPasswordAuthentication { - RLMCredentials *credentials = [self basicCredentialsWithName:self.name register:YES]; - RLMUser *firstUser = [self logInUserForCredentials:credentials]; - RLMUser *secondUser = [self logInUserForCredentials:credentials]; - // Two users created with the same credential should resolve to the same actual user. - XCTAssertTrue([firstUser.identifier isEqualToString:secondUser.identifier]); -} - -/// An invalid email/password credential should not be able to log in a user and a corresponding error should be generated. -- (void)testInvalidPasswordAuthentication { - (void)[self basicCredentialsWithName:self.name register:YES]; - RLMCredentials *credentials = [RLMCredentials credentialsWithEmail:self.name - password:@"INVALID_PASSWORD"]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"login should fail"]; - - [self.app loginWithCredential:credentials completion:^(RLMUser *user, NSError *error) { - XCTAssertNil(user); - RLMValidateError(error, RLMAppErrorDomain, RLMAppErrorInvalidPassword, - @"unauthorized"); - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:2.0 handler:nil]; -} - -/// A non-existsing user should not be able to log in and a corresponding error should be generated. -- (void)testNonExistingEmailAuthentication { - RLMCredentials *credentials = [RLMCredentials credentialsWithEmail:@"INVALID_USERNAME" - password:@"INVALID_PASSWORD"]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"login should fail"]; - - [self.app loginWithCredential:credentials completion:^(RLMUser *user, NSError *error) { - XCTAssertNil(user); - RLMValidateError(error, RLMAppErrorDomain, RLMAppErrorInvalidPassword, - @"unauthorized"); - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:2.0 handler:nil]; -} - -/// Registering a user with existing email should return corresponding error. -- (void)testExistingEmailRegistration { - XCTestExpectation *expectationA = [self expectationWithDescription:@"registration should succeed"]; - [self.app.emailPasswordAuth registerUserWithEmail:self.name - password:@"password" - completion:^(NSError *error) { - XCTAssertNil(error); - [expectationA fulfill]; - }]; - [self waitForExpectationsWithTimeout:2.0 handler:nil]; - - XCTestExpectation *expectationB = [self expectationWithDescription:@"registration should fail"]; - [self.app.emailPasswordAuth registerUserWithEmail:self.name - password:@"password" - completion:^(NSError *error) { - RLMValidateError(error, RLMAppErrorDomain, RLMAppErrorAccountNameInUse, @"name already in use"); - XCTAssertNotNil(error.userInfo[RLMServerLogURLKey]); - [expectationB fulfill]; - }]; - - [self waitForExpectationsWithTimeout:2.0 handler:nil]; -} - -- (void)testSyncErrorHandlerErrorDomain { - RLMRealmConfiguration *config = self.configuration; - XCTestExpectation *expectation = [self expectationWithDescription:@"should fail after setting bad token"]; - self.app.syncManager.errorHandler = ^(NSError *error, RLMSyncSession *) { - RLMValidateError(error, RLMSyncErrorDomain, RLMSyncErrorClientUserError, - @"Unable to refresh the user access token: signature is invalid"); - [expectation fulfill]; - }; - - [self setInvalidTokensForUser:config.syncConfiguration.user]; - [RLMRealm realmWithConfiguration:config error:nil]; - [self waitForExpectations:@[expectation] timeout:3.0]; -} - -#pragma mark - User Profile - -- (void)testUserProfileInitialization { - RLMUserProfile *profile = [[RLMUserProfile alloc] initWithUserProfile:realm::app::UserProfile()]; - XCTAssertNil(profile.name); - XCTAssertNil(profile.maxAge); - XCTAssertNil(profile.minAge); - XCTAssertNil(profile.birthday); - XCTAssertNil(profile.gender); - XCTAssertNil(profile.firstName); - XCTAssertNil(profile.lastName); - XCTAssertNil(profile.pictureURL); - - auto metadata = realm::bson::BsonDocument({{"some_key", "some_value"}}); - - profile = [[RLMUserProfile alloc] initWithUserProfile:realm::app::UserProfile(realm::bson::BsonDocument({ - {"name", "Jane"}, - {"max_age", "40"}, - {"min_age", "30"}, - {"birthday", "October 10th"}, - {"gender", "unknown"}, - {"first_name", "Jane"}, - {"last_name", "Jannson"}, - {"picture_url", "SomeURL"}, - {"other_data", metadata} - }))]; - - XCTAssert([profile.name isEqualToString:@"Jane"]); - XCTAssert([profile.maxAge isEqualToString:@"40"]); - XCTAssert([profile.minAge isEqualToString:@"30"]); - XCTAssert([profile.birthday isEqualToString:@"October 10th"]); - XCTAssert([profile.gender isEqualToString:@"unknown"]); - XCTAssert([profile.firstName isEqualToString:@"Jane"]); - XCTAssert([profile.lastName isEqualToString:@"Jannson"]); - XCTAssert([profile.pictureURL isEqualToString:@"SomeURL"]); - XCTAssertEqualObjects(profile.metadata[@"other_data"], @{@"some_key": @"some_value"}); -} - -#pragma mark - Basic Sync - -/// It should be possible to successfully open a Realm configured for sync with a normal user. -- (void)testOpenRealmWithNormalCredentials { - RLMRealm *realm = [self openRealm]; - XCTAssertTrue(realm.isEmpty); -} - -/// If client B adds objects to a synced Realm, client A should see those objects. -- (void)testAddObjects { - RLMRealm *realm = [self openRealm]; - NSDictionary *values = [AllTypesSyncObject values:1]; - CHECK_COUNT(0, Person, realm); - CHECK_COUNT(0, AllTypesSyncObject, realm); - - [self writeToPartition:self.name block:^(RLMRealm *realm) { - [realm addObjects:@[[Person john], [Person paul], [Person george]]]; - AllTypesSyncObject *obj = [[AllTypesSyncObject alloc] initWithValue:values]; - obj.objectCol = [Person ringo]; - [realm addObject:obj]; - }]; - [self waitForDownloadsForRealm:realm]; - CHECK_COUNT(4, Person, realm); - CHECK_COUNT(1, AllTypesSyncObject, realm); - - AllTypesSyncObject *obj = [[AllTypesSyncObject allObjectsInRealm:realm] firstObject]; - XCTAssertEqual(obj.boolCol, [values[@"boolCol"] boolValue]); - XCTAssertEqual(obj.cBoolCol, [values[@"cBoolCol"] boolValue]); - XCTAssertEqual(obj.intCol, [values[@"intCol"] intValue]); - XCTAssertEqual(obj.doubleCol, [values[@"doubleCol"] doubleValue]); - XCTAssertEqualObjects(obj.stringCol, values[@"stringCol"]); - XCTAssertEqualObjects(obj.binaryCol, values[@"binaryCol"]); - XCTAssertEqualObjects(obj.decimalCol, values[@"decimalCol"]); - XCTAssertEqual(obj.dateCol, values[@"dateCol"]); - XCTAssertEqual(obj.longCol, [values[@"longCol"] longValue]); - XCTAssertEqualObjects(obj.uuidCol, values[@"uuidCol"]); - XCTAssertEqualObjects((NSNumber *)obj.anyCol, values[@"anyCol"]); - XCTAssertEqualObjects(obj.objectCol.firstName, [Person ringo].firstName); -} - -- (void)testAddObjectsWithNilPartitionValue { - RLMRealm *realm = [self openRealmForPartitionValue:nil user:self.anonymousUser]; - - CHECK_COUNT(0, Person, realm); - [self writeToPartition:nil block:^(RLMRealm *realm) { - [realm addObjects:@[[Person john], [Person paul], [Person george], [Person ringo]]]; - }]; - [self waitForDownloadsForRealm:realm]; - CHECK_COUNT(4, Person, realm); -} - -- (void)testRountripForDistinctPrimaryKey { - RLMRealm *realm = [self openRealm]; - - CHECK_COUNT(0, Person, realm); - CHECK_COUNT(0, UUIDPrimaryKeyObject, realm); - CHECK_COUNT(0, StringPrimaryKeyObject, realm); - CHECK_COUNT(0, IntPrimaryKeyObject, realm); - - [self writeToPartition:self.name block:^(RLMRealm *realm) { - Person *person = [[Person alloc] initWithPrimaryKey:[[RLMObjectId alloc] initWithString:@"1234567890ab1234567890ab" error:nil] - age:5 - firstName:@"Ringo" - lastName:@"Starr"]; - UUIDPrimaryKeyObject *uuidPrimaryKeyObject = [[UUIDPrimaryKeyObject alloc] initWithPrimaryKey:[[NSUUID alloc] initWithUUIDString:@"85d4fbee-6ec6-47df-bfa1-615931903d7e"] - strCol:@"Steve" - intCol:10]; - StringPrimaryKeyObject *stringPrimaryKeyObject = [[StringPrimaryKeyObject alloc] initWithPrimaryKey:@"1234567890ab1234567890aa" - strCol:@"Paul" - intCol:20]; - IntPrimaryKeyObject *intPrimaryKeyObject = [[IntPrimaryKeyObject alloc] initWithPrimaryKey:1234567890 - strCol:@"Jackson" - intCol:30]; - - [realm addObject:person]; - [realm addObject:uuidPrimaryKeyObject]; - [realm addObject:stringPrimaryKeyObject]; - [realm addObject:intPrimaryKeyObject]; - }]; - [self waitForDownloadsForRealm:realm]; - CHECK_COUNT(1, Person, realm); - CHECK_COUNT(1, UUIDPrimaryKeyObject, realm); - CHECK_COUNT(1, StringPrimaryKeyObject, realm); - CHECK_COUNT(1, IntPrimaryKeyObject, realm); - - Person *person = [Person objectInRealm:realm forPrimaryKey:[[RLMObjectId alloc] initWithString:@"1234567890ab1234567890ab" error:nil]]; - XCTAssertEqualObjects(person.firstName, @"Ringo"); - XCTAssertEqualObjects(person.lastName, @"Starr"); - - UUIDPrimaryKeyObject *uuidPrimaryKeyObject = [UUIDPrimaryKeyObject objectInRealm:realm forPrimaryKey:[[NSUUID alloc] initWithUUIDString:@"85d4fbee-6ec6-47df-bfa1-615931903d7e"]]; - XCTAssertEqualObjects(uuidPrimaryKeyObject.strCol, @"Steve"); - XCTAssertEqual(uuidPrimaryKeyObject.intCol, 10); - - StringPrimaryKeyObject *stringPrimaryKeyObject = [StringPrimaryKeyObject objectInRealm:realm forPrimaryKey:@"1234567890ab1234567890aa"]; - XCTAssertEqualObjects(stringPrimaryKeyObject.strCol, @"Paul"); - XCTAssertEqual(stringPrimaryKeyObject.intCol, 20); - - IntPrimaryKeyObject *intPrimaryKeyObject = [IntPrimaryKeyObject objectInRealm:realm forPrimaryKey:@1234567890]; - XCTAssertEqualObjects(intPrimaryKeyObject.strCol, @"Jackson"); - XCTAssertEqual(intPrimaryKeyObject.intCol, 30); -} - -- (void)testAddObjectsMultipleApps { - NSString *appId1 = [RealmServer.shared createAppWithPartitionKeyType:@"string" types:@[Person.self] persistent:false error:nil]; - NSString *appId2 = [RealmServer.shared createAppWithPartitionKeyType:@"string" types:@[Person.self] persistent:false error:nil]; - RLMApp *app1 = [self appWithId:appId1]; - RLMApp *app2 = [self appWithId:appId2]; - - auto openRealm = [=](RLMApp *app) { - RLMUser *user = [self createUserForApp:app]; - RLMRealmConfiguration *config = [user configurationWithPartitionValue:self.name]; - config.objectClasses = @[Person.self]; - return [self openRealmWithConfiguration:config]; - }; - - RLMRealm *realm1 = openRealm(app1); - RLMRealm *realm2 = openRealm(app2); - - CHECK_COUNT(0, Person, realm1); - CHECK_COUNT(0, Person, realm2); - - @autoreleasepool { - RLMRealm *realm = openRealm(app1); - [self addPersonsToRealm:realm - persons:@[[Person john], [Person paul]]]; - [self waitForUploadsForRealm:realm]; - } - - // realm2 should not see realm1's objcets despite being the same partition - // as they're from different apps - [self waitForDownloadsForRealm:realm1]; - [self waitForDownloadsForRealm:realm2]; - CHECK_COUNT(2, Person, realm1); - CHECK_COUNT(0, Person, realm2); - - @autoreleasepool { - RLMRealm *realm = openRealm(app2); - [self addPersonsToRealm:realm - persons:@[[Person ringo], [Person george]]]; - [self waitForUploadsForRealm:realm]; - } - - [self waitForDownloadsForRealm:realm1]; - [self waitForDownloadsForRealm:realm2]; - CHECK_COUNT(2, Person, realm1); - CHECK_COUNT(2, Person, realm2); - - XCTAssertEqual([Person objectsInRealm:realm1 where:@"firstName = 'John'"].count, 1UL); - XCTAssertEqual([Person objectsInRealm:realm1 where:@"firstName = 'Paul'"].count, 1UL); - XCTAssertEqual([Person objectsInRealm:realm1 where:@"firstName = 'Ringo'"].count, 0UL); - XCTAssertEqual([Person objectsInRealm:realm1 where:@"firstName = 'George'"].count, 0UL); - - XCTAssertEqual([Person objectsInRealm:realm2 where:@"firstName = 'John'"].count, 0UL); - XCTAssertEqual([Person objectsInRealm:realm2 where:@"firstName = 'Paul'"].count, 0UL); - XCTAssertEqual([Person objectsInRealm:realm2 where:@"firstName = 'Ringo'"].count, 1UL); - XCTAssertEqual([Person objectsInRealm:realm2 where:@"firstName = 'George'"].count, 1UL); -} - -- (void)testSessionRefresh { - RLMUser *user = [self createUser]; - - // Should result in an access token error followed by a refresh when we - // open the Realm which is entirely transparent to the user - realm::RealmJWT token(std::string_view(self.badAccessToken)); - user.user->update_data_for_testing([&](auto& data) { - data.access_token = token; - }); - RLMRealm *realm = [self openRealmForPartitionValue:self.name user:user]; - - RLMRealm *realm2 = [self openRealm]; - [self addPersonsToRealm:realm2 - persons:@[[Person john], - [Person paul], - [Person ringo], - [Person george]]]; - [self waitForUploadsForRealm:realm2]; - [self waitForDownloadsForRealm:realm]; - CHECK_COUNT(4, Person, realm); -} - -- (void)testDeleteObjects { - RLMRealm *realm1 = [self openRealm]; - [self addPersonsToRealm:realm1 persons:@[[Person john]]]; - [self waitForUploadsForRealm:realm1]; - CHECK_COUNT(1, Person, realm1); - - RLMRealm *realm2 = [self openRealm]; - CHECK_COUNT(1, Person, realm2); - [realm2 beginWriteTransaction]; - [realm2 deleteAllObjects]; - [realm2 commitWriteTransaction]; - [self waitForUploadsForRealm:realm2]; - - [self waitForDownloadsForRealm:realm1]; - CHECK_COUNT(0, Person, realm1); -} - -- (void)testIncomingSyncWritesTriggerNotifications { - RLMRealm *syncRealm = [self openRealm]; - RLMRealm *asyncRealm = [self asyncOpenRealmWithConfiguration:self.configuration]; - RLMRealm *writeRealm = [self openRealm]; - - __block XCTestExpectation *ex = [self expectationWithDescription:@"got initial notification"]; - ex.expectedFulfillmentCount = 2; - RLMNotificationToken *token1 = [[Person allObjectsInRealm:syncRealm] addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) { - [ex fulfill]; - }]; - RLMNotificationToken *token2 = [[Person allObjectsInRealm:asyncRealm] addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) { - [ex fulfill]; - }]; - [self waitForExpectations:@[ex] timeout:5.0]; - - ex = [self expectationWithDescription:@"got update notification"]; - ex.expectedFulfillmentCount = 2; - [self addPersonsToRealm:writeRealm persons:@[[Person john]]]; - [self waitForExpectations:@[ex] timeout:5.0]; - - [token1 invalidate]; - [token2 invalidate]; -} - -#pragma mark - RLMValue Sync with missing schema - -- (void)testMissingSchema { - @autoreleasepool { - RLMRealm *realm = [self openRealm]; - AllTypesSyncObject *obj = [[AllTypesSyncObject alloc] initWithValue:[AllTypesSyncObject values:0]]; - RLMSetSyncObject *o = [RLMSetSyncObject new]; - Person *p = [Person john]; - [o.anySet addObjects:@[p]]; - obj.anyCol = o; - obj.objectCol = p; - [realm beginWriteTransaction]; - [realm addObject:obj]; - [realm commitWriteTransaction]; - [self waitForUploadsForRealm:realm]; - CHECK_COUNT(1, AllTypesSyncObject, realm); - } - - RLMUser *user = [self createUser]; - auto c = [user configurationWithPartitionValue:self.name]; - c.objectClasses = @[Person.self, AllTypesSyncObject.self]; - RLMRealm *realm = [RLMRealm realmWithConfiguration:c error:nil]; - [self waitForDownloadsForRealm:realm]; - RLMResults *res = [AllTypesSyncObject allObjectsInRealm:realm]; - AllTypesSyncObject *o = res.firstObject; - Person *p = o.objectCol; - RLMSet *anySet = ((RLMObject *)o.anyCol)[@"anySet"]; - XCTAssertTrue([anySet.allObjects[0][@"firstName"] isEqualToString:p.firstName]); - [realm beginWriteTransaction]; - anySet.allObjects[0][@"firstName"] = @"Bob"; - [realm commitWriteTransaction]; - XCTAssertTrue([anySet.allObjects[0][@"firstName"] isEqualToString:p.firstName]); - CHECK_COUNT(1, AllTypesSyncObject, realm); -} - -#pragma mark - Encryption - - -/// If client B encrypts its synced Realm, client A should be able to access that Realm with a different encryption key. -- (void)testEncryptedSyncedRealm { - RLMUser *user = [self userForTest:_cmd]; - - NSData *key = RLMGenerateKey(); - RLMRealm *realm = [self openRealmForPartitionValue:self.name - user:user - encryptionKey:key - stopPolicy:RLMSyncStopPolicyAfterChangesUploaded]; - - if (self.isParent) { - CHECK_COUNT(0, Person, realm); - RLMRunChildAndWait(); - [self waitForDownloadsForRealm:realm]; - CHECK_COUNT(1, Person, realm); - } else { - [self addPersonsToRealm:realm persons:@[[Person john]]]; - [self waitForUploadsForRealm:realm]; - CHECK_COUNT(1, Person, realm); - } -} - -/// If an encrypted synced Realm is re-opened with the wrong key, throw an exception. -- (void)testEncryptedSyncedRealmWrongKey { - RLMUser *user = [self createUser]; - - NSString *path; - @autoreleasepool { - RLMRealm *realm = [self openRealmForPartitionValue:self.name - user:user - encryptionKey:RLMGenerateKey() - stopPolicy:RLMSyncStopPolicyImmediately]; - path = realm.configuration.pathOnDisk; - } - [user.app.syncManager waitForSessionTermination]; - - RLMRealmConfiguration *c = [RLMRealmConfiguration defaultConfiguration]; - c.fileURL = [NSURL fileURLWithPath:path]; - RLMAssertRealmExceptionContains([RLMRealm realmWithConfiguration:c error:nil], - RLMErrorInvalidDatabase, - @"Failed to open Realm file at path '%@': header has invalid mnemonic. The file is either not a Realm file, is an encrypted Realm file but no encryption key was supplied, or is corrupted.", - c.fileURL.path); - c.encryptionKey = RLMGenerateKey(); - RLMAssertRealmExceptionContains([RLMRealm realmWithConfiguration:c error:nil], - RLMErrorInvalidDatabase, - @"Failed to open Realm file at path '%@': Realm file decryption failed (Decryption failed: page 0 in file of size ", - c.fileURL.path); -} - -#pragma mark - Multiple Realm Sync - -/// If a client opens multiple Realms, there should be one session object for each Realm that was opened. -- (void)testMultipleRealmsSessions { - NSString *partitionValueA = self.name; - NSString *partitionValueB = [partitionValueA stringByAppendingString:@"bar"]; - NSString *partitionValueC = [partitionValueA stringByAppendingString:@"baz"]; - RLMUser *user = [self createUser]; - - __attribute__((objc_precise_lifetime)) - RLMRealm *realmA = [self openRealmForPartitionValue:partitionValueA user:user]; - __attribute__((objc_precise_lifetime)) - RLMRealm *realmB = [self openRealmForPartitionValue:partitionValueB user:user]; - __attribute__((objc_precise_lifetime)) - RLMRealm *realmC = [self openRealmForPartitionValue:partitionValueC user:user]; - // Make sure there are three active sessions for the user. - XCTAssertEqual(user.allSessions.count, 3U); - XCTAssertNotNil([user sessionForPartitionValue:partitionValueA], - @"Expected to get a session for partition value A"); - XCTAssertNotNil([user sessionForPartitionValue:partitionValueB], - @"Expected to get a session for partition value B"); - XCTAssertNotNil([user sessionForPartitionValue:partitionValueC], - @"Expected to get a session for partition value C"); - XCTAssertEqual(realmA.syncSession.state, RLMSyncSessionStateActive); - XCTAssertEqual(realmB.syncSession.state, RLMSyncSessionStateActive); - XCTAssertEqual(realmC.syncSession.state, RLMSyncSessionStateActive); -} - -/// A client should be able to open multiple Realms and add objects to each of them. -- (void)testMultipleRealmsAddObjects { - NSString *partitionValueA = self.name; - NSString *partitionValueB = [partitionValueA stringByAppendingString:@"bar"]; - NSString *partitionValueC = [partitionValueA stringByAppendingString:@"baz"]; - RLMUser *user = [self userForTest:_cmd]; - - RLMRealm *realmA = [self openRealmForPartitionValue:partitionValueA user:user]; - RLMRealm *realmB = [self openRealmForPartitionValue:partitionValueB user:user]; - RLMRealm *realmC = [self openRealmForPartitionValue:partitionValueC user:user]; - - if (self.isParent) { - CHECK_COUNT(0, Person, realmA); - CHECK_COUNT(0, Person, realmB); - CHECK_COUNT(0, Person, realmC); - RLMRunChildAndWait(); - [self waitForDownloadsForRealm:realmA]; - [self waitForDownloadsForRealm:realmB]; - [self waitForDownloadsForRealm:realmC]; - CHECK_COUNT(3, Person, realmA); - CHECK_COUNT(2, Person, realmB); - CHECK_COUNT(5, Person, realmC); - - RLMResults *resultsA = [Person objectsInRealm:realmA where:@"firstName == %@", @"Ringo"]; - RLMResults *resultsB = [Person objectsInRealm:realmB where:@"firstName == %@", @"Ringo"]; - - XCTAssertEqual([resultsA count], 1UL); - XCTAssertEqual([resultsB count], 0UL); - } else { - // Add objects. - [self addPersonsToRealm:realmA - persons:@[[Person john], - [Person paul], - [Person ringo]]]; - [self addPersonsToRealm:realmB - persons:@[[Person john], - [Person paul]]]; - [self addPersonsToRealm:realmC - persons:@[[Person john], - [Person paul], - [Person ringo], - [Person george], - [Person ringo]]]; - [self waitForUploadsForRealm:realmA]; - [self waitForUploadsForRealm:realmB]; - [self waitForUploadsForRealm:realmC]; - CHECK_COUNT(3, Person, realmA); - CHECK_COUNT(2, Person, realmB); - CHECK_COUNT(5, Person, realmC); - } -} - -/// A client should be able to open multiple Realms and delete objects from each of them. -- (void)testMultipleRealmsDeleteObjects { - NSString *partitionValueA = self.name; - NSString *partitionValueB = [partitionValueA stringByAppendingString:@"bar"]; - NSString *partitionValueC = [partitionValueA stringByAppendingString:@"baz"]; - RLMUser *user = [self userForTest:_cmd]; - RLMRealm *realmA = [self openRealmForPartitionValue:partitionValueA user:user]; - RLMRealm *realmB = [self openRealmForPartitionValue:partitionValueB user:user]; - RLMRealm *realmC = [self openRealmForPartitionValue:partitionValueC user:user]; - - if (self.isParent) { - [self addPersonsToRealm:realmA - persons:@[[Person john], - [Person paul], - [Person ringo], - [Person george]]]; - [self addPersonsToRealm:realmB - persons:@[[Person john], - [Person paul], - [Person ringo], - [Person george], - [Person george]]]; - [self addPersonsToRealm:realmC - persons:@[[Person john], - [Person paul]]]; - - [self waitForUploadsForRealm:realmA]; - [self waitForUploadsForRealm:realmB]; - [self waitForUploadsForRealm:realmC]; - CHECK_COUNT(4, Person, realmA); - CHECK_COUNT(5, Person, realmB); - CHECK_COUNT(2, Person, realmC); - RLMRunChildAndWait(); - [self waitForDownloadsForRealm:realmA]; - [self waitForDownloadsForRealm:realmB]; - [self waitForDownloadsForRealm:realmC]; - CHECK_COUNT(0, Person, realmA); - CHECK_COUNT(0, Person, realmB); - CHECK_COUNT(0, Person, realmC); - } else { - // Delete all the objects from the Realms. - CHECK_COUNT(4, Person, realmA); - CHECK_COUNT(5, Person, realmB); - CHECK_COUNT(2, Person, realmC); - [realmA beginWriteTransaction]; - [realmA deleteAllObjects]; - [realmA commitWriteTransaction]; - [realmB beginWriteTransaction]; - [realmB deleteAllObjects]; - [realmB commitWriteTransaction]; - [realmC beginWriteTransaction]; - [realmC deleteAllObjects]; - [realmC commitWriteTransaction]; - [self waitForUploadsForRealm:realmA]; - [self waitForUploadsForRealm:realmB]; - [self waitForUploadsForRealm:realmC]; - CHECK_COUNT(0, Person, realmA); - CHECK_COUNT(0, Person, realmB); - CHECK_COUNT(0, Person, realmC); - } -} - -#pragma mark - Session Lifetime -/// When a session opened by a Realm goes out of scope, it should stay alive long enough to finish any waiting uploads. -- (void)testUploadChangesWhenRealmOutOfScope { - const NSInteger OBJECT_COUNT = 3; - - // Open the Realm in an autorelease pool so that it is destroyed as soon as possible. - @autoreleasepool { - RLMRealm *realm = [self openRealm]; - [self addPersonsToRealm:realm - persons:@[[Person john], [Person paul], [Person ringo]]]; - CHECK_COUNT(OBJECT_COUNT, Person, realm); - } - - [self.app.syncManager waitForSessionTermination]; - - RLMRealm *realm = [self openRealm]; - CHECK_COUNT(OBJECT_COUNT, Person, realm); -} - -#pragma mark - Logging Back In - -/// A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in. -- (void)testLogBackInSameRealmUpload { - RLMCredentials *credentials = [self basicCredentialsWithName:self.name - register:self.isParent]; - RLMUser *user = [self logInUserForCredentials:credentials]; - - RLMRealmConfiguration *config; - @autoreleasepool { - RLMRealm *realm = [self openRealmForPartitionValue:self.name user:user]; - config = realm.configuration; - [self addPersonsToRealm:realm persons:@[[Person john]]]; - CHECK_COUNT(1, Person, realm); - [self waitForUploadsForRealm:realm]; - // Log out the user out and back in - [self logOutUser:user]; - [self addPersonsToRealm:realm - persons:@[[Person john], [Person paul], [Person ringo]]]; - [self logInUserForCredentials:credentials]; - [self waitForUploadsForRealm:realm]; - CHECK_COUNT(4, Person, realm); - [realm.syncSession suspend]; - [self.app.syncManager waitForSessionTermination]; - } - - // Verify that the post-login objects were actually synced - XCTAssertTrue([RLMRealm deleteFilesForConfiguration:config error:nil]); - RLMRealm *realm = [self openRealm]; - CHECK_COUNT(4, Person, realm); -} - -/// A Realm that was opened before a user logged out should be able to resume downloading if the user logs back in. -- (void)testLogBackInSameRealmDownload { - RLMCredentials *credentials = [self basicCredentialsWithName:self.name - register:self.isParent]; - RLMUser *user = [self logInUserForCredentials:credentials]; - RLMRealm *realm = [self openRealmForPartitionValue:self.name user:user]; - - if (self.isParent) { - [self addPersonsToRealm:realm persons:@[[Person john]]]; - CHECK_COUNT(1, Person, realm); - [self waitForUploadsForRealm:realm]; - // Log out the user. - [self logOutUser:user]; - // Log the user back in. - [self logInUserForCredentials:credentials]; - - RLMRunChildAndWait(); - - [self waitForDownloadsForRealm:realm]; - CHECK_COUNT(3, Person, realm); - } else { - [self addPersonsToRealm:realm persons:@[[Person john], [Person paul]]]; - [self waitForUploadsForRealm:realm]; - CHECK_COUNT(3, Person, realm); - } -} - -/// A Realm that was opened while a user was logged out should be able to start uploading if the user logs back in. -- (void)testLogBackInDeferredRealmUpload { - RLMCredentials *credentials = [self basicCredentialsWithName:self.name register:YES]; - RLMUser *user = [self logInUserForCredentials:credentials]; - [self logOutUser:user]; - - // Open a Realm after the user's been logged out. - RLMRealm *realm = [self immediatelyOpenRealmForPartitionValue:self.name user:user]; - - [self addPersonsToRealm:realm persons:@[[Person john]]]; - CHECK_COUNT(1, Person, realm); - - [self logInUserForCredentials:credentials]; - [self addPersonsToRealm:realm - persons:@[[Person john], [Person paul], [Person ringo]]]; - [self waitForUploadsForRealm:realm]; - CHECK_COUNT(4, Person, realm); - - RLMRealm *realm2 = [self openRealm]; - CHECK_COUNT(4, Person, realm2); -} - -/// A Realm that was opened while a user was logged out should be able to start downloading if the user logs back in. -- (void)testLogBackInDeferredRealmDownload { - RLMCredentials *credentials = [self basicCredentialsWithName:self.name - register:self.isParent]; - RLMUser *user = [self logInUserForCredentials:credentials]; - - if (self.isParent) { - [self logOutUser:user]; - RLMRunChildAndWait(); - - // Open a Realm after the user's been logged out. - RLMRealm *realm = [self immediatelyOpenRealmForPartitionValue:self.name user:user]; - [self addPersonsToRealm:realm persons:@[[Person john]]]; - CHECK_COUNT(1, Person, realm); - - [self logInUserForCredentials:credentials]; - [self waitForDownloadsForRealm:realm]; - CHECK_COUNT(4, Person, realm); - - } else { - RLMRealm *realm = [self openRealmForPartitionValue:self.name user:user]; - [self addPersonsToRealm:realm - persons:@[[Person john], [Person paul], [Person ringo]]]; - [self waitForUploadsForRealm:realm]; - [self waitForUploadsForRealm:realm]; - CHECK_COUNT(3, Person, realm); - } -} - -/// After logging back in, a Realm whose path has been opened for the first time should properly upload changes. -- (void)testLogBackInOpenFirstTimePathUpload { - RLMCredentials *credentials = [self basicCredentialsWithName:self.name register:YES]; - RLMUser *user = [self logInUserForCredentials:credentials]; - [self logOutUser:user]; - - @autoreleasepool { - auto c = [user configurationWithPartitionValue:self.name]; - c.objectClasses = @[Person.self]; - RLMRealm *realm = [RLMRealm realmWithConfiguration:c error:nil]; - [self addPersonsToRealm:realm - persons:@[[Person john], [Person paul]]]; - - [self logInUserForCredentials:credentials]; - [self waitForUploadsForRealm:realm]; - } - - RLMRealm *realm = [self openRealm]; - CHECK_COUNT(2, Person, realm); -} - -/// After logging back in, a Realm whose path has been opened for the first time should properly download changes. -- (void)testLogBackInOpenFirstTimePathDownload { - RLMCredentials *credentials = [self basicCredentialsWithName:self.name register:YES]; - RLMUser *user = [self logInUserForCredentials:credentials]; - [self logOutUser:user]; - - auto c = [user configurationWithPartitionValue:self.name]; - c.objectClasses = @[Person.self]; - RLMRealm *realm = [RLMRealm realmWithConfiguration:c error:nil]; - - @autoreleasepool { - RLMRealm *realm = [self openRealm]; - [self addPersonsToRealm:realm - persons:@[[Person john], [Person paul]]]; - [self waitForUploadsForRealm:realm]; - CHECK_COUNT(2, Person, realm); - } - - CHECK_COUNT(0, Person, realm); - [self logInUserForCredentials:credentials]; - [self waitForDownloadsForRealm:realm]; - CHECK_COUNT(2, Person, realm); -} - -/// If a client logs in, connects, logs out, and logs back in, sync should properly upload changes for a new -/// `RLMRealm` that is opened for the same path as a previously-opened Realm. -- (void)testLogBackInReopenRealmUpload { - RLMCredentials *credentials = [self basicCredentialsWithName:self.name - register:self.isParent]; - RLMUser *user = [self logInUserForCredentials:credentials]; - - @autoreleasepool { - RLMRealm *realm = [self openRealmForPartitionValue:self.name user:user]; - [self addPersonsToRealm:realm persons:@[[Person john]]]; - [self waitForUploadsForRealm:realm]; - CHECK_COUNT(1, Person, realm); - [self logOutUser:user]; - user = [self logInUserForCredentials:credentials]; - } - - RLMRealm *realm = [self openRealmForPartitionValue:self.name user:user]; - [self addPersonsToRealm:realm - persons:@[[Person john], [Person paul], [Person george], [Person ringo]]]; - CHECK_COUNT(5, Person, realm); - [self waitForUploadsForRealm:realm]; - - RLMRealm *realm2 = [self openRealmForPartitionValue:self.name user:self.createUser]; - CHECK_COUNT(5, Person, realm2); -} - -/// If a client logs in, connects, logs out, and logs back in, sync should properly download changes for a new -/// `RLMRealm` that is opened for the same path as a previously-opened Realm. -- (void)testLogBackInReopenRealmDownload { - RLMCredentials *credentials = [self basicCredentialsWithName:self.name - register:self.isParent]; - RLMUser *user = [self logInUserForCredentials:credentials]; - - RLMRealm *realm = [self openRealmForPartitionValue:self.name user:user]; - [self addPersonsToRealm:realm persons:@[[Person john]]]; - [self waitForUploadsForRealm:realm]; - XCTAssert([Person allObjectsInRealm:realm].count == 1, @"Expected 1 item"); - [self logOutUser:user]; - user = [self logInUserForCredentials:credentials]; - RLMRealm *realm2 = [self openRealmForPartitionValue:self.name user:self.createUser]; - CHECK_COUNT(1, Person, realm2); - [self addPersonsToRealm:realm2 - persons:@[[Person john], [Person paul], [Person george], [Person ringo]]]; - [self waitForUploadsForRealm:realm2]; - CHECK_COUNT(5, Person, realm2); - - // Open the Realm again and get the items. - [self openRealmForPartitionValue:self.name user:user]; - CHECK_COUNT(5, Person, realm2); -} - -#pragma mark - Session suspend and resume - -- (void)testSuspendAndResume { - RLMUser *user = [self userForTest:_cmd]; - - __attribute__((objc_precise_lifetime)) - RLMRealm *realmA = [self openRealmForPartitionValue:@"suspend and resume 1" user:user]; - __attribute__((objc_precise_lifetime)) - RLMRealm *realmB = [self openRealmForPartitionValue:@"suspend and resume 2" user:user]; - if (self.isParent) { - CHECK_COUNT(0, Person, realmA); - CHECK_COUNT(0, Person, realmB); - - // Suspend the session for realm A and then add an object to each Realm - RLMSyncSession *sessionA = [RLMSyncSession sessionForRealm:realmA]; - RLMSyncSession *sessionB = [RLMSyncSession sessionForRealm:realmB]; - XCTAssertEqual(sessionB.state, RLMSyncSessionStateActive); - [sessionA suspend]; - XCTAssertEqual(realmB.syncSession.state, RLMSyncSessionStateActive); - - [self addPersonsToRealm:realmA persons:@[[Person john]]]; - [self addPersonsToRealm:realmB persons:@[[Person ringo]]]; - [self waitForUploadsForRealm:realmB]; - RLMRunChildAndWait(); - - // A should still be 1 since it's suspended. If it wasn't suspended, it - // should have downloaded before B due to the ordering in the child. - [self waitForDownloadsForRealm:realmB]; - CHECK_COUNT(1, Person, realmA); - CHECK_COUNT(3, Person, realmB); - - // A should see the other two from the child after resuming - [sessionA resume]; - [self waitForDownloadsForRealm:realmA]; - CHECK_COUNT(3, Person, realmA); - } else { - // Child shouldn't see the object in A - CHECK_COUNT(0, Person, realmA); - CHECK_COUNT(1, Person, realmB); - [self addPersonsToRealm:realmA - persons:@[[Person john], [Person paul]]]; - [self waitForUploadsForRealm:realmA]; - [self addPersonsToRealm:realmB - persons:@[[Person john], [Person paul]]]; - [self waitForUploadsForRealm:realmB]; - CHECK_COUNT(2, Person, realmA); - CHECK_COUNT(3, Person, realmB); - } -} - -#pragma mark - Client reset - -/// Ensure that a client reset error is propagated up to the binding successfully. -- (void)testClientReset { - RLMUser *user = [self userForTest:_cmd]; - // Open the Realm - __attribute__((objc_precise_lifetime)) - RLMRealm *realm = [self openRealmForPartitionValue:@"realm_id" - user:user - clientResetMode:RLMClientResetModeManual]; - - __block NSError *theError = nil; - XCTestExpectation *ex = [self expectationWithDescription:@"Waiting for error handler to be called..."]; - [self.app syncManager].errorHandler = ^void(NSError *error, RLMSyncSession *) { - theError = error; - [ex fulfill]; - }; - [user simulateClientResetErrorForSession:@"realm_id"]; - [self waitForExpectationsWithTimeout:30 handler:nil]; - XCTAssertNotNil(theError); - XCTAssertTrue(theError.code == RLMSyncErrorClientResetError); - NSString *pathValue = [theError rlmSync_clientResetBackedUpRealmPath]; - XCTAssertNotNil(pathValue); - // Sanity check the recovery path. - NSString *recoveryPath = [NSString stringWithFormat:@"mongodb-realm/%@/recovered-realms", self.appId]; - XCTAssertTrue([pathValue rangeOfString:recoveryPath].location != NSNotFound); - XCTAssertNotNil([theError rlmSync_errorActionToken]); -} - -/// Test manually initiating client reset. -- (void)testClientResetManualInitiation { - RLMUser *user = [self createUser]; - - __block NSError *theError = nil; - @autoreleasepool { - __attribute__((objc_precise_lifetime)) - RLMRealm *realm = [self openRealmForPartitionValue:self.name user:user - clientResetMode:RLMClientResetModeManual]; - XCTestExpectation *ex = [self expectationWithDescription:@"Waiting for error handler to be called..."]; - self.app.syncManager.errorHandler = ^(NSError *error, RLMSyncSession *) { - theError = error; - [ex fulfill]; - }; - [user simulateClientResetErrorForSession:self.name]; - [self waitForExpectationsWithTimeout:30 handler:nil]; - XCTAssertNotNil(theError); - } - - // At this point the Realm should be invalidated and client reset should be possible. - NSString *pathValue = [theError rlmSync_clientResetBackedUpRealmPath]; - XCTAssertFalse([NSFileManager.defaultManager fileExistsAtPath:pathValue]); - [RLMSyncSession immediatelyHandleError:theError.rlmSync_errorActionToken]; - XCTAssertTrue([NSFileManager.defaultManager fileExistsAtPath:pathValue]); -} - -- (void)testSetClientResetMode { - RLMUser *user = [self createUser]; - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - RLMRealmConfiguration *config = [user configurationWithPartitionValue:self.name - clientResetMode:RLMClientResetModeDiscardLocal]; - XCTAssertEqual(config.syncConfiguration.clientResetMode, RLMClientResetModeDiscardLocal); - #pragma clang diagnostic pop - - // Default is recover - config = [user configurationWithPartitionValue:self.name]; - XCTAssertEqual(config.syncConfiguration.clientResetMode, RLMClientResetModeRecoverUnsyncedChanges); - - RLMSyncErrorReportingBlock block = ^(NSError *, RLMSyncSession *) { - XCTFail("Should never hit"); - }; - RLMAssertThrowsWithReason([user configurationWithPartitionValue:self.name - clientResetMode:RLMClientResetModeDiscardUnsyncedChanges - manualClientResetHandler:block], - @"A manual client reset handler can only be set with RLMClientResetModeManual"); -} - -- (void)testSetClientResetCallbacks { - RLMUser *user = [self createUser]; - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - RLMRealmConfiguration *config = [user configurationWithPartitionValue:self.name - clientResetMode:RLMClientResetModeDiscardLocal]; - - XCTAssertNil(config.syncConfiguration.beforeClientReset); - XCTAssertNil(config.syncConfiguration.afterClientReset); - - RLMClientResetBeforeBlock beforeBlock = ^(RLMRealm *local __unused) { - XCTAssert(false, @"Should not execute callback"); - }; - RLMClientResetAfterBlock afterBlock = ^(RLMRealm *before __unused, RLMRealm *after __unused) { - XCTAssert(false, @"Should not execute callback"); - }; - RLMRealmConfiguration *config2 = [user configurationWithPartitionValue:self.name - clientResetMode:RLMClientResetModeDiscardLocal - notifyBeforeReset:beforeBlock - notifyAfterReset:afterBlock]; - XCTAssertNotNil(config2.syncConfiguration.beforeClientReset); - XCTAssertNotNil(config2.syncConfiguration.afterClientReset); - #pragma clang diagnostic pop - -} - -// TODO: Consider testing with sync_config->on_sync_client_event_hook or a client reset -- (void)testBeforeClientResetCallbackNotVersioned { - // Setup sync config - RLMSyncConfiguration *syncConfig = [[RLMSyncConfiguration alloc] initWithRawConfig:{} path:""]; - XCTestExpectation *beforeExpectation = [self expectationWithDescription:@"block called once"]; - syncConfig.clientResetMode = RLMClientResetModeRecoverUnsyncedChanges; - syncConfig.beforeClientReset = ^(RLMRealm *beforeFrozen) { - XCTAssertNotEqual(RLMNotVersioned, beforeFrozen->_realm->schema_version()); - [beforeExpectation fulfill]; - }; - auto& beforeWrapper = syncConfig.rawConfiguration.notify_before_client_reset; - - // Setup a realm with a versioned schema - RLMRealmConfiguration *configVersioned = [RLMRealmConfiguration defaultConfiguration]; - configVersioned.fileURL = RLMTestRealmURL(); - @autoreleasepool { - RLMRealm *versioned = [RLMRealm realmWithConfiguration:configVersioned error:nil]; - XCTAssertEqual(0U, versioned->_realm->schema_version()); - } - std::shared_ptr versioned = realm::Realm::get_shared_realm(configVersioned.config); - - // Create a config that's not versioned. - RLMRealmConfiguration *configUnversioned = [RLMRealmConfiguration defaultConfiguration]; - configUnversioned.configRef.schema_version = RLMNotVersioned; - std::shared_ptr unversioned = realm::Realm::get_shared_realm(configUnversioned.config); - - XCTAssertNotEqual(versioned->schema_version(), RLMNotVersioned); - XCTAssertEqual(unversioned->schema_version(), RLMNotVersioned); - beforeWrapper(versioned); // one realm should invoke the block - beforeWrapper(unversioned); // while the other should not invoke the block - - [self waitForExpectationsWithTimeout:5 handler:nil]; -} - -// TODO: Consider testing with sync_config->on_sync_client_event_hook or a client reset -- (void)testAfterClientResetCallbackNotVersioned { - // Setup sync config - RLMSyncConfiguration *syncConfig = [[RLMSyncConfiguration alloc] initWithRawConfig:{} path:""]; - XCTestExpectation *afterExpectation = [self expectationWithDescription:@"block should not be called"]; - afterExpectation.inverted = true; - - syncConfig.clientResetMode = RLMClientResetModeRecoverUnsyncedChanges; - syncConfig.afterClientReset = ^(RLMRealm * _Nonnull, RLMRealm * _Nonnull) { - [afterExpectation fulfill]; - }; - auto& afterWrapper = syncConfig.rawConfiguration.notify_after_client_reset; - - // Create a config that's not versioned. - RLMRealmConfiguration *configUnversioned = [RLMRealmConfiguration defaultConfiguration]; - configUnversioned.configRef.schema_version = RLMNotVersioned; - std::shared_ptr unversioned = realm::Realm::get_shared_realm(configUnversioned.config); - - auto unversionedTsr = realm::ThreadSafeReference(unversioned); - XCTAssertEqual(unversioned->schema_version(), RLMNotVersioned); - afterWrapper(unversioned, std::move(unversionedTsr), false); - - [self waitForExpectationsWithTimeout:1 handler:nil]; -} - -#pragma mark - Progress Notifications - -static const NSInteger NUMBER_OF_BIG_OBJECTS = 2; - -- (void)populateData { - NSURL *realmURL; - RLMUser *user = [self createUser]; - @autoreleasepool { - RLMRealm *realm = [self openRealmWithUser:user]; - realmURL = realm.configuration.fileURL; - CHECK_COUNT(0, HugeSyncObject, realm); - [realm beginWriteTransaction]; - for (NSInteger i = 0; i < NUMBER_OF_BIG_OBJECTS; i++) { - [realm addObject:[HugeSyncObject hugeSyncObject]]; - } - [realm commitWriteTransaction]; - [self waitForUploadsForRealm:realm]; - CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm); - } - [user.app.syncManager waitForSessionTermination]; - [self deleteRealmFileAtURL:realmURL]; -} - -- (void)testStreamingDownloadNotifier { - RLMRealm *realm = [self openRealm]; - RLMSyncSession *session = realm.syncSession; - XCTAssertNotNil(session); - - XCTestExpectation *ex = [self expectationWithDescription:@"streaming-download-notifier"]; - std::atomic callCount{0}; - std::atomic transferred{0}; - std::atomic transferrable{0}; - BOOL hasBeenFulfilled = NO; - RLMNotificationToken *token = [session - addProgressNotificationForDirection:RLMSyncProgressDirectionDownload - mode:RLMSyncProgressModeReportIndefinitely - block:[&](NSUInteger xfr, NSUInteger xfb) { - // Make sure the values are increasing, and update our stored copies. - XCTAssertGreaterThanOrEqual(xfr, transferred.load()); - XCTAssertGreaterThanOrEqual(xfb, transferrable.load()); - transferred = xfr; - transferrable = xfb; - callCount++; - if (transferrable > 0 && transferred >= transferrable && !hasBeenFulfilled) { - [ex fulfill]; - hasBeenFulfilled = YES; - } - }]; - - [self populateData]; - - [self waitForExpectationsWithTimeout:30.0 handler:nil]; - [token invalidate]; - // The notifier should have been called at least twice: once at the beginning and at least once - // to report progress. - XCTAssertGreaterThan(callCount.load(), 1); - XCTAssertGreaterThanOrEqual(transferred.load(), transferrable.load()); -} - -- (void)testStreamingUploadNotifier { - RLMRealm *realm = [self openRealm]; - RLMSyncSession *session = realm.syncSession; - XCTAssertNotNil(session); - - XCTestExpectation *ex = [self expectationWithDescription:@"streaming-upload-expectation"]; - std::atomic callCount{0}; - std::atomic transferred{0}; - std::atomic transferrable{0}; - auto token = [session addProgressNotificationForDirection:RLMSyncProgressDirectionUpload - mode:RLMSyncProgressModeReportIndefinitely - block:[&](NSUInteger xfr, NSUInteger xfb) { - // Make sure the values are increasing, and update our stored copies. - XCTAssertGreaterThanOrEqual(xfr, transferred.load()); - XCTAssertGreaterThanOrEqual(xfb, transferrable.load()); - transferred = xfr; - transferrable = xfb; - callCount++; - if (transferred > 0 && transferred >= transferrable && transferrable > 1000000 * NUMBER_OF_BIG_OBJECTS) { - [ex fulfill]; - } - }]; - - // Upload lots of data - [realm beginWriteTransaction]; - for (NSInteger i=0; i 0 && transferredBytes == transferrableBytes) { - [ex2 fulfill]; - } - }]; - [self waitForExpectationsWithTimeout:2.0 handler:nil]; -} - -- (void)testAsyncOpenConnectionTimeout { - TimeoutProxyServer *proxy = [[TimeoutProxyServer alloc] initWithPort:5678 targetPort:9090]; - NSError *error; - [proxy startAndReturnError:&error]; - XCTAssertNil(error); - - RLMAppConfiguration *config = [[RLMAppConfiguration alloc] - initWithBaseURL:@"http://localhost:9090" - transport:[AsyncOpenConnectionTimeoutTransport new] - defaultRequestTimeoutMS:60]; - RLMSyncTimeoutOptions *timeoutOptions = [RLMSyncTimeoutOptions new]; - timeoutOptions.connectTimeout = 1000.0; - config.syncTimeouts = timeoutOptions; - NSString *appId = [RealmServer.shared - createAppWithPartitionKeyType:@"string" - types:@[Person.self] persistent:false error:nil]; - RLMUser *user = [self createUserForApp:[RLMApp appWithId:appId configuration:config]]; - - RLMRealmConfiguration *c = [user configurationWithPartitionValue:appId]; - c.objectClasses = @[Person.class]; - RLMSyncConfiguration *syncConfig = c.syncConfiguration; - syncConfig.cancelAsyncOpenOnNonFatalErrors = true; - c.syncConfiguration = syncConfig; - - // Set delay above the timeout so it should fail - proxy.delay = 2.0; - - XCTestExpectation *ex = [self expectationWithDescription:@"async open"]; - [RLMRealm asyncOpenWithConfiguration:c - callbackQueue:dispatch_get_main_queue() - callback:^(RLMRealm *realm, NSError *error) { - RLMValidateError(error, NSPOSIXErrorDomain, ETIMEDOUT, - @"Sync connection was not fully established in time"); - XCTAssertNil(realm); - [ex fulfill]; - }]; - [self waitForExpectationsWithTimeout:30.0 handler:nil]; - - // Delay below the timeout should work - proxy.delay = 0.5; - - ex = [self expectationWithDescription:@"async open"]; - [RLMRealm asyncOpenWithConfiguration:c - callbackQueue:dispatch_get_main_queue() - callback:^(RLMRealm *realm, NSError *error) { - XCTAssertNotNil(realm); - XCTAssertNil(error); - [ex fulfill]; - }]; - [self waitForExpectationsWithTimeout:30.0 handler:nil]; - - [proxy stop]; -} - -#pragma mark - Compact on Launch - -- (void)testCompactOnLaunch { - RLMRealmConfiguration *config = self.configuration; - NSString *path = config.fileURL.path; - // Create a large object and then delete it in the next transaction so that - // the file is bloated - @autoreleasepool { - RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; - [realm beginWriteTransaction]; - [realm addObject:[HugeSyncObject hugeSyncObject]]; - [realm commitWriteTransaction]; - [self waitForUploadsForRealm:realm]; - - [realm beginWriteTransaction]; - [realm deleteAllObjects]; - [realm commitWriteTransaction]; - } - - RLMWaitForRealmToClose(config.fileURL.path); - - auto fileManager = NSFileManager.defaultManager; - auto initialSize = [[fileManager attributesOfItemAtPath:path error:nil][NSFileSize] unsignedLongLongValue]; - - // Reopen the file with a shouldCompactOnLaunch block and verify that it is - // actually compacted - __block bool blockCalled = false; - __block NSUInteger usedSize = 0; - config.shouldCompactOnLaunch = ^(NSUInteger, NSUInteger used) { - usedSize = used; - blockCalled = true; - return YES; - }; - - @autoreleasepool { - [RLMRealm realmWithConfiguration:config error:nil]; - } - XCTAssertTrue(blockCalled); - - auto finalSize = [[fileManager attributesOfItemAtPath:path error:nil][NSFileSize] unsignedLongLongValue]; - XCTAssertLessThan(finalSize, initialSize); - XCTAssertLessThanOrEqual(finalSize, usedSize + realm::util::page_size()); -} - -- (void)testWriteCopy { - RLMRealm *syncRealm = [self openRealm]; - [self addPersonsToRealm:syncRealm persons:@[[Person john]]]; - - NSError *writeError; - XCTAssertTrue([syncRealm writeCopyToURL:RLMTestRealmURL() - encryptionKey:syncRealm.configuration.encryptionKey - error:&writeError]); - XCTAssertNil(writeError); - - RLMRealmConfiguration *localConfig = [RLMRealmConfiguration new]; - localConfig.fileURL = RLMTestRealmURL(); - localConfig.objectClasses = @[Person.self]; - localConfig.schemaVersion = 1; - - RLMRealm *localCopy = [RLMRealm realmWithConfiguration:localConfig error:nil]; - XCTAssertEqual(1U, [Person allObjectsInRealm:localCopy].count); -} - -#pragma mark - Read Only - -- (void)testOpenSynchronouslyInReadOnlyBeforeRemoteSchemaIsInitialized { - RLMUser *user = [self userForTest:_cmd]; - - if (self.isParent) { - RLMRealmConfiguration *config = [user configurationWithPartitionValue:self.name]; - config.objectClasses = self.defaultObjectTypes; - config.readOnly = true; - RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; - CHECK_COUNT(0, Person, realm); - RLMRunChildAndWait(); - [self waitForDownloadsForRealm:realm]; - CHECK_COUNT(1, Person, realm); - } else { - RLMRealm *realm = [self openRealmForPartitionValue:self.name user:user]; - [self addPersonsToRealm:realm persons:@[[Person john]]]; - [self waitForUploadsForRealm:realm]; - CHECK_COUNT(1, Person, realm); - } -} - -- (void)testAddPropertyToReadOnlyRealmWithExistingLocalCopy { - @autoreleasepool { - RLMRealm *realm = [self openRealm]; - [self addPersonsToRealm:realm persons:@[[Person john]]]; - [self waitForUploadsForRealm:realm]; - } - - RLMRealmConfiguration *config = [self.createUser configurationWithPartitionValue:self.name]; - config.objectClasses = self.defaultObjectTypes; - config.readOnly = true; - @autoreleasepool { - RLMRealm *realm = [self asyncOpenRealmWithConfiguration:config]; - CHECK_COUNT(1, Person, realm); - } - - RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:Person.class]; - objectSchema.properties = [RLMObjectSchema schemaForObjectClass:HugeSyncObject.class].properties; - config.customSchema = [[RLMSchema alloc] init]; - config.customSchema.objectSchema = @[objectSchema]; - - RLMAssertThrowsWithReason([RLMRealm realmWithConfiguration:config error:nil], - @"Property 'Person.dataProp' has been added."); - - @autoreleasepool { - NSError *error = [self asyncOpenErrorWithConfiguration:config]; - XCTAssertNotEqual([error.localizedDescription rangeOfString:@"Property 'Person.dataProp' has been added."].location, - NSNotFound); - } -} - -- (void)testAddPropertyToReadOnlyRealmWithAsyncOpen { - @autoreleasepool { - RLMRealm *realm = [self openRealm]; - [self addPersonsToRealm:realm persons:@[[Person john]]]; - [self waitForUploadsForRealm:realm]; - } - [self.app.syncManager waitForSessionTermination]; - - RLMRealmConfiguration *config = [self configuration]; - config.readOnly = true; - - RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:Person.class]; - objectSchema.properties = [RLMObjectSchema schemaForObjectClass:HugeSyncObject.class].properties; - config.customSchema = [[RLMSchema alloc] init]; - config.customSchema.objectSchema = @[objectSchema]; - - @autoreleasepool { - NSError *error = [self asyncOpenErrorWithConfiguration:config]; - XCTAssert([error.localizedDescription containsString:@"Property 'Person.dataProp' has been added."]); - } -} - -- (void)testSyncConfigShouldNotMigrate { - RLMRealm *realm = [self openRealm]; - RLMAssertThrowsWithReason(realm.configuration.deleteRealmIfMigrationNeeded = YES, - @"Cannot set 'deleteRealmIfMigrationNeeded' when sync is enabled"); - - RLMRealmConfiguration *localRealmConfiguration = [RLMRealmConfiguration defaultConfiguration]; - XCTAssertNoThrow(localRealmConfiguration.deleteRealmIfMigrationNeeded = YES); -} - -#pragma mark - Write Copy For Configuration - -- (void)testWriteCopyForConfigurationLocalToSync { - RLMRealmConfiguration *localConfig = [RLMRealmConfiguration new]; - localConfig.objectClasses = @[Person.class]; - localConfig.fileURL = RLMTestRealmURL(); - - RLMRealmConfiguration *syncConfig = self.configuration; - syncConfig.objectClasses = @[Person.class]; - - RLMRealm *localRealm = [RLMRealm realmWithConfiguration:localConfig error:nil]; - [localRealm transactionWithBlock:^{ - [localRealm addObject:[Person ringo]]; - }]; - - [localRealm writeCopyForConfiguration:syncConfig error:nil]; - - RLMRealm *syncedRealm = [RLMRealm realmWithConfiguration:syncConfig error:nil]; - XCTAssertEqual([[Person allObjectsInRealm:syncedRealm] objectsWhere:@"firstName = 'Ringo'"].count, 1U); - - [self waitForDownloadsForRealm:syncedRealm]; - [syncedRealm transactionWithBlock:^{ - [syncedRealm addObject:[Person john]]; - }]; - [self waitForUploadsForRealm:syncedRealm]; - - RLMResults *syncedResults = [Person allObjectsInRealm:syncedRealm]; - XCTAssertEqual([syncedResults objectsWhere:@"firstName = 'Ringo'"].count, 1U); - XCTAssertEqual([syncedResults objectsWhere:@"firstName = 'John'"].count, 1U); -} - -- (void)testWriteCopyForConfigurationSyncToSyncRealmError { - RLMRealmConfiguration *syncConfig = self.configuration; - RLMRealmConfiguration *syncConfig2 = self.configuration; - - RLMRealm *syncedRealm = [RLMRealm realmWithConfiguration:syncConfig error:nil]; - [syncedRealm.syncSession suspend]; - [syncedRealm transactionWithBlock:^{ - [syncedRealm addObject:[Person ringo]]; - }]; - // Cannot export a synced realm as not all changes have been synced. - NSError *error; - [syncedRealm writeCopyForConfiguration:syncConfig2 error:&error]; - XCTAssertEqual(error.code, RLMErrorFail); - XCTAssertEqualObjects(error.localizedDescription, - @"All client changes must be integrated in server before writing copy"); -} - -- (void)testWriteCopyForConfigurationLocalRealmForSyncWithExistingData { - RLMRealmConfiguration *initialSyncConfig = self.configuration; - initialSyncConfig.objectClasses = @[Person.class]; - - // Make sure objects with confliciting primary keys sync ok. - RLMObjectId *conflictingObjectId = [RLMObjectId objectId]; - Person *person = [Person ringo]; - person._id = conflictingObjectId; - - RLMRealm *initialRealm = [RLMRealm realmWithConfiguration:initialSyncConfig error:nil]; - [initialRealm transactionWithBlock:^{ - [initialRealm addObject:person]; - [initialRealm addObject:[Person john]]; - }]; - [self waitForUploadsForRealm:initialRealm]; - - RLMRealmConfiguration *localConfig = [RLMRealmConfiguration new]; - localConfig.objectClasses = @[Person.class]; - localConfig.fileURL = RLMTestRealmURL(); - - RLMRealmConfiguration *syncConfig = self.configuration; - syncConfig.objectClasses = @[Person.class]; - - RLMRealm *localRealm = [RLMRealm realmWithConfiguration:localConfig error:nil]; - // `person2` will override what was previously stored on the server. - Person *person2 = [Person new]; - person2._id = conflictingObjectId; - person2.firstName = @"John"; - person2.lastName = @"Doe"; - - [localRealm transactionWithBlock:^{ - [localRealm addObject:person2]; - [localRealm addObject:[Person george]]; - }]; - - [localRealm writeCopyForConfiguration:syncConfig error:nil]; - - RLMRealm *syncedRealm = [RLMRealm realmWithConfiguration:syncConfig error:nil]; - [self waitForDownloadsForRealm:syncedRealm]; - XCTAssertEqual([syncedRealm allObjects:@"Person"].count, 3U); - [syncedRealm transactionWithBlock:^{ - [syncedRealm addObject:[Person stuart]]; - }]; - - [self waitForUploadsForRealm:syncedRealm]; - RLMResults *syncedResults = [Person allObjectsInRealm:syncedRealm]; - - NSPredicate *p = [NSPredicate predicateWithFormat:@"firstName = 'John' AND lastName = 'Doe' AND _id = %@", conflictingObjectId]; - XCTAssertEqual([syncedResults objectsWithPredicate:p].count, 1U); - XCTAssertEqual([syncedRealm allObjects:@"Person"].count, 4U); -} - -#pragma mark - File paths - -static NSString *newPathForPartitionValue(RLMUser *user, id partitionValue) { - std::stringstream s; - s << RLMConvertRLMBSONToBson(partitionValue); - // Intentionally not passing the correct partition value here as we (accidentally?) - // don't use the filename generated from the partition value - realm::SyncConfig config(user.user, "null"); - return @(user.user->path_for_realm(config, s.str()).c_str()); -} - -- (void)testSyncFilePaths { - RLMUser *user = self.anonymousUser; - auto configuration = [user configurationWithPartitionValue:@"abc"]; - XCTAssertTrue([configuration.fileURL.path - hasSuffix:([NSString stringWithFormat:@"mongodb-realm/%@/%@/%%22abc%%22.realm", - self.appId, user.identifier])]); - configuration = [user configurationWithPartitionValue:@123]; - XCTAssertTrue([configuration.fileURL.path - hasSuffix:([NSString stringWithFormat:@"mongodb-realm/%@/%@/%@.realm", - self.appId, user.identifier, @"%7B%22%24numberInt%22%3A%22123%22%7D"])]); - configuration = [user configurationWithPartitionValue:nil]; - XCTAssertTrue([configuration.fileURL.path - hasSuffix:([NSString stringWithFormat:@"mongodb-realm/%@/%@/null.realm", - self.appId, user.identifier])]); - - XCTAssertEqualObjects([user configurationWithPartitionValue:@"abc"].fileURL.path, - newPathForPartitionValue(user, @"abc")); - XCTAssertEqualObjects([user configurationWithPartitionValue:@123].fileURL.path, - newPathForPartitionValue(user, @123)); - XCTAssertEqualObjects([user configurationWithPartitionValue:nil].fileURL.path, - newPathForPartitionValue(user, nil)); -} - -static NSString *oldPathForPartitionValue(RLMUser *user, NSString *oldName) { - realm::SyncConfig config(user.user, "null"); - return [NSString stringWithFormat:@"%@/%s%@.realm", - [@(user.user->path_for_realm(config).c_str()) stringByDeletingLastPathComponent], - user.user->user_id().c_str(), oldName]; -} - -- (void)testLegacyFilePathsAreUsedIfFilesArePresent { - RLMUser *user = self.anonymousUser; - - auto testPartitionValue = [&](id partitionValue, NSString *oldName) { - NSURL *url = [NSURL fileURLWithPath:oldPathForPartitionValue(user, oldName)]; - @autoreleasepool { - auto configuration = [user configurationWithPartitionValue:partitionValue]; - configuration.fileURL = url; - configuration.objectClasses = @[Person.class]; - RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; - [realm beginWriteTransaction]; - [Person createInRealm:realm withValue:[Person george]]; - [realm commitWriteTransaction]; - } - - auto configuration = [user configurationWithPartitionValue:partitionValue]; - configuration.objectClasses = @[Person.class]; - XCTAssertEqualObjects(configuration.fileURL, url); - RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; - XCTAssertEqual([Person allObjectsInRealm:realm].count, 1U); - }; - - testPartitionValue(@"abc", @"%2F%2522abc%2522"); - testPartitionValue(@123, @"%2F%257B%2522%24numberInt%2522%253A%2522123%2522%257D"); - testPartitionValue(nil, @"%2Fnull"); -} -@end - -#endif // TARGET_OS_OSX diff --git a/Realm/ObjectServerTests/RLMServerTestObjects.h b/Realm/ObjectServerTests/RLMServerTestObjects.h deleted file mode 100644 index 91823fb739..0000000000 --- a/Realm/ObjectServerTests/RLMServerTestObjects.h +++ /dev/null @@ -1,142 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMTestObjects.h" - -@interface Dog : RLMObject - -@property RLMObjectId *_id; -@property NSString *breed; -@property NSString *name; -@property NSString *partition; -- (instancetype)initWithPrimaryKey:(RLMObjectId *)primaryKey breed:(NSString *)breed name:(NSString *)name; -@end - -@interface Person : RLMObject -@property RLMObjectId *_id; -@property NSInteger age; -@property NSString *firstName; -@property NSString *lastName; -@property NSString *partition; - -- (instancetype)initWithPrimaryKey:(RLMObjectId *)primaryKey age:(NSInteger)age firstName:(NSString *)firstName lastName:(NSString *)lastName; -+ (instancetype)john; -+ (instancetype)paul; -+ (instancetype)ringo; -+ (instancetype)george; -+ (instancetype)stuart; -@end - -@interface HugeSyncObject : RLMObject -@property RLMObjectId *_id; -@property NSData *dataProp; -+ (instancetype)hugeSyncObject; -@end - -@interface UUIDPrimaryKeyObject : RLMObject -@property NSUUID *_id; -@property NSString *strCol; -@property NSInteger intCol; -- (instancetype)initWithPrimaryKey:(NSUUID *)primaryKey strCol:(NSString *)strCol intCol:(NSInteger)intCol; -@end - -@interface StringPrimaryKeyObject : RLMObject -@property NSString *_id; -@property NSString *strCol; -@property NSInteger intCol; -- (instancetype)initWithPrimaryKey:(NSString *)primaryKey strCol:(NSString *)strCol intCol:(NSInteger)intCol; -@end - -@interface IntPrimaryKeyObject : RLMObject -@property NSInteger _id; -@property NSString *strCol; -@property NSInteger intCol; -- (instancetype)initWithPrimaryKey:(NSInteger)primaryKey strCol:(NSString *)strCol intCol:(NSInteger)intCol; -@end - -@interface AllTypesSyncObject : RLMObject -@property RLMObjectId *_id; -@property BOOL boolCol; -@property bool cBoolCol; -@property int intCol; -@property double doubleCol; -@property NSString *stringCol; -@property NSData *binaryCol; -@property NSDate *dateCol; -@property int64_t longCol; -@property RLMDecimal128 *decimalCol; -@property NSUUID *uuidCol; -@property id anyCol; -@property Person *objectCol; -+ (NSDictionary *)values:(int)i; -@end - -RLM_COLLECTION_TYPE(Person); -@interface RLMArraySyncObject : RLMObject -@property RLMObjectId *_id; -@property RLMArray *intArray; -@property RLMArray *boolArray; -@property RLMArray *stringArray; -@property RLMArray *dataArray; -@property RLMArray *doubleArray; -@property RLMArray *objectIdArray; -@property RLMArray *decimalArray; -@property RLMArray *uuidArray; -@property RLMArray *anyArray; -@property RLM_GENERIC_ARRAY(Person) *objectArray; -@end - -@interface RLMSetSyncObject : RLMObject -@property RLMObjectId *_id; -@property RLMSet *intSet; -@property RLMSet *boolSet; -@property RLMSet *stringSet; -@property RLMSet *dataSet; -@property RLMSet *doubleSet; -@property RLMSet *objectIdSet; -@property RLMSet *decimalSet; -@property RLMSet *uuidSet; -@property RLMSet *anySet; -@property RLM_GENERIC_SET(Person) *objectSet; - -@property RLMSet *otherIntSet; -@property RLMSet *otherBoolSet; -@property RLMSet *otherStringSet; -@property RLMSet *otherDataSet; -@property RLMSet *otherDoubleSet; -@property RLMSet *otherObjectIdSet; -@property RLMSet *otherDecimalSet; -@property RLMSet *otherUuidSet; -@property RLMSet *otherAnySet; -@property RLM_GENERIC_SET(Person) *otherObjectSet; -@end - -@interface RLMDictionarySyncObject : RLMObject -@property RLMObjectId *_id; -@property RLMDictionary *intDictionary; -@property RLMDictionary *boolDictionary; -@property RLMDictionary *stringDictionary; -@property RLMDictionary *dataDictionary; -@property RLMDictionary *doubleDictionary; -@property RLMDictionary *objectIdDictionary; -@property RLMDictionary *decimalDictionary; -@property RLMDictionary *uuidDictionary; -@property RLMDictionary *anyDictionary; -@property RLMDictionary *objectDictionary; - -@end diff --git a/Realm/ObjectServerTests/RLMServerTestObjects.m b/Realm/ObjectServerTests/RLMServerTestObjects.m deleted file mode 100644 index cb9b13eda4..0000000000 --- a/Realm/ObjectServerTests/RLMServerTestObjects.m +++ /dev/null @@ -1,338 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import -#import "RLMServerTestObjects.h" - -#pragma mark Dog - -@implementation Dog - -+ (NSString *)primaryKey { - return @"_id"; -} - -+ (NSArray *)requiredProperties { - return @[@"name"]; -} - -+ (NSDictionary *)defaultPropertyValues { - return @{@"_id": [RLMObjectId objectId]}; -} - -- (instancetype)initWithPrimaryKey:(RLMObjectId *)primaryKey breed:(NSString *)breed name:(NSString *)name { - self = [super init]; - if (self) { - self._id = primaryKey; - self.breed = breed; - self.name = name; - } - return self; -} - -@end - -#pragma mark Person - -@implementation Person - -+ (NSDictionary *)defaultPropertyValues { - return @{@"_id": [RLMObjectId objectId]}; -} - -+ (NSString *)primaryKey { - return @"_id"; -} - -+ (NSArray *)requiredProperties { - return @[@"firstName", @"lastName", @"age"]; -} - -- (instancetype)initWithPrimaryKey:(RLMObjectId *)primaryKey age:(NSInteger)age firstName:(NSString *)firstName lastName:(NSString *)lastName { - self = [super init]; - if (self) { - self._id = primaryKey; - self.age = age; - self.firstName = firstName; - self.lastName = lastName; - } - return self; -} - -+ (instancetype)john { - Person *john = [[Person alloc] init]; - john._id = [RLMObjectId objectId]; - john.age = 30; - john.firstName = @"John"; - john.lastName = @"Lennon"; - return john; -} - -+ (instancetype)paul { - Person *paul = [[Person alloc] init]; - paul._id = [RLMObjectId objectId]; - paul.age = 30; - paul.firstName = @"Paul"; - paul.lastName = @"McCartney"; - return paul; -} - -+ (instancetype)ringo { - Person *ringo = [[Person alloc] init]; - ringo._id = [RLMObjectId objectId]; - ringo.age = 30; - ringo.firstName = @"Ringo"; - ringo.lastName = @"Starr"; - return ringo; -} - -+ (instancetype)george { - Person *george = [[Person alloc] init]; - george._id = [RLMObjectId objectId]; - george.age = 30; - george.firstName = @"George"; - george.lastName = @"Harrison"; - return george; -} - -+ (instancetype)stuart { - Person *stuart = [[Person alloc] init]; - stuart._id = [RLMObjectId objectId]; - stuart.age = 30; - stuart.firstName = @"Stuart"; - stuart.lastName = @"Sutcliffe"; - return stuart; -} - -@end - -#pragma mark HugeSyncObject - -@implementation HugeSyncObject - -+ (NSDictionary *)defaultPropertyValues { - return @{@"_id": [RLMObjectId objectId]}; -} - -+ (NSString *)primaryKey { - return @"_id"; -} - -+ (instancetype)hugeSyncObject { - const NSInteger fakeDataSize = 1000000; - HugeSyncObject *object = [[self alloc] init]; - char fakeData[fakeDataSize]; - memset(fakeData, 16, sizeof(fakeData)); - object.dataProp = [NSData dataWithBytes:fakeData length:sizeof(fakeData)]; - return object; -} - -@end - -#pragma mark AllTypeSyncObject - -@implementation AllTypesSyncObject - -+ (NSDictionary *)defaultPropertyValues { - return @{@"_id": [RLMObjectId objectId]}; -} - -+ (NSString *)primaryKey { - return @"_id"; -} - -+ (NSArray *)requiredProperties { - return @[@"boolCol", @"cBoolcol", - @"intCol", @"doubleCol", - @"stringCol", @"binaryCol", - @"dateCol", @"longCol", - @"decimalCol", @"uuidCol", @"objectIdCol"]; -} - -+ (NSDictionary *)values:(int)i { - NSString *str = [NSString stringWithFormat:@"%d", i]; - return @{ - @"boolCol": @(i % 2), - @"cBoolCol": @(i % 2), - @"intCol": @(i), - @"doubleCol": @(1.11 * i), - @"stringCol": [NSString stringWithFormat:@"%d", i], - @"binaryCol": [str dataUsingEncoding:NSUTF8StringEncoding], - @"dateCol": [NSDate dateWithTimeIntervalSince1970:i], - @"longCol": @((long long)i * INT_MAX + 1), - @"decimalCol": [[RLMDecimal128 alloc] initWithNumber:@(i)], - @"uuidCol": i < 4 ? @[[[NSUUID alloc] initWithUUIDString:@"85d4fbee-6ec6-47df-bfa1-615931903d7e"], - [[NSUUID alloc] initWithUUIDString:@"00000000-0000-0000-0000-000000000000"], - [[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD89"], - [[NSUUID alloc] initWithUUIDString:@"b84e8912-a7c2-41cd-8385-86d200d7b31e"]][i] : - [[NSUUID alloc] initWithUUIDString:@"b9d325b0-3058-4838-8473-8f1aaae410db"], - @"anyCol": @(i+1), - }; -} - -@end - -#pragma mark RLMArraySyncObject - -@implementation RLMArraySyncObject - -+ (NSDictionary *)defaultPropertyValues { - return @{@"_id": [RLMObjectId objectId]}; -} - -+ (NSString *)primaryKey { - return @"_id"; -} - -+ (NSArray *)requiredProperties { - return @[@"intArray", @"boolArray", - @"stringArray", @"dataArray", - @"doubleArray", @"objectIdArray", - @"decimalArray", @"uuidArray", @"anyArray"]; -} - -@end - -#pragma mark RLMSetSyncObject - -@implementation RLMSetSyncObject - -+ (NSDictionary *)defaultPropertyValues { - return @{@"_id": [RLMObjectId objectId]}; -} - -+ (NSString *)primaryKey { - return @"_id"; -} - -+ (NSArray *)requiredProperties { - return @[@"intSet", @"boolSet", - @"stringSet", @"dataSet", - @"doubleSet", @"objectIdSet", - @"decimalSet", @"uuidSet", @"anySet", - @"otherIntSet", @"otherBoolSet", - @"otherStringSet", @"otherDataSet", - @"otherDoubleSet", @"otherObjectIdSet", - @"otherDecimalSet", @"otherUuidSet", @"otherAnySet"]; -} - -@end - -#pragma mark RLMDictionarySyncObject - -@implementation RLMDictionarySyncObject - -+ (NSDictionary *)defaultPropertyValues { - return @{@"_id": [RLMObjectId objectId]}; -} - -+ (NSString *)primaryKey { - return @"_id"; -} - -+ (NSArray *)requiredProperties { - return @[@"intDictionary", @"boolDictionary", @"stringDictionary", - @"dataDictionary", @"doubleDictionary", @"objectIdDictionary", - @"decimalDictionary", @"uuidDictionary", @"anyDictionary"]; -} - -@end - -#pragma mark UUIDPrimaryKeyObject - -@implementation UUIDPrimaryKeyObject - -+ (NSString *)primaryKey { - return @"_id"; -} - -+ (NSArray *)requiredProperties { - return @[@"strCol", @"intCol"]; -} - -+ (NSDictionary *)defaultPropertyValues { - return @{@"_id": [[NSUUID alloc] initWithUUIDString:@"85d4fbee-6ec6-47df-bfa1-615931903d7e"]}; -} - -- (instancetype)initWithPrimaryKey:(NSUUID *)primaryKey strCol:(NSString *)strCol intCol:(NSInteger)intCol { - self = [super init]; - if (self) { - self._id = primaryKey; - self.strCol = strCol; - self.intCol = intCol; - } - return self; -} - -@end - -#pragma mark StringPrimaryKeyObject - -@implementation StringPrimaryKeyObject - -+ (NSString *)primaryKey { - return @"_id"; -} - -+ (NSArray *)requiredProperties { - return @[@"strCol", @"intCol"]; -} - -+ (NSDictionary *)defaultPropertyValues { - return @{@"_id": @"1234567890ab1234567890ab"}; -} - -- (instancetype)initWithPrimaryKey:(NSString *)primaryKey strCol:(NSString *)strCol intCol:(NSInteger)intCol { - self = [super init]; - if (self) { - self._id = primaryKey; - self.strCol = strCol; - self.intCol = intCol; - } - return self; -} - -@end - -#pragma mark IntPrimaryKeyObject - -@implementation IntPrimaryKeyObject - -+ (NSString *)primaryKey { - return @"_id"; -} - -+ (NSArray *)requiredProperties { - return @[@"_id", @"strCol", @"intCol"]; -} - -+ (NSDictionary *)defaultPropertyValues { - return @{@"_id": @1234567890}; -} - -- (instancetype)initWithPrimaryKey:(NSInteger)primaryKey strCol:(NSString *)strCol intCol:(NSInteger)intCol { - self = [super init]; - if (self) { - self._id = primaryKey; - self.strCol = strCol; - self.intCol = intCol; - } - return self; -} - -@end diff --git a/Realm/ObjectServerTests/RLMSubscriptionTests.mm b/Realm/ObjectServerTests/RLMSubscriptionTests.mm deleted file mode 100644 index 9d6bbbae3d..0000000000 --- a/Realm/ObjectServerTests/RLMSubscriptionTests.mm +++ /dev/null @@ -1,668 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncTestCase.h" -#import "RLMSyncSubscription_Private.h" -#import "RLMApp_Private.hpp" - -@interface RLMSubscriptionTests : RLMSyncTestCase -@end - -@implementation RLMSubscriptionTests -- (NSArray *)defaultObjectTypes { - return @[Dog.self, Person.self]; -} - -- (NSString *)createAppWithError:(NSError **)error { - return [self createFlexibleSyncAppWithError:error]; -} - -- (RLMRealmConfiguration *)configurationForUser:(RLMUser *)user { - return [user flexibleSyncConfiguration]; -} - -- (void)testCreateFlexibleSyncApp { - NSString *appId = [RealmServer.shared createAppWithFields:@[@"age"] - types:@[Person.self] - persistent:false - error:nil]; - RLMApp *app = [self appWithId:appId]; - XCTAssertNotNil(app); -} - -- (void)testFlexibleSyncOpenRealm { - XCTAssertNotNil([self openRealm]); -} - -- (void)testGetSubscriptionsWhenLocalRealm { - RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; - configuration.objectClasses = @[Person.self]; - RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; - RLMAssertThrowsWithReason(realm.subscriptions, @"This Realm was not configured with flexible sync"); -} - -- (void)testGetSubscriptionsWhenPbsRealm { - RLMRealmConfiguration *config = [self.createUser configurationWithPartitionValue:nil]; - config.objectClasses = @[]; - RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; - RLMAssertThrowsWithReason(realm.subscriptions, @"This Realm was not configured with flexible sync"); -} - -- (void)testFlexibleSyncRealmFilePath { - RLMUser *user = [self createUser]; - RLMRealmConfiguration *config = [user flexibleSyncConfiguration]; - NSString *expected = [NSString stringWithFormat:@"mongodb-realm/%@/%@/flx_sync_default.realm", self.appId, user.identifier]; - XCTAssertTrue([config.fileURL.path hasSuffix:expected]); -} - -- (void)testGetSubscriptionsWhenFlexibleSync { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - XCTAssertNotNil(subs); - XCTAssertEqual(subs.version, 0UL); - XCTAssertEqual(subs.count, 0UL); -} - -- (void)testGetSubscriptionsWhenSameVersion { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs1 = realm.subscriptions; - RLMSyncSubscriptionSet *subs2 = realm.subscriptions; - XCTAssertEqual(subs1.version, 0UL); - XCTAssertEqual(subs2.version, 0UL); -} - -- (void)testCheckVersionAfterAddSubscription { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - XCTAssertNotNil(subs); - XCTAssertEqual(subs.version, 0UL); - XCTAssertEqual(subs.count, 0UL); - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - where:@"age > 15"]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 1UL); -} - -- (void)testEmptyWriteSubscriptions { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - XCTAssertNotNil(subs); - XCTAssertEqual(subs.version, 0UL); - XCTAssertEqual(subs.count, 0UL); - - [subs update:^{}]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 0UL); -} - -- (void)testAddAndFindSubscriptionByQuery { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - where:@"age > 15"]; - }]; - - RLMSyncSubscription *foundSubscription = [subs subscriptionWithClassName:Person.className - where:@"age > 15"]; - XCTAssertNotNil(foundSubscription); - XCTAssertNil(foundSubscription.name); - XCTAssert(foundSubscription.queryString, @"age > 15"); -} - -- (void)testAddAndFindSubscriptionWithCompoundQuery { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - XCTAssertNotNil(subs); - XCTAssertEqual(subs.version, 0UL); - XCTAssertEqual(subs.count, 0UL); - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - where:@"firstName == %@ and lastName == %@", @"John", @"Doe"]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 1UL); - - RLMSyncSubscription *foundSubscription = [subs subscriptionWithClassName:Person.className - where:@"firstName == %@ and lastName == %@", @"John", @"Doe"]; - XCTAssertNotNil(foundSubscription); - XCTAssertNil(foundSubscription.name); - XCTAssert(foundSubscription.queryString, @"firstName == 'John' and lastName == 'Doe'"); -} - -- (void)testAddAndFindSubscriptionWithPredicate { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - XCTAssertNotNil(subs); - XCTAssertEqual(subs.version, 0UL); - XCTAssertEqual(subs.count, 0UL); - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - predicate:[NSPredicate predicateWithFormat:@"age == %d", 20]]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 1UL); - - RLMSyncSubscription *foundSubscription = [subs subscriptionWithClassName:Person.className - predicate:[NSPredicate predicateWithFormat:@"age == %d", 20]]; - XCTAssertNotNil(foundSubscription); - XCTAssertNil(foundSubscription.name); - XCTAssert(foundSubscription.queryString, @"age == 20"); -} - -- (void)testAddSubscriptionWithoutWriteThrow { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - RLMAssertThrowsWithReason([subs addSubscriptionWithClassName:Person.className where:@"age > 15"], - @"Can only add, remove, or update subscriptions within a write subscription block."); -} - -- (void)testAddAndFindSubscriptionByName { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - XCTAssertNotNil(realm.subscriptions); - XCTAssertEqual(realm.subscriptions.version, 0UL); - XCTAssertEqual(realm.subscriptions.count, 0UL); - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_older_15" - where:@"age > 15"]; - }]; - - RLMSyncSubscription *foundSubscription = [subs subscriptionWithName:@"person_older_15"]; - XCTAssertNotNil(foundSubscription); - XCTAssert(foundSubscription.name, @"person_older_15"); - XCTAssert(foundSubscription.queryString, @"age > 15"); -} - -- (void)testAddDuplicateSubscription { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - where:@"age > 15"]; - [subs addSubscriptionWithClassName:Person.className - where:@"age > 15"]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 1UL); -} - -- (void)testAddDuplicateNamedSubscriptionWillThrow { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 15"]; - RLMAssertThrowsWithReason([subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 20"], - @"A subscription named 'person_age' already exists. If you meant to update the existing subscription please use the `update` method."); - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 1UL); - - RLMSyncSubscription *foundSubscription = [subs subscriptionWithName:@"person_age"]; - XCTAssertNotNil(foundSubscription); - - XCTAssertEqualObjects(foundSubscription.name, @"person_age"); - XCTAssertEqualObjects(foundSubscription.queryString, @"age > 15"); - XCTAssertEqualObjects(foundSubscription.objectClassName, @"Person"); -} - -- (void)testAddDuplicateSubscriptionWithPredicate { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - where:@"age > 15"]; - [subs addSubscriptionWithClassName:Person.className - predicate:[NSPredicate predicateWithFormat:@"age > %d", 15]]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 1UL); -} - -- (void)testAddDuplicateSubscriptionWithDifferentName { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age_1" - where:@"age > 15"]; - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age_2" - predicate:[NSPredicate predicateWithFormat:@"age > %d", 15]]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 2UL); - - RLMSyncSubscription *foundSubscription = [subs subscriptionWithName:@"person_age_1"]; - XCTAssertNotNil(foundSubscription); - - RLMSyncSubscription *foundSubscription2 = [subs subscriptionWithName:@"person_age_2"]; - XCTAssertNotNil(foundSubscription2); - - XCTAssertNotEqualObjects(foundSubscription.name, foundSubscription2.name); - XCTAssertEqualObjects(foundSubscription.queryString, foundSubscription2.queryString); - XCTAssertEqualObjects(foundSubscription.objectClassName, foundSubscription2.objectClassName); -} - -- (void)testOverrideNamedWithUnnamedSubscription { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age_1" - where:@"age > 15"]; - [subs addSubscriptionWithClassName:Person.className - predicate:[NSPredicate predicateWithFormat:@"age > %d", 15]]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 2UL); -} - -- (void)testOverrideUnnamedWithNamedSubscription { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - predicate:[NSPredicate predicateWithFormat:@"age > %d", 15]]; - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age_1" - where:@"age > 15"]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 2UL); -} - -- (void)testAddSubscriptionInDifferentWriteBlocks { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age_1" - where:@"age > 15"]; - }]; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age_2" - predicate:[NSPredicate predicateWithFormat:@"age > %d", 20]]; - }]; - - XCTAssertEqual(realm.subscriptions.version, 2UL); - XCTAssertEqual(realm.subscriptions.count, 2UL); - - RLMSyncSubscription *foundSubscription = [subs subscriptionWithName:@"person_age_1"]; - XCTAssertNotNil(foundSubscription); - - RLMSyncSubscription *foundSubscription2 = [subs subscriptionWithName:@"person_age_2"]; - XCTAssertNotNil(foundSubscription2); -} - -- (void)testRemoveSubscriptionByName { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age_1" - where:@"age > 15"]; - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age_2" - predicate:[NSPredicate predicateWithFormat:@"age > %d", 20]]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 2UL); - - [subs update:^{ - [subs removeSubscriptionWithName:@"person_age_1"]; - }]; - - XCTAssertEqual(subs.version, 2UL); - XCTAssertEqual(subs.count, 1UL); - - RLMSyncSubscription *foundSubscription = [subs subscriptionWithName:@"person_age_1"]; - XCTAssertNil(foundSubscription); - - RLMSyncSubscription *foundSubscription2 = [subs subscriptionWithName:@"person_age_2"]; - XCTAssertNotNil(foundSubscription2); -} - -- (void)testRemoveSubscriptionWithoutWriteThrow { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age_1" - where:@"age > 15"]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 1UL); - RLMAssertThrowsWithReason([subs removeSubscriptionWithName:@"person_age_1"], @"Can only add, remove, or update subscriptions within a write subscription block."); -} - -- (void)testRemoveSubscriptionByQuery { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 15"]; - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_firstname" - where:@"firstName == %@", @"John"]; - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_lastname" - predicate:[NSPredicate predicateWithFormat:@"lastName == %@", @"Doe"]]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 3UL); - - [subs update:^{ - [subs removeSubscriptionWithClassName:Person.className where:@"firstName == %@", @"John"]; - [subs removeSubscriptionWithClassName:Person.className predicate:[NSPredicate predicateWithFormat:@"lastName == %@", @"Doe"]]; - }]; - - XCTAssertEqual(subs.version, 2UL); - XCTAssertEqual(subs.count, 1UL); - - RLMSyncSubscription *foundSubscription = [subs subscriptionWithName:@"person_age"]; - XCTAssertNotNil(foundSubscription); - - RLMSyncSubscription *foundSubscription2 = [subs subscriptionWithName:@"person_firstname"]; - XCTAssertNil(foundSubscription2); - - RLMSyncSubscription *foundSubscription3 = [subs subscriptionWithName:@"person_lastname"]; - XCTAssertNil(foundSubscription3); -} - -- (void)testRemoveSubscription { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 15"]; - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_firstname" - where:@"firstName == '%@'", @"John"]; - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_lastname" - predicate:[NSPredicate predicateWithFormat:@"lastName == %@", @"Doe"]]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 3UL); - - RLMSyncSubscription *foundSubscription = [subs subscriptionWithName:@"person_age"]; - XCTAssertNotNil(foundSubscription); - - [subs update:^{ - [subs removeSubscription:foundSubscription]; - }]; - - XCTAssertEqual(subs.version, 2UL); - XCTAssertEqual(subs.count, 2UL); - - RLMSyncSubscription *foundSubscription2 = [subs subscriptionWithName:@"person_firstname"]; - XCTAssertNotNil(foundSubscription2); - - [subs update:^{ - [subs removeSubscription:foundSubscription2]; - }]; - - XCTAssertEqual(subs.version, 3UL); - XCTAssertEqual(subs.count, 1UL); -} - -- (void)testRemoveAllSubscription { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 15"]; - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_firstname" - where:@"firstName == '%@'", @"John"]; - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_lastname" - predicate:[NSPredicate predicateWithFormat:@"lastName == %@", @"Doe"]]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 3UL); - - [subs update:^{ - [subs removeAllSubscriptions]; - }]; - - XCTAssertEqual(subs.version, 2UL); - XCTAssertEqual(subs.count, 0UL); - - RLMSyncSubscription *foundSubscription = [subs subscriptionWithName:@"person_age_3"]; - XCTAssertNil(foundSubscription); -} - -- (void)testRemoveAllSubscriptionForType { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 15"]; - [subs addSubscriptionWithClassName:Dog.className - subscriptionName:@"dog_name" - where:@"name == '%@'", @"Tomas"]; - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_lastname" - predicate:[NSPredicate predicateWithFormat:@"lastName == %@", @"Doe"]]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 3UL); - - [subs update:^{ - [subs removeAllSubscriptionsWithClassName:Person.className]; - }]; - - XCTAssertEqual(subs.version, 2UL); - XCTAssertEqual(subs.count, 1UL); - - RLMSyncSubscription *foundSubscription = [subs subscriptionWithName:@"dog_name"]; - XCTAssertNotNil(foundSubscription); - - [subs update:^{ - [subs removeAllSubscriptionsWithClassName:Dog.className]; - }]; - - XCTAssertEqual(subs.version, 3UL); - XCTAssertEqual(subs.count, 0UL); -} - -- (void)testUpdateSubscriptionQuery { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"person_age" - where:@"age > 15"]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 1UL); - - RLMSyncSubscription *foundSubscription = [subs subscriptionWithName:@"person_age"]; - XCTAssertNotNil(foundSubscription); - - [subs update:^{ - [foundSubscription updateSubscriptionWhere:@"age > 20"]; - }]; - - XCTAssertEqual(subs.version, 2UL); - XCTAssertEqual(subs.count, 1UL); - - RLMSyncSubscription *foundSubscription2 = [subs subscriptionWithName:@"person_age"]; - XCTAssertNotNil(foundSubscription2); - XCTAssertEqualObjects(foundSubscription2.queryString, @"age > 20"); - XCTAssertEqualObjects(foundSubscription2.objectClassName, @"Person"); -} - -- (void)testUpdateSubscriptionQueryWithoutWriteThrow { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - [subs update:^{ - [subs addSubscriptionWithClassName:Person.className - subscriptionName:@"subscription_1" - where:@"age > 15"]; - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, 1UL); - - RLMSyncSubscription *foundSubscription = [subs subscriptionWithName:@"subscription_1"]; - XCTAssertNotNil(foundSubscription); - - RLMAssertThrowsWithReason([foundSubscription updateSubscriptionWithPredicate:[NSPredicate predicateWithFormat:@"name == 'Tomas'"]], @"Can only add, remove, or update subscriptions within a write subscription block."); -} - -- (void)testSubscriptionSetIterate { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - double numberOfSubs = 100; - [subs update:^{ - for (int i = 0; i < numberOfSubs; ++i) { - [subs addSubscriptionWithClassName:Person.className - subscriptionName:[NSString stringWithFormat:@"person_age_%d", i] - where:[NSString stringWithFormat:@"age > %d", i]]; - } - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, (unsigned long)numberOfSubs); - - __weak id objects[(unsigned long)pow(numberOfSubs, 2.0) + (unsigned long)numberOfSubs]; - NSInteger count = 0; - for (RLMSyncSubscription *sub in subs) { - XCTAssertNotNil(sub); - objects[count++] = sub; - for (RLMSyncSubscription *sub in subs) { - objects[count++] = sub; - } - } - XCTAssertEqual(count, pow(numberOfSubs, 2) + numberOfSubs); -} - -- (void)testSubscriptionSetFirstAndLast { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - XCTAssertNil(subs.firstObject); - XCTAssertNil(subs.lastObject); - - int numberOfSubs = 20; - [subs update:^{ - for (int i = 1; i <= numberOfSubs; ++i) { - [subs addSubscriptionWithClassName:Person.className - subscriptionName:[NSString stringWithFormat:@"person_age_%d", i] - where:[NSString stringWithFormat:@"age > %d", i]]; - } - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, (unsigned long)numberOfSubs); - - RLMSyncSubscription *firstSubscription = subs.firstObject; - XCTAssertEqualObjects(firstSubscription.name, @"person_age_1"); - XCTAssertEqualObjects(firstSubscription.queryString, @"age > 1"); - - RLMSyncSubscription *lastSubscription = subs.lastObject; - XCTAssertEqualObjects(lastSubscription.name, ([NSString stringWithFormat:@"person_age_%d", numberOfSubs])); - XCTAssertEqualObjects(lastSubscription.queryString, ([NSString stringWithFormat:@"age > %d", numberOfSubs])); -} - -- (void)testSubscriptionSetSubscript { - RLMRealm *realm = [self openRealm]; - RLMSyncSubscriptionSet *subs = realm.subscriptions; - - XCTAssertEqual(subs.count, 0UL); - - int numberOfSubs = 20; - [subs update:^{ - for (int i = 1; i <= numberOfSubs; ++i) { - [subs addSubscriptionWithClassName:Person.className - subscriptionName:[NSString stringWithFormat:@"person_age_%d", i] - where:[NSString stringWithFormat:@"age > %d", i]]; - } - }]; - - XCTAssertEqual(subs.version, 1UL); - XCTAssertEqual(subs.count, (unsigned long)numberOfSubs); - - RLMSyncSubscription *firstSubscription = subs[0]; - XCTAssertEqualObjects(firstSubscription.name, @"person_age_1"); - XCTAssertEqualObjects(firstSubscription.queryString, @"age > 1"); - - RLMSyncSubscription *lastSubscription = subs[numberOfSubs-1]; - XCTAssertEqualObjects(lastSubscription.name, ([NSString stringWithFormat:@"person_age_%d", numberOfSubs])); - XCTAssertEqualObjects(lastSubscription.queryString, ([NSString stringWithFormat:@"age > %d", numberOfSubs])); - - int index = (numberOfSubs/2); - RLMSyncSubscription *objectAtIndexSubscription = [subs objectAtIndex:index]; - XCTAssertEqualObjects(objectAtIndexSubscription.name, ([NSString stringWithFormat:@"person_age_%d", index+1])); - XCTAssertEqualObjects(objectAtIndexSubscription.queryString, ([NSString stringWithFormat:@"age > %d", index+1])); -} -@end diff --git a/Realm/ObjectServerTests/RLMSyncTestCase.h b/Realm/ObjectServerTests/RLMSyncTestCase.h deleted file mode 100644 index aed186d03f..0000000000 --- a/Realm/ObjectServerTests/RLMSyncTestCase.h +++ /dev/null @@ -1,265 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMMultiProcessTestCase.h" -#import "RLMServerTestObjects.h" - -@class RLMAppConfiguration; -typedef NS_ENUM(NSUInteger, RLMSyncStopPolicy); -typedef void(^RLMSyncBasicErrorReportingBlock)(NSError * _Nullable); - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -// RealmServer is implemented in Swift -@interface RealmServer : NSObject -// Get the shared singleton instance -+ (RealmServer *)shared; -// Check if baas is installed. When running via SPM we can't install it -// automatically, so we skip running tests which require it if it's missing. -+ (bool)haveServer; -// Create a FLX app with the given queryable fields and object types. If -// `persistent:NO` the app will be deleted at the end of the current test, and -// otherwise it will remain until `deleteApp:` is called on it. -- (nullable NSString *)createAppWithFields:(NSArray *)fields - types:(nullable NSArray *)types - persistent:(bool)persistent - error:(NSError **)error; -// Create a PBS app with the given partition key type and object types. If -// `persistent:NO` the app will be deleted at the end of the current test, and -// otherwise it will remain until `deleteApp:` is called on it. -- (nullable NSString *)createAppWithPartitionKeyType:(NSString *)type - types:(nullable NSArray *)types - persistent:(bool)persistent - error:(NSError **)error; - -// Delete all apps created with `persistent:NO`. Called from `-tearDown`. -- (BOOL)deleteAppsAndReturnError:(NSError **)error; -// Delete a specific app created with `persistent:YES`. Called from `+tearDown` -// to delete the shared app for each test case. -- (BOOL)deleteApp:(NSString *)appId error:(NSError **)error; -@end - -@interface AsyncOpenConnectionTimeoutTransport : RLMNetworkTransport -@end - -// RLMSyncTestCase adds some helper functions for writing sync tests, and most -// importantly creates a shared Atlas app which is used by all tests in a test -// case. `self.app` and `self.appId` create the App if needed, and then the -// App is deleted at the end of the test case (i.e. in `+tearDown`). -// -// Each test case subclass must override `defaultObjectTypes` to return the -// `RLMObject` subclasses which the test case uses. These types are the only -// ones which will be present in the server schema, and using any other types -// will result in an error due to developer mode not being used. -// -// By default the app is a partition-based sync app. Test cases which test -// flexible sync must override `createAppWithError:` to call -// `createFlexibleSyncAppWithError:` and `configurationForUser:` to call `[user -// flexibleSyncConfiguration]`. -// -// Most tests can simply call `[self openRealm]` to obtain a synchronized -// Realm. For PBS tests, this will use the current test's name as the partition -// value. This creates a new user each time, so multiple calls to `openRealm` -// will produce separate Realm files. Users can also be created directly with -// `[self createUser]`. -// -// `writeToPartition:block:` for PBS and `populateData:` for FLX is the -// preferred way to populate the server-side state. This creates a new user, -// opens the Realm, calls the block in a write transaction to populate the -// data, waits for uploads to complete, and then deletes the user. -// -// Each test case's server state is fully isolated from other test cases due to -// the combination of creating a new app for each test case and that we add the -// app ID to the name of the collections used by the app. However, state can -// leak between tests within a test case. For partition-based tests this is -// mostly not a problem: each test uses the test name as the partition key and -// so will naturally be partitioned from other tests. For flexible sync, we -// follow the pattern of setting one of the fields in all objects created to -// the test's name and including that in subscriptions. -@interface RLMSyncTestCase : RLMMultiProcessTestCase - -@property (nonatomic, readonly) NSString *appId; -@property (nonatomic, readonly) RLMApp *app; -@property (nonatomic, readonly) RLMUser *anonymousUser; -@property (nonatomic, readonly) RLMAppConfiguration *defaultAppConfiguration; - -/// Any stray app ids passed between processes -@property (nonatomic, readonly) NSArray *appIds; - -#pragma mark - Customization points - -// Override to return the set of RLMObject subclasses used by this test case -- (NSArray *)defaultObjectTypes; -// Override to customize how the shared App is created for this test case. Most -// commonly this is overrided to `return [self createFlexibleSyncAppWithError:error];` -// for flexible sync test cases. -- (nullable NSString *)createAppWithError:(NSError **)error; -- (nullable NSString *)createFlexibleSyncAppWithError:(NSError **)error; -// Override to produce flexible sync configurations instead of the default PBS one. -- (RLMRealmConfiguration *)configurationForUser:(RLMUser *)user; - -#pragma mark - Helpers - -// Obtain a user with a name derived from test selector, registering it first -// if this is the parent process. This should only be used in multi-process -// tests (and most tests should not need to be multi-process). -- (RLMUser *)userForTest:(SEL)sel; -- (RLMUser *)userForTest:(SEL)sel app:(RLMApp *)app; - -// Create new login credentials for this test, possibly registering the user -// first. This is needed to be able to log a user back in after logging out. If -// a user is only logged in one time, use `createUser` instead. -- (RLMCredentials *)basicCredentialsWithName:(NSString *)name - register:(BOOL)shouldRegister NS_SWIFT_NAME(basicCredentials(name:register:)); -- (RLMCredentials *)basicCredentialsWithName:(NSString *)name register:(BOOL)shouldRegister - app:(RLMApp*)app NS_SWIFT_NAME(basicCredentials(name:register:app:)); - -/// Synchronously open a synced Realm via asyncOpen and return the Realm. -- (RLMRealm *)asyncOpenRealmWithConfiguration:(RLMRealmConfiguration *)configuration; - -/// Synchronously open a synced Realm via asyncOpen and return the expected error. -- (NSError *)asyncOpenErrorWithConfiguration:(RLMRealmConfiguration *)configuration; - -// Create a new user, and return a configuration using that user. -- (RLMRealmConfiguration *)configuration NS_REFINED_FOR_SWIFT; - -// Open the realm with the partition value `self.name` using a newly created user -- (RLMRealm *)openRealm NS_REFINED_FOR_SWIFT; -// Open the realm with the partition value `self.name` using the given user -- (RLMRealm *)openRealmWithUser:(RLMUser *)user; - -/// Synchronously open a synced Realm and wait for downloads. -- (RLMRealm *)openRealmForPartitionValue:(nullable id)partitionValue - user:(RLMUser *)user; - -/// Synchronously open a synced Realm and wait for downloads. -- (RLMRealm *)openRealmForPartitionValue:(nullable id)partitionValue - user:(RLMUser *)user - clientResetMode:(RLMClientResetMode)clientResetMode; - -/// Synchronously open a synced Realm with encryption key and stop policy and wait for downloads. -- (RLMRealm *)openRealmForPartitionValue:(nullable id)partitionValue - user:(RLMUser *)user - encryptionKey:(nullable NSData *)encryptionKey - stopPolicy:(RLMSyncStopPolicy)stopPolicy; - -/// Synchronously open a synced Realm. -- (RLMRealm *)openRealmWithConfiguration:(RLMRealmConfiguration *)configuration; - -/// Immediately open a synced Realm. -- (RLMRealm *)immediatelyOpenRealmForPartitionValue:(nullable id)partitionValue user:(RLMUser *)user; - -/// Immediately open a synced Realm with encryption key and stop policy. -- (RLMRealm *)immediatelyOpenRealmForPartitionValue:(nullable id)partitionValue - user:(RLMUser *)user - encryptionKey:(nullable NSData *)encryptionKey - stopPolicy:(RLMSyncStopPolicy)stopPolicy; - -/// Immediately open a synced Realm with encryption key and stop policy. -- (RLMRealm *)immediatelyOpenRealmForPartitionValue:(nullable id)partitionValue - user:(RLMUser *)user - clientResetMode:(RLMClientResetMode)clientResetMode - encryptionKey:(nullable NSData *)encryptionKey - stopPolicy:(RLMSyncStopPolicy)stopPolicy; - -/// Synchronously create, log in, and return a user. -- (RLMUser *)logInUserForCredentials:(RLMCredentials *)credentials; -- (RLMUser *)logInUserForCredentials:(RLMCredentials *)credentials app:(RLMApp *)app; - -/// Synchronously register and log in a new non-anonymous user -- (RLMUser *)createUser; -- (RLMUser *)createUserForApp:(RLMApp *)app; - -- (RLMCredentials *)jwtCredentialWithAppId:(NSString *)appId; - -/// Log out and wait for the completion handler to be called -- (void)logOutUser:(RLMUser *)user; - -- (void)addPersonsToRealm:(RLMRealm *)realm persons:(NSArray *)persons; - -/// Wait for downloads to complete; drop any error. -- (void)waitForDownloadsForRealm:(RLMRealm *)realm; - -/// Wait for uploads to complete; drop any error. -- (void)waitForUploadsForRealm:(RLMRealm *)realm; - -/// Set the user's tokens to invalid ones to test invalid token handling. -- (void)setInvalidTokensForUser:(RLMUser *)user; - -- (void)writeToPartition:(nullable NSString *)partition block:(void (^)(RLMRealm *))block; - -- (void)resetSyncManager; - -- (const char *)badAccessToken; - -- (void)cleanupRemoteDocuments:(RLMMongoCollection *)collection; - -- (nonnull NSURL *)clientDataRoot; - -- (NSString *)partitionBsonType:(id)bson; - -- (RLMApp *)appWithId:(NSString *)appId NS_SWIFT_NAME(app(id:)); - -- (void)resetAppCache; - -#pragma mark Flexible Sync App - -- (void)populateData:(void (^)(RLMRealm *))block; -- (void)writeQueryAndCompleteForRealm:(RLMRealm *)realm block:(void (^)(RLMSyncSubscriptionSet *))block; - -@end - -@interface RLMSyncManager () -// Wait for all sync sessions associated with this sync manager to be fully -// torn down. Once this returns, it is guaranteed that reopening a Realm will -// actually create a new sync session. -- (void)waitForSessionTermination; -@end - -// Suspend or resume a sync session without fully tearing it down. These do -// what `suspend` and `resume` will do in the next major version, but it would -// be a breaking change to swap them. -@interface RLMSyncSession () -- (void)pause; -- (void)unpause; -@end - -@interface RLMUser (Test) -// Get the mongo collection for the given object type in the given app. This -// must be used instead of the normal public API because we scope our -// collection names to the app. -- (RLMMongoCollection *)collectionForType:(Class)type app:(RLMApp *)app NS_SWIFT_NAME(collection(for:app:)); -@end - -FOUNDATION_EXTERN int64_t RLMGetClientFileIdent(RLMRealm *realm); - -RLM_HEADER_AUDIT_END(nullability, sendability) - -#define WAIT_FOR_SEMAPHORE(macro_semaphore, macro_timeout) do { \ - int64_t delay_in_ns = (int64_t)(macro_timeout * NSEC_PER_SEC); \ - BOOL sema_success = dispatch_semaphore_wait(macro_semaphore, dispatch_time(DISPATCH_TIME_NOW, delay_in_ns)) == 0; \ - XCTAssertTrue(sema_success, @"Semaphore timed out."); \ -} while (0) - -#define CHECK_COUNT(d_count, macro_object_type, macro_realm) do { \ - [macro_realm refresh]; \ - RLMResults *r = [macro_object_type allObjectsInRealm:macro_realm]; \ - NSInteger c = r.count; \ - NSString *w = self.isParent ? @"parent" : @"child"; \ - XCTAssert(d_count == c, @"Expected %@ items, but actually got %@ (%@) (%@)", @(d_count), @(c), r, w); \ -} while (0) diff --git a/Realm/ObjectServerTests/RLMSyncTestCase.mm b/Realm/ObjectServerTests/RLMSyncTestCase.mm deleted file mode 100644 index a7a284ca6a..0000000000 --- a/Realm/ObjectServerTests/RLMSyncTestCase.mm +++ /dev/null @@ -1,699 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncTestCase.h" - -#import -#import -#import - -#import "RLMApp_Private.hpp" -#import "RLMChildProcessEnvironment.h" -#import "RLMRealmConfiguration_Private.h" -#import "RLMRealmUtil.hpp" -#import "RLMRealm_Dynamic.h" -#import "RLMRealm_Private.hpp" -#import "RLMSyncConfiguration_Private.h" -#import "RLMSyncManager_Private.hpp" -#import "RLMUser_Private.hpp" -#import "RLMUtil.hpp" - -#import -#import -#import - -#if TARGET_OS_OSX - -// Set this to 1 if you want the test ROS instance to log its debug messages to console. -#define LOG_ROS_OUTPUT 0 - -@interface RLMSyncManager () -+ (void)_setCustomBundleID:(NSString *)customBundleID; -- (NSArray *)_allUsers; -@end - -@interface RLMSyncTestCase () -@property (nonatomic) NSTask *task; -@end - -@interface RLMSyncSession () -- (BOOL)waitForUploadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback; -- (BOOL)waitForDownloadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback; -@end - -@interface TestNetworkTransport : RLMNetworkTransport -- (void)waitForCompletion; -@end - -#pragma mark AsyncOpenConnectionTimeoutTransport - -@implementation AsyncOpenConnectionTimeoutTransport -- (void)sendRequestToServer:(RLMRequest *)request completion:(RLMNetworkTransportCompletionBlock)completionBlock { - if ([request.url hasSuffix:@"location"]) { - RLMResponse *r = [RLMResponse new]; - r.httpStatusCode = 200; - r.body = @"{\"deployment_model\":\"GLOBAL\",\"location\":\"US-VA\",\"hostname\":\"http://localhost:5678\",\"ws_hostname\":\"ws://localhost:5678\"}"; - completionBlock(r); - } else { - [super sendRequestToServer:request completion:completionBlock]; - } -} -@end - -static NSURL *syncDirectoryForChildProcess() { - NSString *path = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0]; - NSBundle *bundle = [NSBundle mainBundle]; - NSString *bundleIdentifier = bundle.bundleIdentifier ?: bundle.executablePath.lastPathComponent; - path = [path stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-child", bundleIdentifier]]; - return [NSURL fileURLWithPath:path isDirectory:YES]; -} - -#pragma mark RLMSyncTestCase - -@implementation RLMSyncTestCase { - RLMApp *_app; -} - -- (NSArray *)defaultObjectTypes { - return @[Person.self]; -} - -#pragma mark - Helper methods - -- (RLMUser *)userForTest:(SEL)sel { - return [self userForTest:sel app:self.app]; -} - -- (RLMUser *)userForTest:(SEL)sel app:(RLMApp *)app { - return [self logInUserForCredentials:[self basicCredentialsWithName:NSStringFromSelector(sel) - register:self.isParent app:app] - app:app]; -} - -- (RLMUser *)anonymousUser { - return [self logInUserForCredentials:[RLMCredentials anonymousCredentials]]; -} - -- (RLMCredentials *)basicCredentialsWithName:(NSString *)name register:(BOOL)shouldRegister { - return [self basicCredentialsWithName:name register:shouldRegister app:self.app]; -} - -- (RLMCredentials *)basicCredentialsWithName:(NSString *)name register:(BOOL)shouldRegister app:(RLMApp *)app { - if (shouldRegister) { - XCTestExpectation *ex = [self expectationWithDescription:@""]; - [app.emailPasswordAuth registerUserWithEmail:name password:@"password" completion:^(NSError *error) { - XCTAssertNil(error); - [ex fulfill]; - }]; - [self waitForExpectations:@[ex] timeout:20.0]; - } - return [RLMCredentials credentialsWithEmail:name password:@"password"]; -} - -- (RLMAppConfiguration*)defaultAppConfiguration { - auto config = [[RLMAppConfiguration alloc] initWithBaseURL:@"http://localhost:9090" - transport:[TestNetworkTransport new] - defaultRequestTimeoutMS:60000]; - config.rootDirectory = self.clientDataRoot; - return config; -} - -- (void)addPersonsToRealm:(RLMRealm *)realm persons:(NSArray *)persons { - [realm beginWriteTransaction]; - [realm addObjects:persons]; - [realm commitWriteTransaction]; -} - -- (RLMRealmConfiguration *)configuration { - RLMRealmConfiguration *configuration = [self configurationForUser:self.createUser]; - configuration.objectClasses = self.defaultObjectTypes; - return configuration; -} - -- (RLMRealmConfiguration *)configurationForUser:(RLMUser *)user { - return [user configurationWithPartitionValue:self.name]; -} - -- (RLMRealm *)openRealm { - return [self openRealmWithUser:self.createUser]; -} - -- (RLMRealm *)openRealmWithUser:(RLMUser *)user { - auto c = [self configurationForUser:user]; - c.objectClasses = self.defaultObjectTypes; - return [self openRealmWithConfiguration:c]; -} - -- (RLMRealm *)openRealmForPartitionValue:(nullable id)partitionValue user:(RLMUser *)user { - auto c = [user configurationWithPartitionValue:partitionValue]; - c.objectClasses = self.defaultObjectTypes; - return [self openRealmWithConfiguration:c]; -} - -- (RLMRealm *)openRealmForPartitionValue:(nullable id)partitionValue - user:(RLMUser *)user - clientResetMode:(RLMClientResetMode)clientResetMode { - auto c = [user configurationWithPartitionValue:partitionValue clientResetMode:clientResetMode]; - c.objectClasses = self.defaultObjectTypes; - return [self openRealmWithConfiguration:c]; -} - -- (RLMRealm *)openRealmForPartitionValue:(nullable id)partitionValue - user:(RLMUser *)user - encryptionKey:(nullable NSData *)encryptionKey - stopPolicy:(RLMSyncStopPolicy)stopPolicy { - return [self openRealmForPartitionValue:partitionValue - user:user - clientResetMode:RLMClientResetModeRecoverUnsyncedChanges - encryptionKey:encryptionKey - stopPolicy:stopPolicy]; -} - -- (RLMRealm *)openRealmForPartitionValue:(nullable id)partitionValue - user:(RLMUser *)user - clientResetMode:(RLMClientResetMode)clientResetMode - encryptionKey:(nullable NSData *)encryptionKey - stopPolicy:(RLMSyncStopPolicy)stopPolicy { - RLMRealm *realm = [self immediatelyOpenRealmForPartitionValue:partitionValue - user:user - clientResetMode:clientResetMode - encryptionKey:encryptionKey - stopPolicy:stopPolicy]; - [self waitForDownloadsForRealm:realm]; - return realm; -} - -- (RLMRealm *)openRealmWithConfiguration:(RLMRealmConfiguration *)configuration { - RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nullptr]; - [self waitForDownloadsForRealm:realm]; - return realm; -} - -- (RLMRealm *)asyncOpenRealmWithConfiguration:(RLMRealmConfiguration *)config { - __block RLMRealm *r = nil; - XCTestExpectation *ex = [self expectationWithDescription:@"Should asynchronously open a Realm"]; - [RLMRealm asyncOpenWithConfiguration:config - callbackQueue:dispatch_get_main_queue() - callback:^(RLMRealm *realm, NSError *err) { - XCTAssertNil(err); - XCTAssertNotNil(realm); - r = realm; - [ex fulfill]; - }]; - [self waitForExpectationsWithTimeout:30.0 handler:nil]; - // Ensure that the block does not retain the Realm, as it may not be dealloced - // immediately and so would extend the lifetime of the Realm an inconsistent amount - auto realm = r; - r = nil; - return realm; -} - -- (NSError *)asyncOpenErrorWithConfiguration:(RLMRealmConfiguration *)config { - __block NSError *error = nil; - XCTestExpectation *ex = [self expectationWithDescription:@"Should fail to asynchronously open a Realm"]; - [RLMRealm asyncOpenWithConfiguration:config - callbackQueue:dispatch_get_main_queue() - callback:^(RLMRealm *r, NSError *err){ - XCTAssertNotNil(err); - XCTAssertNil(r); - error = err; - [ex fulfill]; - }]; - [self waitForExpectationsWithTimeout:30.0 handler:nil]; - return error; -} - -- (RLMRealm *)immediatelyOpenRealmForPartitionValue:(NSString *)partitionValue user:(RLMUser *)user { - return [self immediatelyOpenRealmForPartitionValue:partitionValue - user:user - clientResetMode:RLMClientResetModeRecoverUnsyncedChanges]; -} - -- (RLMRealm *)immediatelyOpenRealmForPartitionValue:(NSString *)partitionValue - user:(RLMUser *)user - clientResetMode:(RLMClientResetMode)clientResetMode { - return [self immediatelyOpenRealmForPartitionValue:partitionValue - user:user - clientResetMode:clientResetMode - encryptionKey:nil - stopPolicy:RLMSyncStopPolicyAfterChangesUploaded]; -} - -- (RLMRealm *)immediatelyOpenRealmForPartitionValue:(NSString *)partitionValue - user:(RLMUser *)user - encryptionKey:(NSData *)encryptionKey - stopPolicy:(RLMSyncStopPolicy)stopPolicy { - return [self immediatelyOpenRealmForPartitionValue:partitionValue - user:user - clientResetMode:RLMClientResetModeRecoverUnsyncedChanges - encryptionKey:encryptionKey - stopPolicy:RLMSyncStopPolicyAfterChangesUploaded]; -} - -- (RLMRealm *)immediatelyOpenRealmForPartitionValue:(NSString *)partitionValue - user:(RLMUser *)user - clientResetMode:(RLMClientResetMode)clientResetMode - encryptionKey:(NSData *)encryptionKey - stopPolicy:(RLMSyncStopPolicy)stopPolicy { - auto c = [user configurationWithPartitionValue:partitionValue clientResetMode:clientResetMode]; - c.encryptionKey = encryptionKey; - c.objectClasses = self.defaultObjectTypes; - RLMSyncConfiguration *syncConfig = c.syncConfiguration; - syncConfig.stopPolicy = stopPolicy; - c.syncConfiguration = syncConfig; - return [RLMRealm realmWithConfiguration:c error:nil]; -} - -- (RLMUser *)createUser { - return [self createUserForApp:self.app]; -} - -- (RLMUser *)createUserForApp:(RLMApp *)app { - NSString *name = [self.name stringByAppendingFormat:@" %@", NSUUID.UUID.UUIDString]; - return [self logInUserForCredentials:[self basicCredentialsWithName:name register:YES app:app] app:app]; -} - -- (RLMUser *)logInUserForCredentials:(RLMCredentials *)credentials { - return [self logInUserForCredentials:credentials app:self.app]; -} - -- (RLMUser *)logInUserForCredentials:(RLMCredentials *)credentials app:(RLMApp *)app { - __block RLMUser* user; - XCTestExpectation *expectation = [self expectationWithDescription:@""]; - [app loginWithCredential:credentials completion:^(RLMUser *u, NSError *e) { - XCTAssertNotNil(u); - XCTAssertNil(e); - user = u; - [expectation fulfill]; - }]; - [self waitForExpectations:@[expectation] timeout:20.0]; - XCTAssertTrue(user.state == RLMUserStateLoggedIn, @"User should have been valid, but wasn't"); - return user; -} - -- (void)logOutUser:(RLMUser *)user { - XCTestExpectation *expectation = [self expectationWithDescription:@""]; - [user logOutWithCompletion:^(NSError * error) { - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectations:@[expectation] timeout:20.0]; - XCTAssertTrue(user.state == RLMUserStateLoggedOut, @"User should have been logged out, but wasn't"); -} - -- (NSString *)createJWTWithAppId:(NSString *)appId { - NSDictionary *header = @{@"alg": @"HS256", @"typ": @"JWT"}; - NSDictionary *payload = @{ - @"aud": appId, - @"sub": @"someUserId", - @"exp": @1961896476, - @"user_data": @{ - @"name": @"Foo Bar", - @"occupation": @"firefighter" - }, - @"my_metadata": @{ - @"name": @"Bar Foo", - @"occupation": @"stock analyst" - } - }; - - NSData *jsonHeader = [NSJSONSerialization dataWithJSONObject:header options:0 error:nil]; - NSData *jsonPayload = [NSJSONSerialization dataWithJSONObject:payload options:0 error:nil]; - - NSString *base64EncodedHeader = [jsonHeader base64EncodedStringWithOptions:0]; - NSString *base64EncodedPayload = [jsonPayload base64EncodedStringWithOptions:0]; - - // Remove padding characters. - base64EncodedHeader = [base64EncodedHeader stringByReplacingOccurrencesOfString:@"=" withString:@""]; - base64EncodedPayload = [base64EncodedPayload stringByReplacingOccurrencesOfString:@"=" withString:@""]; - - std::string jwtPayload = [[NSString stringWithFormat:@"%@.%@", base64EncodedHeader, base64EncodedPayload] UTF8String]; - std::string jwtKey = [@"My_very_confidential_secretttttt" UTF8String]; - - NSString *key = @"My_very_confidential_secretttttt"; - NSString *data = @(jwtPayload.c_str()); - - const char *cKey = [key cStringUsingEncoding:NSASCIIStringEncoding]; - const char *cData = [data cStringUsingEncoding:NSASCIIStringEncoding]; - - unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH]; - CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC); - - NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC - length:sizeof(cHMAC)]; - NSString *hmac = [HMAC base64EncodedStringWithOptions:0]; - - hmac = [hmac stringByReplacingOccurrencesOfString:@"=" withString:@""]; - hmac = [hmac stringByReplacingOccurrencesOfString:@"+" withString:@"-"]; - hmac = [hmac stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; - - return [NSString stringWithFormat:@"%@.%@", @(jwtPayload.c_str()), hmac]; -} - -- (RLMCredentials *)jwtCredentialWithAppId:(NSString *)appId { - return [RLMCredentials credentialsWithJWT:[self createJWTWithAppId:appId]]; -} - -- (void)waitForUploadsForRealm:(RLMRealm *)realm { - [self waitForUploadsForRealm:realm error:nil]; -} - -- (void)waitForUploadsForRealm:(RLMRealm *)realm error:(NSError **)error { - RLMSyncSession *session = realm.syncSession; - NSAssert(session, @"Cannot call with invalid Realm"); - XCTestExpectation *ex = [self expectationWithDescription:@"Wait for upload completion"]; - __block NSError *completionError; - BOOL queued = [session waitForUploadCompletionOnQueue:dispatch_get_global_queue(0, 0) - callback:^(NSError *error) { - completionError = error; - [ex fulfill]; - }]; - if (!queued) { - XCTFail(@"Upload waiter did not queue; session was invalid or errored out."); - return; - } - [self waitForExpectations:@[ex] timeout:60.0]; -} - -- (void)waitForDownloadsForRealm:(RLMRealm *)realm { - RLMSyncSession *session = realm.syncSession; - NSAssert(session, @"Cannot call with invalid Realm"); - XCTestExpectation *ex = [self expectationWithDescription:@"Wait for download completion"]; - __block NSError *completionError; - BOOL queued = [session waitForDownloadCompletionOnQueue:dispatch_get_global_queue(0, 0) - callback:^(NSError *error) { - completionError = error; - [ex fulfill]; - }]; - if (!queued) { - XCTFail(@"Download waiter did not queue; session was invalid or errored out."); - return; - } - [self waitForExpectations:@[ex] timeout:60.0]; - [realm refresh]; -} - -- (void)setInvalidTokensForUser:(RLMUser *)user { - realm::RealmJWT token(std::string_view(self.badAccessToken)); - user.user->update_data_for_testing([&](auto& data) { - data.access_token = token; - data.refresh_token = token; - }); -} - -- (void)writeToPartition:(NSString *)partition block:(void (^)(RLMRealm *))block { - @autoreleasepool { - RLMUser *user = [self createUser]; - auto c = [user configurationWithPartitionValue:partition]; - c.objectClasses = self.defaultObjectTypes; - [self writeToConfiguration:c block:block]; - } -} - -- (void)writeToConfiguration:(RLMRealmConfiguration *)config block:(void (^)(RLMRealm *))block { - @autoreleasepool { - RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nullptr]; - [self waitForDownloadsForRealm:realm]; - [realm beginWriteTransaction]; - block(realm); - [realm commitWriteTransaction]; - [self waitForUploadsForRealm:realm]; - } - - // A synchronized Realm is not closed immediately when we release our last - // reference as the sync worker thread also has to clean up, so retry deleting - // it until we can, waiting up to one second. This typically takes a single - // retry. - int retryCount = 0; - NSError *error; - while (![RLMRealm deleteFilesForConfiguration:config error:&error]) { - XCTAssertEqual(error.code, RLMErrorAlreadyOpen); - if (++retryCount > 1000) { - XCTFail(@"Waiting for Realm to be closed timed out"); - break; - } - usleep(1000); - } -} - -#pragma mark - XCUnitTest Lifecycle - -+ (XCTestSuite *)defaultTestSuite { - if ([RealmServer haveServer]) { - return [super defaultTestSuite]; - } - NSLog(@"Skipping sync tests: server is not present. Run `build.sh setup-baas` to install it."); - return [[XCTestSuite alloc] initWithName:[super defaultTestSuite].name]; -} - -+ (void)setUp { - [super setUp]; - // Wait for the server to launch - if ([RealmServer haveServer]) { - (void)[RealmServer shared]; - } -} - -- (void)setUp { - [super setUp]; - self.continueAfterFailure = NO; - if (auto ids = NSProcessInfo.processInfo.environment[@"RLMParentAppIds"]) { - _appIds = [ids componentsSeparatedByString:@","]; //take the one array for split the string - } - NSURL *clientDataRoot = self.clientDataRoot; - [NSFileManager.defaultManager removeItemAtURL:clientDataRoot error:nil]; - NSError *error; - [NSFileManager.defaultManager createDirectoryAtURL:clientDataRoot - withIntermediateDirectories:YES attributes:nil error:&error]; - XCTAssertNil(error); -} - -- (void)tearDown { - [self resetSyncManager]; - [RealmServer.shared deleteAppsAndReturnError:nil]; - [super tearDown]; -} - -static NSString *s_appId; -static bool s_opensApp; -+ (void)tearDown { - if (s_appId && s_opensApp) { - [RealmServer.shared deleteApp:s_appId error:nil]; - s_appId = nil; - s_opensApp = false; - } -} - -- (NSString *)appId { - if (s_appId) { - return s_appId; - } - if (NSString *appId = NSProcessInfo.processInfo.environment[@"RLMParentAppId"]) { - return s_appId = appId; - } - NSError *error; - s_appId = [self createAppWithError:&error]; - if (error) { - NSLog(@"Failed to create app: %@", error); - abort(); - } - s_opensApp = true; - return s_appId; -} - -- (NSString *)createAppWithError:(NSError **)error { - return [RealmServer.shared createAppWithPartitionKeyType:@"string" - types:self.defaultObjectTypes - persistent:true error:error]; -} - -- (RLMApp *)app { - if (!_app) { - _app = [self appWithId:self.appId]; - } - return _app; -} - -- (void)resetSyncManager { - _app = nil; - [self resetAppCache]; -} - -- (void)resetAppCache { - NSArray *apps = [RLMApp allApps]; - NSMutableArray *exs = [NSMutableArray new]; - for (RLMApp *app : apps) @autoreleasepool { - [app.allUsers enumerateKeysAndObjectsUsingBlock:^(NSString *, RLMUser *user, BOOL *) { - XCTestExpectation *ex = [self expectationWithDescription:@"Wait for logout"]; - [exs addObject:ex]; - [user logOutWithCompletion:^(NSError *) { - [ex fulfill]; - }]; - }]; - - // Sessions are removed from the user asynchronously after a logout. - // We need to wait for this to happen before calling resetForTesting as - // that expects all sessions to be cleaned up first. - [exs addObject:[self expectationForPredicate:[NSPredicate predicateWithFormat:@"hasAnySessions = false"] - evaluatedWithObject:app.syncManager handler:nil]]; - } - - if (exs.count) { - [self waitForExpectations:exs timeout:60.0]; - } - - for (RLMApp *app : apps) { - if (auto transport = RLMDynamicCast(app.configuration.transport)) { - [transport waitForCompletion]; - } - [app.syncManager resetForTesting]; - } - [RLMApp resetAppCache]; -} - -- (const char *)badAccessToken { - return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJl" - "eHAiOjE1ODE1MDc3OTYsImlhdCI6MTU4MTUwNTk5NiwiaXNzIjoiN" - "WU0M2RkY2M2MzZlZTEwNmVhYTEyYmRjIiwic3RpdGNoX2RldklkIjo" - "iMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbk" - "lkIjoiNWUxNDk5MTNjOTBiNGFmMGViZTkzNTI3Iiwic3ViIjoiNWU0M2R" - "kY2M2MzZlZTEwNmVhYTEyYmRhIiwidHlwIjoiYWNjZXNzIn0.0q3y9KpFx" - "EnbmRwahvjWU1v9y1T1s3r2eozu93vMc3s"; -} - -- (void)cleanupRemoteDocuments:(RLMMongoCollection *)collection { - XCTestExpectation *deleteManyExpectation = [self expectationWithDescription:@"should delete many documents"]; - [collection deleteManyDocumentsWhere:@{} - completion:^(NSInteger, NSError *error) { - XCTAssertNil(error); - [deleteManyExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:60.0 handler:nil]; -} - -- (NSURL *)clientDataRoot { - if (self.isParent) { - return [NSURL fileURLWithPath:RLMDefaultDirectoryForBundleIdentifier(nil)]; - } else { - return syncDirectoryForChildProcess(); - } -} - -- (NSTask *)childTask { - return [self childTaskWithAppIds:s_appId ? @[s_appId] : @[]]; -} - -- (RLMApp *)appWithId:(NSString *)appId { - auto config = self.defaultAppConfiguration; - config.appId = appId; - RLMApp *app = [RLMApp appWithConfiguration:config]; - RLMSyncManager *syncManager = app.syncManager; - syncManager.userAgent = self.name; - RLMLogger.defaultLogger.level = RLMLogLevelWarn; - return app; -} - -- (NSString *)partitionBsonType:(id)bson { - switch (bson.bsonType){ - case RLMBSONTypeString: - return @"string"; - case RLMBSONTypeUUID: - return @"uuid"; - case RLMBSONTypeInt32: - case RLMBSONTypeInt64: - return @"long"; - case RLMBSONTypeObjectId: - return @"objectId"; - default: - return @""; - } -} - -#pragma mark Flexible Sync App - -- (NSString *)createFlexibleSyncAppWithError:(NSError **)error { - NSArray *fields = @[@"age", @"breed", @"partition", @"firstName", @"boolCol", @"intCol", @"stringCol", @"dateCol", @"lastName", @"_id", @"uuidCol"]; - return [RealmServer.shared createAppWithFields:fields - types:self.defaultObjectTypes - persistent:true - error:error]; -} - -- (void)populateData:(void (^)(RLMRealm *))block { - RLMRealm *realm = [self openRealm]; - RLMRealmSubscribeToAll(realm); - [realm beginWriteTransaction]; - block(realm); - [realm commitWriteTransaction]; - [self waitForUploadsForRealm:realm]; -} - -- (void)writeQueryAndCompleteForRealm:(RLMRealm *)realm block:(void (^)(RLMSyncSubscriptionSet *))block { - RLMSyncSubscriptionSet *subs = realm.subscriptions; - XCTAssertNotNil(subs); - - XCTestExpectation *ex = [self expectationWithDescription:@"state changes"]; - [subs update:^{ - block(subs); - } onComplete:^(NSError* error) { - XCTAssertNil(error); - [ex fulfill]; - }]; - XCTAssertNotNil(subs); - [self waitForExpectationsWithTimeout:20.0 handler:nil]; - [self waitForDownloadsForRealm:realm]; -} - -@end - -@implementation TestNetworkTransport { - dispatch_group_t _group; -} -- (instancetype)init { - if (self = [super init]) { - _group = dispatch_group_create(); - } - return self; -} -- (void)sendRequestToServer:(RLMRequest *)request - completion:(RLMNetworkTransportCompletionBlock)completionBlock { - dispatch_group_enter(_group); - [super sendRequestToServer:request completion:^(RLMResponse *response) { - completionBlock(response); - dispatch_group_leave(_group); - }]; -} - -- (void)waitForCompletion { - dispatch_group_wait(_group, DISPATCH_TIME_FOREVER); -} -@end - -@implementation RLMUser (Test) -- (RLMMongoCollection *)collectionForType:(Class)type app:(RLMApp *)app { - return [[[self mongoClientWithServiceName:@"mongodb1"] - databaseWithName:@"test_data"] - collectionWithName:[NSString stringWithFormat:@"%@ %@", [type className], app.appId]]; -} -@end - -int64_t RLMGetClientFileIdent(RLMRealm *realm) { - return realm->_realm->sync_session()->get_file_ident().ident; -} - -#endif // TARGET_OS_OSX diff --git a/Realm/ObjectServerTests/RLMUser+ObjectServerTests.h b/Realm/ObjectServerTests/RLMUser+ObjectServerTests.h deleted file mode 100644 index d73e7865f2..0000000000 --- a/Realm/ObjectServerTests/RLMUser+ObjectServerTests.h +++ /dev/null @@ -1,31 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability) - -@interface RLMUser (ObjectServerTests) -- (void)simulateClientResetErrorForSession:(NSString *)partitionValue; -@end - -@interface RLMSyncSession (ObjectServerTests) -+ (dispatch_queue_t)notificationsQueue; -@end - -RLM_HEADER_AUDIT_END(nullability) diff --git a/Realm/ObjectServerTests/RLMUser+ObjectServerTests.mm b/Realm/ObjectServerTests/RLMUser+ObjectServerTests.mm deleted file mode 100644 index 073605054c..0000000000 --- a/Realm/ObjectServerTests/RLMUser+ObjectServerTests.mm +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMUser+ObjectServerTests.h" - -#import "RLMSyncSession_Private.hpp" -#import "RLMRealmUtil.hpp" - -#import -#import -#import - -using namespace realm; - -@implementation RLMUser (ObjectServerTests) - -- (void)simulateClientResetErrorForSession:(NSString *)partitionValue { - RLMSyncSession *session = [self sessionForPartitionValue:partitionValue]; - NSAssert(session, @"Cannot call with invalid URL"); - - std::shared_ptr raw_session = session->_session.lock(); - realm::sync::SessionErrorInfo error = {{realm::ErrorCodes::BadChangeset, "Not a real error message"}, true}; - error.server_requests_action = realm::sync::ProtocolErrorInfo::Action::ClientReset; - SyncSession::OnlyForTesting::handle_error(*raw_session, std::move(error)); -} - -@end diff --git a/Realm/ObjectServerTests/RLMWatchTestUtility.h b/Realm/ObjectServerTests/RLMWatchTestUtility.h deleted file mode 100644 index b46e6366f0..0000000000 --- a/Realm/ObjectServerTests/RLMWatchTestUtility.h +++ /dev/null @@ -1,47 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import -#import -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -/// Used to process watch change events and assert tests. -@interface RLMWatchTestUtility : XCTestCase - -@property (nonatomic, strong) dispatch_semaphore_t semaphore; -@property (nonatomic, strong) dispatch_semaphore_t isOpenSemaphore; - -/// Sets up an object that subscribes to the RLMChangeEventDelegate -/// @param changeEventCount The target amount of change events for the test to succeed -/// @param expectation The expectation for the test to fulfil. -- (instancetype)initWithChangeEventCount:(NSUInteger)changeEventCount - expectation:(XCTestExpectation *)expectation; - -/// Sets up an object that subscribes to the RLMChangeEventDelegate -/// @param changeEventCount The target amount of change events for the test to succeed -/// @param matchingObjectId An objectId that the change event must match. -/// @param expectation The expectation for the test to fulfil. -- (instancetype)initWithChangeEventCount:(NSUInteger)changeEventCount - matchingObjectId:(RLMObjectId *)matchingObjectId - expectation:(XCTestExpectation *)expectation; - - -@end -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/ObjectServerTests/RLMWatchTestUtility.m b/Realm/ObjectServerTests/RLMWatchTestUtility.m deleted file mode 100644 index ff0bbe11db..0000000000 --- a/Realm/ObjectServerTests/RLMWatchTestUtility.m +++ /dev/null @@ -1,84 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMWatchTestUtility.h" - -#import - -@implementation RLMWatchTestUtility { - NSUInteger _targetChangeEventCount; - NSUInteger _currentChangeEventCount; - RLMObjectId *_matchingObjectId; - BOOL _didOpenWasCalled; - __weak XCTestExpectation *_expectation; -} - -- (instancetype)initWithChangeEventCount:(NSUInteger)changeEventCount - expectation:(XCTestExpectation *)expectation { - if (self = [super init]) { - _targetChangeEventCount = changeEventCount; - _semaphore = dispatch_semaphore_create(0); - _isOpenSemaphore = dispatch_semaphore_create(0); - _expectation = expectation; - return self; - } - return nil; -} - -- (instancetype)initWithChangeEventCount:(NSUInteger)changeEventCount - matchingObjectId:(RLMObjectId *)matchingObjectId - expectation:(XCTestExpectation *)expectation { - if (self = [super init]) { - _targetChangeEventCount = changeEventCount; - _semaphore = dispatch_semaphore_create(0); - _isOpenSemaphore = dispatch_semaphore_create(0); - _matchingObjectId = matchingObjectId; - _expectation = expectation; - return self; - } - return nil; -} - -- (void)changeStreamDidCloseWithError:(nullable NSError *)error { - XCTAssertNil(error); - XCTAssertTrue(_didOpenWasCalled); - XCTAssertEqual(_currentChangeEventCount, _targetChangeEventCount); - [_expectation fulfill]; -} - -- (void)changeStreamDidOpen:(nonnull __unused RLMChangeStream *)changeStream { - _didOpenWasCalled = YES; - dispatch_semaphore_signal(self.isOpenSemaphore); -} - -- (void)changeStreamDidReceiveChangeEvent:(nonnull id)changeEvent { - _currentChangeEventCount++; - if (_matchingObjectId) { - RLMObjectId *objectId = ((NSDictionary *)changeEvent)[@"fullDocument"][@"_id"]; - XCTAssertTrue([objectId.stringValue isEqualToString:_matchingObjectId.stringValue]); - dispatch_semaphore_signal(self.semaphore); - } else { - dispatch_semaphore_signal(self.semaphore); - } -} - -- (void)changeStreamDidReceiveError:(nonnull NSError *)error { - XCTAssertNil(error); -} - -@end diff --git a/Realm/ObjectServerTests/RealmServer.swift b/Realm/ObjectServerTests/RealmServer.swift deleted file mode 100644 index 8c8b400a2b..0000000000 --- a/Realm/ObjectServerTests/RealmServer.swift +++ /dev/null @@ -1,1243 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import Foundation -import Realm.Private -import RealmSwift -import XCTest - -#if canImport(RealmSwiftTestSupport) -import RealmSwiftTestSupport -import RealmSyncTestSupport -#endif - -#if os(macOS) - -extension URLSession { - fileprivate func resultDataTask(with request: URLRequest, - _ completionHandler: @Sendable @escaping (Result) -> Void) { - URLSession(configuration: .default, delegate: nil, delegateQueue: OperationQueue()).dataTask(with: request) { (data, response, error) in - if let httpResponse = response as? HTTPURLResponse, - httpResponse.statusCode >= 200 && httpResponse.statusCode < 300, - let data = data { - completionHandler(.success(data)) - } else if let error = error { - completionHandler(.failure(error)) - // swiftlint:disable:next non_optional_string_data_conversion - } else if let data = data, let string = String(data: data, encoding: .utf8) { - completionHandler(.failure(NSError(domain: URLError.errorDomain, - code: URLError.badServerResponse.rawValue, - userInfo: [NSLocalizedDescriptionKey: string]))) - } else { - completionHandler(.failure(URLError(.badServerResponse))) - } - }.resume() - } - - // Synchronously perform a data task, returning the data from it - fileprivate func resultDataTask(with request: URLRequest) -> Result { - let result = Locked(Result?.none) - let group = DispatchGroup() - group.enter() - resultDataTask(with: request) { - result.value = $0 - group.leave() - } - guard case .success = group.wait(timeout: .now() + 10) else { - return .failure(URLError(.cannotFindHost)) - } - return result.value! - } -} - -private func bsonType(_ type: PropertyType) -> String { - switch type { - case .UUID: return "uuid" - case .any: return "mixed" - case .bool: return "bool" - case .data: return "binData" - case .date: return "date" - case .decimal128: return "decimal" - case .double: return "double" - case .float: return "float" - case .int: return "long" - case .object: return "object" - case .objectId: return "objectId" - case .string: return "string" - case .linkingObjects: return "linkingObjects" - } -} - -private extension Property { - func stitchRule(_ objectSchema: ObjectSchema) -> [String: Json] { - let type: String - if self.type == .object { - type = bsonType(objectSchema.primaryKeyProperty!.type) - } else { - type = bsonType(self.type) - } - - if isArray { - return [ - "bsonType": "array", - "items": [ - "bsonType": type - ] - ] - } - if isSet { - return [ - "bsonType": "array", - "uniqueItems": true, - "items": [ - "bsonType": type - ] - ] - } - if isMap { - return [ - "bsonType": "object", - "properties": [:], - "additionalProperties": [ - "bsonType": type - ] - ] - } - - return [ - "bsonType": type - ] - } -} - -internal protocol Json {} -extension Bool: Json {} -extension Int: Json {} -extension Int64: Json {} -extension String: Json {} -extension Double: Json {} -extension Dictionary: Json where Key == String, Value == Json {} -extension Array: Json where Element == Json {} -extension Optional: Json where Wrapped: Json {} - -private extension ObjectSchema { - func stitchRule(_ partitionKeyType: String?, id: String? = nil, appId: String) -> [String: Json] { - var stitchProperties: [String: Json] = [:] - - // We only add a partition property for pbs - if let partitionKeyType = partitionKeyType { - stitchProperties["realm_id"] = [ - "bsonType": "\(partitionKeyType)" - ] - } - - var relationships: [String: Json] = [:] - - // First pass we only add the properties to the schema as we can't add - // links until the targets of the links exist. - let pk = primaryKeyProperty! - stitchProperties[pk.columnName] = pk.stitchRule(self) - for property in properties { - if property.type != .object { - stitchProperties[property.columnName] = property.stitchRule(self) - } else if id != nil { - stitchProperties[property.columnName] = property.stitchRule(self) - relationships[property.columnName] = [ - "ref": "#/relationship/mongodb1/test_data/\(property.objectClassName!) \(appId)", - "foreign_key": "_id", - "is_list": property.isArray || property.isSet || property.isMap - ] - } - } - - return [ - "_id": id as Json, - "schema": [ - "properties": stitchProperties, - // The server currently only supports non-optional collections - // but requires them to be marked as optional - "required": properties.compactMap { $0.isOptional || $0.type == .any || $0.isArray || $0.isMap || $0.isSet ? nil : $0.columnName }, - "title": "\(className)" - ], - "metadata": [ - "data_source": "mongodb1", - "database": "test_data", - "collection": "\(className) \(appId)" - ], - "relationships": relationships - ] - } -} - -// MARK: - AdminProfile -struct AdminProfile: Codable { - struct Role: Codable { - enum CodingKeys: String, CodingKey { - case groupId = "group_id" - case roleName = "role_name" - } - - let roleName: String - let groupId: String? - } - - let roles: [Role] -} - -// Dispatch has not yet been annotated for sendability -extension DispatchGroup: @unchecked Sendable { -} - -private extension DispatchGroup { - func throwingWait(timeout: DispatchTime) throws { - if wait(timeout: timeout) == .timedOut { - throw URLError(.timedOut) - } - } -} - -// MARK: AdminSession -/// An authenticated session for using the Admin API -final class AdminSession: Sendable { - /// The access token of the authenticated user - let accessToken: String - /// The group id associated with the authenticated user - let groupId: String - - init(accessToken: String, groupId: String) { - self.accessToken = accessToken - self.groupId = groupId - apps = .init(accessToken: accessToken, - groupId: groupId, - url: URL(string: "http://localhost:9090/api/admin/v3.0/groups/\(groupId)/apps")!) - privateApps = .init(accessToken: accessToken, - groupId: groupId, - url: URL(string: "http://localhost:9090/api/private/v1.0/groups/\(groupId)/apps")!) - } - - // MARK: AdminEndpoint - /// Representation of a given admin endpoint. - /// This allows us to call a give endpoint dynamically with loose typing. - @dynamicMemberLookup - struct AdminEndpoint { - /// The access token of the authenticated user - var accessToken: String - /// The group id associated with the authenticated user - var groupId: String - /// The endpoint url. This will be appending to dynamically by appending the dynamic member called - /// as if it were a path. - var url: URL - - /** - Append the given member to the path. E.g., if the current URL is set to - http://localhost:9090/api/admin/v3.0/groups/groupId/apps/appId - and you currently have a: - ``` - var app: AdminEndpoint - ``` - you can fetch a list of all services by calling - ``` - app.services.get() - ``` - */ - subscript(dynamicMember member: String) -> AdminEndpoint { - let pattern = "([a-z0-9])([A-Z])" - - let regex = try? NSRegularExpression(pattern: pattern, options: []) - let range = NSRange(location: 0, length: member.count) - let snakeCaseMember = regex?.stringByReplacingMatches(in: member, - options: [], - range: range, - withTemplate: "$1_$2").lowercased() - return AdminEndpoint(accessToken: accessToken, - groupId: groupId, - url: url.appendingPathComponent(snakeCaseMember!)) - } - - /** - Append the given id to the path. E.g., if the current URL is set to - http://localhost:9090/api/admin/v3.0/groups/groupId/apps/ - and you currently have a: - ``` - var apps: AdminEndpoint - var appId: String - ``` - you can fetch the app from its appId with - ``` - apps[appId].get() - ``` - */ - subscript(_ id: String) -> AdminEndpoint { - return AdminEndpoint(accessToken: accessToken, - groupId: groupId, - url: url.appendingPathComponent(id)) - } - - typealias Completion = @Sendable (Result) -> Void - - private func request(httpMethod: String, data: Any? = nil, - completionHandler: @escaping Completion) { - var components = URLComponents(url: self.url, resolvingAgainstBaseURL: false)! - components.query = "bypass_service_change=DestructiveSyncProtocolVersionIncrease" - var request = URLRequest(url: components.url!) - request.httpMethod = httpMethod - request.allHTTPHeaderFields = [ - "Authorization": "Bearer \(accessToken)", - "Content-Type": "application/json;charset=utf-8", - "Accept": "application/json" - ] - if let data = data { - do { - request.httpBody = try JSONSerialization.data(withJSONObject: data) - } catch { - completionHandler(.failure(error)) - } - } - - URLSession(configuration: URLSessionConfiguration.default, - delegate: nil, delegateQueue: OperationQueue()) - .resultDataTask(with: request) { result in - completionHandler(result.flatMap { data in - Result { - data.count > 0 ? try JSONSerialization.jsonObject(with: data) : nil - } - }) - } - } - - private func request(on group: DispatchGroup, httpMethod: String, data: Any? = nil, - _ completionHandler: @escaping Completion) { - group.enter() - request(httpMethod: httpMethod, data: data) { result in - completionHandler(result) - group.leave() - } - } - - private func request(httpMethod: String, data: Any? = nil) -> Result { - let group = DispatchGroup() - let result = Locked(Result?.none) - group.enter() - request(httpMethod: httpMethod, data: data) { - result.value = $0 - group.leave() - } - guard case .success = group.wait(timeout: .now() + 60) else { - print("HTTP request timed out: \(httpMethod) \(self.url)") - return .failure(URLError(.timedOut)) - } - return result.value! - } - - func get(_ completionHandler: @escaping Completion) { - request(httpMethod: "GET", completionHandler: completionHandler) - } - - func get(on group: DispatchGroup, - _ completionHandler: @escaping Completion) { - request(on: group, httpMethod: "GET", completionHandler) - } - - func get() -> Result { - request(httpMethod: "GET") - } - - func post(_ data: [String: Json], _ completionHandler: @escaping Completion) { - request(httpMethod: "POST", data: data, completionHandler: completionHandler) - } - - func post(on group: DispatchGroup, _ data: [String: Json], - _ completionHandler: @escaping Completion) { - request(on: group, httpMethod: "POST", data: data, completionHandler) - } - - func post(_ data: [String: Json]) -> Result { - request(httpMethod: "POST", data: data) - } - - func put(_ completionHandler: @escaping Completion) { - request(httpMethod: "PUT", completionHandler: completionHandler) - } - - func put(on group: DispatchGroup, data: Json? = nil, - _ completionHandler: @escaping Completion) { - request(on: group, httpMethod: "PUT", data: data, completionHandler) - } - - func put(data: [String: Json]? = nil, _ completionHandler: @escaping Completion) { - request(httpMethod: "PUT", data: data, completionHandler: completionHandler) - } - - func put(_ data: [String: Json]) -> Result { - request(httpMethod: "PUT", data: data) - } - - func delete(_ completionHandler: @escaping Completion) { - request(httpMethod: "DELETE", completionHandler: completionHandler) - } - - func delete(on group: DispatchGroup, _ completionHandler: @escaping Completion) { - request(on: group, httpMethod: "DELETE", completionHandler) - } - - func delete() -> Result { - request(httpMethod: "DELETE") - } - - func patch(on group: DispatchGroup, _ data: [String: Json], - _ completionHandler: @escaping Completion) { - request(on: group, httpMethod: "PATCH", data: data, completionHandler) - } - - func patch(_ data: Any) -> Result { - request(httpMethod: "PATCH", data: data) - } - - func patch(_ data: [String: Json], _ completionHandler: @escaping Completion) { - request(httpMethod: "PATCH", data: data, completionHandler: completionHandler) - } - } - - /// The initial endpoint to access the admin server - let apps: AdminEndpoint/// - - /// The initial endpoint to access the private API - let privateApps: AdminEndpoint -} - -// MARK: - Admin -class Admin { - private func userProfile(accessToken: String) -> Result { - var request = URLRequest(url: URL(string: "http://localhost:9090/api/admin/v3.0/auth/profile")!) - request.allHTTPHeaderFields = [ - "Authorization": "Bearer \(String(describing: accessToken))" - ] - return URLSession.shared.resultDataTask(with: request) - .flatMap { data in - Result { - try JSONDecoder().decode(AdminProfile.self, from: data) - } - } - } - - /// Synchronously authenticate an admin session - func login() throws -> AdminSession { - let authUrl = URL(string: "http://localhost:9090/api/admin/v3.0/auth/providers/local-userpass/login")! - var loginRequest = URLRequest(url: authUrl) - loginRequest.httpMethod = "POST" - loginRequest.allHTTPHeaderFields = ["Content-Type": "application/json;charset=utf-8", - "Accept": "application/json"] - - loginRequest.httpBody = try JSONEncoder().encode(["provider": "userpass", - "username": "unique_user@domain.com", - "password": "password"]) - return try URLSession(configuration: .default, delegate: nil, delegateQueue: OperationQueue()) - .resultDataTask(with: loginRequest) - .flatMap { data in - return Result { - if let accessToken = try JSONDecoder().decode([String: String].self, from: data)["access_token"] { - return accessToken - } - throw URLError(.badServerResponse) - } - } - .flatMap { (accessToken: String) -> Result in - self.userProfile(accessToken: accessToken).map { - AdminSession(accessToken: accessToken, groupId: $0.roles.first(where: { role in - role.roleName == "GROUP_OWNER" - })!.groupId!) - } - } - .get() - } -} - -// Sync mode -public enum SyncMode { - case pbs(String) // partition based - case flx([String]) // flexible sync - case none -} - -// MARK: RealmServer - -/** - A sandboxed server. This singleton launches and maintains all server processes - and allows for app creation. - */ -@objc(RealmServer) -final public class RealmServer: NSObject, Sendable { - public enum LogLevel: Sendable { - case none, info, warn, error - } - - /// Shared RealmServer. This class only needs to be initialized and torn down once per test suite run. - @objc public static let shared: RealmServer! = RealmServer(()) - - /// Log level for the server and mongo processes. - public let logLevel = LogLevel.none - - /// Process that runs the local mongo server. Should be terminated on exit. - private let mongoProcess: Process - /// Process that runs the local backend server. Should be terminated on exit. - private let serverProcess: Process - - /// The root URL of the project. - private static let rootUrl = URL(string: #filePath)! - .deletingLastPathComponent() // RealmServer.swift - .deletingLastPathComponent() // ObjectServerTests - .deletingLastPathComponent() // Realm - private static let buildDir = rootUrl.appendingPathComponent("ci_scripts/setup_baas/.baas") - private static let binDir = buildDir.appendingPathComponent("bin") - - /// The directory where mongo stores its files. This is a unique value so that - /// we have a fresh mongo each run. - private let tempDir = URL(fileURLWithPath: NSTemporaryDirectory(), - isDirectory: true).appendingPathComponent("realm-test-\(UUID().uuidString)") - - /// Whether or not this is a parent or child process. - private let isParentProcess = (getenv("RLMProcessIsChild") == nil) - - /// The current admin session - private let session: AdminSession - - /// Created appIds which should be cleaned up - private let appIds = Locked([String]()) - - /// Check if the BaaS files are present and we can run the server - @objc public static func haveServer() -> Bool { - let goDir = RealmServer.buildDir.appendingPathComponent("stitch") - return FileManager.default.fileExists(atPath: goDir.path) - } - - private init?(_: Void) { - guard isParentProcess else { return nil } - atexit { - RealmServer.shared.tearDown() - } - - do { - mongoProcess = try Self.launchMongoProcess(at: tempDir) - serverProcess = try Self.launchServerProcess(at: tempDir, logLevel: logLevel) - session = try Admin().login() - super.init() - try makeUserAdmin() - } catch { - fatalError("Could not initiate admin session: \(error.localizedDescription)") - } - } - - private func tearDown() { - serverProcess.terminate() - - let mongo = RealmServer.binDir.appendingPathComponent("mongo").path - - // step down the replica set - let rsStepDownProcess = Process() - rsStepDownProcess.launchPath = mongo - rsStepDownProcess.arguments = [ - "admin", - "--port", "26000", - "--eval", "'db.adminCommand({replSetStepDown: 0, secondaryCatchUpPeriodSecs: 0, force: true})'"] - try? rsStepDownProcess.run() - rsStepDownProcess.waitUntilExit() - - // step down the replica set - let mongoShutdownProcess = Process() - mongoShutdownProcess.launchPath = mongo - mongoShutdownProcess.arguments = [ - "admin", - "--port", "26000", - "--eval", "'db.shutdownServer({force: true})'"] - try? mongoShutdownProcess.run() - mongoShutdownProcess.waitUntilExit() - - mongoProcess.terminate() - - try? FileManager().removeItem(at: tempDir) - } - - /// Launch the mongo server in the background. - /// This process should run until the test suite is complete. - private static func launchMongoProcess(at tempDir: URL) throws -> Process { - try FileManager().createDirectory(at: tempDir, - withIntermediateDirectories: false, - attributes: nil) - - let mongoProcess = Process() - mongoProcess.launchPath = RealmServer.binDir.appendingPathComponent("mongod").path - mongoProcess.arguments = [ - "--quiet", - "--dbpath", tempDir.path, - "--bind_ip", "localhost", - "--port", "26000", - "--replSet", "test" - ] - mongoProcess.standardOutput = nil - try mongoProcess.run() - - let initProcess = Process() - initProcess.launchPath = RealmServer.binDir.appendingPathComponent("mongo").path - initProcess.arguments = [ - "--port", "26000", - "--eval", "rs.initiate()" - ] - initProcess.standardOutput = nil - try initProcess.run() - initProcess.waitUntilExit() - return mongoProcess - } - - private static func launchServerProcess(at tempDir: URL, logLevel: LogLevel) throws -> Process { - let binDir = Self.buildDir.appendingPathComponent("bin").path - let libDir = Self.buildDir.appendingPathComponent("lib").path - let binPath = "$PATH:\(binDir)" - let awsAccessKeyId = ProcessInfo.processInfo.environment["AWS_ACCESS_KEY_ID"]! - let awsSecretAccessKey = ProcessInfo.processInfo.environment["AWS_SECRET_ACCESS_KEY"]! - let env = [ - "PATH": binPath, - "DYLD_LIBRARY_PATH": libDir, - "AWS_ACCESS_KEY_ID": awsAccessKeyId, - "AWS_SECRET_ACCESS_KEY": awsSecretAccessKey - ] - - let stitchRoot = RealmServer.buildDir.path + "/go/src/github.com/10gen/stitch" - - for _ in 0..<5 { - // create the admin user - let userProcess = Process() - userProcess.environment = env - userProcess.launchPath = "\(binDir)/create_user" - userProcess.arguments = [ - "addUser", - "-domainID", - "000000000000000000000000", - "-mongoURI", "mongodb://localhost:26000", - "-salt", "DQOWene1723baqD!_@#", - "-id", "unique_user@domain.com", - "-password", "password" - ] - try userProcess.run() - userProcess.waitUntilExit() - if userProcess.terminationStatus == 0 { - break - } - } - - let serverProcess = Process() - serverProcess.environment = env - // golang server needs a tmp directory - - try FileManager.default.createDirectory(atPath: "\(tempDir.path)/tmp", - withIntermediateDirectories: false, attributes: nil) - serverProcess.launchPath = "\(binDir)/stitch_server" - serverProcess.currentDirectoryPath = tempDir.path - serverProcess.arguments = [ - "--configFile", - "\(stitchRoot)/etc/configs/test_config.json", - "--configFile", - "\(RealmServer.rootUrl)/Realm/ObjectServerTests/config_overrides.json" - ] - - let pipe = Pipe() - pipe.fileHandleForReading.readabilityHandler = { file in - guard file.availableData.count > 0, - // swiftlint:disable:next non_optional_string_data_conversion - let available = String(data: file.availableData, encoding: .utf8)?.split(separator: "\t") else { - return - } - - // prettify server output - var parts = [String]() - for part in available { - if part.contains("INFO") { - guard logLevel == .info else { - return - } - parts.append("🔵") - } else if part.contains("DEBUG") { - guard logLevel == .info || logLevel == .warn else { - return - } - parts.append("🟡") - } else if part.contains("ERROR") { - parts.append("🔴") - } else if let json = try? JSONSerialization.jsonObject(with: part.data(using: .utf8)!) { - parts.append(String(decoding: try! JSONSerialization.data(withJSONObject: json, - options: .prettyPrinted), - as: UTF8.self)) - } else if !part.isEmpty { - parts.append(String(part)) - } - } - print(parts.joined(separator: "\t")) - } - - serverProcess.standardError = nil - if logLevel != .none { - serverProcess.standardOutput = pipe - } else { - serverProcess.standardOutput = nil - } - - try serverProcess.run() - waitForServerToStart() - return serverProcess - } - - private static func waitForServerToStart() { - let group = DispatchGroup() - group.enter() - @Sendable func pingServer(_ tries: Int = 0) { - let session = URLSession(configuration: URLSessionConfiguration.default, - delegate: nil, - delegateQueue: OperationQueue()) - session.dataTask(with: URL(string: "http://localhost:9090/api/admin/v3.0/groups/groupId/apps/appId")!) { (_, _, error) in - if error != nil { - Thread.sleep(forTimeInterval: 0.1) - pingServer(tries + 1) - } else { - group.leave() - } - }.resume() - } - pingServer() - guard case .success = group.wait(timeout: .now() + 20) else { - return XCTFail("Server did not start") - } - } - - private func makeUserAdmin() throws { - let p = Process() - p.launchPath = RealmServer.binDir.appendingPathComponent("mongo").path - p.arguments = [ - "--quiet", - "mongodb://localhost:26000/auth", - "--eval", """ - // Sometimes the user seems to not exist immediately - let id = null; - for (let i = 0; i < 5; ++i) { - let user = db.users.findOne({"data.email" : "unique_user@domain.com"}); - if (user) { - id = user._id; - break; - } - } - if (id === null) { - throw "could not find admin user"; - } - - let res = db.users.updateOne({"_id": id}, { - "$addToSet": - {"roles": {"$each": [{"roleName": "GLOBAL_STITCH_ADMIN"}, - {"roleName": "GLOBAL_BAAS_FEATURE_ADMIN"}]}} - }); - if (res.modifiedCount != 1) { - throw "could not update admin user"; - } - """ - ] - try p.run() - p.waitUntilExit() - } - - public typealias AppId = String - - /// Create a new server app - func createApp(syncMode: SyncMode, types: [ObjectBase.Type], persistent: Bool) throws -> AppId { - let session = try XCTUnwrap(session) - - let info = try session.apps.post(["name": "test"]).get() - guard let appInfo = info as? [String: Any], - let clientAppId = appInfo["client_app_id"] as? String, - let appId = appInfo["_id"] as? String else { - throw URLError(.badServerResponse) - } - - let app = session.apps[appId] - let group = DispatchGroup() - - _ = app.secrets.post([ - "name": "customTokenKey", - "value": "My_very_confidential_secretttttt" - ]) - - app.authProviders.post(on: group, [ - "type": "custom-token", - "config": [ - "audience": [], - "signingAlgorithm": "HS256", - "useJWKURI": false - ], - "secret_config": ["signingKeys": ["customTokenKey"]], - "metadata_fields": [ - ["required": false, "name": "user_data.name", "field_name": "name"], - ["required": false, "name": "user_data.occupation", "field_name": "occupation"], - ["required": false, "name": "my_metadata.name", "field_name": "anotherName"] - ] - ], failOnError) - - app.authProviders.post(on: group, ["type": "anon-user"], failOnError) - app.authProviders.post(on: group, [ - "type": "local-userpass", - "config": [ - "emailConfirmationUrl": "http://foo.com", - "resetPasswordUrl": "http://foo.com", - "confirmEmailSubject": "Hi", - "resetPasswordSubject": "Bye", - "autoConfirm": true - ] - ], failOnError) - - app.authProviders.get(on: group) { authProviders in - do { - guard let authProviders = try authProviders.get() as? [[String: Any]] else { - return XCTFail("Bad formatting for authProviders") - } - guard let provider = authProviders.first(where: { $0["type"] as? String == "api-key" }) else { - return XCTFail("Did not find api-key provider") - } - app.authProviders[provider["_id"] as! String].enable.put(on: group, failOnError) - } catch { - XCTFail(error.localizedDescription) - } - } - - if case .none = syncMode { - try group.throwingWait(timeout: .now() + 5.0) - return clientAppId - } - - app.secrets.post(on: group, [ - "name": "BackingDB_uri", - "value": "mongodb://localhost:26000" - ], failOnError) - - try group.throwingWait(timeout: .now() + 5.0) - - let appService: [String: Json] = [ - "name": "mongodb1", - "type": "mongodb", - "config": [ - "uri": "mongodb://localhost:26000" - ] - ] - - let serviceResponse = app.services.post(appService) - guard let serviceId = (try serviceResponse.get() as? [String: Any])?["_id"] as? String else { - throw URLError(.badServerResponse) - } - - let schema = types.map { ObjectiveCSupport.convert(object: $0.sharedSchema()!) } - - let partitionKeyType: String? - if case .pbs(let bsonType) = syncMode { - partitionKeyType = bsonType - } else { - partitionKeyType = nil - } - - // Creating the schema is a two-step process where we first add all the - // objects with their properties to them so that we can add relationships - let lockedSchemaIds = Locked([String: String]()) - for objectSchema in schema { - app.schemas.post(on: group, objectSchema.stitchRule(partitionKeyType, appId: clientAppId)) { - switch $0 { - case .success(let data): - lockedSchemaIds.withLock { - $0[objectSchema.className] = ((data as! [String: Any])["_id"] as! String) - } - case .failure(let error): - XCTFail(error.localizedDescription) - } - } - } - try group.throwingWait(timeout: .now() + 5.0) - - let schemaIds = lockedSchemaIds.value - for objectSchema in schema { - let schemaId = schemaIds[objectSchema.className]! - app.schemas[schemaId].put(on: group, data: objectSchema.stitchRule(partitionKeyType, id: schemaId, appId: clientAppId), failOnError) - } - try group.throwingWait(timeout: .now() + 5.0) - - let asymmetricTables = schema.compactMap { - $0.isAsymmetric ? $0.className : nil - } - let serviceConfig: [String: Json] - switch syncMode { - case .pbs(let bsonType): - serviceConfig = [ - "sync": [ - "state": "enabled", - "database_name": "test_data", - "partition": [ - "key": "realm_id", - "type": "\(bsonType)", - "required": false, - "permissions": [ - "read": true, - "write": true - ] - ] - ] - ] - - // We only need to create the userData rule for .pbs since for .flx we - // have a default rule that covers all collections - let userDataRule: [String: Json] = [ - "database": "test_data", - "collection": "UserData", - "roles": [[ - "name": "default", - "apply_when": [:], - "insert": true, - "delete": true, - "additional_fields": [:] - ]] - ] - _ = app.services[serviceId].rules.post(userDataRule) - case .flx(let fields): - serviceConfig = [ - "flexible_sync": [ - "state": "enabled", - "database_name": "test_data", - "queryable_fields_names": fields as [Json], - "asymmetric_tables": asymmetricTables as [Json] - ] - ] - _ = try app.services[serviceId].default_rule.post([ - "roles": [[ - "name": "all", - "apply_when": [String: Json](), - "document_filters": [ - "read": true, - "write": true - ], - "write": true, - "read": true, - "insert": true, - "delete": true - ]] - ]).get() - default: - fatalError() - } - _ = try app.services[serviceId].config.patch(serviceConfig).get() - - app.functions.post(on: group, [ - "name": "sum", - "private": false, - "can_evaluate": [:], - "source": """ - exports = function(...args) { - return parseInt(args.reduce((a,b) => a + b, 0)); - }; - """ - ], failOnError) - - app.functions.post(on: group, [ - "name": "updateUserData", - "private": false, - "can_evaluate": [:], - "source": """ - exports = async function(data) { - const user = context.user; - const mongodb = context.services.get("mongodb1"); - const userDataCollection = mongodb.db("test_data").collection("UserData"); - await userDataCollection.updateOne( - { "user_id": user.id }, - { "$set": data }, - { "upsert": true } - ); - return true; - }; - """ - ], failOnError) - - app.customUserData.patch(on: group, [ - "mongo_service_id": serviceId, - "enabled": true, - "database_name": "test_data", - "collection_name": "UserData", - "user_id_field": "user_id" - ], failOnError) - - _ = app.secrets.post([ - "name": "gcm", - "value": "gcm" - ]) - - app.services.post(on: group, [ - "name": "gcm", - "type": "gcm", - "config": [ - "senderId": "gcm" - ], - "secret_config": [ - "apiKey": "gcm" - ], - "version": 1 - ], failOnError) - - // Disable exponential backoff when the server isn't ready for us to connect - // TODO: this is returning 403 with current server. Reenable once it's fixed - see https://mongodb.slack.com/archives/C0121N9LJ14/p1713885482349059 - // session.privateApps[appId].settings.patch(on: group, [ - // "sync": ["disable_client_error_backoff": true] - // ], failOnError) - - try group.throwingWait(timeout: .now() + 5.0) - - // Wait for initial sync to complete as connecting before that has a lot of problems - try waitForSync(appServerId: appId, expectedCount: schema.count - asymmetricTables.count) - - if !persistent { - appIds.withLock { $0.append(appId) } - } - - return clientAppId - } - - @objc public func createApp(fields: [String], types: [ObjectBase.Type], persistent: Bool = false) throws -> AppId { - return try createApp(syncMode: .flx(fields), types: types, persistent: persistent) - } - - @objc public func createApp(partitionKeyType: String = "string", types: [ObjectBase.Type], persistent: Bool = false) throws -> AppId { - return try createApp(syncMode: .pbs(partitionKeyType), types: types, persistent: persistent) - } - - @objc public func createNonSyncApp() throws -> AppId { - return try createApp(syncMode: .none, types: [], persistent: false) - } - - /// Delete all Apps created without `persistent: true` - @objc func deleteApps() throws { - for appId in appIds.value { - let app = try XCTUnwrap(session).apps[appId] - _ = try app.delete().get() - } - appIds.value = [] - } - - @objc func deleteApp(_ appId: String) throws { - let serverAppId = try retrieveAppServerId(appId) - let app = try XCTUnwrap(session).apps[serverAppId] - _ = try app.delete().get() - } - - // Retrieve Atlas App Services AppId with ClientAppId using the Admin API - public func retrieveAppServerId(_ clientAppId: String) throws -> String { - let session = try XCTUnwrap(session) - let appsListInfo = try session.apps.get().get() - guard let appsList = appsListInfo as? [[String: Any]] else { - throw URLError(.badServerResponse) - } - - let app = appsList.first(where: { - guard let clientId = $0["client_app_id"] as? String else { - return false - } - - return clientId == clientAppId - }) - - guard let appId = app?["_id"] as? String else { - throw URLError(.badServerResponse) - } - return appId - } - - public func retrieveSyncServiceId(appServerId: String) throws -> String { - let session = try XCTUnwrap(session) - let app = session.apps[appServerId] - // Get all services - guard let syncServices = try app.services.get().get() as? [[String: Any]] else { - throw URLError(.unknown) - } - // Find sync service - guard let syncService = syncServices.first(where: { - $0["name"] as? String == "mongodb1" - }) else { - throw URLError(.unknown) - } - // Return sync service id - guard let serviceId = syncService["_id"] as? String else { throw URLError(.unknown) } - return serviceId - } - - public func getSyncServiceConfiguration(appServerId: String, syncServiceId: String) throws -> [String: Any]? { - let app = session.apps[appServerId] - do { - return try app.services[syncServiceId].config.get().get() as? [String: Any] - } catch { - throw URLError(.unknown) - } - } - - public func isSyncEnabled(appServerId: String, syncServiceId: String) throws -> Bool { - let session = try XCTUnwrap(session) - let app = session.apps[appServerId] - let response = try app.services[syncServiceId].config.get().get() as? [String: Any] - guard let syncInfo = response?["flexible_sync"] as? [String: Any] else { - return false - } - return syncInfo["state"] as? String == "enabled" - } - - public func isDevModeEnabled(appServerId: String, syncServiceId: String) throws -> Bool { - let app = session.apps[appServerId] - let res = try app.sync.config.get().get() as! [String: Any] - return res["development_mode_enabled"] as? Bool ?? false - } - - public func enableDevMode(appServerId: String, syncServiceId: String, syncServiceConfiguration: [String: Any]) -> Result { - let app = session.apps[appServerId] - return app.sync.config.put(["development_mode_enabled": true]) - } - - public func disableSync(appServerId: String, syncServiceId: String) throws -> Any? { - let app = session.apps[appServerId] - return app.services[syncServiceId].config.patch(["flexible_sync": ["state": ""]]) - } - - public func enableSync(appServerId: String, syncServiceId: String, syncServiceConfiguration: [String: Any]) -> Result { - var syncConfig = syncServiceConfiguration - let app = session.apps[appServerId] - guard var syncInfo = syncConfig["flexible_sync"] as? [String: Any] else { - return .failure(URLError(.unknown)) - } - syncInfo["state"] = "enabled" - syncConfig["flexible_sync"] = syncInfo - return app.services[syncServiceId].config.patch(syncConfig) - } - - public func patchRecoveryMode(flexibleSync: Bool, disable: Bool, _ appServerId: String, - _ syncServiceId: String, _ syncServiceConfiguration: [String: Any]) -> Result { - let configOption = flexibleSync ? "flexible_sync" : "sync" - let app = session.apps[appServerId] - var syncConfig = syncServiceConfiguration - return app.services[syncServiceId].config.get() - .map { response in - guard let config = response as? [String: Json] else { return false } - guard let syncInfo = config[configOption] as? [String: Any] else { return false } - return syncInfo["is_recovery_mode_disabled"] as? Bool ?? false - } - .flatMap { (isDisabled: Bool) in - if isDisabled == disable { - return .success(syncConfig) - } - - guard var syncInfo = syncConfig[configOption] as? [String: Any] else { - return .failure(URLError(.unknown)) - } - - syncInfo["is_recovery_mode_disabled"] = disable - syncConfig[configOption] = syncInfo - return app.services[syncServiceId].config.patch(syncConfig) - } - } - - public func retrieveUser(_ appId: String, userId: String) -> Result { - guard let appServerId = try? RealmServer.shared.retrieveAppServerId(appId) else { - return .failure(URLError(.unknown)) - } - return session.apps[appServerId].users[userId].get() - } - - // Remove User from Atlas App Services using the Admin API - public func removeUserForApp(_ appId: String, userId: String) -> Result { - guard let appServerId = try? RealmServer.shared.retrieveAppServerId(appId) else { - return .failure(URLError(.unknown)) - } - return session.apps[appServerId].users[userId].delete() - } - - public func revokeUserSessions(_ appId: String, userId: String) -> Result { - guard let appServerId = try? RealmServer.shared.retrieveAppServerId(appId) else { - return .failure(URLError(.unknown)) - } - return session.apps[appServerId].users[userId].logout.put([:]) - } - - public func retrieveSchemaProperties(_ appId: String, className: String, - _ completion: @escaping (Result<[String], Error>) -> Void) { - let appServerId = try! RealmServer.shared.retrieveAppServerId(appId) - - guard let schemasList = try? session.apps[appServerId].schemas.get().get(), - let schemas = schemasList as? [[String: Any]], - let schemaSelected = schemas.first(where: { ($0["metadata"] as? [String: String])?["collection"] == className }) else { - completion(.failure(URLError(.unknown))) - return - } - - guard let schema = try? session.apps[appServerId].schemas[schemaSelected["_id"] as! String].get().get(), - let schemaProperties = ((schema as? [String: Any])?["schema"] as? [String: Any])?["properties"] as? [String: Any] else { - completion(.failure(URLError(.unknown))) - return - } - - completion(.success(schemaProperties.compactMap { $0.key })) - } - - public func triggerClientReset(_ appId: String, _ realm: Realm) throws { - let session = try XCTUnwrap(session) - let appServerId = try retrieveAppServerId(appId) - let ident = RLMGetClientFileIdent(ObjectiveCSupport.convert(object: realm)) - XCTAssertNotEqual(ident, 0) - _ = try session.apps[appServerId].sync.forceReset.put(["file_ident": ident]).get() - } - - public func waitForSync(appId: String) throws { - try waitForSync(appServerId: retrieveAppServerId(appId), expectedCount: 1) - } - - public func waitForSync(appServerId: String, expectedCount: Int) throws { - let session = try XCTUnwrap(session) - let start = Date() - while true { - let complete = try session.apps[appServerId].sync.progress.get() - .map { resp in - guard let resp = resp as? Dictionary else { return false } - guard let progress = resp["progress"] else { return false } - guard let progress = progress as? Dictionary else { return false } - let values = progress.compactMapValues { $0 as? Dictionary } - let complete = values.allSatisfy { $0.value["complete"] as? Bool ?? false } - return complete && progress.count >= expectedCount - } - .get() - if complete { - break - } - if -start.timeIntervalSinceNow > 60.0 { - throw "Waiting for sync to complete timed out" - } - Thread.sleep(forTimeInterval: 0.1) - } - } -} - -@Sendable private func failOnError(_ result: Result) { - if case .failure(let error) = result { - XCTFail(error.localizedDescription) - } -} - -extension String: Error { -} - -#endif diff --git a/Realm/ObjectServerTests/SwiftAsymmetricSyncServerTests.swift b/Realm/ObjectServerTests/SwiftAsymmetricSyncServerTests.swift deleted file mode 100644 index 8313ec3b28..0000000000 --- a/Realm/ObjectServerTests/SwiftAsymmetricSyncServerTests.swift +++ /dev/null @@ -1,300 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#if os(macOS) -import Realm -import Realm.Private -import RealmSwift -import XCTest - -#if canImport(RealmTestSupport) -import RealmSwiftSyncTestSupport -import RealmSyncTestSupport -import RealmTestSupport -#endif - -class SwiftObjectAsymmetric: AsymmetricObject { - @Persisted(primaryKey: true) var _id: ObjectId - @Persisted var string: String - @Persisted var int: Int - @Persisted var bool: Bool - @Persisted var double: Double = 1.1 - @Persisted var long: Int64 = 1 - @Persisted var decimal: Decimal128 = Decimal128(1) - @Persisted var uuid: UUID = UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")! - @Persisted var objectId: ObjectId = ObjectId("6058f12682b2fbb1f334ef1d") - - @Persisted var intList: List - @Persisted var boolList: List - @Persisted var stringList: List - @Persisted var dataList: List - @Persisted var dateList: List - @Persisted var doubleList: List - @Persisted var objectIdList: List - @Persisted var decimalList: List - @Persisted var uuidList: List - @Persisted var anyList: List - - @Persisted var intSet: MutableSet - @Persisted var stringSet: MutableSet - @Persisted var dataSet: MutableSet - @Persisted var dateSet: MutableSet - @Persisted var doubleSet: MutableSet - @Persisted var objectIdSet: MutableSet - @Persisted var decimalSet: MutableSet - @Persisted var uuidSet: MutableSet - @Persisted var anySet: MutableSet - - @Persisted var otherIntSet: MutableSet - @Persisted var otherStringSet: MutableSet - @Persisted var otherDataSet: MutableSet - @Persisted var otherDateSet: MutableSet - @Persisted var otherDoubleSet: MutableSet - @Persisted var otherObjectIdSet: MutableSet - @Persisted var otherDecimalSet: MutableSet - @Persisted var otherUuidSet: MutableSet - @Persisted var otherAnySet: MutableSet - - @Persisted var intMap: Map - @Persisted var stringMap: Map - @Persisted var dataMap: Map - @Persisted var dateMap: Map - @Persisted var doubleMap: Map - @Persisted var objectIdMap: Map - @Persisted var decimalMap: Map - @Persisted var uuidMap: Map - @Persisted var anyMap: Map - - override class func _realmIgnoreClass() -> Bool { - return true - } - - convenience init(string: String, int: Int, bool: Bool) { - self.init() - self.string = string - self.int = int - self.bool = bool - } -} - -class HugeObjectAsymmetric: AsymmetricObject { - @Persisted(primaryKey: true) public var _id: ObjectId - @Persisted public var data: Data? - - override class func _realmIgnoreClass() -> Bool { - return true - } -} - -let customColumnAsymmetricPropertiesMapping: [String: String] = ["id": "_id", - "boolCol": "custom_boolCol", - "intCol": "custom_intCol", - "doubleCol": "custom_doubleCol", - "stringCol": "custom_stringCol"] - -class SwiftCustomColumnAsymmetricObject: AsymmetricObject { - @Persisted(primaryKey: true) public var id: ObjectId - @Persisted public var boolCol: Bool = true - @Persisted public var intCol: Int = 1 - @Persisted public var doubleCol: Double = 1.1 - @Persisted public var stringCol: String = "string" - - override class func propertiesMapping() -> [String: String] { - customColumnAsymmetricPropertiesMapping - } - - override class func _realmIgnoreClass() -> Bool { - return true - } -} - -@available(macOS 13, *) -class SwiftAsymmetricSyncTests: SwiftSyncTestCase { - override class var defaultTestSuite: XCTestSuite { - // async/await is currently incompatible with thread sanitizer and will - // produce many false positives - // https://bugs.swift.org/browse/SR-15444 - if RLMThreadSanitizerEnabled() { - return XCTestSuite(name: "\(type(of: self))") - } - return super.defaultTestSuite - } - - nonisolated static let objectTypes = [ - HugeObjectAsymmetric.self, - SwiftCustomColumnAsymmetricObject.self, - SwiftObjectAsymmetric.self, - ] - - override func createApp() throws -> String { - try RealmServer.shared.createApp(fields: [], types: SwiftAsymmetricSyncTests.objectTypes, persistent: true) - } - - override var objectTypes: [ObjectBase.Type] { - SwiftAsymmetricSyncTests.objectTypes - } - - override func configuration(user: User) -> Realm.Configuration { - user.flexibleSyncConfiguration() - } - - @MainActor - func testAsymmetricObjectSchema() throws { - let realm = try openRealm() - XCTAssertTrue(realm.schema.objectSchema[0].isAsymmetric) - } - - func testOpenLocalRealmWithAsymmetricObjectError() throws { - let configuration = Realm.Configuration(objectTypes: [SwiftObjectAsymmetric.self]) - XCTAssertThrowsError(try Realm(configuration: configuration)) { error in - XCTAssertEqual(error.localizedDescription, "Schema validation failed due to the following errors:\n- Asymmetric table \'SwiftObjectAsymmetric\' not allowed in a local Realm") - } - } - - func testOpenPBSConfigurationRealmWithAsymmetricObjectError() throws { - let user = createUser() - var configuration = user.configuration(partitionValue: #function) - configuration.objectTypes = [SwiftObjectAsymmetric.self] - - XCTAssertThrowsError(try Realm(configuration: configuration)) { error in - XCTAssert(error.localizedDescription.contains("Asymmetric table 'SwiftObjectAsymmetric' not allowed in partition based sync")) - } - } - - func testCustomColumnNameAsymmetricObjectSchema() { - let modernCustomObjectSchema = SwiftCustomColumnAsymmetricObject().objectSchema - for property in modernCustomObjectSchema.properties { - XCTAssertEqual(customColumnAsymmetricPropertiesMapping[property.name], property.columnName) - } - } -} - -@available(macOS 13.0, *) -extension SwiftAsymmetricSyncTests { - @MainActor - func setupCollection(_ type: ObjectBase.Type) async throws -> MongoCollection { - let user = try await app.login(credentials: .anonymous) - let collection = user.collection(for: type, app: app) - if try await collection.count(filter: [:]) > 0 { - removeAllFromCollection(collection) - } - return collection - } - - @MainActor - func checkCountInMongo(_ expectedCount: Int, type: ObjectBase.Type) async throws { - let waitStart = Date() - let user = try await app.login(credentials: .anonymous) - let collection = user.collection(for: type, app: app) - while try await collection.count(filter: [:]) < expectedCount && waitStart.timeIntervalSinceNow > -600.0 { - try await Task.sleep(for: .seconds(5)) - } - - XCTAssertEqual(collection.count(filter: [:]).await(self), expectedCount) - } - - @MainActor - func testCreateAsymmetricObject() async throws { - _ = try await setupCollection(SwiftObjectAsymmetric.self) - let realm = try await openRealm() - - try realm.write { - for i in 1...15 { - realm.create(SwiftObjectAsymmetric.self, - value: SwiftObjectAsymmetric(string: "name_\(#function)_\(i)", - int: i, bool: Bool.random())) - } - } - waitForUploads(for: realm) - - try await checkCountInMongo(15, type: SwiftObjectAsymmetric.self) - } - - @MainActor - func testPropertyTypesAsymmetricObject() async throws { - let collection = try await setupCollection(SwiftObjectAsymmetric.self) - let realm = try await openRealm() - - try realm.write { - realm.create(SwiftObjectAsymmetric.self, - value: SwiftObjectAsymmetric(string: "name_\(#function)", - int: 15, bool: true)) - } - waitForUploads(for: realm) - - try await checkCountInMongo(1, type: SwiftObjectAsymmetric.self) - - let document = try await collection.find(filter: [:])[0] - XCTAssertEqual(document["string"]??.stringValue, "name_\(#function)") - XCTAssertEqual(document["int"]??.int64Value, 15) - XCTAssertEqual(document["bool"]??.boolValue, true) - XCTAssertEqual(document["double"]??.doubleValue, 1.1) - XCTAssertEqual(document["long"]??.int64Value, 1) - XCTAssertEqual(document["decimal"]??.decimal128Value, Decimal128(1)) - XCTAssertEqual(document["uuid"]??.uuidValue, UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")!) - XCTAssertEqual(document["objectId"]??.objectIdValue, ObjectId("6058f12682b2fbb1f334ef1d")) - } - - @MainActor - func testCreateHugeAsymmetricObject() async throws { - _ = try await setupCollection(HugeObjectAsymmetric.self) - let realm = try await openRealm() - - // Create Asymmetric Objects - try realm.write { - for _ in 0..<2 { - realm.create(HugeObjectAsymmetric.self, value: ["data": Data(repeating: 16, count: 1000000)]) - } - } - waitForUploads(for: realm) - - try await checkCountInMongo(2, type: HugeObjectAsymmetric.self) - } - - @MainActor - func testCreateCustomAsymmetricObject() async throws { - let collection = try await setupCollection(SwiftCustomColumnAsymmetricObject.self) - let realm = try await openRealm() - - let objectId = ObjectId.generate() - let valuesDictionary: [String: Any] = ["id": objectId, - "boolCol": false, - "intCol": 1234, - "doubleCol": 1234.1234, - "stringCol": "$%&/("] - - // Create Asymmetric Objects - try realm.write { - realm.create(SwiftCustomColumnAsymmetricObject.self, value: valuesDictionary) - } - waitForUploads(for: realm) - - try await checkCountInMongo(1, type: SwiftCustomColumnAsymmetricObject.self) - - let filter: Document = ["_id": .objectId(objectId)] - let document = try await collection.findOneDocument(filter: filter) - XCTAssertNotNil(document) - - XCTAssertEqual(document!["_id"], AnyBSON(objectId)) - XCTAssertEqual(document!["custom_boolCol"], AnyBSON(false)) - XCTAssertEqual(document!["custom_intCol"], AnyBSON(1234)) - XCTAssertEqual(document!["custom_doubleCol"], AnyBSON(1234.1234)) - XCTAssertEqual(document!["custom_stringCol"], AnyBSON("$%&/(")) - } -} -#endif // os(macOS) diff --git a/Realm/ObjectServerTests/SwiftCollectionSyncTests.swift b/Realm/ObjectServerTests/SwiftCollectionSyncTests.swift deleted file mode 100644 index 5ff05a1eb2..0000000000 --- a/Realm/ObjectServerTests/SwiftCollectionSyncTests.swift +++ /dev/null @@ -1,929 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import Realm -import RealmSwift -import XCTest - -#if canImport(RealmTestSupport) -import RealmSwiftSyncTestSupport -import RealmSyncTestSupport -import RealmTestSupport -#endif - -@available(macOS 13, *) -class CollectionSyncTestCase: SwiftSyncTestCase { - var readRealm: Realm! - - override var objectTypes: [ObjectBase.Type] { - [SwiftCollectionSyncObject.self, SwiftPerson.self] - } - - @MainActor - func write(_ fn: (Realm) -> Void) throws { - try super.write(fn) - waitForDownloads(for: readRealm) - } - - func assertEqual(_ left: T, _ right: T, _ line: UInt = #line) { - if let person = left as? SwiftPerson, let otherPerson = right as? SwiftPerson { - XCTAssertEqual(person.firstName, otherPerson.firstName, line: line) - } else { - XCTAssertEqual(left, right, line: line) - } - } - - @MainActor - private func roundTrip(keyPath: KeyPath>, - values: [T], partitionValue: String = #function) throws { - autoreleasepool { - readRealm = try! openRealm() - } - - checkCount(expected: 0, readRealm, SwiftCollectionSyncObject.self) - - // Create the object - try write { realm in - realm.add(SwiftCollectionSyncObject()) - } - checkCount(expected: 1, readRealm, SwiftCollectionSyncObject.self) - let object = readRealm.objects(SwiftCollectionSyncObject.self).first! - let collection = object[keyPath: keyPath] - XCTAssertEqual(collection.count, 0) - - // Populate the collection - try write { realm in - let object = realm.objects(SwiftCollectionSyncObject.self).first! - object[keyPath: keyPath].append(objectsIn: values + values) - } - checkCount(expected: 1, readRealm, SwiftCollectionSyncObject.self) - XCTAssertEqual(collection.count, values.count*2) - for (el, ex) in zip(collection, values + values) { - assertEqual(el, ex) - } - - // Remove the last three objects from the collection - try write { realm in - let object = realm.objects(SwiftCollectionSyncObject.self).first! - object[keyPath: keyPath].removeSubrange(3...5) - } - XCTAssertEqual(collection.count, values.count) - - // Modify the first element - try write { realm in - let object = realm.objects(SwiftCollectionSyncObject.self).first! - let collection = object[keyPath: keyPath] - if T.self is SwiftPerson.Type { - (collection as! List)[0].firstName - = (values as! [SwiftPerson])[1].firstName - } else { - collection[0] = values[1] - } - } - assertEqual(collection[0], values[1]) - - try write { realm in - realm.deleteAll() - } - - readRealm = nil - } - - @MainActor - func testLists() throws { - try roundTrip(keyPath: \.intList, values: [1, 2, 3]) - try roundTrip(keyPath: \.boolList, values: [true, false, false]) - try roundTrip(keyPath: \.stringList, values: ["Hey", "Hi", "Bye"]) - try roundTrip(keyPath: \.dataList, values: [Data(repeating: 0, count: 64), - Data(repeating: 1, count: 64), - Data(repeating: 2, count: 64)]) - try roundTrip(keyPath: \.dateList, values: [Date(timeIntervalSince1970: 10000000), - Date(timeIntervalSince1970: 20000000), - Date(timeIntervalSince1970: 30000000)]) - try roundTrip(keyPath: \.doubleList, values: [123.456, 234.456, 567.333]) - try roundTrip(keyPath: \.objectIdList, values: [.init("6058f12b957ba06156586a7c"), - .init("6058f12682b2fbb1f334ef1d"), - .init("6058f12d42e5a393e67538d0")]) - try roundTrip(keyPath: \.decimalList, values: [123.345, 213.345, 321.345]) - try roundTrip(keyPath: \.uuidList, values: [UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd")!, - UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fe")!, - UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90ff")!]) - try roundTrip(keyPath: \.objectList, values: [SwiftPerson(firstName: "Peter", lastName: "Parker"), - SwiftPerson(firstName: "Bruce", lastName: "Wayne"), - SwiftPerson(firstName: "Stephen", lastName: "Strange")]) - try roundTrip(keyPath: \.anyList, values: [.int(12345), .string("Hello"), .none]) - } - - private typealias MutableSetKeyPath = KeyPath> - private typealias MutableSetKeyValues = (keyPath: MutableSetKeyPath, values: [T]) - - @MainActor - private func roundTrip(set: MutableSetKeyValues, - otherSet: MutableSetKeyValues, - partitionValue: String = #function) throws { - autoreleasepool { - readRealm = try! openRealm() - } - checkCount(expected: 0, readRealm, SwiftCollectionSyncObject.self) - - // Create the object - try write { realm in - realm.add(SwiftCollectionSyncObject()) - } - checkCount(expected: 1, readRealm, SwiftCollectionSyncObject.self) - let object = readRealm.objects(SwiftCollectionSyncObject.self).first! - let collection = object[keyPath: set.keyPath] - let otherCollection = object[keyPath: otherSet.keyPath] - XCTAssertEqual(collection.count, 0) - XCTAssertEqual(otherCollection.count, 0) - - // Populate the collection - try write { realm in - let object = realm.objects(SwiftCollectionSyncObject.self).first! - object[keyPath: set.keyPath].insert(objectsIn: set.values) - object[keyPath: otherSet.keyPath].insert(objectsIn: otherSet.values) - } - checkCount(expected: 1, readRealm, SwiftCollectionSyncObject.self) - XCTAssertEqual(collection.count, set.values.count) - XCTAssertEqual(otherCollection.count, otherSet.values.count) - - // Intersect the values - try write { realm in - let object = realm.objects(SwiftCollectionSyncObject.self).first! - let collection = object[keyPath: set.keyPath] - let otherCollection = object[keyPath: otherSet.keyPath] - if T.self is SwiftPerson.Type { - // formIntersection won't work with unique Objects - collection.removeAll() - collection.insert(realm.create(SwiftPerson.self, value: set.values[0], update: .all) as! T) - } else { - collection.formIntersection(otherCollection) - } - } - - if !(T.self is SwiftPerson.Type) { - XCTAssertTrue(collection.intersects(object[keyPath: otherSet.keyPath])) - XCTAssertEqual(collection.count, 1) - // The intersection should have assigned the last value from `values` - XCTAssertTrue(collection.contains(set.values.last!)) - } - - // Delete the objects from the sets - try write { realm in - let object = realm.objects(SwiftCollectionSyncObject.self).first! - object[keyPath: set.keyPath].removeAll() - object[keyPath: otherSet.keyPath].removeAll() - } - XCTAssertEqual(collection.count, 0) - XCTAssertEqual(otherCollection.count, 0) - - try write { realm in - realm.deleteAll() - } - readRealm = nil - } - - @MainActor - func testSets() throws { - try roundTrip(set: (\.intSet, [1, 2, 3]), otherSet: (\.otherIntSet, [3, 4, 5])) - try roundTrip(set: (\.stringSet, ["Who", "What", "When"]), - otherSet: (\.otherStringSet, ["When", "Strings", "Collide"])) - try roundTrip(set: (\.dataSet, [Data(repeating: 1, count: 64), - Data(repeating: 2, count: 64), - Data(repeating: 3, count: 64)]), - otherSet: (\.otherDataSet, [Data(repeating: 3, count: 64), - Data(repeating: 4, count: 64), - Data(repeating: 5, count: 64)])) - try roundTrip(set: (\.dateSet, [Date(timeIntervalSince1970: 10000000), - Date(timeIntervalSince1970: 20000000), - Date(timeIntervalSince1970: 30000000)]), - otherSet: (\.otherDateSet, [Date(timeIntervalSince1970: 30000000), - Date(timeIntervalSince1970: 40000000), - Date(timeIntervalSince1970: 50000000)])) - try roundTrip(set: (\.doubleSet, [123.456, 345.456, 789.456]), - otherSet: (\.otherDoubleSet, [789.456, - 888.456, - 987.456])) - try roundTrip(set: (\.objectIdSet, [.init("6058f12b957ba06156586a7c"), - .init("6058f12682b2fbb1f334ef1d"), - .init("6058f12d42e5a393e67538d0")]), - otherSet: (\.otherObjectIdSet, [.init("6058f12d42e5a393e67538d0"), - .init("6058f12682b2fbb1f334ef1f"), - .init("6058f12d42e5a393e67538d1")])) - try roundTrip(set: (\.decimalSet, [123.345, - 213.345, - 321.345]), - otherSet: (\.otherDecimalSet, [321.345, - 333.345, - 444.345])) - try roundTrip(set: (\.uuidSet, [UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd")!, - UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fe")!, - UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90ff")!]), - otherSet: (\.otherUuidSet, [UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90ff")!, - UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90ae")!, - UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90bf")!])) - try roundTrip(set: (\.objectSet, [SwiftPerson(firstName: "Peter", lastName: "Parker"), - SwiftPerson(firstName: "Bruce", lastName: "Wayne"), - SwiftPerson(firstName: "Stephen", lastName: "Strange")]), - otherSet: (\.otherObjectSet, [SwiftPerson(firstName: "Stephen", lastName: "Strange"), - SwiftPerson(firstName: "Tony", lastName: "Stark"), - SwiftPerson(firstName: "Clark", lastName: "Kent")])) - try roundTrip(set: (\.anySet, [.int(12345), .none, .string("Hello")]), - otherSet: (\.otherAnySet, [.string("Hello"), .double(765.6543), .objectId(.generate())])) - } - - private typealias MapKeyPath = KeyPath> - - @MainActor - private func roundTrip(keyPath: MapKeyPath, values: [T], - partitionValue: String = #function) throws { - autoreleasepool { - readRealm = try! openRealm() - } - - checkCount(expected: 0, readRealm, SwiftCollectionSyncObject.self) - - // Create the object - try write { realm in - realm.add(SwiftCollectionSyncObject()) - } - checkCount(expected: 1, readRealm, SwiftCollectionSyncObject.self) - let object = readRealm.objects(SwiftCollectionSyncObject.self).first! - let collection = object[keyPath: keyPath] - XCTAssertEqual(collection.count, 0) - - // Populate the collection - try write { realm in - let object = realm.objects(SwiftCollectionSyncObject.self).first! - let collection = object[keyPath: keyPath] - for (i, value) in values.enumerated() { - collection["\(i)"] = value - } - } - checkCount(expected: 1, readRealm, SwiftCollectionSyncObject.self) - XCTAssertEqual(collection.count, values.count) - for (i, value) in values.enumerated() { - assertEqual(collection["\(i)"]!, value) - } - - // Remove the last three objects from the collection - try write { realm in - let object = realm.objects(SwiftCollectionSyncObject.self).first! - let collection = object[keyPath: keyPath] - for i in 0..<3 { - collection.removeObject(for: "\(i)") - } - } - XCTAssertEqual(collection.count, values.count - 3) - - // Modify the first element - try write { realm in - let object = realm.objects(SwiftCollectionSyncObject.self).first! - let collection = object[keyPath: keyPath] - collection["3"] = collection["4"] - } - assertEqual(collection["3"]!, values[4]) - - try write { realm in - realm.deleteAll() - } - readRealm = nil - } - - @MainActor - func testMaps() throws { - try roundTrip(keyPath: \.intMap, values: [1, 2, 3, 4, 5]) - try roundTrip(keyPath: \.stringMap, values: ["Who", "What", "When", "Strings", "Collide"]) - try roundTrip(keyPath: \.dataMap, values: [Data(repeating: 1, count: 64), - Data(repeating: 2, count: 64), - Data(repeating: 3, count: 64), - Data(repeating: 4, count: 64), - Data(repeating: 5, count: 64)]) - try roundTrip(keyPath: \.dateMap, values: [Date(timeIntervalSince1970: 10000000), - Date(timeIntervalSince1970: 20000000), - Date(timeIntervalSince1970: 30000000), - Date(timeIntervalSince1970: 40000000), - Date(timeIntervalSince1970: 50000000)]) - try roundTrip(keyPath: \.doubleMap, values: [123.456, 345.456, 789.456, 888.456, 987.456]) - try roundTrip(keyPath: \.objectIdMap, values: [ObjectId("6058f12b957ba06156586a7c"), - ObjectId("6058f12682b2fbb1f334ef1d"), - ObjectId("6058f12d42e5a393e67538d0"), - ObjectId("6058f12682b2fbb1f334ef1f"), - ObjectId("6058f12d42e5a393e67538d1")]) - try roundTrip(keyPath: \.decimalMap, values: [Decimal128(123.345), - Decimal128(213.345), - Decimal128(321.345), - Decimal128(333.345), - Decimal128(444.345)]) - try roundTrip(keyPath: \.uuidMap, values: [UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd")!, - UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fe")!, - UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90ff")!, - UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90ae")!, - UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90bf")!]) - // FIXME: We need to add a test where a value in a map of objects is `null`. currently the server - // is throwing a bad changeset error when that happens. - try roundTrip(keyPath: \.objectMap, values: [SwiftPerson(firstName: "Peter", lastName: "Parker"), - SwiftPerson(firstName: "Bruce", lastName: "Wayne"), - SwiftPerson(firstName: "Stephen", lastName: "Strange"), - SwiftPerson(firstName: "Tony", lastName: "Stark"), - SwiftPerson(firstName: "Clark", lastName: "Kent")]) - try roundTrip(keyPath: \.anyMap, values: [.int(12345), .none, .string("Hello"), .double(765.6543), - .objectId(ObjectId("507f1f77bcf86cd799439011"))]) - } -} - -@available(macOS 13, *) -class AsyncAnyRealmValueSyncTest: SwiftSyncTestCase { - override class var defaultTestSuite: XCTestSuite { - // async/await is currently incompatible with thread sanitizer and will - // produce many false positives - // https://bugs.swift.org/browse/SR-15444 - if RLMThreadSanitizerEnabled() { - return XCTestSuite(name: "\(type(of: self))") - } - return super.defaultTestSuite - } - - override func configuration(user: User) -> Realm.Configuration { - user.flexibleSyncConfiguration(initialSubscriptions: { - $0.append(QuerySubscription()) - $0.append(QuerySubscription()) - }) - } - - override var objectTypes: [ObjectBase.Type] { - [SwiftTypesSyncObject.self, SwiftPerson.self] - } - - override func createApp() throws -> String { - try createFlexibleSyncApp() - } - - private var sampleData: Array { - let so = SwiftPerson() - so.firstName = name - let oid = ObjectId.generate() - let uuid = UUID() - let date = Date() - return [ - .string("hello"), - .bool(false), - .int(234), - .double(12345.678901), - .float(12.34), - .data(Data("a".utf8)), - .date(date), - .object(so), - .objectId(oid), - .uuid(uuid), - .decimal128(Decimal128(number: 567)) - ] - } - - private func assertValue(_ value: AnyRealmValue, _ expectedValue: AnyRealmValue) { - switch value { - case .object: - XCTAssertEqual(value.object(SwiftPerson.self)?.firstName, expectedValue.object(SwiftPerson.self)?.firstName) - case .double: - // All float values are converted to doubles in the server. - if let floatValue = expectedValue.floatValue { - XCTAssertEqual(value, .double(Double(floatValue))) - } else { - XCTAssertEqual(value, expectedValue) - } - case .date: - // Date is not exact when synced - XCTAssertTrue(Calendar.current.isDate(value.dateValue!, equalTo: expectedValue.dateValue!, toGranularity: .second)) - default: - XCTAssertEqual(value, expectedValue) - } - } - - private func assertListEqual(_ object: SwiftTypesSyncObject, _ index: Int, _ expectedValue: AnyRealmValue) { - let value: AnyRealmValue? = object.anyCol.listValue?[index] - assertValue(value!, expectedValue) - } - - private func assertDictionaryEqual(_ object: SwiftTypesSyncObject, _ key: String, _ expectedValue: AnyRealmValue) { - let value: AnyRealmValue? = object.anyCol.dictionaryValue?[key] - assertValue(value!, expectedValue) - } - - @MainActor func testSyncAnyRealmValue() async throws { - let list = sampleData - try await write { realm in - for (index, expectedValue) in list.enumerated() { - let object = SwiftTypesSyncObject() - object.anyCol = expectedValue - object.stringCol = "\(self.name)_\(index)" - realm.add(object) - } - } - - let realm = try await openRealm() - for (index, expectedValue) in list.enumerated() { - let results = realm.objects(SwiftTypesSyncObject.self).where { $0.stringCol == "\(self.name)_\(index)" } - assertValue(results.first!.anyCol, expectedValue) - } - } - - @MainActor func testSyncMixedArray() async throws { - let list = sampleData - try await write { realm in - let object = SwiftTypesSyncObject() - object.anyCol = AnyRealmValue.fromArray(list) - object.stringCol = self.name - realm.add(object) - } - - let realm = try await openRealm() - let results = realm.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - XCTAssertEqual(results.count, 1) - - let obj = results.first! - for (index, value) in list.enumerated() { - assertListEqual(obj, index, value) - } - } - - @MainActor func testSyncMixedDictionary() async throws { - let dictionary = Dictionary(uniqueKeysWithValues: sampleData.enumerated().map { ("\($0)", $1) }) - try await write { realm in - let object = SwiftTypesSyncObject() - object.anyCol = AnyRealmValue.fromDictionary(dictionary) - object.stringCol = self.name - realm.add(object) - } - - let realm = try await openRealm() - let results = realm.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - XCTAssertEqual(results.count, 1) - - let obj = results.first! - for (key, value) in dictionary { - assertDictionaryEqual(obj, key, value) - } - } - - @MainActor func testSyncMixedNestedArray() async throws { - let so = SwiftPerson() - so.firstName = "Doe" - let subArray3: AnyRealmValue = AnyRealmValue.fromArray([ .object(so), .double(123.456) ]) - let subArray2: AnyRealmValue = AnyRealmValue.fromArray([ subArray3 ]) - let subArray1: AnyRealmValue = AnyRealmValue.fromArray([ subArray2 ]) - let array: Array = [ - subArray1, .bool(false) - ] - try await write { realm in - let object = SwiftTypesSyncObject() - object.anyCol = AnyRealmValue.fromArray(array) - object.stringCol = self.name - realm.add(object) - } - - let realm = try await openRealm() - let results = try await realm.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name }.subscribe() - XCTAssertEqual(results.count, 1) - - let listValue = results.first!.anyCol.listValue - XCTAssertEqual(listValue?[1], .bool(false)) - XCTAssertEqual(listValue?[0].listValue?[0].listValue?[0].listValue?[1], .double(123.456)) - - XCTAssertEqual(listValue?[0].listValue?[0].listValue?[0].listValue?[0].object(SwiftPerson.self)?.firstName, so.firstName) - XCTAssertEqual(listValue?[1].boolValue, false) - XCTAssertEqual(listValue?[0].listValue?[0].listValue?[0].listValue?[1].doubleValue, 123.456) - } - - @MainActor func testSyncMixedNestedDictionary() async throws { - let so = SwiftPerson() - so.firstName = "Doe" - let subDict3: AnyRealmValue = AnyRealmValue.fromDictionary([ "key4": .object(so), "key5": .int(1202) ]) - let subDict2: AnyRealmValue = AnyRealmValue.fromDictionary([ "key3": subDict3 ]) - let subDict1: AnyRealmValue = AnyRealmValue.fromDictionary([ "key2": subDict2 ]) - let dictionary: Dictionary = [ - "key0": subDict1, - "key1": .bool(false) - ] - try await write { realm in - let object = SwiftTypesSyncObject() - object.anyCol = AnyRealmValue.fromDictionary(dictionary) - object.stringCol = self.name - realm.add(object) - } - - let realm = try await openRealm() - let results = realm.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - XCTAssertEqual(results.count, 1) - - let dictionaryValue = results.first!.anyCol.dictionaryValue - XCTAssertEqual(dictionaryValue?["key1"], .bool(false)) - XCTAssertEqual(dictionaryValue?["key0"]?.dictionaryValue?["key2"]?.dictionaryValue?["key3"]?.dictionaryValue?["key5"], .int(1202)) - - XCTAssertEqual(dictionaryValue?["key0"]?.dictionaryValue?["key2"]?.dictionaryValue?["key3"]?.dictionaryValue?["key4"]?.object(SwiftPerson.self)?.firstName, so.firstName) - XCTAssertEqual(dictionaryValue?["key1"]?.boolValue, false) - XCTAssertEqual(dictionaryValue?["key0"]?.dictionaryValue?["key2"]?.dictionaryValue?["key3"]?.dictionaryValue?["key5"]?.intValue, 1202) - } - - @MainActor func testSyncMixedNestedCollection() async throws { - let so = SwiftPerson() - so.firstName = "Doe" - let subArray2: AnyRealmValue = AnyRealmValue.fromArray([ .object(so), .decimal128(Decimal128(number: 457)) ]) - let subDict2: AnyRealmValue = AnyRealmValue.fromDictionary([ "key1": subArray2 ]) - let subArray3: AnyRealmValue = AnyRealmValue.fromArray([ subArray2, subDict2]) - let subDict3: AnyRealmValue = AnyRealmValue.fromDictionary([ "key2": subArray3 ]) - let subArray4: AnyRealmValue = AnyRealmValue.fromArray([ subDict3 ]) - let subDict4: AnyRealmValue = AnyRealmValue.fromDictionary([ "key3": subArray4 ]) - let subArray5: AnyRealmValue = AnyRealmValue.fromArray([ subDict4 ]) - let subDict5: AnyRealmValue = AnyRealmValue.fromDictionary([ "key4": subArray5 ]) - let subArray6: AnyRealmValue = AnyRealmValue.fromArray([ subDict5 ]) - let subDict6: AnyRealmValue = AnyRealmValue.fromDictionary([ "key5": subArray6 ]) - let dictionary: Dictionary = [ - "key0": subDict6, - ] - - try await write { realm in - let object = SwiftTypesSyncObject() - object.anyCol = AnyRealmValue.fromDictionary(dictionary) - object.stringCol = self.name - realm.add(object) - } - - let realm = try await openRealm() - let results = realm.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - XCTAssertEqual(results.count, 1) - - let obj = results.first! - let baseNested: List? = obj.anyCol.dictionaryValue?["key0"]?.dictionaryValue?["key5"]?.listValue?[0].dictionaryValue?["key4"]?.listValue?[0].dictionaryValue?["key3"]?.listValue - let nested1: String? = baseNested?[0].dictionaryValue?["key2"]?.listValue?[0].listValue?[0].object(SwiftPerson.self)?.firstName - XCTAssertEqual(nested1, so.firstName) - let nested2: AnyRealmValue? = baseNested?[0].dictionaryValue?["key2"]?.listValue?[0].listValue?[1] - XCTAssertEqual(nested2, .decimal128(Decimal128(number: 457))) - let nested3: String? = baseNested?[0].dictionaryValue?["key2"]?.listValue?[1].dictionaryValue?["key1"]?.listValue?[0].object(SwiftPerson.self)?.firstName - XCTAssertEqual(nested3, so.firstName) - let nested4: AnyRealmValue? = baseNested?[0].dictionaryValue?["key2"]?.listValue?[1].dictionaryValue?["key1"]?.listValue?[1] - XCTAssertEqual(nested4, .decimal128(Decimal128(number: 457))) - - XCTAssertEqual(nested2?.decimal128Value, Decimal128(number: 457)) - XCTAssertEqual(nested4?.decimal128Value, Decimal128(number: 457)) - } - - @MainActor func testUpdateMixedList() async throws { - let realm1 = try await openRealm() - let results1 = realm1.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - - let realm2 = try await openRealm() - let results2 = realm2.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - - // Add initial list - try realm1.write { - let object = SwiftTypesSyncObject() - object.anyCol = AnyRealmValue.fromArray([]) - object.stringCol = self.name - realm1.add(object) - } - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .download) - await realm2.asyncRefresh() - XCTAssertNotEqual(results2.first?.anyCol, .int(1)) - XCTAssertEqual(results2.first?.anyCol.listValue?.count, 0) - - let list = sampleData - // Append new value to list - for (index, value) in list.enumerated() { - try realm1.write { - results1.first?.anyCol.listValue?.append(value) - } - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .download) - await realm2.asyncRefresh() - - XCTAssertEqual(results1.first?.anyCol.listValue?.count, results2.first?.anyCol.listValue?.count) - assertListEqual(results1.first!, index, value) - assertListEqual(results2.first!, index, value) - } - - // Remove value from list - for value in list { - try realm1.write { - results1.first?.anyCol.listValue?.remove(at: 0) - } - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .download) - await realm2.asyncRefresh() - - XCTAssertEqual(results1.first?.anyCol.listValue?.count, results2.first?.anyCol.listValue?.count) - XCTAssertFalse(results1.first?.anyCol.listValue?.contains(value) ?? true) - XCTAssertFalse(results2.first?.anyCol.listValue?.contains(value) ?? true) - } - - // insert value at index - for value in list { - try realm1.write { - results1.first?.anyCol.listValue?.insert(value, at: 0) - } - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .download) - await realm2.asyncRefresh() - - XCTAssertEqual(results1.first?.anyCol.listValue?.count, results2.first?.anyCol.listValue?.count) - assertListEqual(results1.first!, 0, value) - assertListEqual(results2.first!, 0, value) - } - - try realm1.write { - results1.first?.anyCol.listValue?.removeAll() - } - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .download) - await realm2.asyncRefresh() - XCTAssertEqual(results1.first?.anyCol.listValue?.count, results2.first?.anyCol.listValue?.count) - XCTAssertEqual(results1.first?.anyCol.listValue?.count, 0) - XCTAssertEqual(results2.first?.anyCol.listValue?.count, 0) - } - - @MainActor func testUpdateMixedDictionary() async throws { - let realm1 = try await openRealm() - let results1 = realm1.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - - let realm2 = try await openRealm() - let results2 = realm2.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - - // Add initial list - try realm1.write { - let object = SwiftTypesSyncObject() - object.anyCol = AnyRealmValue.fromDictionary([:]) - object.stringCol = self.name - realm1.add(object) - } - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .download) - await realm2.asyncRefresh() - XCTAssertNotEqual(results2.first?.anyCol, .int(1)) - XCTAssertEqual(results2.first?.anyCol.dictionaryValue?.count, 0) - - let dictionary = Dictionary(uniqueKeysWithValues: sampleData.enumerated().map { ("\($0)", $1) }) - // Append new value to dictionary - for (key, value) in dictionary { - try realm1.write { - results1.first?.anyCol.dictionaryValue?[key] = value - } - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .download) - await realm2.asyncRefresh() - - XCTAssertEqual(results1.first?.anyCol.dictionaryValue?.count, results2.first?.anyCol.dictionaryValue?.count) - assertDictionaryEqual(results1.first!, key, value) - assertDictionaryEqual(results2.first!, key, value) - } - - // Remove value from list - for (key, _) in dictionary { - try realm1.write { - results1.first?.anyCol.dictionaryValue?[key] = nil - } - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .download) - await realm2.asyncRefresh() - - XCTAssertEqual(results1.first?.anyCol.dictionaryValue?.count, results2.first?.anyCol.dictionaryValue?.count) - XCTAssertFalse(results1.first?.anyCol.dictionaryValue?.contains(where: { $0.key == key }) ?? true) - XCTAssertFalse(results2.first?.anyCol.dictionaryValue?.contains(where: { $0.key == key }) ?? true) - } - - // insert value at index - for (key, value) in dictionary { - try realm1.write { - results1.first?.anyCol.dictionaryValue?.setValue(value, forKey: key) - } - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .download) - await realm2.asyncRefresh() - - XCTAssertEqual(results1.first?.anyCol.dictionaryValue?.count, results2.first?.anyCol.dictionaryValue?.count) - assertDictionaryEqual(results1.first!, key, value) - assertDictionaryEqual(results2.first!, key, value) - } - - try realm1.write { - results1.first?.anyCol.dictionaryValue?.removeAll() - } - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .download) - await realm2.asyncRefresh() - XCTAssertEqual(results1.first?.anyCol.dictionaryValue?.count, results2.first?.anyCol.dictionaryValue?.count) - XCTAssertEqual(results1.first?.anyCol.dictionaryValue?.count, 0) - XCTAssertEqual(results2.first?.anyCol.dictionaryValue?.count, 0) - } - - @MainActor func testUpdateListTwoUsers() async throws { - let realm1 = try await openRealm() - let results1 = realm1.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - - let realm2 = try await openRealm() - let results2 = realm2.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - - // Add initial list - try realm1.write { - let object = SwiftTypesSyncObject() - object.anyCol = AnyRealmValue.fromArray([.string("John")]) - object.stringCol = self.name - realm1.add(object) - } - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .download) - await realm2.asyncRefresh() - - try realm1.write { - results1.first?.anyCol.listValue?.append(.bool(false)) - } - - try realm2.write { - results2.first?.anyCol.listValue?.append(.bool(true)) - } - - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .upload) - try await realm1.syncSession?.wait(for: .download) - try await realm2.syncSession?.wait(for: .download) - - XCTAssertEqual(results1.first?.anyCol.listValue?.count, results2.first?.anyCol.listValue?.count) - (1..<3).forEach { - XCTAssertEqual(results1.first?.anyCol.listValue?[$0], results2.first?.anyCol.listValue?[$0]) - } - - try realm1.write { - results1.first?.anyCol.listValue?.insert(.int(32), at: 0) - } - - try realm2.write { - results2.first?.anyCol.listValue?.insert(.int(32), at: 0) - } - - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .upload) - try await realm1.syncSession?.wait(for: .download) - try await realm2.syncSession?.wait(for: .download) - - XCTAssertEqual(results1.first?.anyCol.listValue?.count, results2.first?.anyCol.listValue?.count) - (1..<4).forEach { - XCTAssertEqual(results1.first?.anyCol.listValue?[$0], results2.first?.anyCol.listValue?[$0]) - } - - try realm1.write { - results1.first?.anyCol.listValue?.remove(at: 0) - } - - try realm2.write { - results2.first?.anyCol.listValue?.remove(at: 0) - } - - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .upload) - try await realm1.syncSession?.wait(for: .download) - try await realm2.syncSession?.wait(for: .download) - - XCTAssertEqual(results1.first?.anyCol.listValue?.count, results2.first?.anyCol.listValue?.count) - (1..<3).forEach { - XCTAssertEqual(results1.first?.anyCol.listValue?[$0], results2.first?.anyCol.listValue?[$0]) - } - } - - @MainActor func testUpdateDictionaryTwoUsers() async throws { - let realm1 = try await openRealm() - let results1 = realm1.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - - let realm2 = try await openRealm() - let results2 = realm2.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - - // Add initial list - try realm1.write { - let object = SwiftTypesSyncObject() - object.anyCol = AnyRealmValue.fromDictionary(["\(0)": .string("John")]) - object.stringCol = self.name - realm1.add(object) - } - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .download) - - try realm1.write { - results1.first?.anyCol.dictionaryValue?["\(1)"] = .bool(false) - } - - try realm2.write { - results2.first?.anyCol.dictionaryValue?["\(1)"] = .bool(true) - } - - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .upload) - try await realm1.syncSession?.wait(for: .download) - try await realm2.syncSession?.wait(for: .download) - - XCTAssertEqual(results1.first?.anyCol.dictionaryValue?.count, results2.first?.anyCol.dictionaryValue?.count) - (1..<3).forEach { - XCTAssertEqual(results1.first?.anyCol.dictionaryValue?["\($0)"], results2.first?.anyCol.dictionaryValue?["\($0)"]) - } - - XCTAssertEqual(results1.first?.anyCol.dictionaryValue?.count, results2.first?.anyCol.dictionaryValue?.count) - (1..<2).forEach { - XCTAssertEqual(results1.first?.anyCol.dictionaryValue?["\($0)"], results2.first?.anyCol.dictionaryValue?["\($0)"]) - } - - try realm1.write { - results1.first?.anyCol.dictionaryValue?["\(0)"] = nil - } - - try realm2.write { - results2.first?.anyCol.dictionaryValue?["\(0)"] = nil - } - - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .upload) - try await realm1.syncSession?.wait(for: .download) - try await realm2.syncSession?.wait(for: .download) - - XCTAssertEqual(results1.first?.anyCol.dictionaryValue?.count, results2.first?.anyCol.dictionaryValue?.count) - (1..<2).forEach { - XCTAssertEqual(results1.first?.anyCol.dictionaryValue?["\($0)"], results2.first?.anyCol.dictionaryValue?["\($0)"]) - } - } - - @MainActor func testAssignMixedListWithSamePrimaryKey() async throws { - let realm1 = try await openRealm() - let results1 = realm1.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - - let realm2 = try await openRealm() - let results2 = realm2.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - - let primaryKey = ObjectId.generate() - - let object = SwiftTypesSyncObject(id: primaryKey) - object.stringCol = name - object.anyCol = AnyRealmValue.fromArray([.string("John")]) - try realm1.write { - realm1.add(object) - } - - let object2 = SwiftTypesSyncObject(id: primaryKey) - object2.stringCol = name - object2.anyCol = AnyRealmValue.fromArray([.string("Marie")]) - try realm2.write { - realm2.add(object2) - } - - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .upload) - try await realm1.syncSession?.wait(for: .download) - try await realm2.syncSession?.wait(for: .download) - - XCTAssertEqual(results1.first?.anyCol.listValue?.count, 1) - XCTAssertEqual(results2.first?.anyCol.listValue?.count, 1) - XCTAssertEqual(results1.first?.anyCol.listValue?[0], results2.first?.anyCol.listValue?[0]) - } - - @MainActor func testAssignMixedDictionaryWithSamePrimaryKey() async throws { - let realm1 = try await openRealm() - let results1 = realm1.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - - let realm2 = try await openRealm() - let results2 = realm2.objects(SwiftTypesSyncObject.self).where { $0.stringCol == name } - - let primaryKey = ObjectId.generate() - - let object = SwiftTypesSyncObject(id: primaryKey) - object.stringCol = name - object.anyCol = AnyRealmValue.fromDictionary(["key": .string("John")]) - try realm1.write { - realm1.add(object) - } - - let object2 = SwiftTypesSyncObject(id: primaryKey) - object2.stringCol = name - object2.anyCol = AnyRealmValue.fromDictionary(["key1": .string("Marie")]) - try realm2.write { - realm2.add(object2) - } - - try await realm1.syncSession?.wait(for: .upload) - try await realm2.syncSession?.wait(for: .upload) - try await realm1.syncSession?.wait(for: .download) - try await realm2.syncSession?.wait(for: .download) - - XCTAssertEqual(results1.first?.anyCol.dictionaryValue?.count, 1) - XCTAssertEqual(results2.first?.anyCol.dictionaryValue?.count, 1) - } -} diff --git a/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift b/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift deleted file mode 100644 index 96b52bd341..0000000000 --- a/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift +++ /dev/null @@ -1,1253 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#if os(macOS) -import RealmSwift -import XCTest -import Combine - -#if canImport(RealmTestSupport) -import RealmSwiftSyncTestSupport -import RealmSyncTestSupport -import RealmTestSupport -import SwiftUI -import RealmSwiftTestSupport -#endif - -@available(macOS 13.0, *) -class SwiftFlexibleSyncTests: SwiftSyncTestCase { - override func configuration(user: User) -> Realm.Configuration { - user.flexibleSyncConfiguration() - } - - override var objectTypes: [ObjectBase.Type] { - [SwiftPerson.self, SwiftTypesSyncObject.self, SwiftHugeSyncObject.self] - } - - override func createApp() throws -> String { - try createFlexibleSyncApp() - } - - @MainActor - func testCreateFlexibleSyncApp() throws { - let appId = try RealmServer.shared.createApp(fields: ["age"], types: [SwiftPerson.self]) - let flexibleApp = app(id: appId) - _ = try logInUser(for: basicCredentials(app: flexibleApp), app: flexibleApp) - } - - func testGetSubscriptionsWhenLocalRealm() throws { - var configuration = Realm.Configuration.defaultConfiguration - configuration.objectTypes = [SwiftPerson.self] - let realm = try Realm(configuration: configuration) - assertThrows(realm.subscriptions) - } - - // FIXME: Using `assertThrows` within a Server test will crash on tear down - func skip_testGetSubscriptionsWhenPbsRealm() throws { - let realm = try Realm(configuration: createUser().configuration(partitionValue: name)) - assertThrows(realm.subscriptions) - } - - @MainActor - func testFlexibleSyncPath() throws { - let config = try configuration() - let user = config.syncConfiguration!.user - XCTAssertTrue(config.fileURL!.path.hasSuffix("mongodb-realm/\(appId)/\(user.id)/flx_sync_default.realm")) - } - - @MainActor - func testGetSubscriptions() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 0) - } - - @MainActor - func testWriteEmptyBlock() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update {} - - XCTAssertEqual(subscriptions.count, 0) - } - - @MainActor - func testAddOneSubscriptionWithoutName() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append(QuerySubscription { - $0.age > 15 - }) - } - - XCTAssertEqual(subscriptions.count, 1) - } - - @MainActor - func testAddOneSubscriptionWithName() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append(QuerySubscription(name: "person_age") { - $0.age > 15 - }) - } - - XCTAssertEqual(subscriptions.count, 1) - } - - @MainActor - func testAddSubscriptionsInDifferentBlocks() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append(QuerySubscription(name: "person_age") { - $0.age > 15 - }) - } - subscriptions.update { - subscriptions.append(QuerySubscription { - $0.boolCol == true - }) - } - - XCTAssertEqual(subscriptions.count, 2) - } - - @MainActor - func testAddSeveralSubscriptionsWithoutName() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append( - QuerySubscription { - $0.age > 15 - }, - QuerySubscription { - $0.age > 20 - }, - QuerySubscription { - $0.age > 25 - }) - } - - XCTAssertEqual(subscriptions.count, 3) - } - - @MainActor - func testAddSeveralSubscriptionsWithName() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append( - QuerySubscription(name: "person_age_15") { - $0.age > 15 - }, - QuerySubscription(name: "person_age_20") { - $0.age > 20 - }, - QuerySubscription(name: "person_age_25") { - $0.age > 25 - }) - } - XCTAssertEqual(subscriptions.count, 3) - } - - @MainActor - func testAddMixedSubscriptions() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append(QuerySubscription(name: "person_age_15") { - $0.age > 15 - }) - subscriptions.append( - QuerySubscription { - $0.boolCol == true - }, - QuerySubscription(name: "object_date_now") { - $0.dateCol <= Date() - }) - } - XCTAssertEqual(subscriptions.count, 3) - } - - @MainActor - func testAddDuplicateSubscriptions() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append( - QuerySubscription { - $0.age > 15 - }, - QuerySubscription { - $0.age > 15 - }) - } - XCTAssertEqual(subscriptions.count, 1) - } - - @MainActor - func testAddDuplicateSubscriptionWithDifferentName() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append( - QuerySubscription(name: "person_age_1") { - $0.age > 15 - }, - QuerySubscription(name: "person_age_2") { - $0.age > 15 - }) - } - XCTAssertEqual(subscriptions.count, 2) - } - - // FIXME: Using `assertThrows` within a Server test will crash on tear down - @MainActor - func skip_testSameNamedSubscriptionThrows() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append(QuerySubscription(name: "person_age_1") { - $0.age > 15 - }) - assertThrows(subscriptions.append(QuerySubscription(name: "person_age_1") { - $0.age > 20 - })) - } - XCTAssertEqual(subscriptions.count, 1) - } - - // FIXME: Using `assertThrows` within a Server test will crash on tear down - @MainActor - func skip_testAddSubscriptionOutsideWriteThrows() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - assertThrows(subscriptions.append(QuerySubscription(name: "person_age_1") { - $0.age > 15 - })) - } - - @MainActor - func testFindSubscriptionByName() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append( - QuerySubscription(name: "person_age_15") { - $0.age > 15 - }, - QuerySubscription(name: "person_age_20") { - $0.age > 20 - }) - } - XCTAssertEqual(subscriptions.count, 2) - - let foundSubscription1 = subscriptions.first(named: "person_age_15") - XCTAssertNotNil(foundSubscription1) - XCTAssertEqual(foundSubscription1!.name, "person_age_15") - - let foundSubscription2 = subscriptions.first(named: "person_age_20") - XCTAssertNotNil(foundSubscription2) - XCTAssertEqual(foundSubscription2!.name, "person_age_20") - } - - @MainActor - func testFindSubscriptionByQuery() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append(QuerySubscription(name: "person_firstname_james") { - $0.firstName == "James" - }) - subscriptions.append(QuerySubscription(name: "object_int_more_than_zero") { - $0.intCol > 0 - }) - } - XCTAssertEqual(subscriptions.count, 2) - - let foundSubscription1 = subscriptions.first(ofType: SwiftPerson.self, where: { - $0.firstName == "James" - }) - XCTAssertNotNil(foundSubscription1) - XCTAssertEqual(foundSubscription1!.name, "person_firstname_james") - - let foundSubscription2 = subscriptions.first(ofType: SwiftTypesSyncObject.self, where: { - $0.intCol > 0 - }) - XCTAssertNotNil(foundSubscription2) - XCTAssertEqual(foundSubscription2!.name, "object_int_more_than_zero") - } - - @MainActor - func testRemoveSubscriptionByName() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append(QuerySubscription(name: "person_firstname_james") { - $0.firstName == "James" - }) - subscriptions.append( - QuerySubscription(name: "object_int_more_than_zero") { - $0.intCol > 0 - }, - QuerySubscription(name: "object_string") { - $0.stringCol == "John" || $0.stringCol == "Tom" - }) - } - XCTAssertEqual(subscriptions.count, 3) - - subscriptions.update { - subscriptions.remove(named: "person_firstname_james") - } - XCTAssertEqual(subscriptions.count, 2) - } - - @MainActor - func testRemoveSubscriptionByQuery() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append( - QuerySubscription { - $0.firstName == "Alex" - }, - QuerySubscription { - $0.firstName == "Belle" - }, - QuerySubscription { - $0.firstName == "Charles" - }) - subscriptions.append(QuerySubscription { - $0.intCol > 0 - }) - } - XCTAssertEqual(subscriptions.count, 4) - - subscriptions.update { - subscriptions.remove(ofType: SwiftPerson.self, { - $0.firstName == "Alex" - }) - subscriptions.remove(ofType: SwiftPerson.self, { - $0.firstName == "Belle" - }) - subscriptions.remove(ofType: SwiftTypesSyncObject.self, { - $0.intCol > 0 - }) - } - XCTAssertEqual(subscriptions.count, 1) - } - - @MainActor - func testRemoveSubscription() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append(QuerySubscription(name: "person_names") { - $0.firstName != "Alex" && $0.lastName != "Roy" - }) - subscriptions.append(QuerySubscription { - $0.intCol > 0 - }) - } - XCTAssertEqual(subscriptions.count, 2) - - let foundSubscription1 = subscriptions.first(named: "person_names") - XCTAssertNotNil(foundSubscription1) - subscriptions.update { - subscriptions.remove(foundSubscription1!) - } - - XCTAssertEqual(subscriptions.count, 1) - - let foundSubscription2 = subscriptions.first(ofType: SwiftTypesSyncObject.self, where: { - $0.intCol > 0 - }) - XCTAssertNotNil(foundSubscription2) - subscriptions.update { - subscriptions.remove(foundSubscription2!) - } - - XCTAssertEqual(subscriptions.count, 0) - } - - @MainActor - func testRemoveSubscriptionsByType() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append( - QuerySubscription { - $0.firstName == "Alex" - }, - QuerySubscription { - $0.firstName == "Belle" - }, - QuerySubscription { - $0.firstName == "Charles" - }) - subscriptions.append(QuerySubscription { - $0.intCol > 0 - }) - } - XCTAssertEqual(subscriptions.count, 4) - - subscriptions.update { - subscriptions.removeAll(ofType: SwiftPerson.self) - } - XCTAssertEqual(subscriptions.count, 1) - - subscriptions.update { - subscriptions.removeAll(ofType: SwiftTypesSyncObject.self) - } - XCTAssertEqual(subscriptions.count, 0) - } - - @MainActor - func testRemoveAllSubscriptions() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append( - QuerySubscription { - $0.firstName == "Alex" - }, - QuerySubscription { - $0.firstName == "Belle" - }, - QuerySubscription { - $0.firstName == "Charles" - }) - subscriptions.append(QuerySubscription { - $0.intCol > 0 - }) - } - XCTAssertEqual(subscriptions.count, 4) - - subscriptions.update { - subscriptions.removeAll() - } - - XCTAssertEqual(subscriptions.count, 0) - } - - @MainActor - func testRemoveAllUnnamedSubscriptions() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append( - QuerySubscription(name: "alex") { - $0.firstName == "Alex" - }, - QuerySubscription { - $0.firstName == "Belle" - }, - QuerySubscription { - $0.firstName == "Charles" - }) - subscriptions.append(QuerySubscription(name: "zero") { - $0.intCol > 0 - }) - } - XCTAssertEqual(subscriptions.count, 4) - - subscriptions.update { - subscriptions.removeAll(unnamedOnly: true) - } - - XCTAssertEqual(subscriptions.count, 2) - } - - @MainActor - func testSubscriptionSetIterate() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - - let numberOfSubs = 50 - subscriptions.update { - for i in 1...numberOfSubs { - subscriptions.append(QuerySubscription(name: "person_age_\(i)") { - $0.age > i - }) - } - } - - XCTAssertEqual(subscriptions.count, numberOfSubs) - - var count = 0 - for subscription in subscriptions { - XCTAssertNotNil(subscription) - count += 1 - } - - XCTAssertEqual(count, numberOfSubs) - } - - @MainActor - func testSubscriptionSetFirstAndLast() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - - let numberOfSubs = 20 - subscriptions.update { - for i in 1...numberOfSubs { - subscriptions.append(QuerySubscription(name: "person_age_\(i)") { - $0.age > i - }) - } - } - - XCTAssertEqual(subscriptions.count, numberOfSubs) - - let firstSubscription = subscriptions.first - XCTAssertNotNil(firstSubscription!) - XCTAssertEqual(firstSubscription!.name, "person_age_1") - - let lastSubscription = subscriptions.last - XCTAssertNotNil(lastSubscription!) - XCTAssertEqual(lastSubscription!.name, "person_age_\(numberOfSubs)") - } - - @MainActor - func testSubscriptionSetSubscript() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - - let numberOfSubs = 20 - subscriptions.update { - for i in 1...numberOfSubs { - subscriptions.append(QuerySubscription(name: "person_age_\(i)") { - $0.age > i - }) - } - } - - XCTAssertEqual(subscriptions.count, numberOfSubs) - - let firstSubscription = subscriptions[0] - XCTAssertNotNil(firstSubscription!) - XCTAssertEqual(firstSubscription!.name, "person_age_1") - - let lastSubscription = subscriptions[numberOfSubs-1] - XCTAssertNotNil(lastSubscription!) - XCTAssertEqual(lastSubscription!.name, "person_age_\(numberOfSubs)") - } - - @MainActor - func testUpdateQueries() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append( - QuerySubscription(name: "person_age_15") { - $0.age > 15 - }, - QuerySubscription(name: "person_age_20") { - $0.age > 20 - }) - } - XCTAssertEqual(subscriptions.count, 2) - - let foundSubscription1 = subscriptions.first(named: "person_age_15") - let foundSubscription2 = subscriptions.first(named: "person_age_20") - - subscriptions.update { - foundSubscription1?.updateQuery(toType: SwiftPerson.self, where: { $0.age > 0 }) - foundSubscription2?.updateQuery(toType: SwiftPerson.self, where: { $0.age > 0 }) - } - - XCTAssertEqual(subscriptions.count, 2) - } - - @MainActor - func testUpdateQueriesWithoutName() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append( - QuerySubscription { $0.age > 15 }, - QuerySubscription { $0.age > 20 }) - } - XCTAssertEqual(subscriptions.count, 2) - - let foundSubscription1 = subscriptions.first(ofType: SwiftPerson.self, where: { - $0.age > 15 - }) - let foundSubscription2 = subscriptions.first(ofType: SwiftPerson.self, where: { - $0.age > 20 - }) - - subscriptions.update { - foundSubscription1?.updateQuery(toType: SwiftPerson.self, where: { $0.age > 0 }) - foundSubscription2?.updateQuery(toType: SwiftPerson.self, where: { $0.age > 5 }) - } - - XCTAssertEqual(subscriptions.count, 2) - } - - // FIXME: Using `assertThrows` within a Server test will crash on tear down - @MainActor - func skip_testFlexibleSyncAppUpdateQueryWithDifferentObjectTypeWillThrow() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append( - QuerySubscription(name: "person_age_15") { - $0.age > 15 - }) - } - XCTAssertEqual(subscriptions.count, 1) - - let foundSubscription1 = subscriptions.first(named: "person_age_15") - - subscriptions.update { - assertThrows(foundSubscription1?.updateQuery(toType: SwiftTypesSyncObject.self, where: { $0.intCol > 0 })) - } - } - - @MainActor - func testFlexibleSyncTransactionsWithPredicateFormatAndNSPredicate() throws { - let realm = try openRealm() - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append( - QuerySubscription(name: "name_alex", where: "firstName == %@", "Alex"), - QuerySubscription(name: "name_charles", where: "firstName == %@", "Charles"), - QuerySubscription(where: NSPredicate(format: "firstName == 'Belle'"))) - subscriptions.append(QuerySubscription(where: NSPredicate(format: "intCol > 0"))) - } - XCTAssertEqual(subscriptions.count, 4) - - let foundSubscription1 = subscriptions.first(ofType: SwiftPerson.self, where: "firstName == %@", "Alex") - XCTAssertNotNil(foundSubscription1) - let foundSubscription2 = subscriptions.first(ofType: SwiftTypesSyncObject.self, where: NSPredicate(format: "intCol > 0")) - XCTAssertNotNil(foundSubscription2) - - subscriptions.update { - subscriptions.remove(ofType: SwiftPerson.self, where: NSPredicate(format: "firstName == 'Belle'")) - subscriptions.remove(ofType: SwiftPerson.self, where: "firstName == %@", "Charles") - - foundSubscription1?.updateQuery(to: NSPredicate(format: "lastName == 'Wightman'")) - foundSubscription2?.updateQuery(to: "stringCol == %@", "string") - } - - XCTAssertEqual(subscriptions.count, 2) - } - - @MainActor - func populateSwiftPerson(count: Int = 10) throws { - try write { realm in - for i in 1...count { - realm.add(SwiftPerson(firstName: self.name, lastName: "lastname_\(i)", age: i)) - } - } - } - - @MainActor - func populateSwiftTypesObject(count: Int = 1) throws { - try write { realm in - for _ in 1...count { - let swiftTypes = SwiftTypesSyncObject() - swiftTypes.stringCol = self.name - realm.add(swiftTypes) - } - } - } - - @MainActor - func testFlexibleSyncAppWithoutQuery() throws { - try populateSwiftPerson() - - let realm = try openRealm() - checkCount(expected: 0, realm, SwiftPerson.self) - - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 0) - - waitForDownloads(for: realm) - checkCount(expected: 0, realm, SwiftPerson.self) - } - - @MainActor - func testFlexibleSyncAppAddQuery() throws { - try populateSwiftPerson(count: 25) - - let realm = try openRealm() - checkCount(expected: 0, realm, SwiftPerson.self) - - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 0) - - let ex = expectation(description: "state change complete") - subscriptions.update({ - subscriptions.append(QuerySubscription(name: "person_age_15") { - $0.age > 15 && $0.firstName == name - }) - }, onComplete: { error in - XCTAssertNil(error) - ex.fulfill() - }) - - waitForExpectations(timeout: 20.0, handler: nil) - - waitForDownloads(for: realm) - checkCount(expected: 10, realm, SwiftPerson.self) - } - - @MainActor - func testFlexibleSyncAppMultipleQuery() throws { - try populateSwiftPerson(count: 20) - try populateSwiftTypesObject() - - let realm = try openRealm() - checkCount(expected: 0, realm, SwiftPerson.self) - - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 0) - - let ex = expectation(description: "state change complete") - subscriptions.update({ - subscriptions.append(QuerySubscription(name: "person_age_10") { - $0.age > 10 && $0.firstName == name - }) - subscriptions.append(QuerySubscription(name: "swift_object_equal_1") { - $0.intCol == 1 && $0.stringCol == name - }) - }, onComplete: { error in - XCTAssertNil(error) - ex.fulfill() - }) - waitForExpectations(timeout: 20.0, handler: nil) - - waitForDownloads(for: realm) - checkCount(expected: 10, realm, SwiftPerson.self) - checkCount(expected: 1, realm, SwiftTypesSyncObject.self) - } - - @MainActor - func testFlexibleSyncAppRemoveQuery() throws { - try populateSwiftPerson(count: 30) - try populateSwiftTypesObject() - - let realm = try openRealm() - checkCount(expected: 0, realm, SwiftPerson.self) - - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 0) - - let ex = expectation(description: "state change complete") - subscriptions.update({ - subscriptions.append(QuerySubscription(name: "person_age_5") { - $0.age > 5 && $0.firstName == name - }) - subscriptions.append(QuerySubscription(name: "swift_object_equal_1") { - $0.intCol == 1 && $0.stringCol == name - }) - }, onComplete: { error in - XCTAssertNil(error) - ex.fulfill() - }) - waitForExpectations(timeout: 20.0, handler: nil) - - waitForDownloads(for: realm) - checkCount(expected: 25, realm, SwiftPerson.self) - checkCount(expected: 1, realm, SwiftTypesSyncObject.self) - - let ex2 = expectation(description: "state change complete") - subscriptions.update({ - subscriptions.remove(named: "person_age_5") - }, onComplete: { error in - XCTAssertNil(error) - ex2.fulfill() - }) - waitForExpectations(timeout: 20.0, handler: nil) - - waitForDownloads(for: realm) - checkCount(expected: 0, realm, SwiftPerson.self) - checkCount(expected: 1, realm, SwiftTypesSyncObject.self) - } - - @MainActor - func testFlexibleSyncAppRemoveAllQueries() throws { - try populateSwiftPerson(count: 25) - try populateSwiftTypesObject() - - let realm = try openRealm() - checkCount(expected: 0, realm, SwiftPerson.self) - - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 0) - - let ex = expectation(description: "state change complete") - subscriptions.update({ - subscriptions.append(QuerySubscription(name: "person_age_5") { - $0.age > 5 && $0.firstName == name - }) - subscriptions.append(QuerySubscription(name: "swift_object_equal_1") { - $0.intCol == 1 && $0.stringCol == name - }) - }, onComplete: { error in - XCTAssertNil(error) - ex.fulfill() - }) - - waitForExpectations(timeout: 20.0, handler: nil) - - waitForDownloads(for: realm) - checkCount(expected: 20, realm, SwiftPerson.self) - checkCount(expected: 1, realm, SwiftTypesSyncObject.self) - - let ex2 = expectation(description: "state change complete") - subscriptions.update({ - subscriptions.removeAll() - subscriptions.append(QuerySubscription(name: "person_age_20") { - $0.age > 20 && $0.firstName == name - }) - }, onComplete: { error in - XCTAssertNil(error) - ex2.fulfill() - }) - waitForExpectations(timeout: 20.0, handler: nil) - - waitForDownloads(for: realm) - checkCount(expected: 5, realm, SwiftPerson.self) - checkCount(expected: 0, realm, SwiftTypesSyncObject.self) - } - - @MainActor - func testFlexibleSyncAppRemoveQueriesByType() throws { - try populateSwiftPerson(count: 21) - try populateSwiftTypesObject() - - let realm = try openRealm() - checkCount(expected: 0, realm, SwiftPerson.self) - - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 0) - - let ex = expectation(description: "state change complete") - subscriptions.update({ - subscriptions.append( - QuerySubscription(name: "person_age_5") { - $0.age > 20 && $0.firstName == name - }, - QuerySubscription(name: "person_age_10") { - $0.lastName == "lastname_1" && $0.firstName == name - }) - subscriptions.append(QuerySubscription(name: "swift_object_equal_1") { - $0.intCol == 1 && $0.stringCol == name - }) - }, onComplete: { error in - XCTAssertNil(error) - ex.fulfill() - }) - waitForExpectations(timeout: 20.0, handler: nil) - - waitForDownloads(for: realm) - checkCount(expected: 2, realm, SwiftPerson.self) - checkCount(expected: 1, realm, SwiftTypesSyncObject.self) - - let ex2 = expectation(description: "state change complete") - subscriptions.update({ - subscriptions.removeAll(ofType: SwiftPerson.self) - }, onComplete: { error in - XCTAssertNil(error) - ex2.fulfill() - }) - waitForExpectations(timeout: 20.0, handler: nil) - - waitForDownloads(for: realm) - checkCount(expected: 0, realm, SwiftPerson.self) - checkCount(expected: 1, realm, SwiftTypesSyncObject.self) - } - - @MainActor - func testFlexibleSyncAppUpdateQuery() throws { - try populateSwiftPerson(count: 25) - - let realm = try openRealm() - checkCount(expected: 0, realm, SwiftPerson.self) - - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 0) - - let ex = expectation(description: "state change complete") - subscriptions.update({ - subscriptions.append(QuerySubscription(name: "person_age") { - $0.age > 20 && $0.firstName == name - }) - }, onComplete: { error in - XCTAssertNil(error) - ex.fulfill() - }) - waitForExpectations(timeout: 20.0, handler: nil) - - waitForDownloads(for: realm) - checkCount(expected: 5, realm, SwiftPerson.self) - - let foundSubscription = subscriptions.first(named: "person_age") - XCTAssertNotNil(foundSubscription) - - let ex2 = expectation(description: "state change complete") - subscriptions.update({ - foundSubscription?.updateQuery(toType: SwiftPerson.self, where: { - $0.age > 5 && $0.firstName == name - }) - }, onComplete: { error in - XCTAssertNil(error) - ex2.fulfill() - }) - waitForExpectations(timeout: 20.0, handler: nil) - - waitForDownloads(for: realm) - checkCount(expected: 20, realm, SwiftPerson.self) - } - - @MainActor - func testFlexibleSyncInitialSubscriptions() throws { - try populateSwiftPerson(count: 20) - - let name = self.name - let user = createUser() - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subscriptions in - subscriptions.append(QuerySubscription(name: "person_age_10") { - $0.age > 10 && $0.firstName == name - }) - }) - config.objectTypes = [SwiftPerson.self, SwiftTypesSyncObject.self] - let realm = try Realm(configuration: config) - let subscriptions = realm.subscriptions - XCTAssertEqual(subscriptions.count, 1) - - checkCount(expected: 0, realm, SwiftPerson.self) - - let start = Date() - while subscriptions.state != .complete && start.timeIntervalSinceNow > -5.0 { - sleep(1) // wait until state is on complete state - } - XCTAssertEqual(subscriptions.state, .complete) - - waitForDownloads(for: realm) - checkCount(expected: 10, realm, SwiftPerson.self) - } - - @MainActor - func testFlexibleSyncCancelOnNonFatalError() throws { - let proxy = TimeoutProxyServer(port: 5678, targetPort: 9090) - try proxy.start() - - let appConfig = AppConfiguration(baseURL: "http://localhost:5678", - transport: AsyncOpenConnectionTimeoutTransport(), - syncTimeouts: SyncTimeoutOptions(connectTimeout: 2000)) - let app = App(id: appId, configuration: appConfig) - - let user = try logInUser(for: basicCredentials(app: app), app: app) - let config = user.flexibleSyncConfiguration(cancelAsyncOpenOnNonFatalErrors: true) - - autoreleasepool { - proxy.delay = 3.0 - let ex = expectation(description: "async open") - Realm.asyncOpen(configuration: config) { result in - guard case .failure(let error) = result else { - XCTFail("Did not fail: \(result)") - return - } - if let error = error as NSError? { - XCTAssertEqual(error.code, Int(ETIMEDOUT)) - XCTAssertEqual(error.domain, NSPOSIXErrorDomain) - } - ex.fulfill() - } - waitForExpectations(timeout: 20.0, handler: nil) - } - - proxy.stop() - } - - // MARK: - Progress notifiers - @MainActor - func testAsyncOpenProgress() throws { - try populateRealm() - - let asyncOpenEx = expectation(description: "async open") - - let user = createUser() - let name = self.name - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subscriptions in - subscriptions.append(QuerySubscription { - $0.partition == name - }) - }) - config.objectTypes = objectTypes - var downloadRealm: Realm? - let task = Realm.asyncOpen(configuration: config) { result in - try! { XCTAssertNoThrow(try result.get()) }() - downloadRealm = try! result.get() - asyncOpenEx.fulfill() - } - - let callCount = Locked(0) - let progress = Locked(nil) - - task.addProgressNotification { p in - if let progress = progress.value { - if progress.progressEstimate < 1.0 { - XCTAssertGreaterThanOrEqual(p.progressEstimate, progress.progressEstimate) - } - } - progress.value = p - callCount.withLock({ $0 += 1 }) - } - - waitForExpectations(timeout: 10.0, handler: nil) - - XCTAssertEqual(try XCTUnwrap(downloadRealm).objects(SwiftHugeSyncObject.self).count, 2) - - let p1 = try XCTUnwrap(progress.value) - XCTAssertEqual(p1.progressEstimate, 1.0) - XCTAssertTrue(p1.isTransferComplete) - } - - @MainActor - func testNonStreamingDownloadNotifier() async throws { - try populateRealm() - - let realm = try openRealm(wait: false) - - let session = try XCTUnwrap(realm.syncSession) - let callCount = Locked(0) - let progress = Locked(nil) - - let test = Locked(false) - - let token = session.addProgressNotification(for: .download, mode: .forCurrentlyOutstandingWork) { p in - // Verify that progress increases. - if let progress = progress.value { - XCTAssertGreaterThanOrEqual(p.progressEstimate, progress.progressEstimate) - } - progress.value = p - callCount.withLock { $0 += 1 } - } - XCTAssertNotNil(token) - - let subscriptions = realm.subscriptions - try await subscriptions.update { - subscriptions.append(QuerySubscription { - $0.partition == self.name - }) - } - - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, SwiftSyncTestCase.bigObjectCount) - - XCTAssertGreaterThanOrEqual(callCount.value, 1) - let p1 = try XCTUnwrap(progress.value) - XCTAssertEqual(p1.progressEstimate, 1.0) - XCTAssertTrue(p1.isTransferComplete) - let initialCallCount = callCount.value - progress.value = nil - test.value = true - - // Run a second time to upload more data and verify that the callback continues to be called - try populateRealm() - waitForDownloads(for: realm) - - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, 2*SwiftSyncTestCase.bigObjectCount) - - // We expect that the progress notifier is not called again since those objects were - // added after it has completed. - XCTAssertEqual(callCount.value, initialCallCount) - XCTAssertNil(progress.value) - - token!.invalidate() - } - - @MainActor - func testStreamingDownloadNotifier() throws { - try populateRealm() - - let realm = try openRealm(wait: false) - - let session = try XCTUnwrap(realm.syncSession) - let ex = expectation(description: "first download") - let callCount = Locked(0) - let progress = Locked(nil) - let token = session.addProgressNotification(for: .download, mode: .reportIndefinitely) { p in - // Verify that progress increases. If it has reached 1.0, it may decrease again - // since we're adding more data - if let progress = progress.value { - if progress.progressEstimate < 1.0 { - XCTAssertGreaterThanOrEqual(p.progressEstimate, progress.progressEstimate) - } - } - progress.value = p - callCount.withLock({ $0 += 1 }) - } - XCTAssertNotNil(token) - - let subscriptions = realm.subscriptions - subscriptions.update({ - subscriptions.append(QuerySubscription { - $0.partition == self.name - }) - }, onComplete: { err in - XCTAssertNil(err) - ex.fulfill() - }) - - waitForExpectations(timeout: 60.0) - - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, SwiftSyncTestCase.bigObjectCount) - - XCTAssertGreaterThanOrEqual(callCount.value, 1) - let p1 = try XCTUnwrap(progress.value) - XCTAssertEqual(p1.progressEstimate, 1.0) - XCTAssertTrue(p1.isTransferComplete) - let initialCallCount = callCount.value - progress.value = nil - - // Run a second time to upload more data and verify that the callback continues to be called - try populateRealm() - waitForDownloads(for: realm) - - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, 2*SwiftSyncTestCase.bigObjectCount) - - XCTAssertGreaterThan(callCount.value, initialCallCount) - let p2 = try XCTUnwrap(progress.value) - XCTAssertEqual(p2.progressEstimate, 1.0) - XCTAssertTrue(p2.isTransferComplete) - - token!.invalidate() - } - - @MainActor - func testStreamingUploadNotifier() throws { - let realm = try openRealm(wait: false) - let subscriptions = realm.subscriptions - subscriptions.update { - subscriptions.append(QuerySubscription { - $0.partition == self.name - }) - } - let session = try XCTUnwrap(realm.syncSession) - - let progress = Locked(nil) - let callCount = Locked(0) - - let token = session.addProgressNotification(for: .upload, mode: .reportIndefinitely) { p in - if let progress = progress.value, progress.progressEstimate < 1 { - XCTAssertGreaterThanOrEqual(p.progressEstimate, progress.progressEstimate) - } - progress.value = p - callCount.withLock { $0 += 1 } - } - XCTAssertNotNil(token) - waitForUploads(for: realm) - - for _ in 0..<5 { - progress.value = nil - let currentCount = callCount.value - try realm.write { - for _ in 0.. MongoCollection { - let collection = createUser().collection(for: Dog.self, app: app) - removeAllFromCollection(collection) - return collection - } - - func testMongoOptions() { - let findOptions = FindOptions(1) - let findOptions1 = FindOptions(5, ["name": 1], [["_id": 1]]) - let findOptions2 = FindOptions(5, ["names": ["fido", "bob", "rex"]], [["_id": 1]]) - let findOptions3 = FindOptions(5, ["names": ["fido", "bob", "rex"]], [["_id": 1], ["breed": 0]]) - - XCTAssertEqual(findOptions.limit, 1) - XCTAssertEqual(findOptions.projection, nil) - XCTAssertTrue(findOptions.sorting.isEmpty) - - XCTAssertEqual(findOptions1.limit, 5) - XCTAssertEqual(findOptions1.projection, ["name": 1]) - XCTAssertTrue(findOptions1.sorting == [["_id": 1]]) - - XCTAssertEqual(findOptions2.limit, 5) - XCTAssertEqual(findOptions2.projection, ["names": ["fido", "bob", "rex"]]) - XCTAssertTrue(findOptions2.sorting == [["_id": 1]]) - - XCTAssertEqual(findOptions3.limit, 5) - XCTAssertEqual(findOptions3.projection, ["names": ["fido", "bob", "rex"]]) - XCTAssertTrue(findOptions3.sorting == [["_id": 1], ["breed": 0]]) - - let findModifyOptions = FindOneAndModifyOptions(["name": 1], [["_id": 1], ["breed": 0]], true, true) - XCTAssertEqual(findModifyOptions.projection, ["name": 1]) - XCTAssertEqual(findModifyOptions.sorting, [["_id": 1], ["breed": 0]]) - XCTAssertTrue(findModifyOptions.upsert) - XCTAssertTrue(findModifyOptions.shouldReturnNewDocument) - } - - func testMongoInsertResultCompletion() { - let collection = setupMongoCollection() - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "tibetan mastiff"] - - let insertOneEx1 = expectation(description: "Insert one document") - collection.insertOne(document) { result in - if case .failure = result { - XCTFail("Should insert") - } - insertOneEx1.fulfill() - } - wait(for: [insertOneEx1], timeout: 20.0) - - let insertManyEx1 = expectation(description: "Insert many documents") - collection.insertMany([document, document2]) { result in - switch result { - case .success(let objectIds): - XCTAssertEqual(objectIds.count, 2) - case .failure: - XCTFail("Should insert") - } - insertManyEx1.fulfill() - } - wait(for: [insertManyEx1], timeout: 20.0) - - let findEx1 = expectation(description: "Find documents") - collection.find(filter: [:]) { result in - switch result { - case .success(let documents): - XCTAssertEqual(documents.count, 3) - XCTAssertEqual(documents[0]["name"]??.stringValue, "fido") - XCTAssertEqual(documents[1]["name"]??.stringValue, "fido") - XCTAssertEqual(documents[2]["name"]??.stringValue, "rex") - case .failure: - XCTFail("Should find") - } - findEx1.fulfill() - } - wait(for: [findEx1], timeout: 20.0) - } - - func testMongoFindResultCompletion() { - let collection = setupMongoCollection() - - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "tibetan mastiff"] - let document3: Document = ["name": "rex", "breed": "tibetan mastiff", "coat": ["fawn", "brown", "white"]] - let findOptions = FindOptions(1, nil) - - let insertManyEx1 = expectation(description: "Insert many documents") - collection.insertMany([document, document2, document3]) { result in - switch result { - case .success(let objectIds): - XCTAssertEqual(objectIds.count, 3) - case .failure: - XCTFail("Should insert") - } - insertManyEx1.fulfill() - } - wait(for: [insertManyEx1], timeout: 20.0) - - let findEx1 = expectation(description: "Find documents") - collection.find(filter: [:]) { result in - switch result { - case .success(let documents): - XCTAssertEqual(documents.count, 3) - XCTAssertEqual(documents[0]["name"]??.stringValue, "fido") - XCTAssertEqual(documents[1]["name"]??.stringValue, "rex") - XCTAssertEqual(documents[2]["name"]??.stringValue, "rex") - case .failure: - XCTFail("Should find") - } - findEx1.fulfill() - } - wait(for: [findEx1], timeout: 20.0) - - let findEx2 = expectation(description: "Find documents") - collection.find(filter: [:], options: findOptions) { result in - switch result { - case .success(let document): - XCTAssertEqual(document.count, 1) - XCTAssertEqual(document[0]["name"]??.stringValue, "fido") - case .failure: - XCTFail("Should find") - } - findEx2.fulfill() - } - wait(for: [findEx2], timeout: 20.0) - - let findEx3 = expectation(description: "Find documents") - collection.find(filter: document3, options: findOptions) { result in - switch result { - case .success(let documents): - XCTAssertEqual(documents.count, 1) - case .failure: - XCTFail("Should find") - } - findEx3.fulfill() - } - wait(for: [findEx3], timeout: 20.0) - - let findOneEx1 = expectation(description: "Find one document") - collection.findOneDocument(filter: document) { result in - switch result { - case .success(let document): - XCTAssertNotNil(document) - case .failure: - XCTFail("Should find") - } - findOneEx1.fulfill() - } - wait(for: [findOneEx1], timeout: 20.0) - - let findOneEx2 = expectation(description: "Find one document") - collection.findOneDocument(filter: document, options: findOptions) { result in - switch result { - case .success(let document): - XCTAssertNotNil(document) - case .failure: - XCTFail("Should find") - } - findOneEx2.fulfill() - } - wait(for: [findOneEx2], timeout: 20.0) - } - - func testMongoFindAndReplaceResultCompletion() { - let collection = setupMongoCollection() - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - - let findOneReplaceEx1 = expectation(description: "Find one document and replace") - collection.findOneAndReplace(filter: document, replacement: document2) { result in - switch result { - case .success(let document): - // no doc found, both should be nil - XCTAssertNil(document) - case .failure: - XCTFail("Should find") - } - findOneReplaceEx1.fulfill() - } - wait(for: [findOneReplaceEx1], timeout: 20.0) - - let options1 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, true) - let findOneReplaceEx2 = expectation(description: "Find one document and replace") - collection.findOneAndReplace(filter: document2, replacement: document3, options: options1) { result in - switch result { - case .success(let document): - XCTAssertEqual(document!["name"]??.stringValue, "john") - case .failure: - XCTFail("Should find") - } - findOneReplaceEx2.fulfill() - } - wait(for: [findOneReplaceEx2], timeout: 20.0) - - let options2 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, false) - let findOneReplaceEx3 = expectation(description: "Find one document and replace") - collection.findOneAndReplace(filter: document, replacement: document2, options: options2) { result in - switch result { - case .success(let document): - // upsert but do not return document - XCTAssertNil(document) - case .failure: - XCTFail("Should find") - } - findOneReplaceEx3.fulfill() - } - wait(for: [findOneReplaceEx3], timeout: 20.0) - } - - func testMongoFindAndUpdateResultCompletion() { - let collection = setupMongoCollection() - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - - let findOneUpdateEx1 = expectation(description: "Find one document and update") - collection.findOneAndUpdate(filter: document, update: document2) { result in - switch result { - case .success(let document): - // no doc found, both should be nil - XCTAssertNil(document) - case .failure: - XCTFail("Should find") - } - findOneUpdateEx1.fulfill() - } - wait(for: [findOneUpdateEx1], timeout: 20.0) - - let options1 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, true) - let findOneUpdateEx2 = expectation(description: "Find one document and update") - collection.findOneAndUpdate(filter: document2, update: document3, options: options1) { result in - switch result { - case .success(let document): - XCTAssertNotNil(document) - XCTAssertEqual(document!["name"]??.stringValue, "john") - case .failure: - XCTFail("Should find") - } - findOneUpdateEx2.fulfill() - } - wait(for: [findOneUpdateEx2], timeout: 20.0) - - let options2 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, true) - let findOneUpdateEx3 = expectation(description: "Find one document and update") - collection.findOneAndUpdate(filter: document, update: document2, options: options2) { result in - switch result { - case .success(let document): - XCTAssertNotNil(document) - XCTAssertEqual(document!["name"]??.stringValue, "rex") - case .failure: - XCTFail("Should find") - } - findOneUpdateEx3.fulfill() - } - wait(for: [findOneUpdateEx3], timeout: 20.0) - } - - func testMongoFindAndDeleteResultCompletion() { - let collection = setupMongoCollection() - let document: Document = ["name": "fido", "breed": "cane corso"] - - let insertManyEx = expectation(description: "Insert many documents") - collection.insertMany([document, document]) { result in - switch result { - case .success(let objectIds): - XCTAssertEqual(objectIds.count, 2) - case .failure: - XCTFail("Should insert") - } - insertManyEx.fulfill() - } - wait(for: [insertManyEx], timeout: 20.0) - - let findOneDeleteEx1 = expectation(description: "Find one document and delete") - collection.findOneAndDelete(filter: document) { result in - switch result { - case .success(let document): - // Document does not exist, but should not return an error because of that - XCTAssertNotNil(document) - case .failure: - XCTFail("Should find") - } - findOneDeleteEx1.fulfill() - } - wait(for: [findOneDeleteEx1], timeout: 20.0) - - let options1 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], false, false) - let findOneDeleteEx2 = expectation(description: "Find one document and delete") - collection.findOneAndDelete(filter: document, options: options1) { result in - switch result { - case .success(let document): - XCTAssertNotNil(document) - XCTAssertEqual(document!["name"]??.stringValue, "fido") - findOneDeleteEx2.fulfill() - case .failure: - XCTFail("Should find") - } - } - wait(for: [findOneDeleteEx2], timeout: 20.0) - - let options2 = FindOneAndModifyOptions(["name": 1], [["_id": 1]]) - let findOneDeleteEx3 = expectation(description: "Find one document and delete") - collection.findOneAndDelete(filter: document, options: options2) { result in - switch result { - case .success(let document): - // Document does not exist, but should not return an error because of that - XCTAssertNil(document) - findOneDeleteEx3.fulfill() - case .failure: - XCTFail("Should find") - } - } - wait(for: [findOneDeleteEx3], timeout: 20.0) - - let findEx = expectation(description: "Find documents") - collection.find(filter: [:]) { result in - switch result { - case .success(let documents): - XCTAssertEqual(documents.count, 0) - case .failure: - XCTFail("Should find") - } - findEx.fulfill() - } - wait(for: [findEx], timeout: 20.0) - } - - func testMongoUpdateOneResultCompletion() { - let collection = setupMongoCollection() - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - let document4: Document = ["name": "ted", "breed": "bullmastiff"] - let document5: Document = ["name": "bill", "breed": "great dane"] - - let insertManyEx = expectation(description: "Insert many documents") - collection.insertMany([document, document2, document3, document4]) { result in - switch result { - case .success(let objectIds): - XCTAssertEqual(objectIds.count, 4) - case .failure: - XCTFail("Should insert") - } - insertManyEx.fulfill() - } - wait(for: [insertManyEx], timeout: 20.0) - - let updateEx1 = expectation(description: "Update one document") - collection.updateOneDocument(filter: document, update: document2) { result in - switch result { - case .success(let updateResult): - XCTAssertEqual(updateResult.matchedCount, 1) - XCTAssertEqual(updateResult.modifiedCount, 1) - XCTAssertNil(updateResult.documentId) - case .failure: - XCTFail("Should update") - } - updateEx1.fulfill() - } - wait(for: [updateEx1], timeout: 20.0) - - let updateEx2 = expectation(description: "Update one document") - collection.updateOneDocument(filter: document5, update: document2, upsert: true) { result in - switch result { - case .success(let updateResult): - XCTAssertEqual(updateResult.matchedCount, 0) - XCTAssertEqual(updateResult.modifiedCount, 0) - XCTAssertNotNil(updateResult.documentId) - case .failure: - XCTFail("Should update") - } - updateEx2.fulfill() - } - wait(for: [updateEx2], timeout: 20.0) - } - - func testMongoUpdateManyResultCompletion() { - let collection = setupMongoCollection() - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - let document4: Document = ["name": "ted", "breed": "bullmastiff"] - let document5: Document = ["name": "bill", "breed": "great dane"] - - let insertManyEx = expectation(description: "Insert many documents") - collection.insertMany([document, document2, document3, document4]) { result in - switch result { - case .success(let objectIds): - XCTAssertEqual(objectIds.count, 4) - case .failure: - XCTFail("Should insert") - } - insertManyEx.fulfill() - } - wait(for: [insertManyEx], timeout: 20.0) - - let updateEx1 = expectation(description: "Update one document") - collection.updateManyDocuments(filter: document, update: document2) { result in - switch result { - case .success(let updateResult): - XCTAssertEqual(updateResult.matchedCount, 1) - XCTAssertEqual(updateResult.modifiedCount, 1) - XCTAssertNil(updateResult.documentId) - case .failure: - XCTFail("Should update") - } - updateEx1.fulfill() - } - wait(for: [updateEx1], timeout: 20.0) - - let updateEx2 = expectation(description: "Update one document") - collection.updateManyDocuments(filter: document5, update: document2, upsert: true) { result in - switch result { - case .success(let updateResult): - XCTAssertEqual(updateResult.matchedCount, 0) - XCTAssertEqual(updateResult.modifiedCount, 0) - XCTAssertNotNil(updateResult.documentId) - case .failure: - XCTFail("Should update") - } - updateEx2.fulfill() - } - wait(for: [updateEx2], timeout: 20.0) - } - - func testMongoDeleteOneResultCompletion() { - let collection = setupMongoCollection() - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - - let deleteEx1 = expectation(description: "Delete 0 documents") - collection.deleteOneDocument(filter: document) { result in - switch result { - case .success(let count): - XCTAssertEqual(count, 0) - case .failure: - XCTFail("Should delete") - } - deleteEx1.fulfill() - } - wait(for: [deleteEx1], timeout: 20.0) - - let insertManyEx = expectation(description: "Insert many documents") - collection.insertMany([document, document2]) { result in - switch result { - case .success(let objectIds): - XCTAssertEqual(objectIds.count, 2) - case .failure: - XCTFail("Should insert") - } - insertManyEx.fulfill() - } - wait(for: [insertManyEx], timeout: 20.0) - - let deleteEx2 = expectation(description: "Delete one document") - collection.deleteOneDocument(filter: document) { result in - switch result { - case .success(let count): - XCTAssertEqual(count, 1) - case .failure: - XCTFail("Should delete") - } - deleteEx2.fulfill() - } - wait(for: [deleteEx2], timeout: 20.0) - } - - func testMongoDeleteManyResultCompletion() { - let collection = setupMongoCollection() - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - - let deleteEx1 = expectation(description: "Delete 0 documents") - collection.deleteManyDocuments(filter: document) { result in - switch result { - case .success(let count): - XCTAssertEqual(count, 0) - case .failure: - XCTFail("Should delete") - } - deleteEx1.fulfill() - } - wait(for: [deleteEx1], timeout: 20.0) - - let insertManyEx = expectation(description: "Insert many documents") - collection.insertMany([document, document2]) { result in - switch result { - case .success(let objectIds): - XCTAssertEqual(objectIds.count, 2) - case .failure: - XCTFail("Should insert") - } - insertManyEx.fulfill() - } - wait(for: [insertManyEx], timeout: 20.0) - - let deleteEx2 = expectation(description: "Delete one document") - collection.deleteManyDocuments(filter: ["breed": "cane corso"]) { result in - switch result { - case .success(let count): - XCTAssertEqual(count, 2) - case .failure: - XCTFail("Should selete") - } - deleteEx2.fulfill() - } - wait(for: [deleteEx2], timeout: 20.0) - } - - func testMongoCountAndAggregateResultCompletion() { - let collection = setupMongoCollection() - let document: Document = ["name": "fido", "breed": "cane corso"] - - let insertManyEx1 = expectation(description: "Insert many documents") - collection.insertMany([document]) { result in - switch result { - case .success(let objectIds): - XCTAssertEqual(objectIds.count, 1) - case .failure(let error): - XCTFail("Insert failed: \(error)") - } - insertManyEx1.fulfill() - } - wait(for: [insertManyEx1], timeout: 20.0) - - collection.aggregate(pipeline: [["$match": ["name": "fido"]], ["$group": ["_id": "$name"]]]) { result in - switch result { - case .success(let documents): - XCTAssertNotNil(documents) - case .failure(let error): - XCTFail("Aggregate failed: \(error)") - } - } - - let countEx1 = expectation(description: "Count documents") - collection.count(filter: document) { result in - switch result { - case .success(let count): - XCTAssertNotNil(count) - case .failure(let error): - XCTFail("Count failed: \(error)") - } - countEx1.fulfill() - } - wait(for: [countEx1], timeout: 20.0) - - let countEx2 = expectation(description: "Count documents") - collection.count(filter: document, limit: 1) { result in - switch result { - case .success(let count): - XCTAssertEqual(count, 1) - case .failure(let error): - XCTFail("Count failed: \(error)") - } - countEx2.fulfill() - } - wait(for: [countEx2], timeout: 20.0) - } - - func testWatch() throws { - try performWatchTest(.main) - } - - func testWatchAsync() throws { - let queue = DispatchQueue(label: "io.realm.watchQueue", attributes: .concurrent) - try performWatchTest(queue) - } - - func performWatchTest(_ queue: DispatchQueue) throws { - let collection = setupMongoCollection() - let document: Document = ["name": "fido", "breed": "cane corso"] - - let watchTestUtility = WatchTestUtility(testCase: self) - let changeStream = collection.watch(delegate: watchTestUtility, queue: queue) - watchTestUtility.waitForOpen() - for _ in 0..<3 { - watchTestUtility.expectEvent() - collection.insertOne(document) { result in - if case .failure = result { - XCTFail("Should insert") - } - } - try watchTestUtility.waitForEvent() - } - changeStream.close() - watchTestUtility.waitForClose() - } - - @MainActor - func testWatchWithMatchFilter() throws { - try performWatchWithMatchFilterTest(.main) - } - - @MainActor - func testWatchWithMatchFilterQueue() throws { - let queue = DispatchQueue(label: "io.realm.watchQueue", attributes: .concurrent) - try performWatchWithMatchFilterTest(queue) - } - - @MainActor - func insertDocuments(_ collection: MongoCollection) -> [ObjectId] { - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - let document4: Document = ["name": "ted", "breed": "bullmastiff"] - - let objectIds = collection.insertMany([document, document2, document3, document4]) - .map { @Sendable in $0.map(\.objectIdValue!) } - .await(self) - XCTAssertEqual(objectIds.count, 4) - return objectIds - } - - @MainActor - func performWatchWithMatchFilterTest(_ queue: DispatchQueue?) throws { - let collection = setupMongoCollection() - let objectIds = insertDocuments(collection) - let watchTestUtility = WatchTestUtility(testCase: self, matchingObjectId: objectIds.first!) - - let filter = ["fullDocument._id": AnyBSON.objectId(objectIds[0])] - let changeStream: ChangeStream - if let queue = queue { - changeStream = collection.watch(matchFilter: filter, delegate: watchTestUtility, queue: queue) - } else { - changeStream = collection.watch(matchFilter: filter, delegate: watchTestUtility) - } - watchTestUtility.waitForOpen() - - for i in 0..<3 { - watchTestUtility.expectEvent() - let name: AnyBSON = .string("fido-\(i)") - collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[0])], - update: ["name": name, "breed": "king charles"]) { result in - if case .failure = result { - XCTFail("Should update") - } - } - collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[1])], - update: ["name": name, "breed": "king charles"]) { result in - if case .failure = result { - XCTFail("Should update") - } - } - try watchTestUtility.waitForEvent() - } - changeStream.close() - watchTestUtility.waitForClose() - } - - @MainActor - func testWatchWithFilterIds() throws { - try performWatchWithFilterIdsTest(nil) - } - - @MainActor - func testWatchWithFilterIdsQueue() throws { - let queue = DispatchQueue(label: "io.realm.watchQueue", attributes: .concurrent) - try performWatchWithFilterIdsTest(queue) - } - - @MainActor - func performWatchWithFilterIdsTest(_ queue: DispatchQueue?) throws { - let collection = setupMongoCollection() - let objectIds = insertDocuments(collection) - let watchTestUtility = WatchTestUtility(testCase: self, matchingObjectId: objectIds.first!) - let changeStream: ChangeStream - if let queue = queue { - changeStream = collection.watch(filterIds: [objectIds[0]], delegate: watchTestUtility, queue: queue) - } else { - changeStream = collection.watch(filterIds: [objectIds[0]], delegate: watchTestUtility) - } - watchTestUtility.waitForOpen() - - for i in 0..<3 { - watchTestUtility.expectEvent() - let name: AnyBSON = .string("fido-\(i)") - collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[0])], - update: ["name": name, "breed": "king charles"]) { result in - if case .failure = result { - XCTFail("Should update") - } - } - collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[1])], - update: ["name": name, "breed": "king charles"]) { result in - if case .failure = result { - XCTFail("Should update") - } - } - try watchTestUtility.waitForEvent() - } - changeStream.close() - watchTestUtility.waitForClose() - } - - @available(macOS 13, *) - @MainActor - func performAsyncWatchTest(filterIds: Bool = false, matchFilter: Bool = false) async throws { - let collection = setupMongoCollection() - let objectIds = insertDocuments(collection) - - let openEx = expectation(description: "open watch stream") - @Locked var ex: XCTestExpectation! - let task = Task { - let changeEvents: AsyncThrowingPublisher - if filterIds { - changeEvents = collection.changeEvents(filterIds: [objectIds[0]], - onOpen: { openEx.fulfill() }) - } else if matchFilter { - let filter = ["fullDocument._id": AnyBSON.objectId(objectIds[0])] - changeEvents = collection.changeEvents(matchFilter: filter, onOpen: { openEx.fulfill() }) - } else { - changeEvents = collection.changeEvents(onOpen: { openEx.fulfill() }) - } - - for try await event in changeEvents { - let doc = event.documentValue! - XCTAssertEqual(doc["operationType"], "replace") - let id = try XCTUnwrap(doc["documentKey"]??.documentValue?["_id"]??.objectIdValue) - XCTAssertEqual(id, objectIds[0]) - ex.fulfill() - } - } - await fulfillment(of: [openEx], timeout: 2.0) - - for i in 0..<3 { - ex = expectation(description: "got change event") - let name: AnyBSON = .string("fido-\(i)") - _ = try await collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[0])], - update: ["name": name, "breed": "king charles"]) - if filterIds || matchFilter { - _ = try await collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[1])], - update: ["name": name, "breed": "king charles"]) - } - await fulfillment(of: [ex], timeout: 2.0) - } - - task.cancel() - _ = await task.result - } - - @available(macOS 13, *) - func testWatchAsync() async throws { - try await performAsyncWatchTest() - } - - @available(macOS 13, *) - func testWatchWithMatchFilterAsync() async throws { - try await performAsyncWatchTest(matchFilter: true) - } - - @available(macOS 13, *) - func testWatchWithFilterIdsAsync() async throws { - try await performAsyncWatchTest(filterIds: true) - } - - @MainActor - func testWatchMultipleFilterStreams() throws { - try performMultipleWatchStreamsTest(nil) - } - - @MainActor - func testWatchMultipleFilterStreamsAsync() throws { - let queue = DispatchQueue(label: "io.realm.watchQueue", attributes: .concurrent) - try performMultipleWatchStreamsTest(queue) - } - - @MainActor - func performMultipleWatchStreamsTest(_ queue: DispatchQueue?) throws { - let collection = setupMongoCollection() - let objectIds = insertDocuments(collection) - let watchTestUtility1 = WatchTestUtility(testCase: self, matchingObjectId: objectIds[0]) - let watchTestUtility2 = WatchTestUtility(testCase: self, matchingObjectId: objectIds[1]) - - let changeStream1: ChangeStream - let changeStream2: ChangeStream - if let queue = queue { - changeStream1 = collection.watch(filterIds: [objectIds[0]], delegate: watchTestUtility1, queue: queue) - changeStream2 = collection.watch(filterIds: [objectIds[1]], delegate: watchTestUtility2, queue: queue) - } else { - changeStream1 = collection.watch(filterIds: [objectIds[0]], delegate: watchTestUtility1) - changeStream2 = collection.watch(filterIds: [objectIds[1]], delegate: watchTestUtility2) - } - watchTestUtility1.waitForOpen() - watchTestUtility2.waitForOpen() - - for i in 0..<3 { - watchTestUtility1.expectEvent() - watchTestUtility2.expectEvent() - let name: AnyBSON = .string("fido-\(i)") - collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[0])], - update: ["name": name, "breed": "king charles"]) { result in - if case .failure = result { - XCTFail("Should update") - } - } - collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[1])], - update: ["name": name, "breed": "king charles"]) { result in - if case .failure = result { - XCTFail("Should update") - } - } - try watchTestUtility1.waitForEvent() - try watchTestUtility2.waitForEvent() - } - changeStream1.close() - changeStream2.close() - watchTestUtility1.waitForClose() - watchTestUtility2.waitForClose() - } - - @MainActor - func testShouldNotDeleteOnMigrationWithSync() throws { - var configuration = try configuration() - assertThrows(configuration.deleteRealmIfMigrationNeeded = true, - reason: "Cannot set 'deleteRealmIfMigrationNeeded' when sync is enabled ('syncConfig' is set).") - - var localConfiguration = Realm.Configuration.defaultConfiguration - assertSucceeds { - localConfiguration.deleteRealmIfMigrationNeeded = true - } - } -} - -// MARK: - AsyncAwaitMongoClientTests -@available(macOS 13, *) -class AsyncAwaitMongoClientTests: SwiftSyncTestCase { - override var objectTypes: [ObjectBase.Type] { - [Dog.self] - } - - override class var defaultTestSuite: XCTestSuite { - // async/await is currently incompatible with thread sanitizer and will - // produce many false positives - // https://bugs.swift.org/browse/SR-15444 - if RLMThreadSanitizerEnabled() { - return XCTestSuite(name: "\(type(of: self))") - } - return super.defaultTestSuite - - } - - func setupMongoCollection() async throws -> MongoCollection { - let collection = try await createUser().collection(for: Dog.self, app: app) - _ = try await collection.deleteManyDocuments(filter: [:]) - return collection - } - - func testMongoFindSortOptions() async throws { - let collection = try await setupMongoCollection() - - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "tibetan mastiff"] - let document3: Document = ["name": "rex", "breed": "cane corso"] - let document4: Document = ["name": "fido", "breed": "tibetan mastiff"] - - let objectIds = try await collection.insertMany([document, document2, document3, document4]) - XCTAssertNotNil(objectIds) - XCTAssertEqual(objectIds.count, 4) - - let findOptions = FindOptions(0, nil, [["name": 1], ["breed": 1]]) - let fetchedDocuments = try await collection.find(filter: [:], options: findOptions) - XCTAssertEqual(fetchedDocuments.count, 4) - XCTAssertEqual(fetchedDocuments[0]["name"]??.stringValue, "fido") - XCTAssertEqual(fetchedDocuments[0]["breed"]??.stringValue, "cane corso") - XCTAssertEqual(fetchedDocuments[1]["name"]??.stringValue, "fido") - XCTAssertEqual(fetchedDocuments[1]["breed"]??.stringValue, "tibetan mastiff") - XCTAssertEqual(fetchedDocuments[2]["name"]??.stringValue, "rex") - XCTAssertEqual(fetchedDocuments[2]["breed"]??.stringValue, "cane corso") - XCTAssertEqual(fetchedDocuments[3]["name"]??.stringValue, "rex") - XCTAssertEqual(fetchedDocuments[3]["breed"]??.stringValue, "tibetan mastiff") - - - for try _ in 0...10 { - let findOptions2 = FindOptions(0, nil, [["name": 1], ["breed": 1]]) - let fetchedDocuments2 = try await collection.find(filter: [:], options: findOptions2) - XCTAssertEqual(fetchedDocuments[0]["name"], fetchedDocuments2[0]["name"]) - XCTAssertEqual(fetchedDocuments[0]["breed"], fetchedDocuments2[0]["breed"]) - XCTAssertEqual(fetchedDocuments[1]["name"], fetchedDocuments2[1]["name"]) - XCTAssertEqual(fetchedDocuments[1]["breed"], fetchedDocuments2[1]["breed"]) - XCTAssertEqual(fetchedDocuments[2]["name"], fetchedDocuments2[2]["name"]) - XCTAssertEqual(fetchedDocuments[2]["breed"], fetchedDocuments2[2]["breed"]) - XCTAssertEqual(fetchedDocuments[3]["name"], fetchedDocuments2[3]["name"]) - XCTAssertEqual(fetchedDocuments[3]["breed"], fetchedDocuments2[3]["breed"]) - } - - let findOptions3 = FindOptions(0, nil, [["breed": 1], ["name": 1]]) - let fetchedDocuments3 = try await collection.find(filter: [:], options: findOptions3) - XCTAssertEqual(fetchedDocuments3.count, 4) - XCTAssertEqual(fetchedDocuments3[0]["name"]??.stringValue, "fido") - XCTAssertEqual(fetchedDocuments3[0]["breed"]??.stringValue, "cane corso") - XCTAssertEqual(fetchedDocuments3[3]["name"]??.stringValue, "rex") - XCTAssertEqual(fetchedDocuments3[3]["breed"]??.stringValue, "tibetan mastiff") - } - - func testMongoCollectionInsertOneAsyncAwait() async throws { - let collection = try await setupMongoCollection() - - let document: Document = ["name": "tomas", "breed": "jack rusell"] - let objectId = try await collection.insertOne(document) - XCTAssertNotNil(objectId) - let fetchedDocument = try await collection.find(filter: document) - XCTAssertEqual(fetchedDocument[0]["name"]??.stringValue, "tomas") - } - - func testMongoCollectionInsertManyAsyncAwait() async throws { - let collection = try await setupMongoCollection() - - let document1: Document = ["name": "lucas", "breed": "german shepard"] - let document2: Document = ["name": "fito", "breed": "goberian"] - let objectIds = try await collection.insertMany([document1, document2]) - XCTAssertNotNil(objectIds) - XCTAssertEqual(objectIds.count, 2) - - let fetchedDocuments = try await collection.find(filter: [:]) - XCTAssertEqual(fetchedDocuments.count, 2) - XCTAssertEqual(fetchedDocuments[0]["name"]??.stringValue, "lucas") - } - - func testMongoCollectionFindAsyncAwait() async throws { - let collection = try await setupMongoCollection() - - let document: Document = ["name": "tomas", "breed": "jack rusell"] - let document1: Document = ["name": "lucas", "breed": "german shepard"] - let document2: Document = ["name": "fito", "breed": "goberian"] - let document3: Document = ["name": "fosca", "breed": "labradoodle"] - let objectIds = try await collection.insertMany([document, document1, document2, document3]) - XCTAssertNotNil(objectIds) - XCTAssertEqual(objectIds.count, 4) - - // Test filter all - let fetchedDocuments = try await collection.find(filter: [:]) - XCTAssertEqual(fetchedDocuments.count, 4) - XCTAssertEqual(fetchedDocuments[0]["name"]??.stringValue, "tomas") - XCTAssertEqual(fetchedDocuments[1]["name"]??.stringValue, "lucas") - XCTAssertEqual(fetchedDocuments[2]["breed"]??.stringValue, "goberian") - XCTAssertEqual(fetchedDocuments[3]["breed"]??.stringValue, "labradoodle") - - // Test filter all with option limit to one - let findOptions = FindOptions(1, nil) - let fetchedDocuments2 = try await collection.find(filter: [:], options: findOptions) - XCTAssertEqual(fetchedDocuments2.count, 1) - XCTAssertEqual(fetchedDocuments2[0]["name"]??.stringValue, "tomas") - - // Test filter by document - let fetchedDocuments3 = try await collection.find(filter: document1, options: findOptions) - XCTAssertEqual(fetchedDocuments3.count, 1) - XCTAssertEqual(fetchedDocuments3[0]["name"]??.stringValue, document1["name"]??.stringValue) - - // Test filter not matching - let fetchedDocuments4 = try await collection.find(filter: ["name": "oliver"]) - XCTAssertEqual(fetchedDocuments4.count, 0) - } - - func testMongoCollectionFindOneAsyncAwait() async throws { - let collection = try await setupMongoCollection() - - let document: Document = ["name": "tomas", "breed": "jack rusell"] - let document1: Document = ["name": "lucas", "breed": "german shepard"] - let document2: Document = ["name": "fito", "breed": "goberian"] - let document3: Document = ["name": "fosca", "breed": "labradoodle"] - let objectIds = try await collection.insertMany([document, document1, document2, document3]) - XCTAssertNotNil(objectIds) - XCTAssertEqual(objectIds.count, 4) - - // Test findOne all - let fetchedOneDocument = try await collection.findOneDocument(filter: document) - XCTAssertNotNil(fetchedOneDocument) - XCTAssertEqual(fetchedOneDocument?["name"]??.stringValue, "tomas") - XCTAssertEqual(fetchedOneDocument?["breed"]??.stringValue, "jack rusell") - - // Test findOne all with option limit - let findOptions = FindOptions(1, nil) - let fetchedOneDocument2 = try await collection.findOneDocument(filter: [:], options: findOptions) - XCTAssertNotNil(fetchedOneDocument2) - - // Test filter not matching - let fetchedOneDocument3 = try await collection.findOneDocument(filter: ["name": "oliver"]) - XCTAssertNil(fetchedOneDocument3) - } - - func testMongoCollectionFindAndReplaceAsyncAwait() async throws { - let collection = try await setupMongoCollection() - - let document: Document = ["name": "tomas", "breed": "jack rusell"] - let document1: Document = ["name": "lucas", "breed": "german shepard"] - let document2: Document = ["name": "fito", "breed": "goberian"] - let document3: Document = ["name": "fosca", "breed": "labradoodle"] - - // Test find and replace non-existent element - let resultDocument = try await collection.findOneAndReplace(filter: document, replacement: document3) - XCTAssertNil(resultDocument) - - let objectId = try await collection.insertOne(document) - XCTAssertNotNil(objectId) - - let resultReplacedDocument = try await collection.findOneAndReplace(filter: document, replacement: document3) - XCTAssertNotNil(resultReplacedDocument) - XCTAssertEqual(resultReplacedDocument?["name"]??.stringValue, "tomas") // shouldReturnNewDocument is false that is why returns old document - - let options1 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, true) - let resultReplacedDocument2 = try await collection.findOneAndReplace(filter: document1, replacement: document2, options: options1) - XCTAssertNotNil(resultReplacedDocument2) - XCTAssertEqual(resultReplacedDocument2?["name"]??.stringValue, "fito") - - let options2 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, false) - let resultReplacedDocument3 = try await collection.findOneAndReplace(filter: document, replacement: document1, options: options2) - XCTAssertNil(resultReplacedDocument3) - } - - func testMongoCollectionFindAndUpdateAsyncAwait() async throws { - let collection = try await setupMongoCollection() - - let document: Document = ["name": "tomas", "breed": "jack rusell"] - let document1: Document = ["name": "lucas", "breed": "german shepard"] - let document2: Document = ["name": "fito", "breed": "goberian"] - let document3: Document = ["name": "fosca", "breed": "labradoodle"] - - // Test find and update non-existent element - let resultDocument = try await collection.findOneAndUpdate(filter: document, update: document3) - XCTAssertNil(resultDocument) - - let objectId = try await collection.insertOne(document) - XCTAssertNotNil(objectId) - - let resultUpdatedDocument = try await collection.findOneAndUpdate(filter: document, update: document3) - XCTAssertNotNil(resultUpdatedDocument) - XCTAssertEqual(resultUpdatedDocument?["name"]??.stringValue, "tomas") // shouldReturnNewDocument is false that is why returns old document - - let options1 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, true) - let resultUpdatedDocument2 = try await collection.findOneAndUpdate(filter: document1, update: document2, options: options1) - XCTAssertNotNil(resultUpdatedDocument2) - XCTAssertEqual(resultUpdatedDocument2?["name"]??.stringValue, "fito") - - let options2 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, false) - let resultUpdatedDocument3 = try await collection.findOneAndUpdate(filter: document, update: document1, options: options2) - XCTAssertNil(resultUpdatedDocument3) - } - - func testMongoCollectionFindAndDeleteAsyncAwait() async throws { - let collection = try await setupMongoCollection() - - let document: Document = ["name": "tomas", "breed": "jack rusell"] - let document1: Document = ["name": "lucas", "breed": "german shepard"] - let document2: Document = ["name": "fito", "breed": "goberian"] - let document3: Document = ["name": "fosca", "breed": "labradoodle"] - - let objectIds = try await collection.insertMany([document, document1, document2, document3]) - XCTAssertNotNil(objectIds) - XCTAssertEqual(objectIds.count, 4) - - // Returning a document means that it was found and deleted - let deletedDocument = try await collection.findOneAndDelete(filter: document) - XCTAssertNotNil(deletedDocument) - XCTAssertEqual(deletedDocument?["name"]??.stringValue, "tomas") - - // Document already deleted, should return nil - let options2 = FindOneAndModifyOptions(["name": 1], [["_id": 1]]) - let deletedDocument3 = try await collection.findOneAndDelete(filter: document, options: options2) - XCTAssertNil(deletedDocument3) - - let resultObjectIds = try await collection.find(filter: [:]) - XCTAssertEqual(resultObjectIds.count, 3) - } - - func testMongoCollectionUpdateOneAsyncAwait() async throws { - let collection = try await setupMongoCollection() - - let document: Document = ["name": "tomas", "breed": "jack rusell"] - let document1: Document = ["name": "lucas", "breed": "german shepard"] - let document2: Document = ["name": "fito", "breed": "goberian"] - - let objectIds = try await collection.insertMany([document, document1]) - XCTAssertNotNil(objectIds) - XCTAssertEqual(objectIds.count, 2) - - let updatedResult = try await collection.updateOneDocument(filter: document, - update: document2) - XCTAssertNil(updatedResult.documentId) - XCTAssertEqual(updatedResult.matchedCount, 1) - XCTAssertEqual(updatedResult.modifiedCount, 1) - - let updatedResult1 = try await collection.updateOneDocument(filter: document, - update: document2, - upsert: true) - XCTAssertNotNil(updatedResult1.documentId) - XCTAssertEqual(updatedResult1.matchedCount, 0) - XCTAssertEqual(updatedResult1.modifiedCount, 0) - } - - func testMongoCollectionUpdateManyAsyncAwait() async throws { - let collection = try await setupMongoCollection() - - let document: Document = ["name": "tomas", "breed": "jack rusell"] - let document1: Document = ["name": "fito", "breed": "goberian"] - let document2: Document = ["name": "fosca", "breed": "labradoodle"] - - let objectIds = try await collection.insertMany([document, document1]) - XCTAssertNotNil(objectIds) - XCTAssertEqual(objectIds.count, 2) - - let updatedResult = try await collection.updateManyDocuments(filter: document, - update: document2) - XCTAssertNil(updatedResult.documentId) - XCTAssertEqual(updatedResult.matchedCount, 1) - XCTAssertEqual(updatedResult.modifiedCount, 1) - - let updatedResult2 = try await collection.updateManyDocuments(filter: document, - update: document2, - upsert: true) - XCTAssertNotNil(updatedResult2.documentId) - XCTAssertEqual(updatedResult2.matchedCount, 0) - XCTAssertEqual(updatedResult2.modifiedCount, 0) - } - - func testMongoCollectionDeleteOneAsyncAwait() async throws { - let collection = try await setupMongoCollection() - - let document: Document = ["name": "tomas", "breed": "jack rusell"] - let document1: Document = ["name": "lucas", "breed": "german shepard"] - let document2: Document = ["name": "fito", "breed": "goberian"] - let document3: Document = ["name": "fosca", "breed": "labradoodle"] - - let deletedDocumentCount = try await collection.deleteOneDocument(filter: document) - XCTAssertEqual(deletedDocumentCount, 0) - - let objectIds = try await collection.insertMany([document, document1, document2, document3]) - XCTAssertNotNil(objectIds) - XCTAssertEqual(objectIds.count, 4) - - let deletedDocumentCount2 = try await collection.deleteOneDocument(filter: document) - XCTAssertEqual(deletedDocumentCount2, 1) - - let resultObjectIds2 = try await collection.find(filter: [:]) - XCTAssertEqual(resultObjectIds2.count, 3) - } - - func testMongoCollectionDeleteManyAsyncAwait() async throws { - let collection = try await setupMongoCollection() - - let document: Document = ["name": "tomas", "breed": "jack rusell"] - let document1: Document = ["name": "fito", "breed": "jack rusell"] - let document2: Document = ["name": "fosca", "breed": "labradoodle"] - let document3: Document = ["name": "balo", "breed": "pug"] - - let deletedDocumentCount = try await collection.deleteManyDocuments(filter: document) - XCTAssertEqual(deletedDocumentCount, 0) - - let objectIds = try await collection.insertMany([document, document1, document2, document3]) - XCTAssertNotNil(objectIds) - XCTAssertEqual(objectIds.count, 4) - - let deletedDocumentCount2 = try await collection.deleteManyDocuments(filter: ["breed": "jack rusell"]) - XCTAssertEqual(deletedDocumentCount2, 2) - - let resultObjectIds2 = try await collection.find(filter: [:]) - XCTAssertEqual(resultObjectIds2.count, 2) - } - - func testMongoCollectionAggregateAsyncAwait() async throws { - let collection = try await setupMongoCollection() - - let document: Document = ["name": "tomas", "breed": "jack rusell"] - let document1: Document = ["name": "lucas", "breed": "german shepard"] - - let objectIds = try await collection.insertMany([document, document1]) - XCTAssertNotNil(objectIds) - XCTAssertEqual(objectIds.count, 2) - - let documents = try await collection.aggregate(pipeline: [["$match": ["name": "tomas"]], ["$group": ["_id": "$name"]]]) - XCTAssertNotNil(documents) - XCTAssertEqual(documents.count, 1) - } - - func testMongoCollectionCountAsyncAwait() async throws { - let collection = try await setupMongoCollection() - - let document: Document = ["name": "tomas", "breed": "jack rusell"] - let document1: Document = ["name": "lucas", "breed": "jack rusell"] - let document2: Document = ["name": "fito", "breed": "goberian"] - let document3: Document = ["name": "balo", "breed": "pug"] - - let objectIds = try await collection.insertMany([document, document1, document2, document3]) - XCTAssertNotNil(objectIds) - XCTAssertEqual(objectIds.count, 4) - - let count = try await collection.count(filter: [:]) - XCTAssertEqual(count, 4) - - let count2 = try await collection.count(filter: document) - XCTAssertEqual(count2, 1) - - let count3 = try await collection.count(filter: ["breed": "jack rusell"]) - XCTAssertEqual(count3, 2) - - let count4 = try await collection.count(filter: [:], limit: 2) - XCTAssertEqual(count4, 2) - - let count5 = try await collection.count(filter: ["breed": "jack rusell"], limit: 1) - XCTAssertEqual(count5, 1) - } -} - -#endif // os(macOS) diff --git a/Realm/ObjectServerTests/SwiftObjectServerPartitionTests.swift b/Realm/ObjectServerTests/SwiftObjectServerPartitionTests.swift deleted file mode 100644 index 0aabd8c1e5..0000000000 --- a/Realm/ObjectServerTests/SwiftObjectServerPartitionTests.swift +++ /dev/null @@ -1,84 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import RealmSwift -import XCTest - -#if canImport(RealmTestSupport) -import RealmSwiftSyncTestSupport -#endif - -@available(macOS 13, *) -@objc(SwiftObjectServerPartitionTests) -class SwiftObjectServerPartitionTests: SwiftSyncTestCase { - func configuration(_ user: User, _ partitionValue: some BSON) -> Realm.Configuration { - var config = user.configuration(partitionValue: partitionValue) - config.objectTypes = [SwiftPerson.self] - return config - } - - - func writeObjects(_ user: User, _ partitionValue: some BSON) throws { - try autoreleasepool { - let realm = try Realm(configuration: configuration(user, partitionValue)) - try realm.write { - realm.add(SwiftPerson(firstName: "Ringo", lastName: "Starr")) - realm.add(SwiftPerson(firstName: "John", lastName: "Lennon")) - realm.add(SwiftPerson(firstName: "Paul", lastName: "McCartney")) - realm.add(SwiftPerson(firstName: "George", lastName: "Harrison")) - } - waitForUploads(for: realm) - } - } - - func roundTripForPartitionValue(partitionValue: some BSON) throws { - let partitionType = partitionBsonType(ObjectiveCSupport.convert(object: AnyBSON(partitionValue))!) - let appId = try RealmServer.shared.createApp(partitionKeyType: partitionType, types: [SwiftPerson.self]) - let partitionApp = app(id: appId) - let user = createUser(for: partitionApp) - let user2 = createUser(for: partitionApp) - let realm = try Realm(configuration: configuration(user, partitionValue)) - checkCount(expected: 0, realm, SwiftPerson.self) - - try writeObjects(user2, partitionValue) - waitForDownloads(for: realm) - checkCount(expected: 4, realm, SwiftPerson.self) - XCTAssertEqual(realm.objects(SwiftPerson.self).filter { $0.firstName == "Ringo" }.count, 1) - - try writeObjects(user2, partitionValue) - waitForDownloads(for: realm) - checkCount(expected: 8, realm, SwiftPerson.self) - XCTAssertEqual(realm.objects(SwiftPerson.self).filter { $0.firstName == "Ringo" }.count, 2) - } - - func testSwiftRoundTripForObjectIdPartitionValue() throws { - try roundTripForPartitionValue(partitionValue: ObjectId("1234567890ab1234567890ab")) - } - - func testSwiftRoundTripForUUIDPartitionValue() throws { - try roundTripForPartitionValue(partitionValue: UUID(uuidString: "b1c11e54-e719-4275-b631-69ec3f2d616d")!) - } - - func testSwiftRoundTripForStringPartitionValue() throws { - try roundTripForPartitionValue(partitionValue: "1234567890ab1234567890ab") - } - - func testSwiftRoundTripForIntPartitionValue() throws { - try roundTripForPartitionValue(partitionValue: 1234567890) - } -} diff --git a/Realm/ObjectServerTests/SwiftObjectServerTests.swift b/Realm/ObjectServerTests/SwiftObjectServerTests.swift deleted file mode 100644 index 97100ef1d2..0000000000 --- a/Realm/ObjectServerTests/SwiftObjectServerTests.swift +++ /dev/null @@ -1,1390 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#if os(macOS) - -import Combine -import Realm -import Realm.Private -@_spi(RealmSwiftExperimental) import RealmSwift -import XCTest - -#if canImport(RealmTestSupport) -import RealmSwiftSyncTestSupport -import RealmSyncTestSupport -import RealmTestSupport -import RealmSwiftTestSupport -#endif - -func assertAppError(_ error: AppError, _ code: AppError.Code, _ message: String, - line: UInt = #line, file: StaticString = #filePath) { - XCTAssertEqual(error.code, code, file: file, line: line) - XCTAssertEqual(error.localizedDescription, message, file: file, line: line) -} - -func assertSyncError(_ error: Error, _ code: SyncError.Code, _ message: String, - line: UInt = #line, file: StaticString = #filePath) { - let e = error as NSError - XCTAssertEqual(e.domain, RLMSyncErrorDomain, file: file, line: line) - XCTAssertEqual(e.code, code.rawValue, file: file, line: line) - XCTAssertEqual(e.localizedDescription, message, file: file, line: line) -} - -@available(macOS 13.0, *) -@objc(SwiftObjectServerTests) -class SwiftObjectServerTests: SwiftSyncTestCase { - override var objectTypes: [ObjectBase.Type] { - [ - SwiftCustomColumnObject.self, - SwiftPerson.self, - SwiftTypesSyncObject.self, - SwiftHugeSyncObject.self, - SwiftIntPrimaryKeyObject.self, - SwiftUUIDPrimaryKeyObject.self, - SwiftStringPrimaryKeyObject.self, - SwiftMissingObject.self, - SwiftAnyRealmValueObject.self, - ] - } - - func testUpdateBaseUrl() { - let app = App(id: appId) - XCTAssertEqual(app.baseURL, "https://services.cloud.mongodb.com") - - app.updateBaseUrl(to: "http://localhost:9090").await(self) - XCTAssertEqual(app.baseURL, "http://localhost:9090") - - app.updateBaseUrl(to: "http://127.0.0.1:9090").await(self) - XCTAssertEqual(app.baseURL, "http://127.0.0.1:9090") - - app.updateBaseUrl(to: nil).awaitFailure(self) - XCTAssertEqual(app.baseURL, "http://127.0.0.1:9090") - } - - @MainActor - func testBasicSwiftSync() throws { - XCTAssert(try openRealm().isEmpty, "Freshly synced Realm was not empty...") - } - - @MainActor - func testSwiftAddObjects() throws { - let realm = try openRealm() - checkCount(expected: 0, realm, SwiftPerson.self) - checkCount(expected: 0, realm, SwiftTypesSyncObject.self) - - try write { realm in - realm.add(SwiftPerson(firstName: "Ringo", lastName: "Starr")) - realm.add(SwiftPerson(firstName: "John", lastName: "Lennon")) - realm.add(SwiftPerson(firstName: "Paul", lastName: "McCartney")) - realm.add(SwiftTypesSyncObject(person: SwiftPerson(firstName: "George", lastName: "Harrison"))) - } - - waitForDownloads(for: realm) - checkCount(expected: 4, realm, SwiftPerson.self) - checkCount(expected: 1, realm, SwiftTypesSyncObject.self) - - let obj = realm.objects(SwiftTypesSyncObject.self).first! - XCTAssertEqual(obj.boolCol, true) - XCTAssertEqual(obj.intCol, 1) - XCTAssertEqual(obj.doubleCol, 1.1) - XCTAssertEqual(obj.stringCol, "string") - XCTAssertEqual(obj.binaryCol, Data("string".utf8)) - XCTAssertEqual(obj.decimalCol, Decimal128(1)) - XCTAssertEqual(obj.dateCol, Date(timeIntervalSince1970: -1)) - XCTAssertEqual(obj.longCol, Int64(1)) - XCTAssertEqual(obj.uuidCol, UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")!) - XCTAssertEqual(obj.anyCol.intValue, 1) - XCTAssertEqual(obj.objectCol!.firstName, "George") - } - - @MainActor - func testSwiftRountripForDistinctPrimaryKey() throws { - let realm = try openRealm() - checkCount(expected: 0, realm, SwiftPerson.self) // ObjectId - checkCount(expected: 0, realm, SwiftUUIDPrimaryKeyObject.self) - checkCount(expected: 0, realm, SwiftStringPrimaryKeyObject.self) - checkCount(expected: 0, realm, SwiftIntPrimaryKeyObject.self) - - try write { realm in - let swiftPerson = SwiftPerson(firstName: "Ringo", lastName: "Starr") - swiftPerson._id = ObjectId("1234567890ab1234567890ab") - realm.add(swiftPerson) - realm.add(SwiftUUIDPrimaryKeyObject(id: UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")!, strCol: "Steve", intCol: 10)) - realm.add(SwiftStringPrimaryKeyObject(id: "1234567890ab1234567890ab", strCol: "Paul", intCol: 20)) - realm.add(SwiftIntPrimaryKeyObject(id: 1234567890, strCol: "Jackson", intCol: 30)) - } - - waitForDownloads(for: realm) - checkCount(expected: 1, realm, SwiftPerson.self) - checkCount(expected: 1, realm, SwiftUUIDPrimaryKeyObject.self) - checkCount(expected: 1, realm, SwiftStringPrimaryKeyObject.self) - checkCount(expected: 1, realm, SwiftIntPrimaryKeyObject.self) - - let swiftOjectIdPrimaryKeyObject = realm.object(ofType: SwiftPerson.self, - forPrimaryKey: ObjectId("1234567890ab1234567890ab"))! - XCTAssertEqual(swiftOjectIdPrimaryKeyObject.firstName, "Ringo") - XCTAssertEqual(swiftOjectIdPrimaryKeyObject.lastName, "Starr") - - let swiftUUIDPrimaryKeyObject = realm.object(ofType: SwiftUUIDPrimaryKeyObject.self, - forPrimaryKey: UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")!)! - XCTAssertEqual(swiftUUIDPrimaryKeyObject.strCol, "Steve") - XCTAssertEqual(swiftUUIDPrimaryKeyObject.intCol, 10) - - let swiftStringPrimaryKeyObject = realm.object(ofType: SwiftStringPrimaryKeyObject.self, - forPrimaryKey: "1234567890ab1234567890ab")! - XCTAssertEqual(swiftStringPrimaryKeyObject.strCol, "Paul") - XCTAssertEqual(swiftStringPrimaryKeyObject.intCol, 20) - - let swiftIntPrimaryKeyObject = realm.object(ofType: SwiftIntPrimaryKeyObject.self, - forPrimaryKey: 1234567890)! - XCTAssertEqual(swiftIntPrimaryKeyObject.strCol, "Jackson") - XCTAssertEqual(swiftIntPrimaryKeyObject.intCol, 30) - } - - @MainActor - func testSwiftAddObjectsWithNilPartitionValue() throws { - // Use a fresh app as other tests touch the nil partition on the shared app - let app = app(id: try RealmServer.shared.createApp(types: [SwiftPerson.self])) - var config = createUser(for: app).configuration(partitionValue: .null) - config.objectTypes = [SwiftPerson.self] - - let realm = try Realm(configuration: config) - checkCount(expected: 0, realm, SwiftPerson.self) - - try autoreleasepool { - var config = createUser(for: app).configuration(partitionValue: .null) - config.objectTypes = [SwiftPerson.self] - let realm = try Realm(configuration: config) - try realm.write { - realm.add(SwiftPerson(firstName: "Ringo", lastName: "Starr")) - realm.add(SwiftPerson(firstName: "John", lastName: "Lennon")) - realm.add(SwiftPerson(firstName: "Paul", lastName: "McCartney")) - } - waitForUploads(for: realm) - } - - waitForDownloads(for: realm) - checkCount(expected: 3, realm, SwiftPerson.self) - } - - @MainActor - func testSwiftDeleteObjects() throws { - let realm = try openRealm() - try realm.write { - realm.add(SwiftPerson(firstName: "Ringo", lastName: "Starr")) - realm.add(SwiftPerson(firstName: "John", lastName: "Lennon")) - realm.add(SwiftPerson(firstName: "Paul", lastName: "McCartney")) - realm.add(SwiftTypesSyncObject(person: SwiftPerson(firstName: "George", lastName: "Harrison"))) - } - waitForUploads(for: realm) - checkCount(expected: 4, realm, SwiftPerson.self) - checkCount(expected: 1, realm, SwiftTypesSyncObject.self) - - try write { realm in - realm.deleteAll() - } - - checkCount(expected: 0, realm, SwiftPerson.self) - checkCount(expected: 0, realm, SwiftTypesSyncObject.self) - } - - @MainActor - func testMultiplePartitions() throws { - let partitionValueA = name - let partitionValueB = "\(name)bar" - let partitionValueC = "\(name)baz" - - let user1 = createUser() - - let realmA = try openRealm(user: user1, partitionValue: partitionValueA) - let realmB = try openRealm(user: user1, partitionValue: partitionValueB) - let realmC = try openRealm(user: user1, partitionValue: partitionValueC) - checkCount(expected: 0, realmA, SwiftPerson.self) - checkCount(expected: 0, realmB, SwiftPerson.self) - checkCount(expected: 0, realmC, SwiftPerson.self) - - try autoreleasepool { - let user2 = createUser() - - let realmA = try openRealm(user: user2, partitionValue: partitionValueA) - try realmA.write { - realmA.add(SwiftPerson(firstName: "Ringo", lastName: "Starr")) - realmA.add(SwiftPerson(firstName: "John", lastName: "Lennon")) - realmA.add(SwiftPerson(firstName: "Paul", lastName: "McCartney")) - } - - let realmB = try openRealm(user: user2, partitionValue: partitionValueB) - try realmB.write { - realmB.add(SwiftPerson(firstName: "John", lastName: "Lennon")) - realmB.add(SwiftPerson(firstName: "Paul", lastName: "McCartney")) - } - - let realmC = try openRealm(user: user2, partitionValue: partitionValueC) - try realmC.write { - realmC.add(SwiftPerson(firstName: "Ringo", lastName: "Starr")) - realmC.add(SwiftPerson(firstName: "John", lastName: "Lennon")) - realmC.add(SwiftPerson(firstName: "Paul", lastName: "McCartney")) - realmC.add(SwiftPerson(firstName: "George", lastName: "Harrison")) - realmC.add(SwiftPerson(firstName: "Pete", lastName: "Best")) - } - - waitForUploads(for: realmA) - waitForUploads(for: realmB) - waitForUploads(for: realmC) - } - - waitForDownloads(for: realmA) - waitForDownloads(for: realmB) - waitForDownloads(for: realmC) - - checkCount(expected: 3, realmA, SwiftPerson.self) - checkCount(expected: 2, realmB, SwiftPerson.self) - checkCount(expected: 5, realmC, SwiftPerson.self) - - XCTAssertEqual(realmA.objects(SwiftPerson.self).filter("firstName == %@", "Ringo").count, 1) - XCTAssertEqual(realmB.objects(SwiftPerson.self).filter("firstName == %@", "Ringo").count, 0) - } - - @MainActor - func testConnectionState() throws { - let realm = try openRealm(wait: false) - let session = realm.syncSession! - - func wait(forState desiredState: SyncSession.ConnectionState) { - let ex = expectation(description: "Wait for connection state: \(desiredState)") - let token = session.observe(\SyncSession.connectionState, options: .initial) { s, _ in - if s.connectionState == desiredState { - ex.fulfill() - } - } - waitForExpectations(timeout: 5.0) - token.invalidate() - } - - wait(forState: .connected) - - session.suspend() - wait(forState: .disconnected) - - session.resume() - wait(forState: .connecting) - wait(forState: .connected) - } - - // MARK: - Progress notifiers - @MainActor - @available(*, deprecated) - func testStreamingDownloadNotifier() throws { - let realm = try openRealm(wait: false) - let session = try XCTUnwrap(realm.syncSession) - var callCount = 0 - var progress: SyncSession.Progress? - let token = session.addProgressNotification(for: .download, mode: .reportIndefinitely) { p in - DispatchQueue.main.async { @MainActor in - // Verify that progress doesn't decrease, but sometimes it won't - // have increased since the last call - if let progress = progress { - XCTAssertGreaterThanOrEqual(p.progressEstimate, progress.progressEstimate) - } - progress = p - callCount += 1 - } - } - XCTAssertNotNil(token) - - try populateRealm() - waitForDownloads(for: realm) - - XCTAssertGreaterThanOrEqual(callCount, 1) - let p1 = try XCTUnwrap(progress) - XCTAssertEqual(p1.transferredBytes, p1.transferrableBytes) - XCTAssertEqual(p1.progressEstimate, 1.0) - XCTAssertTrue(p1.isTransferComplete) - let initialCallCount = callCount - - // Run a second time to upload more data and verify that the callback continues to be called - try populateRealm() - waitForDownloads(for: realm) - - XCTAssertGreaterThan(callCount, initialCallCount) - let p2 = try XCTUnwrap(progress) - XCTAssertEqual(p2.transferredBytes, p2.transferrableBytes) - XCTAssertEqual(p2.progressEstimate, 1.0) - XCTAssertTrue(p2.isTransferComplete) - - token!.invalidate() - } - - @MainActor - @available(*, deprecated) - func testStreamingUploadNotifier() throws { - let realm = try openRealm(wait: false) - let session = try XCTUnwrap(realm.syncSession) - - let progress = Locked(nil) - - let token = session.addProgressNotification(for: .upload, mode: .reportIndefinitely) { p in - progress.withLock { progress in - if let progress { - XCTAssertGreaterThanOrEqual(p.progressEstimate, progress.progressEstimate) - } - progress = p - } - } - XCTAssertNotNil(token) - waitForUploads(for: realm) - - for _ in 0..<5 { - progress.value = nil - try realm.write { - for _ in 0.. Int { - if let attr = try? FileManager.default.attributesOfItem(atPath: path) { - return attr[.size] as! Int - } - return 0 - } - XCTAssertFalse(RLMHasCachedRealmForPath(pathOnDisk)) - waitForExpectations(timeout: 10.0, handler: nil) - XCTAssertGreaterThan(fileSize(path: pathOnDisk), 0) - XCTAssertFalse(RLMHasCachedRealmForPath(pathOnDisk)) - } - - @MainActor - func testDownloadRealmToCustomPath() throws { - try populateRealm() - - let ex = expectation(description: "download-realm") - var config = try configuration() - config.fileURL = realmURLForFile("copy") - let pathOnDisk = ObjectiveCSupport.convert(object: config).pathOnDisk - XCTAssertEqual(pathOnDisk, config.fileURL!.path) - XCTAssertFalse(FileManager.default.fileExists(atPath: pathOnDisk)) - Realm.asyncOpen(configuration: config) { result in - switch result { - case .success(let realm): - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - case .failure(let error): - XCTFail("No realm on async open: \(error)") - } - ex.fulfill() - } - func fileSize(path: String) -> Int { - if let attr = try? FileManager.default.attributesOfItem(atPath: path) { - return attr[.size] as! Int - } - return 0 - } - XCTAssertFalse(RLMHasCachedRealmForPath(pathOnDisk)) - waitForExpectations(timeout: 10.0, handler: nil) - XCTAssertGreaterThan(fileSize(path: pathOnDisk), 0) - XCTAssertFalse(RLMHasCachedRealmForPath(pathOnDisk)) - } - - @MainActor - func testCancelDownloadRealm() throws { - try populateRealm() - - // Use a serial queue for asyncOpen to ensure that the first one adds - // the completion block before the second one cancels it - let queue = DispatchQueue(label: "io.realm.asyncOpen") - RLMSetAsyncOpenQueue(queue) - - let ex = expectation(description: "async open") - ex.expectedFulfillmentCount = 2 - let config = try configuration() - let completion = { (result: Result) in - guard case .failure = result else { - XCTFail("No error on cancelled async open") - return ex.fulfill() - } - ex.fulfill() - } - Realm.asyncOpen(configuration: config, callback: completion) - let task = Realm.asyncOpen(configuration: config, callback: completion) - queue.sync { task.cancel() } - waitForExpectations(timeout: 10.0, handler: nil) - } - - @MainActor func testAsyncOpenProgress() throws { - try populateRealm() - - let ex1 = expectation(description: "async open") - let ex2 = expectation(description: "download progress") - let config = try configuration() - let task = Realm.asyncOpen(configuration: config) { result in - XCTAssertNotNil(try? result.get()) - ex1.fulfill() - } - - task.addProgressNotification { progress in - if progress.isTransferComplete { - ex2.fulfill() - } - } - - waitForExpectations(timeout: 10.0, handler: nil) - } - - @MainActor - func config(baseURL: String, transport: RLMNetworkTransport, syncTimeouts: SyncTimeoutOptions? = nil) throws -> Realm.Configuration { - let appId = try RealmServer.shared.createApp(types: []) - let appConfig = AppConfiguration(baseURL: baseURL, transport: transport, syncTimeouts: syncTimeouts) - let app = App(id: appId, configuration: appConfig) - - let user = try logInUser(for: basicCredentials(app: app), app: app) - var config = user.configuration(partitionValue: name, cancelAsyncOpenOnNonFatalErrors: true) - config.objectTypes = [] - return config - } - - @MainActor - func testAsyncOpenTimeout() throws { - let proxy = TimeoutProxyServer(port: 5678, targetPort: 9090) - try proxy.start() - - let config = try config(baseURL: "http://localhost:5678", - transport: AsyncOpenConnectionTimeoutTransport(), - syncTimeouts: .init(connectTimeout: 2000, connectionLingerTime: 1)) - - // Two second timeout with a one second delay should work - autoreleasepool { - proxy.delay = 1.0 - let ex = expectation(description: "async open") - Realm.asyncOpen(configuration: config) { result in - let realm = try? result.get() - XCTAssertNotNil(realm) - realm?.syncSession?.suspend() - ex.fulfill() - } - waitForExpectations(timeout: 10.0, handler: nil) - } - - // The client doesn't disconnect immediately, and there isn't a good way - // to wait for it. In practice this should take more like 10ms to happen - // so a 1s sleep is plenty. - sleep(1) - - // Two second timeout with a two second delay should fail - autoreleasepool { - proxy.delay = 3.0 - let ex = expectation(description: "async open") - Realm.asyncOpen(configuration: config) { result in - guard case .failure(let error) = result else { - XCTFail("Did not fail: \(result)") - return - } - if let error = error as NSError? { - XCTAssertEqual(error.code, Int(ETIMEDOUT)) - XCTAssertEqual(error.domain, NSPOSIXErrorDomain) - } - ex.fulfill() - } - waitForExpectations(timeout: 20.0, handler: nil) - } - - proxy.stop() - } - - class LocationOverrideTransport: RLMNetworkTransport, Sendable { - let hostname: String - let wsHostname: String - init(hostname: String = "http://localhost:9090", wsHostname: String = "ws://invalid.com:9090") { - self.hostname = hostname - self.wsHostname = wsHostname - } - - override func sendRequest(toServer request: RLMRequest, completion: @escaping RLMNetworkTransportCompletionBlock) { - if request.url.hasSuffix("location") { - let response = RLMResponse() - response.httpStatusCode = 200 - response.body = "{\"deployment_model\":\"GLOBAL\",\"location\":\"US-VA\",\"hostname\":\"\(hostname)\",\"ws_hostname\":\"\(wsHostname)\"}" - completion(response) - } else { - super.sendRequest(toServer: request, completion: completion) - } - } - } - - @MainActor - func testDNSError() throws { - let config = try config(baseURL: "http://localhost:9090", transport: LocationOverrideTransport(wsHostname: "ws://invalid.com:9090")) - Realm.asyncOpen(configuration: config).awaitFailure(self, timeout: 40) { error in - assertSyncError(error, .connectionFailed, "Failed to connect to sync: Host not found (authoritative)") - } - } - - @MainActor - func testTLSError() throws { - let config = try config(baseURL: "http://localhost:9090", transport: LocationOverrideTransport(wsHostname: "wss://localhost:9090")) - Realm.asyncOpen(configuration: config).awaitFailure(self) { error in - assertSyncError(error, .tlsHandshakeFailed, "TLS handshake failed: SecureTransport error: record overflow (-9847)") - } - } - - func testAppCredentialSupport() { - XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.facebook(accessToken: "accessToken")), - RLMCredentials(facebookToken: "accessToken")) - - XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.google(serverAuthCode: "serverAuthCode")), - RLMCredentials(googleAuthCode: "serverAuthCode")) - - XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.apple(idToken: "idToken")), - RLMCredentials(appleToken: "idToken")) - - XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.emailPassword(email: "email", password: "password")), - RLMCredentials(email: "email", password: "password")) - - XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.jwt(token: "token")), - RLMCredentials(jwt: "token")) - - XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.function(payload: ["dog": ["name": "fido"]])), - RLMCredentials(functionPayload: ["dog": ["name" as NSString: "fido" as NSString] as NSDictionary])) - - XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.userAPIKey("key")), - RLMCredentials(userAPIKey: "key")) - - XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.serverAPIKey("key")), - RLMCredentials(serverAPIKey: "key")) - - XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.anonymous), - RLMCredentials.anonymous()) - } - - func testAppBaseUrl() { - let appConfig = AppConfiguration() - XCTAssertEqual(appConfig.baseURL, "https://services.cloud.mongodb.com") - - appConfig.baseURL = "https://foo.bar" - XCTAssertEqual(appConfig.baseURL, "https://foo.bar") - - appConfig.baseURL = nil - XCTAssertEqual(appConfig.baseURL, "https://services.cloud.mongodb.com") - } - - // MARK: - Authentication - - @MainActor func testInvalidCredentials() throws { - let email = "testInvalidCredentialsEmail" - let credentials = basicCredentials() - let user = try logInUser(for: credentials) - XCTAssertEqual(user.state, .loggedIn) - - let credentials2 = Credentials.emailPassword(email: email, password: "NOT_A_VALID_PASSWORD") - let ex = expectation(description: "Should fail to log in the user") - - self.app.login(credentials: credentials2) { result in - guard case .failure = result else { - XCTFail("Login should not have been successful") - return ex.fulfill() - } - ex.fulfill() - } - - waitForExpectations(timeout: 10, handler: nil) - } - - func testCustomTokenAuthentication() { - let user = logInUser(for: jwtCredential(withAppId: appId)) - XCTAssertEqual(user.profile.metadata["anotherName"], "Bar Foo") - XCTAssertEqual(user.profile.metadata["name"], "Foo Bar") - XCTAssertEqual(user.profile.metadata["occupation"], "firefighter") - } - - // MARK: - User-specific functionality - - @MainActor func testUserExpirationCallback() throws { - let user = createUser() - - // Set a callback on the user - let blockCalled = Locked(false) - let ex = expectation(description: "Error callback should fire upon receiving an error") - app.syncManager.errorHandler = { @Sendable (error, _) in - assertSyncError(error, .clientUserError, "Unable to refresh the user access token: signature is invalid") - blockCalled.value = true - ex.fulfill() - } - - // Screw up the token on the user. - setInvalidTokensFor(user) - // Try to open a Realm with the user; this will cause our errorHandler block defined above to be fired. - XCTAssertFalse(blockCalled.value) - var config = user.configuration(partitionValue: name) - config.objectTypes = [SwiftPerson.self] - _ = try Realm(configuration: config) - - waitForExpectations(timeout: 10.0, handler: nil) - } - - private func realmURLForFile(_ fileName: String) -> URL { - let testDir = RLMRealmPathForFile("mongodb-realm") - let directory = URL(fileURLWithPath: testDir, isDirectory: true) - return directory.appendingPathComponent(fileName, isDirectory: false) - } - - // MARK: - App tests - - private func appConfig() -> AppConfiguration { - return AppConfiguration(baseURL: "http://localhost:9090") - } - - func testAppInit() { - let appName = "translate-utwuv" - - let appWithNoConfig = App(id: appName) - XCTAssertEqual(appWithNoConfig.allUsers.count, 0) - - let appWithConfig = App(id: appName, configuration: appConfig()) - XCTAssertEqual(appWithConfig.allUsers.count, 0) - } - - func testAppLogin() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - let syncUser = app.login(credentials: Credentials.emailPassword(email: email, password: password)).await(self) - - XCTAssertEqual(syncUser.id, app.currentUser?.id) - XCTAssertEqual(app.allUsers.count, 1) - } - - func testAppSwitchAndRemove() { - let email1 = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password1 = randomString(10) - let email2 = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password2 = randomString(10) - - app.emailPasswordAuth.registerUser(email: email1, password: password1).await(self) - app.emailPasswordAuth.registerUser(email: email2, password: password2).await(self) - - let syncUser1 = app.login(credentials: Credentials.emailPassword(email: email1, password: password1)).await(self) - let syncUser2 = app.login(credentials: Credentials.emailPassword(email: email2, password: password2)).await(self) - - XCTAssertEqual(app.allUsers.count, 2) - - XCTAssertEqual(syncUser2.id, app.currentUser!.id) - - app.switch(to: syncUser1) - XCTAssertTrue(syncUser1.id == app.currentUser?.id) - - syncUser1.remove().await(self) - - XCTAssertEqual(syncUser2.id, app.currentUser!.id) - XCTAssertEqual(app.allUsers.count, 1) - } - - func testSafelyRemoveUser() throws { - // A user can have its state updated asynchronously so we need to make sure - // that remotely disabling / deleting a user is handled correctly in the - // sync error handler. - let user = createUser() - _ = try RealmServer.shared.removeUserForApp(appId, userId: user.id).get() - - // Set a callback on the user - let ex = expectation(description: "Error callback should fire upon receiving an error") - ex.assertForOverFulfill = false // error handler can legally be called multiple times - app.syncManager.errorHandler = { @Sendable (error, _) in - // Connecting to sync with a deleted user sometimes triggers an - // internal server error instead of the desired error - if (error as NSError).code == SyncError.clientSessionError.rawValue && error.localizedDescription == "error" { - ex.fulfill() - return - } - assertSyncError(error, .clientUserError, "Unable to refresh the user access token: invalid session: failed to find refresh token") - ex.fulfill() - } - - // Try to open a Realm with the user; this will cause our errorHandler block defined above to be fired. - var config = user.configuration(partitionValue: name) - config.objectTypes = [SwiftPerson.self] - _ = try Realm(configuration: config) - wait(for: [ex], timeout: 20.0) - } - - func testDeleteUser() { - func userExistsOnServer(_ user: User) -> Bool { - var userExists = false - switch RealmServer.shared.retrieveUser(appId, userId: user.id) { - case .success(let u): - let u = u as! [String: Any] - XCTAssertEqual(u["_id"] as! String, user.id) - userExists = true - case .failure: - userExists = false - } - return userExists - } - - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - - let syncUser = app.login(credentials: Credentials.emailPassword(email: email, password: password)).await(self) - XCTAssertTrue(userExistsOnServer(syncUser)) - - XCTAssertEqual(syncUser.id, app.currentUser?.id) - XCTAssertEqual(app.allUsers.count, 1) - - syncUser.delete().await(self) - - XCTAssertFalse(userExistsOnServer(syncUser)) - XCTAssertNil(app.currentUser) - XCTAssertEqual(app.allUsers.count, 0) - } - - func testAppLinkUser() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - let credentials = Credentials.emailPassword(email: email, password: password) - let syncUser = app.login(credentials: Credentials.anonymous).await(self) - syncUser.linkUser(credentials: credentials).await(self) - XCTAssertEqual(syncUser.id, app.currentUser?.id) - XCTAssertEqual(syncUser.identities.count, 2) - } - - // MARK: - Provider Clients - - func testEmailPasswordProviderClient() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - - app.emailPasswordAuth.confirmUser("atoken", tokenId: "atokenid").awaitFailure(self) { - assertAppError($0, .badRequest, "invalid token data") - } - app.emailPasswordAuth.resendConfirmationEmail(email: "atoken").awaitFailure(self) { - assertAppError($0, .userNotFound, "user not found") - } - app.emailPasswordAuth.retryCustomConfirmation(email: email).awaitFailure(self) { - assertAppError($0, .unknown, - "cannot run confirmation for \(email): automatic confirmation is enabled") - } - app.emailPasswordAuth.sendResetPasswordEmail(email: "atoken").awaitFailure(self) { - assertAppError($0, .userNotFound, "user not found") - } - app.emailPasswordAuth.resetPassword(to: "password", token: "atoken", tokenId: "tokenId").awaitFailure(self) { - assertAppError($0, .badRequest, "invalid token data") - } - app.emailPasswordAuth.callResetPasswordFunction(email: email, - password: randomString(10), - args: [[:]]).awaitFailure(self) { - assertAppError($0, .unknown, "failed to reset password for user \"\(email)\"") - } - } - - func testUserAPIKeyProviderClient() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - - let credentials = Credentials.emailPassword(email: email, password: password) - let syncUser = app.login(credentials: credentials).await(self) - - let apiKey = syncUser.apiKeysAuth.createAPIKey(named: "my-api-key").await(self) - XCTAssertEqual(apiKey.name, "my-api-key") - XCTAssertNotNil(apiKey.key) - XCTAssertNotEqual(apiKey.key!, "my-api-key") - XCTAssertFalse(apiKey.key!.isEmpty) - - syncUser.apiKeysAuth.fetchAPIKey(apiKey.objectId).await(self) - - let apiKeys = syncUser.apiKeysAuth.fetchAPIKeys().await(self) - XCTAssertEqual(apiKeys.count, 1) - - syncUser.apiKeysAuth.disableAPIKey(apiKey.objectId).await(self) - syncUser.apiKeysAuth.enableAPIKey(apiKey.objectId).await(self) - syncUser.apiKeysAuth.deleteAPIKey(apiKey.objectId).await(self) - - let apiKeys2 = syncUser.apiKeysAuth.fetchAPIKeys().await(self) - XCTAssertEqual(apiKeys2.count, 0) - } - - func testCallFunction() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - - let credentials = Credentials.emailPassword(email: email, password: password) - let syncUser = app.login(credentials: credentials).await(self) - - let bson = syncUser.functions.sum(1, 2, 3, 4, 5).await(self) - guard case let .int32(sum) = bson else { - XCTFail("unexpected bson type in sum: \(bson)") - return - } - XCTAssertEqual(sum, 15) - } - - func testPushRegistration() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - let credentials = Credentials.emailPassword(email: email, password: password) - app.login(credentials: credentials).await(self) - - let client = app.pushClient(serviceName: "gcm") - client.registerDevice(token: "some-token", user: app.currentUser!).await(self) - client.deregisterDevice(user: app.currentUser!).await(self) - } - - func testCustomUserData() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - let credentials = Credentials.emailPassword(email: email, password: password) - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - let user = app.login(credentials: credentials).await(self) - user.functions.updateUserData(["favourite_colour": "green", "apples": 10]).await(self) - - let customData = user.refreshCustomData().await(self) - XCTAssertEqual(customData["apples"] as! Int, 10) - XCTAssertEqual(customData["favourite_colour"] as! String, "green") - - XCTAssertEqual(user.customData["favourite_colour"], .string("green")) - XCTAssertEqual(user.customData["apples"], .int64(10)) - } - - // MARK: User Profile - - func testUserProfileInitialization() { - let profile = UserProfile() - XCTAssertNil(profile.name) - XCTAssertNil(profile.maxAge) - XCTAssertNil(profile.minAge) - XCTAssertNil(profile.birthday) - XCTAssertNil(profile.gender) - XCTAssertNil(profile.firstName) - XCTAssertNil(profile.lastName) - XCTAssertNil(profile.pictureURL) - XCTAssertEqual(profile.metadata, [:]) - } - - // MARK: Seed file path - - func testSeedFilePathOpenLocalToSync() throws { - var config = Realm.Configuration() - config.fileURL = RLMTestRealmURL() - config.objectTypes = [SwiftHugeSyncObject.self] - let realm = try Realm(configuration: config) - try realm.write { - for _ in 0.. SwiftMissingObject.anyCol -> SwiftPerson.firstName - let anyCol = ((obj!.anyCol.dynamicObject?.anyCol as? Object)?["anyCol"] as? Object) - XCTAssertEqual((anyCol?["firstName"] as? String), "Rick") - try realm.write { - anyCol?["firstName"] = "Morty" - } - XCTAssertEqual((anyCol?["firstName"] as? String), "Morty") - let objectCol = (obj!.anyCol.dynamicObject?.objectCol as? Object) - XCTAssertEqual((objectCol?["firstName"] as? String), "Morty") - } - - func testRevokeUserSessions() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - - let syncUser = app.login(credentials: Credentials.emailPassword(email: email, password: password)).await(self) - - // Should succeed refreshing custom data - syncUser.refreshCustomData().await(self) - - _ = try? RealmServer.shared.revokeUserSessions(appId, userId: syncUser.id).get() - - // Should fail refreshing custom data. This verifies we're correctly handling the error in RLMSessionDelegate - syncUser.refreshCustomData().awaitFailure(self) - - // This verifies that we don't crash in RLMEventSessionDelegate when creating a watch stream - // with a revoked user. See https://github.com/realm/realm-swift/issues/8519 - let watchTestUtility = WatchTestUtility(testCase: self, expectError: true) - _ = syncUser.collection(for: Dog.self, app: app).watch(delegate: watchTestUtility) - watchTestUtility.waitForOpen() - watchTestUtility.waitForClose() - - let didCloseError = watchTestUtility.didCloseError! as NSError - XCTAssertNotNil(didCloseError) - XCTAssertEqual(didCloseError.localizedDescription, "URLSession HTTP error code: 403") - XCTAssertNil(didCloseError.userInfo[NSUnderlyingErrorKey]) - } -} - -#endif // os(macOS) diff --git a/Realm/ObjectServerTests/SwiftServerObjects.swift b/Realm/ObjectServerTests/SwiftServerObjects.swift deleted file mode 100644 index 44a0a2de32..0000000000 --- a/Realm/ObjectServerTests/SwiftServerObjects.swift +++ /dev/null @@ -1,227 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import Foundation -import RealmSwift - -public class SwiftPerson: Object { - @Persisted(primaryKey: true) public var _id: ObjectId - @Persisted public var firstName: String = "" - @Persisted public var lastName: String = "" - @Persisted public var age: Int = 30 - - public convenience init(firstName: String, lastName: String, age: Int = 30) { - self.init() - self.firstName = firstName - self.lastName = lastName - self.age = age - } -} - -public class SwiftPersonWithAdditionalProperty: SwiftPerson { - @Persisted public var newProperty: Int - - public override class func _realmIgnoreClass() -> Bool { - true - } - public override class func _realmObjectName() -> String { - "SwiftPerson" - } - public override class func className() -> String { - "SwiftPersonWithAdditionalProperty" - } -} - -public class LinkToSwiftPerson: Object { - @Persisted(primaryKey: true) public var _id: ObjectId - @Persisted public var person: SwiftPerson? - @Persisted public var people: List - @Persisted public var peopleByName: Map -} - -@available(macOS 10.15, *) -extension SwiftPerson: ObjectKeyIdentifiable {} - -public class SwiftTypesSyncObject: Object { - @Persisted(primaryKey: true) public var _id: ObjectId - @Persisted public var boolCol: Bool = true - @Persisted public var intCol: Int = 1 - @Persisted public var doubleCol: Double = 1.1 - @Persisted public var stringCol: String = "string" - @Persisted public var binaryCol: Data = Data("string".utf8) - @Persisted public var dateCol: Date = Date(timeIntervalSince1970: -1) - @Persisted public var longCol: Int64 = 1 - @Persisted public var decimalCol: Decimal128 = Decimal128(1) - @Persisted public var uuidCol: UUID = UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")! - @Persisted public var objectIdCol: ObjectId - @Persisted public var objectCol: SwiftPerson? - @Persisted public var anyCol: AnyRealmValue = .int(1) - - public convenience init(person: SwiftPerson) { - self.init() - self.objectCol = person - } - - public convenience init(id: ObjectId) { - self.init() - self._id = id - } -} - -public class SwiftCollectionSyncObject: Object { - @Persisted(primaryKey: true) public var _id: ObjectId - @Persisted public var intList: List - @Persisted public var boolList: List - @Persisted public var stringList: List - @Persisted public var dataList: List - @Persisted public var dateList: List - @Persisted public var doubleList: List - @Persisted public var objectIdList: List - @Persisted public var decimalList: List - @Persisted public var uuidList: List - @Persisted public var anyList: List - @Persisted public var objectList: List - - @Persisted public var intSet: MutableSet - @Persisted public var stringSet: MutableSet - @Persisted public var dataSet: MutableSet - @Persisted public var dateSet: MutableSet - @Persisted public var doubleSet: MutableSet - @Persisted public var objectIdSet: MutableSet - @Persisted public var decimalSet: MutableSet - @Persisted public var uuidSet: MutableSet - @Persisted public var anySet: MutableSet - @Persisted public var objectSet: MutableSet - - @Persisted public var otherIntSet: MutableSet - @Persisted public var otherStringSet: MutableSet - @Persisted public var otherDataSet: MutableSet - @Persisted public var otherDateSet: MutableSet - @Persisted public var otherDoubleSet: MutableSet - @Persisted public var otherObjectIdSet: MutableSet - @Persisted public var otherDecimalSet: MutableSet - @Persisted public var otherUuidSet: MutableSet - @Persisted public var otherAnySet: MutableSet - @Persisted public var otherObjectSet: MutableSet - - @Persisted public var intMap: Map - @Persisted public var stringMap: Map - @Persisted public var dataMap: Map - @Persisted public var dateMap: Map - @Persisted public var doubleMap: Map - @Persisted public var objectIdMap: Map - @Persisted public var decimalMap: Map - @Persisted public var uuidMap: Map - @Persisted public var anyMap: Map - @Persisted public var objectMap: Map -} - -public class SwiftAnyRealmValueObject: Object { - @Persisted(primaryKey: true) public var _id: ObjectId - @Persisted public var anyCol: AnyRealmValue - @Persisted public var otherAnyCol: AnyRealmValue -} - -public class SwiftMissingObject: Object { - @Persisted(primaryKey: true) public var _id: ObjectId - @Persisted public var objectCol: SwiftPerson? - @Persisted public var anyCol: AnyRealmValue -} - -public class SwiftUUIDPrimaryKeyObject: Object { - @Persisted(primaryKey: true) public var _id: UUID? = UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")! - @Persisted public var strCol: String = "" - @Persisted public var intCol: Int = 0 - - public convenience init(id: UUID?, strCol: String, intCol: Int) { - self.init() - self._id = id - self.strCol = strCol - self.intCol = intCol - } -} - -public class SwiftStringPrimaryKeyObject: Object { - @Persisted(primaryKey: true) public var _id: String? = "1234567890ab1234567890ab" - @Persisted public var strCol: String = "" - @Persisted public var intCol: Int = 0 - - public convenience init(id: String, strCol: String, intCol: Int) { - self.init() - self._id = id - self.strCol = strCol - self.intCol = intCol - } -} - -public class SwiftIntPrimaryKeyObject: Object { - @Persisted(primaryKey: true) public var _id: Int = 1234567890 - @Persisted public var strCol: String = "" - @Persisted public var intCol: Int = 0 - - public convenience init(id: Int, strCol: String, intCol: Int) { - self.init() - self._id = id - self.strCol = strCol - self.intCol = intCol - } -} - -public class SwiftHugeSyncObject: Object { - @Persisted(primaryKey: true) public var _id: ObjectId - @Persisted public var data: Data? - @Persisted public var partition: String - - public class func create(key: String = "") -> SwiftHugeSyncObject { - let fakeDataSize = 1000000 - return SwiftHugeSyncObject(value: ["data": Data(repeating: 16, count: fakeDataSize), - "partition": key]) - } -} - -public let customColumnPropertiesMapping: [String: String] = ["id": "_id", - "boolCol": "custom_boolCol", - "intCol": "custom_intCol", - "doubleCol": "custom_doubleCol", - "stringCol": "custom_stringCol", - "binaryCol": "custom_binaryCol", - "dateCol": "custom_dateCol", - "longCol": "custom_longCol", - "decimalCol": "custom_decimalCol", - "uuidCol": "custom_uuidCol", - "objectIdCol": "custom_objectIdCol", - "objectCol": "custom_objectCol"] - -public class SwiftCustomColumnObject: Object { - @Persisted(primaryKey: true) public var id: ObjectId - @Persisted public var boolCol: Bool = true - @Persisted public var intCol: Int = 1 - @Persisted public var doubleCol: Double = 1.1 - @Persisted public var stringCol: String = "string" - @Persisted public var binaryCol = Data("string".utf8) - @Persisted public var dateCol: Date = Date(timeIntervalSince1970: -1) - @Persisted public var longCol: Int64 = 1 - @Persisted public var decimalCol: Decimal128 = Decimal128(1) - @Persisted public var uuidCol: UUID = UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")! - @Persisted public var objectIdCol: ObjectId? - @Persisted public var objectCol: SwiftCustomColumnObject? - - override class public func propertiesMapping() -> [String: String] { - customColumnPropertiesMapping - } -} diff --git a/Realm/ObjectServerTests/SwiftSyncTestCase.swift b/Realm/ObjectServerTests/SwiftSyncTestCase.swift deleted file mode 100644 index bf592a78bc..0000000000 --- a/Realm/ObjectServerTests/SwiftSyncTestCase.swift +++ /dev/null @@ -1,346 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#if os(macOS) - -import Combine -import XCTest -import RealmSwift - -#if canImport(RealmTestSupport) -import RealmTestSupport -import RealmSyncTestSupport -import RealmSwiftTestSupport -#endif - -public func randomString(_ length: Int) -> String { - let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - return String((0.. Realm.Configuration { - user.configuration(partitionValue: self.name) - } - - // Must be overriden in each subclass to specify which types will be used - // in this test case. - nonisolated open var objectTypes: [ObjectBase.Type] { - [SwiftPerson.self] - } - - override open func defaultObjectTypes() -> [AnyClass] { - objectTypes - } - - public func executeChild(file: StaticString = #filePath, line: UInt = #line) { - XCTAssert(0 == runChildAndWait(), "Tests in child process failed", file: file, line: line) - } - - public func basicCredentials(usernameSuffix: String = "", app: App? = nil) -> Credentials { - let email = "\(randomString(10))\(usernameSuffix)" - let password = "abcdef" - let credentials = Credentials.emailPassword(email: email, password: password) - let ex = expectation(description: "Should register in the user properly") - (app ?? self.app).emailPasswordAuth.registerUser(email: email, password: password, completion: { error in - XCTAssertNil(error) - ex.fulfill() - }) - wait(for: [ex], timeout: 4) - return credentials - } - - @MainActor - public func openRealm(app: App? = nil, wait: Bool = true) throws -> Realm { - let realm = try Realm(configuration: configuration(app: app)) - if wait { - waitForDownloads(for: realm) - } - return realm - } - - @MainActor - public func configuration(app: App? = nil) throws -> Realm.Configuration { - let user = try createUser(app: app) - var config = configuration(user: user) - config.objectTypes = self.objectTypes - return config - } - - @MainActor - public func openRealm(configuration: Realm.Configuration) throws -> Realm { - Realm.asyncOpen(configuration: configuration).await(self) - } - - @MainActor - public func openRealm(user: User, partitionValue: String) throws -> Realm { - var config = user.configuration(partitionValue: partitionValue) - config.objectTypes = self.objectTypes - return try openRealm(configuration: config) - } - - @MainActor - public func createUser(app: App? = nil) throws -> User { - let app = app ?? self.app - return try logInUser(for: basicCredentials(app: app), app: app) - } - - @MainActor - public func logInUser(for credentials: Credentials, app: App? = nil) throws -> User { - let user = (app ?? self.app).login(credentials: credentials).await(self, timeout: 60.0) - XCTAssertTrue(user.isLoggedIn) - return user - } - - public func waitForUploads(for realm: Realm) { - waitForUploads(for: ObjectiveCSupport.convert(object: realm)) - } - - public func waitForDownloads(for realm: Realm) { - waitForDownloads(for: ObjectiveCSupport.convert(object: realm)) - } - - // Populate the server-side data using the given block, which is called in - // a write transaction. Note that unlike the obj-c versions, this works for - // both PBS and FLX sync. - @MainActor - public func write(app: App? = nil, _ block: (Realm) throws -> Void) throws { - try autoreleasepool { - let realm = try openRealm(app: app) - RLMRealmSubscribeToAll(ObjectiveCSupport.convert(object: realm)) - - try realm.write { - try block(realm) - } - waitForUploads(for: realm) - - let syncSession = try XCTUnwrap(realm.syncSession) - syncSession.suspend() - syncSession.parentUser()?.remove().await(self) - } - } - - public func checkCount(expected: Int, - _ realm: Realm, - _ type: T.Type, - file: StaticString = #filePath, - line: UInt = #line) { - realm.refresh() - let actual = realm.objects(type).count - XCTAssertEqual(actual, expected, - "Error: expected \(expected) items, but got \(actual) (process: \(isParent ? "parent" : "child"))", - file: file, line: line) - } - - var exceptionThrown = false - - public func assertThrows(_ block: @autoclosure () -> T, named: String? = RLMExceptionName, - _ message: String? = nil, fileName: String = #file, lineNumber: UInt = #line) { - exceptionThrown = true - RLMAssertThrowsWithName(self, { _ = block() }, named, message, fileName, lineNumber) - } - - public func assertThrows(_ block: @autoclosure () -> T, reason: String, - _ message: String? = nil, fileName: String = #file, lineNumber: UInt = #line) { - exceptionThrown = true - RLMAssertThrowsWithReason(self, { _ = block() }, reason, message, fileName, lineNumber) - } - - public func assertThrows(_ block: @autoclosure () -> T, reasonMatching regexString: String, - _ message: String? = nil, fileName: String = #file, lineNumber: UInt = #line) { - exceptionThrown = true - RLMAssertThrowsWithReasonMatching(self, { _ = block() }, regexString, message, fileName, lineNumber) - } - - public static let bigObjectCount = 2 - @MainActor - public func populateRealm() throws { - try write { realm in - for _ in 0.. MongoCollection { - let collection = anonymousUser.collection(for: type, app: app) - removeAllFromCollection(collection) - return collection - } - - public func removeAllFromCollection(_ collection: MongoCollection) { - let deleteEx = expectation(description: "Delete all from Mongo collection") - collection.deleteManyDocuments(filter: [:]) { result in - if case .failure = result { - XCTFail("Should delete") - } - deleteEx.fulfill() - } - wait(for: [deleteEx], timeout: 30.0) - } - - @MainActor - public func waitForCollectionCount(_ collection: MongoCollection, _ count: Int) { - let waitStart = Date() - while collection.count(filter: [:]).await(self) < count && waitStart.timeIntervalSinceNow > -600.0 { - sleep(1) - } - XCTAssertEqual(collection.count(filter: [:]).await(self), count) - } - - // MARK: - Async helpers - - // These are async versions of the synchronous functions defined above. - // They should function identically other than being async rather than using - // expecatations to synchronously await things. -#if compiler(<6) - public func basicCredentials(usernameSuffix: String = "", app: App? = nil) async throws -> Credentials { - let email = "\(randomString(10))\(usernameSuffix)" - let password = "abcdef" - let credentials = Credentials.emailPassword(email: email, password: password) - try await (app ?? self.app).emailPasswordAuth.registerUser(email: email, password: password) - return credentials - } -#else - public func basicCredentials( - usernameSuffix: String = "", app: App? = nil, _isolation: isolated (any Actor)? = #isolation - ) async throws -> Credentials { - let email = "\(randomString(10))\(usernameSuffix)" - let password = "abcdef" - let credentials = Credentials.emailPassword(email: email, password: password) - try await (app ?? self.app).emailPasswordAuth.registerUser(email: email, password: password) - return credentials - } -#endif - - @MainActor - @nonobjc public func openRealm() async throws -> Realm { - try await Realm(configuration: configuration(), downloadBeforeOpen: .always) - } - - @MainActor - public func write(_ block: @escaping (Realm) throws -> Void) async throws { - try await Task { - let realm = try await openRealm() - try await realm.asyncWrite { - try block(realm) - } - let syncSession = try XCTUnwrap(realm.syncSession) - try await syncSession.wait(for: .upload) - syncSession.suspend() - try await syncSession.parentUser()?.remove() - }.value - } - -#if compiler(<6) - public func createUser(app: App? = nil) async throws -> User { - let credentials = try await basicCredentials(app: app) - return try await (app ?? self.app).login(credentials: credentials) - } -#else - public func createUser(app: App? = nil, _isolation: isolated (any Actor)? = #isolation) async throws -> User { - let credentials = try await basicCredentials(app: app) - return try await (app ?? self.app).login(credentials: credentials) - } -#endif -} - -@available(macOS 10.15, watchOS 6.0, iOS 13.0, tvOS 13.0, *) -public extension Publisher { - func expectValue(_ testCase: XCTestCase, _ expectation: XCTestExpectation, - receiveValue: (@Sendable (Self.Output) -> Void)? = nil) -> AnyCancellable { - sink(receiveCompletion: { result in - if case .failure(let error) = result { - XCTFail("Unexpected failure: \(error)") - } - }, receiveValue: { value in - receiveValue?(value) - expectation.fulfill() - }) - } - - // Synchronously await non-error completion of the publisher, calling the - // `receiveValue` callback with the value if supplied. - func await(_ testCase: XCTestCase, timeout: TimeInterval = 20.0, receiveValue: (@Sendable (Self.Output) -> Void)? = nil) { - let expectation = testCase.expectation(description: "Async combine pipeline") - let cancellable = self.expectValue(testCase, expectation, receiveValue: receiveValue) - testCase.wait(for: [expectation], timeout: timeout) - cancellable.cancel() - } - - // Synchronously await non-error completion of the publisher, returning the published value. - @discardableResult - func await(_ testCase: XCTestCase, timeout: TimeInterval = 20.0) -> Self.Output { - let expectation = testCase.expectation(description: "Async combine pipeline") - let value = Locked(Self.Output?.none) - let cancellable = self.expectValue(testCase, expectation, receiveValue: { value.wrappedValue = $0 }) - testCase.wait(for: [expectation], timeout: timeout) - cancellable.cancel() - return value.wrappedValue! - } - - // Synchrously await error completion of the publisher - func awaitFailure(_ testCase: XCTestCase, timeout: TimeInterval = 20.0, - _ errorHandler: (@Sendable (Self.Failure) -> Void)? = nil) { - let expectation = testCase.expectation(description: "Async combine pipeline should fail") - let cancellable = sink(receiveCompletion: { @Sendable result in - if case .failure(let error) = result { - errorHandler?(error) - expectation.fulfill() - } - }, receiveValue: { @Sendable value in - XCTFail("Should have failed but got \(value)") - }) - testCase.wait(for: [expectation], timeout: timeout) - cancellable.cancel() - } - - func awaitFailure(_ testCase: XCTestCase, timeout: TimeInterval = 20.0, - _ errorHandler: @escaping (@Sendable (E) -> Void)) { - awaitFailure(testCase, timeout: timeout) { error in - guard let error = error as? E else { - XCTFail("Expected error of type \(E.self), got \(error)") - return - } - errorHandler(error) - } - } -} - -#endif // os(macOS) diff --git a/Realm/ObjectServerTests/SwiftUIServerTests.swift b/Realm/ObjectServerTests/SwiftUIServerTests.swift deleted file mode 100644 index eef1bfd2d4..0000000000 --- a/Realm/ObjectServerTests/SwiftUIServerTests.swift +++ /dev/null @@ -1,635 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import RealmSwift -import XCTest -import SwiftUI -import Combine - -#if canImport(RealmTestSupport) -import RealmSwiftSyncTestSupport -import RealmSyncTestSupport -import RealmSwiftTestSupport -#endif - -@MainActor protocol AsyncOpenStateWrapper { - func cancel() - var wrappedValue: AsyncOpenState { get } - var projectedValue: Published.Publisher { get } -} -extension AutoOpen: AsyncOpenStateWrapper {} -extension AsyncOpen: AsyncOpenStateWrapper {} - -@available(macOS 13, *) -@MainActor -class SwiftUIServerTests: SwiftSyncTestCase { - override var objectTypes: [ObjectBase.Type] { - [SwiftHugeSyncObject.self] - } - - nonisolated let cancellables = Locked(Set()) - override func tearDown() { - cancellables.withLock { - $0.forEach { $0.cancel() } - $0 = [] - } - super.tearDown() - } - - override class var defaultTestSuite: XCTestSuite { - // Don't run tests for the base class - if self === SwiftUIServerTests.self { - return XCTestSuite(name: "SwiftUIServerTests") - } - return super.defaultTestSuite - } - - - func awaitOpen(_ wrapper: some AsyncOpenStateWrapper, - handler: @escaping (AsyncOpenState) -> Void) { - _ = wrapper.wrappedValue // Retrieving the wrappedValue to simulate a SwiftUI environment where this is called when initialising the view. - wrapper.projectedValue - .sink(receiveValue: handler) - .store(in: cancellables) - waitForExpectations(timeout: 10.0) - wrapper.cancel() - } - - // Configuration for tests - func configuration(user: User, partition: String) -> Realm.Configuration { - fatalError() - } - - // MARK: - AsyncOpen - func asyncOpen(appId: String?, partitionValue: String, configuration: Realm.Configuration, - timeout: UInt? = nil, handler: @escaping (AsyncOpenState) -> Void) { - fatalError() - } - - func asyncOpen(user: User, appId: String?, partitionValue: String, timeout: UInt? = nil, - handler: @escaping (AsyncOpenState) -> Void) { - fatalError() - } - - func asyncOpen(handler: @escaping (AsyncOpenState) -> Void) throws { - asyncOpen(appId: appId, partitionValue: self.name, configuration: try configuration(), - handler: handler) - } - - func testAsyncOpenOpenRealm() throws { - let ex = expectation(description: "download-realm-async-open") - try asyncOpen { asyncOpenState in - if case .open = asyncOpenState { - ex.fulfill() - } - } - } - - func testAsyncOpenDownloadRealm() throws { - try populateRealm() - let ex = expectation(description: "download-populated-realm-async-open") - try asyncOpen { asyncOpenState in - if case let .open(realm) = asyncOpenState { - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - ex.fulfill() - } - } - } - - func testAsyncOpenWaitingForUserWithoutUserLoggedIn() throws { - let user = createUser() - user.logOut().await(self) - - let ex = expectation(description: "download-realm-async-open-not-logged") - asyncOpen(user: user, appId: appId, partitionValue: name) { asyncOpenState in - if case .waitingForUser = asyncOpenState { - ex.fulfill() - } - } - } - - // In case of no internet connection AsyncOpen should return an error if there is a timeout - func testAsyncOpenFailWithoutInternetConnection() throws { - let proxy = TimeoutProxyServer(port: 5678, targetPort: 9090) - try proxy.start() - - let appId = try RealmServer.shared.createApp(types: self.objectTypes) - let appConfig = AppConfiguration(baseURL: "http://localhost:5678", - transport: AsyncOpenConnectionTimeoutTransport()) - let app = App(id: appId, configuration: appConfig) - let user = try createUser(app: app) - - proxy.dropConnections = true - let ex = expectation(description: "download-realm-async-open-no-connection") - asyncOpen(user: user, appId: appId, - partitionValue: name, timeout: 1000) { asyncOpenState in - if case let .error(error) = asyncOpenState, - let nsError = error as NSError? { - XCTAssertEqual(nsError.code, Int(ETIMEDOUT)) - XCTAssertEqual(nsError.domain, NSPOSIXErrorDomain) - ex.fulfill() - } - } - - proxy.stop() - } - - @MainActor - func testAsyncOpenProgressNotification() throws { - try populateRealm() - let ex = expectation(description: "progress-async-open") - try asyncOpen { asyncOpenState in - if case let .progress(progress) = asyncOpenState { - XCTAssertTrue(progress.fractionCompleted > 0) - if progress.isFinished { - ex.fulfill() - } - } - } - } - - // Cached App is already created on the setup of the test - func testAsyncOpenWithACachedApp() throws { - try populateRealm() - let ex = expectation(description: "download-cached-app-async-open") - asyncOpen(user: createUser(), appId: nil, partitionValue: name) { asyncOpenState in - if case let .open(realm) = asyncOpenState { - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - ex.fulfill() - } - } - } - - func testAsyncOpenThrowExceptionWithoutCachedApp() throws { - resetAppCache() - assertThrows(AsyncOpen(partitionValue: name), - reason: "Cannot AsyncOpen the Realm because no appId was found. You must either explicitly pass an appId or initialize an App before displaying your View.") - } - - func testAsyncOpenThrowExceptionWithMoreThanOneCachedApp() throws { - _ = App(id: "fake 1") - _ = App(id: "fake 2") - assertThrows(AsyncOpen(partitionValue: name), - reason: "Cannot AsyncOpen the Realm because more than one appId was found. When using multiple Apps you must explicitly pass an appId to indicate which to use.") - } - - func testAsyncOpenWithDifferentPartitionValues() throws { - try populateRealm() - let emptyPartition = "\(name) empty partition" - - let ex = expectation(description: "download-partition-value-async-open") - try asyncOpen { asyncOpenState in - if case let .open(realm) = asyncOpenState { - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - ex.fulfill() - } - } - - let ex2 = expectation(description: "download-other-partition-value-async-open") - asyncOpen(user: createUser(), appId: nil, partitionValue: emptyPartition) { asyncOpenState in - if case let .open(realm) = asyncOpenState { - self.checkCount(expected: 0, realm, SwiftHugeSyncObject.self) - ex2.fulfill() - } - } - } - - func testAsyncOpenWithMultiUserApp() throws { - try populateRealm() - - let syncUser1 = createUser() - let syncUser2 = createUser() - XCTAssertEqual(app.allUsers.count, 2) - XCTAssertEqual(syncUser2.id, app.currentUser?.id) - - let ex = expectation(description: "test-multiuser1-app-async-open") - asyncOpen(user: syncUser2, appId: appId, partitionValue: name) { asyncOpenState in - if case let .open(realm) = asyncOpenState { - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - XCTAssertEqual(realm.syncSession?.parentUser(), syncUser2) - ex.fulfill() - } - } - - app.switch(to: syncUser1) - XCTAssertEqual(app.allUsers.count, 2) - XCTAssertEqual(syncUser1.id, app.currentUser?.id) - - let ex2 = expectation(description: "test-multiuser2-app-async-open") - asyncOpen(user: syncUser1, appId: appId, partitionValue: name) { asyncOpenState in - if case let .open(realm) = asyncOpenState { - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - XCTAssertEqual(realm.syncSession?.parentUser(), syncUser1) - ex2.fulfill() - } - } - } - - func testAsyncOpenWithUserAfterLogoutFromAnonymous() throws { - try populateRealm() - - let anonymousUser = self.anonymousUser - let ex = expectation(description: "download-realm-anonymous-user-async-open") - asyncOpen(user: anonymousUser, appId: appId, partitionValue: name) { asyncOpenState in - if case let .open(realm) = asyncOpenState { - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - ex.fulfill() - } - } - - anonymousUser.logOut().await(self) - - let ex2 = expectation(description: "download-realm-after-logout-async-open") - asyncOpen(user: createUser(), appId: appId, partitionValue: name) { asyncOpenState in - if case let .open(realm) = asyncOpenState { - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - ex2.fulfill() - } - } - } - - // MARK: - AutoOpen - func autoOpen(appId: String?, partitionValue: String, configuration: Realm.Configuration, - timeout: UInt?, handler: @escaping (AsyncOpenState) -> Void) { - fatalError() - } - - func autoOpen(user: User, appId: String?, partitionValue: String, timeout: UInt? = nil, - handler: @escaping (AsyncOpenState) -> Void) { - fatalError() - } - - func autoOpen(handler: @escaping (AsyncOpenState) -> Void) throws { - autoOpen(appId: appId, partitionValue: self.name, configuration: try configuration(), - timeout: nil, handler: handler) - } - - func testAutoOpenOpenRealm() throws { - let ex = expectation(description: "download-realm-auto-open") - try autoOpen { autoOpenState in - if case .open = autoOpenState { - ex.fulfill() - } - } - } - - func testAutoOpenDownloadRealm() throws { - try populateRealm() - let ex = expectation(description: "download-populated-realm-auto-open") - try autoOpen { autoOpenState in - if case let .open(realm) = autoOpenState { - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - ex.fulfill() - } - } - } - - @MainActor - func testAutoOpenWaitingForUserWithoutUserLoggedIn() throws { - let user = try logInUser(for: basicCredentials()) - user.logOut().await(self) - - let ex = expectation(description: "download-realm-auto-open-not-logged") - autoOpen(user: user, appId: appId, partitionValue: name) { autoOpenState in - if case .waitingForUser = autoOpenState { - ex.fulfill() - } - } - } - - // In case of no internet connection AutoOpen should return an opened Realm, offline-first approach - func testAutoOpenOpenRealmWithoutInternetConnection() throws { - try populateRealm() - resetAppCache() - - let proxy = TimeoutProxyServer(port: 5678, targetPort: 9090) - try proxy.start() - let appConfig = AppConfiguration(baseURL: "http://localhost:5678", - transport: AsyncOpenConnectionTimeoutTransport()) - let app = App(id: appId, configuration: appConfig) - let user = try createUser(app: app) - proxy.dropConnections = true - let ex = expectation(description: "download-realm-auto-open-no-connection") - autoOpen(user: user, appId: appId, partitionValue: name, timeout: 1000) { autoOpenState in - if case let .open(realm) = autoOpenState { - XCTAssertTrue(realm.isEmpty) // should not have downloaded anything - ex.fulfill() - } - } - - // Clear cache to avoid leaking our app which connects to the proxy - App.resetAppCache() - proxy.stop() - } - - func testAutoOpenProgressNotification() throws { - try populateRealm() - - let user = createUser() - let ex = expectation(description: "progress-auto-open") - autoOpen(user: user, appId: appId, partitionValue: name) { autoOpenState in - if case let .progress(progress) = autoOpenState { - XCTAssertTrue(progress.fractionCompleted > 0) - if progress.isFinished { - ex.fulfill() - } - } - } - } - - // App is already created on the setup of the test - func testAutoOpenWithACachedApp() throws { - try populateRealm() - - let ex = expectation(description: "download-cached-app-auto-open") - try autoOpen { autoOpenState in - if case let .open(realm) = autoOpenState { - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - ex.fulfill() - } - } - } - - func testAutoOpenThrowExceptionWithoutCachedApp() throws { - resetAppCache() - assertThrows(AutoOpen(partitionValue: name), - reason: "Cannot AsyncOpen the Realm because no appId was found. You must either explicitly pass an appId or initialize an App before displaying your View.") - } - - @MainActor - func testAutoOpenThrowExceptionWithMoreThanOneCachedApp() throws { - _ = App(id: "fake 1") - _ = App(id: "fake 2") - assertThrows(AutoOpen(partitionValue: name), - reason: "Cannot AsyncOpen the Realm because more than one appId was found. When using multiple Apps you must explicitly pass an appId to indicate which to use.") - } - - @MainActor - func testAutoOpenWithMultiUserApp() throws { - try populateRealm() - let partitionValueA = name - let partitionValueB = "\(name) 2" - - let syncUser1 = createUser() - let syncUser2 = createUser() - XCTAssertEqual(app.allUsers.count, 2) - XCTAssertEqual(syncUser2.id, app.currentUser!.id) - - let ex = expectation(description: "test-multiuser1-app-auto-open") - autoOpen(user: syncUser2, appId: appId, partitionValue: partitionValueB) { autoOpenState in - if case let .open(realm) = autoOpenState { - self.checkCount(expected: 0, realm, SwiftHugeSyncObject.self) - ex.fulfill() - } - } - - app.switch(to: syncUser1) - XCTAssertEqual(app.allUsers.count, 2) - XCTAssertEqual(syncUser1.id, app.currentUser!.id) - - let ex2 = expectation(description: "test-multiuser2-app-auto-open") - autoOpen(user: syncUser1, appId: appId, partitionValue: partitionValueA) { autoOpenState in - if case let .open(realm) = autoOpenState { - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - ex2.fulfill() - } - } - } - - func testAutoOpenWithUserAfterLogoutFromAnonymous() throws { - let partitionValueA = name - let partitionValueB = "\(name) 2" - try populateRealm() - - let anonymousUser = self.anonymousUser - let ex = expectation(description: "download-realm-anonymous-user-auto-open") - autoOpen(user: anonymousUser, appId: appId, partitionValue: partitionValueB) { autoOpenState in - if case let .open(realm) = autoOpenState { - self.checkCount(expected: 0, realm, SwiftHugeSyncObject.self) - ex.fulfill() - } - } - - anonymousUser.logOut().await(self) - - let ex2 = expectation(description: "download-realm-after-logout-auto-open") - autoOpen(user: createUser(), appId: appId, partitionValue: partitionValueA) { autoOpenState in - if case let .open(realm) = autoOpenState { - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - ex2.fulfill() - } - } - } - - func testAutoOpenWithDifferentPartitionValues() throws { - try populateRealm() - let partitionValueA = name - let partitionValueB = "\(name) 2" - - let user = createUser() - let ex = expectation(description: "download-partition-value-auto-open") - autoOpen(user: user, appId: nil, partitionValue: partitionValueA) { autoOpenState in - if case let .open(realm) = autoOpenState { - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - ex.fulfill() - } - } - - let ex2 = expectation(description: "download-other-partition-value-auto-open") - autoOpen(user: user, appId: nil, partitionValue: partitionValueB) { autoOpenState in - if case let .open(realm) = autoOpenState { - self.checkCount(expected: 0, realm, SwiftHugeSyncObject.self) - ex2.fulfill() - } - } - } - - // MARK: - Mixed AsyncOpen & AutoOpen - func testCombineAsyncOpenAutoOpenWithDifferentPartitionValues() throws { - try populateRealm() - let partitionValueA = name - let partitionValueB = "\(name) 2" - - let user = createUser() - let ex = expectation(description: "download-partition-value-async-open-mixed") - asyncOpen(user: user, appId: nil, partitionValue: partitionValueA) { asyncOpenState in - if case let .open(realm) = asyncOpenState { - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - ex.fulfill() - } - } - - let ex2 = expectation(description: "download-partition-value-auto-open-mixed") - autoOpen(user: user, appId: nil, partitionValue: partitionValueB) { autoOpenState in - if case let .open(realm) = autoOpenState { - self.checkCount(expected: 0, realm, SwiftHugeSyncObject.self) - ex2.fulfill() - } - } - } - - func testCombineAsyncOpenAutoOpenWithMultiUserApp() throws { - try populateRealm() - let partitionValueA = name - let partitionValueB = "\(name) 2" - - let syncUser1 = createUser() - let syncUser2 = createUser() - XCTAssertEqual(app.allUsers.count, 2) - XCTAssertEqual(syncUser2.id, app.currentUser!.id) - - anonymousUser.remove().await(self) - - let ex = expectation(description: "test-combine-multiuser1-app-auto-open") - autoOpen(user: syncUser2, appId: appId, partitionValue: partitionValueB) { autoOpenState in - if case let .open(realm) = autoOpenState { - self.checkCount(expected: 0, realm, SwiftHugeSyncObject.self) - ex.fulfill() - } - } - - app.switch(to: syncUser1) - XCTAssertEqual(app.allUsers.count, 2) - XCTAssertEqual(syncUser1.id, app.currentUser!.id) - - let ex2 = expectation(description: "test-combine-multiuser2-app-auto-open") - asyncOpen(user: syncUser1, appId: appId, partitionValue: partitionValueA) { asyncOpenState in - if case let .open(realm) = asyncOpenState { - self.checkCount(expected: SwiftSyncTestCase.bigObjectCount, realm, SwiftHugeSyncObject.self) - ex2.fulfill() - } - } - } -} - -@available(macOS 13, *) -@MainActor -class PBSSwiftUIServerTests: SwiftUIServerTests { - override func configuration(user: User, partition: String) -> Realm.Configuration { - var userConfiguration = user.configuration(partitionValue: partition) - userConfiguration.objectTypes = self.objectTypes - return userConfiguration - } - - // MARK: - AsyncOpen - override func asyncOpen(appId: String?, partitionValue: String, configuration: Realm.Configuration, - timeout: UInt? = nil, handler: @escaping (AsyncOpenState) -> Void) { - let asyncOpen = AsyncOpen(appId: appId, - partitionValue: partitionValue, - configuration: configuration, - timeout: timeout) - awaitOpen(asyncOpen, handler: handler) - } - - override func asyncOpen(user: User, appId: String?, partitionValue: String, timeout: UInt? = nil, - handler: @escaping (AsyncOpenState) -> Void) { - let configuration = self.configuration(user: user, partition: partitionValue) - asyncOpen(appId: appId, - partitionValue: partitionValue, - configuration: configuration, - timeout: timeout, - handler: handler) - } - - override func autoOpen(appId: String?, partitionValue: String, configuration: Realm.Configuration, - timeout: UInt?, handler: @escaping (AsyncOpenState) -> Void) { - let autoOpen = AutoOpen(appId: appId, - partitionValue: partitionValue, - configuration: configuration, - timeout: timeout) - awaitOpen(autoOpen, handler: handler) - } - - override func autoOpen(user: User, appId: String?, partitionValue: String, timeout: UInt? = nil, - handler: @escaping (AsyncOpenState) -> Void) { - let configuration = self.configuration(user: user, partition: partitionValue) - autoOpen(appId: appId, - partitionValue: partitionValue, - configuration: configuration, - timeout: timeout, - handler: handler) - } -} - -@available(macOS 13, *) -@MainActor -class FLXSwiftUIServerTests: SwiftUIServerTests, Sendable { - override func createApp() throws -> String { - try createFlexibleSyncApp() - } - - override func configuration(user: User) -> Realm.Configuration { - user.flexibleSyncConfiguration { subs in - subs.append(QuerySubscription { - $0.partition == self.name - }) - } - } - - override func configuration(user: User, partition: String) -> Realm.Configuration { - var userConfiguration = user.flexibleSyncConfiguration { subs in - subs.append(QuerySubscription { - $0.partition == partition - }) - } - userConfiguration.objectTypes = self.objectTypes - return userConfiguration - } - - // MARK: - AsyncOpen - override func asyncOpen(appId: String?, partitionValue: String, configuration: Realm.Configuration, - timeout: UInt? = nil, handler: @escaping (AsyncOpenState) -> Void) { - let asyncOpen = AsyncOpen(appId: appId, - configuration: configuration, - timeout: timeout) - awaitOpen(asyncOpen, handler: handler) - } - - override func asyncOpen(user: User, appId: String?, partitionValue: String, timeout: UInt? = nil, - handler: @escaping (AsyncOpenState) -> Void) { - let configuration = self.configuration(user: user, partition: partitionValue) - asyncOpen(appId: appId, - partitionValue: partitionValue, - configuration: configuration, - timeout: timeout, - handler: handler) - } - - override func autoOpen(appId: String?, partitionValue: String, configuration: Realm.Configuration, - timeout: UInt?, handler: @escaping (AsyncOpenState) -> Void) { - let autoOpen = AutoOpen(appId: appId, - configuration: configuration, - timeout: timeout) - awaitOpen(autoOpen, handler: handler) - } - - override func autoOpen(user: User, appId: String?, partitionValue: String, timeout: UInt? = nil, - handler: @escaping (AsyncOpenState) -> Void) { - let configuration = self.configuration(user: user, partition: partitionValue) - autoOpen(appId: appId, - partitionValue: partitionValue, - configuration: configuration, - timeout: timeout, - handler: handler) - } - - // These two tests are expecting different partition values to result in - // different Realm files, which isn't applicable to FLX - override func testAutoOpenWithDifferentPartitionValues() throws {} - override func testCombineAsyncOpenAutoOpenWithDifferentPartitionValues() throws {} -} diff --git a/Realm/ObjectServerTests/TimeoutProxyServer.swift b/Realm/ObjectServerTests/TimeoutProxyServer.swift deleted file mode 100644 index 47e35b83b5..0000000000 --- a/Realm/ObjectServerTests/TimeoutProxyServer.swift +++ /dev/null @@ -1,126 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#if os(macOS) - -import Foundation -import Network - -@available(OSX 10.14, *) -@objc(TimeoutProxyServer) -public class TimeoutProxyServer: NSObject, @unchecked Sendable { - let port: NWEndpoint.Port - let targetPort: NWEndpoint.Port - - let queue = DispatchQueue(label: "TimeoutProxyServer") - var listener: NWListener! - var connections = [NWConnection]() - - let serverEndpoint = NWEndpoint.Host("127.0.0.1") - - private var _delay: Double = 0 - @objc public var delay: Double { - get { - _delay - } - set { - queue.sync { - _delay = newValue - } - } - } - - private var _dropConnections: Bool = false - @objc public var dropConnections: Bool { - get { - _dropConnections - } - set { - queue.sync { - _dropConnections = newValue - } - } - } - - @objc public init(port: UInt16, targetPort: UInt16) { - self.port = NWEndpoint.Port(rawValue: port)! - self.targetPort = NWEndpoint.Port(rawValue: targetPort)! - } - - @objc public func start() throws { - listener = try NWListener(using: NWParameters.tcp, on: port) - listener.newConnectionHandler = { @Sendable [weak self] incomingConnection in - guard let self = self else { return } - self.connections.append(incomingConnection) - incomingConnection.start(queue: self.queue) - - let targetConnection = NWConnection(host: self.serverEndpoint, port: self.targetPort, using: .tcp) - targetConnection.start(queue: self.queue) - self.connections.append(targetConnection) - - if self.dropConnections { - return - } - - self.queue.asyncAfter(deadline: .now() + self.delay) { - copyData(from: incomingConnection, to: targetConnection) - copyData(from: targetConnection, to: incomingConnection) - } - } - listener.start(queue: self.queue) - } - - @objc public func stop() { - listener.cancel() - queue.sync { - for connection in connections { - connection.forceCancel() - } - } - } -} - -@available(macOS 10.14, *) -private func copyData(from: NWConnection, to: NWConnection) { - from.receive(minimumIncompleteLength: 1, maximumLength: 8192) { (data, context, isComplete, error) in - if let error = error { - switch error { - case .posix(.ECANCELED), .posix(.ECONNRESET): - return - default: - fatalError("\(error)") - } - } - - guard let data = data else { - if !isComplete { - copyData(from: from, to: to) - } - return - } - to.send(content: data, contentContext: context ?? .defaultMessage, - isComplete: isComplete, completion: .contentProcessed({ _ in - if !isComplete { - copyData(from: from, to: to) - } - })) - } -} - - -#endif // os(macOS) diff --git a/Realm/ObjectServerTests/WatchTestUtility.swift b/Realm/ObjectServerTests/WatchTestUtility.swift deleted file mode 100644 index 3bca8465cc..0000000000 --- a/Realm/ObjectServerTests/WatchTestUtility.swift +++ /dev/null @@ -1,89 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import Foundation -import RealmSwift -import XCTest - -final public class WatchTestUtility: ChangeEventDelegate { - private let testCase: XCTestCase - private let matchingObjectId: ObjectId? - private let openExpectation: XCTestExpectation - private let closeExpectation: XCTestExpectation - private var changeExpectation: XCTestExpectation? - private let expectError: Bool - public var didCloseError: Error? - - public init(testCase: XCTestCase, matchingObjectId: ObjectId? = nil, expectError: Bool = false) { - self.testCase = testCase - self.matchingObjectId = matchingObjectId - self.expectError = expectError - openExpectation = testCase.expectation(description: "Open watch stream") - closeExpectation = testCase.expectation(description: "Close watch stream") - } - - public func waitForOpen() { - testCase.wait(for: [openExpectation], timeout: 20.0) - } - - public func waitForClose() { - testCase.wait(for: [closeExpectation], timeout: 20.0) - } - - public func expectEvent() { - XCTAssertNil(changeExpectation) - changeExpectation = testCase.expectation(description: "Watch change event") - } - - public func waitForEvent() throws { - try testCase.wait(for: [XCTUnwrap(changeExpectation)], timeout: 20.0) - changeExpectation = nil - } - - public func changeStreamDidOpen(_ changeStream: ChangeStream) { - openExpectation.fulfill() - } - - public func changeStreamDidClose(with error: Error?) { - if expectError { - XCTAssertNotNil(error) - } else { - XCTAssertNil(error) - } - - didCloseError = error - closeExpectation.fulfill() - } - - public func changeStreamDidReceive(error: Error) { - XCTAssertNil(error) - } - - public func changeStreamDidReceive(changeEvent: AnyBSON?) { - XCTAssertNotNil(changeEvent) - XCTAssertNotNil(changeExpectation) - guard let changeEvent = changeEvent else { return } - guard let document = changeEvent.documentValue else { return } - - if let matchingObjectId = matchingObjectId { - let objectId = document["fullDocument"]??.documentValue!["_id"]??.objectIdValue! - XCTAssertEqual(objectId, matchingObjectId) - } - changeExpectation?.fulfill() - } -} diff --git a/Realm/ObjectServerTests/certificates/ca-key.pem b/Realm/ObjectServerTests/certificates/ca-key.pem deleted file mode 100644 index a31229689e..0000000000 --- a/Realm/ObjectServerTests/certificates/ca-key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEA2jW6EOo+UQGlUGtEqQX1OD54tsqQ6xDZ5YWJqiRx33CdeGc0 -+KlC+Y9hGiOxM+lb0CJuV/w1kHRqlfwIkQNSfQYlREdkdDW3hqBiHBfscahuBBJJ -q36HBeynxUBcdlxlOYPlZ7QmcEb6CMWmUERbScgZFQVB1VFT2/Ev05g832I3XtrJ -OCJvDElsnXOvi1Pd2qvmNYe6ap4CsVRDli7k+URyKBIQ7ejHBDx4ufs9bSSMPsFd -hq7/oek1qWZ4vomI/kv2A1IWpFxzfkXSknm385+ioecEy9f4+ObYJAaR8vcHTzqb -yKkS50zgO0inW6YvbT8hnzZkoma/w9CHRC6o5wIDAQABAoIBAE9OlZiOOiXg7j9j -LSotQym08tSknLUCg986gIU5B7YIzQK0p/j0M6ZWKRmB/WZ7muXzjZ0myiT9Wru2 -RTrHNVTBRgwh0m22FES2x9GkFEheydVq/CF7NAHAOF2lfbV68UNNH7RoMkw+T6Oe -ikrD/VW8hvkv1vR7wXm/l/1UaFNbSBSVou8EjHGK9/3fgvHRMALN0Jr1mHRMgK56 -cU7t7oziOmjr/bjm/VowNbJKpF43FT+95/Af6n6H2bLFSfG+4rVAAk37UnCKp0Pb -AlWVl8lpMXjK64qNK2I4yf+sQXRNwlsNWxd6gdNfyOqXbfU+c0F1kQL+ODYu5pit -DNTfObECgYEA5JuY1SsBNVxF0U9Q9QhcZ23h0hgrxvD/DgLNL9/2+C1Lc1iC+qSh -MV8q5faUAqbkAI0+NhEmmrRMNtvhJXfrO5SO1CO/rRrGHF4MJ+Sj1TpefNktYR/6 -fo2BIbfmAoY3O142RWi1u/6Cv1GONp0EyLbBnxin0PB36OUkNxUjhvkCgYEA9Fsu -Ar9EolZPJYtLqGitIhkyCXHSyMEHfQRNyz8kUORtFA6yjvD6vFAtVf2DNaY4FQ0a -TYQ3O0akyPM2lG2c6TtojXKHkDLPJOV0Kh6BMImAqDq2Fsj+nTdoojNw4ypAMU4m -KLAiA3nd03X7es7a6uvrSvQxJRB9fwiBzO70Rt8CgYEAm7ipuLscjZ6XKGbg/Kh/ -WSzuYFB6sX9EHeUmo+/pqVAhTycBwX4XFyx+ajs2wz+vm/iaYfX41/Ts3YmVqhIv -uFwPls3rKR1NydD+csY6G2sxJdZCJSDFXyNAzRkZoqqOQPCCA3G6KZ7KrUv+lZEL -yzVCWv9OgPLsm0ZLDwJlOvECgYEA7+SkIyZL52P8h8tdF5TMhHFf4k3Qti5rf5y+ -Ew+GQ7Q+MjbLrfF+92lvWMBuFDl/TYtziy6GWrdcB7xelRGXvpIIbvVFiZeYLYzm -ooMYKeKUYJRjN7NT5F0FaFhAN4S/SKiEZeWlPuxhjryBi2uRGJlMgmWB6fVqf1CG -vf7J6tkCgYEAqySHnPPm8aHPo9/Vx8JMbalPDgR/18jv3rABbQiMSU+C8Z8rVEmH -MI1DmbWx+9yCqMff1i5xGKt4QDaqGLW7+xy142f1eu+tr7bxx2PusaEpAF2uoAW4 -c/DgWecBLDEhEW3NowHsI3ddNTpNQiNfZRd0TNdr9gwy/N8BfzMZtB4= ------END RSA PRIVATE KEY----- diff --git a/Realm/ObjectServerTests/certificates/ca.pem b/Realm/ObjectServerTests/certificates/ca.pem deleted file mode 100644 index 08655c2e07..0000000000 --- a/Realm/ObjectServerTests/certificates/ca.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDCTCCAfGgAwIBAgIIcWSopWiXJPMwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE -AxMVbWluaWNhIHJvb3QgY2EgNzE2NGE4MCAXDTE4MDgyOTE5MTcxM1oYDzIxMTgw -ODI5MjAxNzEzWjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSA3MTY0YTgwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaNboQ6j5RAaVQa0SpBfU4Pni2 -ypDrENnlhYmqJHHfcJ14ZzT4qUL5j2EaI7Ez6VvQIm5X/DWQdGqV/AiRA1J9BiVE -R2R0NbeGoGIcF+xxqG4EEkmrfocF7KfFQFx2XGU5g+VntCZwRvoIxaZQRFtJyBkV -BUHVUVPb8S/TmDzfYjde2sk4Im8MSWydc6+LU93aq+Y1h7pqngKxVEOWLuT5RHIo -EhDt6McEPHi5+z1tJIw+wV2Grv+h6TWpZni+iYj+S/YDUhakXHN+RdKSebfzn6Kh -5wTL1/j45tgkBpHy9wdPOpvIqRLnTOA7SKdbpi9tPyGfNmSiZr/D0IdELqjnAgMB -AAGjRTBDMA4GA1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB -BQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsFAAOCAQEARxf4 -85JGwLkxN6bxwc7o3dJow+0xX9zZ0AY3Kvlsp9PSMrydRme1qvzejCfn0n8a9381 -1AerczkhnjJwKFYdy+TxcL0KcnTnJRnSwum+Aq7RXH0NVt/2X28/oX3m4u9rHkjB -jQ3QxBzfBHWelZ8twxU8wXJVrb45G5Hw5cXOHs4GIXGbCZjJNvyyz9/60qpUV9sh -q8DMuWkP2eHKP4BjsJ0eX6UitAaZjR0VWZK40yGkouxqLjrE7LesNioQhDGS3Te2 -gg/hAbN4c3lpsPwjeR4RspqjMZzUhY3cJ0t81jVFpbRhKjfU02ww6EDmexjQNAOt -3yrKmabyT80mBaiL9Q== ------END CERTIFICATE----- diff --git a/Realm/ObjectServerTests/certificates/localhost-cert-key.pem b/Realm/ObjectServerTests/certificates/localhost-cert-key.pem deleted file mode 100644 index ede652e547..0000000000 --- a/Realm/ObjectServerTests/certificates/localhost-cert-key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAyh8jYiwYLiZdZNx8fwugGElA0z4km+0qColhU2iaUfK3YalX -DS11rjg+vO+zrE85/+UVaQ2X0G81PVH5wid+NcFm6N95Mysqum/iNnPwdOOkRPb3 -8bOFxZ7xRs8L+UjptFXwAqD9CvE1q8CegmAcTMrZInxmwjvnOaHriutQoN6eWDsL -dRxdak3plfNRlR0A8BRhe/uVi/YksLZRBSpij/8ehA7aaDglyvE+K1JTiAbSDm2h -P+QADkXK5TIUA2tdDpG4Ey1q/NKCrEoVv8XTraa6+t5BC3chuVcsc4o0aTQpNAug -TY/riBNV0Tsq9OLU3rA2loWCkW08DbnoLhUpuwIDAQABAoIBAGnH8CHCCAmnJHvT -9QgcknYDaHzl6gz9C2KmUZxwg0teJuFi2d9Yql06rIL/RF5qvGUThKgNaCW6/fcQ -vxEA0Enb5Tr9MhY4gk0+nvp2pSLXvLInOs5xcRJjQ80WMSMPKNirnUgS7zvZz4gK -8Co1mLoL86Xby3/eD/6Woop66yC1Uri8RDndgMR8ntC8VtmA0Z2zOM1zMVBz0kam -YS576pgJh4xJ1Q7SCKr3HTO7/7HeopzXrSP5xQWdYNBo7VPgG2bRPZ2zUaFfm+Ps -T5Lw5wByqexGnUiCZ98gF6lnFHDOzQSJy45ce7XuQSkO7+0WhGz1QZ+VlEiR/1x6 -0colZEECgYEAz373IfktDj8+L3F930cQK8oKD1LUEMgd44OydvICO+yHUZWgmTWE -aJPmn8Dw4GuPyjesACR56RCP990T7zdvcwkwj/5h+509xAxNHJZoYRL8m6fw+uo5 -wW34uTCDg+MxP2ipJ2E1iW1NIEYq4wInw6RiEDZLPu33pnX+Ad/6BeECgYEA+V6P -mFJyGYaw+OUqMT6M40BSTD6TT+yUvUPZa+sCKs296xYc3BEGXe62L/XVE0MrbSvG -KPNMG5u3IoAGdKc4wbtsdBe+S9zu71ZIKsdkWDl9GwZ2xHX0tqV0/Ca140zlkxAY -+dJL9nGq82WcEfhPcXwInPBrMpzOlyJXptNx6xsCgYEAmyxVPwfshPIQ3EQgoQCw -/D5tBYao5x/xEjtkFIXp28yIah/e6ZTXP6oT17bfrMVj1BOMQtMEhKKJOBESHlyz -sTDXK2hO+G9gSKP2awGkb6xWU0Xl9o+Bv8ExN7UrNU+LfeMUVUniUrL18cPnwLrU -5/+gAoXIAfjOsqMc4WQRw6ECgYEAzr/XzjKM5x0FHVbi5HE33jI2CYDYIivEJida -3F68LUDndUGgK9Txsm94Hct0HcRS/PCOGuWc9EbmT3RV5eG+7OC3yojk/YDvmP+w -Vcd7Kqp/TyjMz5X8jnIfy+9MXmgi7wspqfbxhCI52hMkksGNHEn52iR6vDvGDQgs -I+SrToECgYAdbzZ0FNeBAOvoEi6svApUNw4w3fHujVpg8b/FhadKaAx+kJBxcqy7 -Xfj3UrfKU7TuR8kO/EK9z9YXo5MiNVWP56QfzbLkvr5Obfi/sJrIHAYqeqPKpgaz -acclIzLL5HsrbVjKiJs9Q8j9+Rx4RgIFTG0lM+25J8JnOnVFnPWdvw== ------END RSA PRIVATE KEY----- diff --git a/Realm/ObjectServerTests/certificates/localhost-cert.pem b/Realm/ObjectServerTests/certificates/localhost-cert.pem deleted file mode 100644 index fec83a6b14..0000000000 --- a/Realm/ObjectServerTests/certificates/localhost-cert.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDDTCCAfWgAwIBAgIISzy7BX9hEJkwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE -AxMVbWluaWNhIHJvb3QgY2EgNzE2NGE4MCAXDTE4MDgyOTE5MTcxNFoYDzIxMDgw -ODI5MjAxNzE0WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDKHyNiLBguJl1k3Hx/C6AYSUDTPiSb7SoKiWFTaJpR -8rdhqVcNLXWuOD6877OsTzn/5RVpDZfQbzU9UfnCJ341wWbo33kzKyq6b+I2c/B0 -46RE9vfxs4XFnvFGzwv5SOm0VfACoP0K8TWrwJ6CYBxMytkifGbCO+c5oeuK61Cg -3p5YOwt1HF1qTemV81GVHQDwFGF7+5WL9iSwtlEFKmKP/x6EDtpoOCXK8T4rUlOI -BtIObaE/5AAORcrlMhQDa10OkbgTLWr80oKsShW/xdOtprr63kELdyG5VyxzijRp -NCk0C6BNj+uIE1XROyr04tTesDaWhYKRbTwNueguFSm7AgMBAAGjVTBTMA4GA1Ud -DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T -AQH/BAIwADAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEB -AIYlOrkjRZJ/4SubG7MJB9FK5un+0vDPXWmlz90szGCIhM8/SBxwIZyMWEDAPhJ9 -PmhBngrDZjJt1C5TfsH2utGwh2ONErnBp8MC/DK5qSwiCuZTFRGwyZVI7Rpmk2RJ -AtMUKsR8TRlaZ9QBlDF+6owMgKUb+VN+I806ryaBNKHuS4cmGdqOxzhb5/+9MXh/ -UfAnHNoie0r8eZhE0mUJ0JjTQOKhZMhAmHR5Pm/9S2f+VwsBriR0v3Fxw+9TN9cV -PXfwR1gRJ1hpCvYUMp6PLsEwSAdTpBm6+vea5iRytSjny+R4E27q1ohTey0+6sUk -WnVQqUUINxQulv7m60R2foY= ------END CERTIFICATE----- diff --git a/Realm/ObjectServerTests/certificates/localhost-other-cert.pem b/Realm/ObjectServerTests/certificates/localhost-other-cert.pem deleted file mode 100644 index f92c601588..0000000000 --- a/Realm/ObjectServerTests/certificates/localhost-other-cert.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDDTCCAfWgAwIBAgIIUfBnG/nIaSAwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE -AxMVbWluaWNhIHJvb3QgY2EgNzE2NGE4MCAXDTE4MDgzMDE5MzE1NFoYDzIxMDgw -ODMwMjAzMTU0WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQCvm9qn97bX+M7Vu/z7Hd1OUcVynryrISrVdHbs/dv6 -UsXhrl4Z47y0/U0I013gGK8+yblxRLYyLE3jua7nL+pBGOULG2P95KWHVgLtJECl -wDKLgnn2TUZ6eL72Mu3XkrYmDRd50queGgEIkDvXRGbOs2VfH2giY0ttQK3pXAa9 -0sBuOhFWUd8oyOCplBlRUf+PsjbcyW/LC58WtmizO7QC40ANz0dpR04UZxNe0+gM -ATKEksELQwSKQoVQwCp1VOZ5jyzk9RtBS5ZJP5n8cl6/ZVSOekLBoE8DQibR3ga2 -E0I/r7bSzCYJr9K4jTFYGwPoY/U2Yk38mGp8/d9x35W1AgMBAAGjVTBTMA4GA1Ud -DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T -AQH/BAIwADAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEB -ABoxyKJmBAWuxDAYGnmopWhBkAqh6GHvC4TkVAOaNGhfs+U0g5lwgVm2sFz1+lsZ -zF/ECVsZhQ5MTZaaMKrlMZ/NEvOll3C5L6LutFMeSQoffnkcIUDmveX9Yp+KoLdV -tbvVAt2D6BNsNDhmftyB2i8FH0fe1M+oxtAU54pqWGPoQo9+7N7UytFVimbCPNAt -57rzegPyu4AcDdGO3SZBnhqT/rxUzO5nPJ+6MiXkCROSm26X8IvUR9/bO/QuC0t+ -CT6NJlEcn8X9eLsbSLrjHfXknbneteNCfZWU3qYbEh1Va98vYI3jku62pGyv9A7k -Fs6VtIfrN3GRpSxs0Xw8hPw= ------END CERTIFICATE----- diff --git a/Realm/ObjectServerTests/certificates/localhost-other.cer b/Realm/ObjectServerTests/certificates/localhost-other.cer deleted file mode 100644 index 1d6c3d794c866d094fefdc2edd41610cf40f1d77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 785 zcmXqLV&*kyV*I**nTe5!i6ihsy7bQznFhY6^ionOB8^#xuKa!qJ@EiIIp3FfrYVwp{22*sY#RszmcH=E&sml`j2y0cmMe< zd)F`UXwkeqs};4bmXy8ud;3?=(TD5eBp>hD^4FK+a_j?%^>!zB7P@RR((!$~bKP_O zSB?@-xuui;K3Uox#`IRjVd(**?xxCbzHU_&`@R{yy*_E18n1ZerPcGK7&#_bUw276 zw>dRlK0_(lJJ(_D%NVx3mk#7v35EsU*EsQDn%)= z9eB^XXS(}|qzlJge!;_N)H3NHw=+wZQ)|Eht)Bl7w@rc6Lzv+zwOc)HO}>ycJvxXNHf1k{%V%w`)5X0&ENZl_or@UVrFDu zTpVf;Y`_N$J6V24#{VoV%uK8c3}iukRTeP=5jGBOHbz!fc4kI6i^+foBrOb5&Sby< zR0HG*vhW&kHz5T+YAgc-o{>Sy@Wi4t7S?q~3?!r~S1iqNoWQm4MdEwzmM0<1vrIDL zH$OFLo>|Zsxotzt*I&_+XX1}=MoYHx`S?zoWw7e0;rz2gpO;QA*r~th-IicEPcHeo zN*P6mXM3OiO`6}eV0-A+-B+3JHop+gF|kOiyVH0}pH<%d-j(w!j$IIW-jx-R{KBcf z?#;a`r!Izer5&=lp!^=+|bM<(ZWDMoY&C8z{1GV(9+P{&?HKN-^kDaEMQ~+7BCPo z6fzKin8BHopPZPJkzZT_v|9<;GDcPg<|amd2B0_>QxhX2!zp>?BpnGowb+z9HTB#J zBs?81+o{ZctHsrs7@RRH@YD9hmEpX)rRyy0_PpP`#^3V)Q_)P`=@;@%Z3BNEQm->T znD*j+rLnfwuKY)4#UDx@FLC+y{p04=qw_wxo#+1P@p4P(2c`voxjve%J}|E-LB{9Q zO{JQ&L)OnN7ryR#9kAfuya;RVQkmE+-LgY6Pu?n3(~41BJqo*IcTXUFnQ+##%+_2*L48ZXiPM=!5kw(HkD zNA7aPo#8sgT_%|(nkL)}eEVN_2!~#@*81}3%DoL{(^{J*=GyS?e4!_*xtocZk%4h> zs6ns+A294>`577iv#>BTu`V!>1@To`#0*5(IJDUqSy|bc8R0A@10ImHFi1I*0RvDC zkSEB(Yrx%v6!@sI3=DWihBj5JoyxA0>K|&)mfpRNCYePo~QvT1RSpC8N@>FitV7d)33!uvGeIaGmm5tMzJ)CJWzrx2s9s z>N{=`{rvx4!;1RA59%_vl&Zb{RL*d@l*)Nw#$|^`3sX)w%qXd}%m3@0{x6)Hah*!Z z{=&k;?}N>+i`tfdaE}mFkI3ZuCSo+NU+hY6^ionOB8^#v3at&agu?8IIp3FfrXK!k%6IwiFuR+zmcHj~RSl^1HY#++6nil$(F1+h3er?0BlYNMHW z*IYxHb=K*9e3O@~fBSfyeEJnmdq>3-L{Pbb2nDy$hVRz5PPYilo-M?f;Yb7?fV99gI2hC@)tvxtE13iv6W|&TK{CVz57yCm)8}CszyG#^2TrH6QPI)Z1{`~HzrovyBocX0_>S}i2d8`=#)bu*M diff --git a/Realm/ObjectServerTests/config_overrides.json b/Realm/ObjectServerTests/config_overrides.json deleted file mode 100644 index 40cd76c4b8..0000000000 --- a/Realm/ObjectServerTests/config_overrides.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "events": { - "database": { - "maxCoordinatorChangeStreams": "50000" - }, - "log_forwarder": { - "enabled": false - }, - "mediator": { - "eventSubscriptionPollingPeriodSeconds": 2, - "unownedEventSubscriptionPollingPeriodSeconds": 1, - "staleOwnedJobPollingPeriodSec": 1 - }, - "streams": { - "eventSubscriptionPollingPeriodSec": 2, - "eventSubscriptionHeartbeatPeriodSec": 1 - }, - "translator": { - "maxCoordinatorTranslators": 1000 - } - }, - "graphql": { - "enabled": false - }, - "metrics": { - "enabled": false - }, - "secretsManager": { - "enabled": false - }, - "sync": { - "allowSyncSessionTestCommands": true - } -} diff --git a/Realm/ObjectServerTests/include/RLMSyncTestCase.h b/Realm/ObjectServerTests/include/RLMSyncTestCase.h deleted file mode 120000 index 398bdb68ac..0000000000 --- a/Realm/ObjectServerTests/include/RLMSyncTestCase.h +++ /dev/null @@ -1 +0,0 @@ -../RLMSyncTestCase.h \ No newline at end of file diff --git a/Realm/ObjectServerTests/include/RLMUser+ObjectServerTests.h b/Realm/ObjectServerTests/include/RLMUser+ObjectServerTests.h deleted file mode 120000 index f46ce3b633..0000000000 --- a/Realm/ObjectServerTests/include/RLMUser+ObjectServerTests.h +++ /dev/null @@ -1 +0,0 @@ -../RLMUser+ObjectServerTests.h \ No newline at end of file diff --git a/Realm/ObjectServerTests/setup_baas.rb b/Realm/ObjectServerTests/setup_baas.rb deleted file mode 100644 index 432489e19c..0000000000 --- a/Realm/ObjectServerTests/setup_baas.rb +++ /dev/null @@ -1,225 +0,0 @@ -#!/usr/bin/ruby - -require 'net/http' -require 'fileutils' -require 'pathname' - -BASE_DIR = Dir.pwd -BUILD_DIR = "#{BASE_DIR}/ci_scripts/setup_baas/.baas" -BIN_DIR = "#{BUILD_DIR}/bin" -LIB_DIR = "#{BUILD_DIR}/lib" -PID_FILE = "#{BUILD_DIR}/pid.txt" - -MONGO_EXE = "'#{BIN_DIR}'/mongo" -MONGOD_EXE = "'#{BIN_DIR}'/mongod" - -DEPENDENCIES = File.open("#{BASE_DIR}/dependencies.list").map { |line| - line.chomp.split("=") -}.to_h - -MONGODB_VERSION='5.0.6' -GO_VERSION='1.21.4' -NODE_VERSION='16.13.1' -STITCH_VERSION=DEPENDENCIES["STITCH_VERSION"] - -MONGODB_URL="https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-#{MONGODB_VERSION}.tgz" -TRANSPILER_TARGET='node16-macos' -SERVER_STITCH_LIB_URL="https://s3.amazonaws.com/stitch-artifacts/stitch-support/stitch-support-macos-debug-4.3.2-721-ge791a2e-patch-5e2a6ad2a4cf473ae2e67b09.tgz" -STITCH_SUPPORT_URL="https://static.realm.io/downloads/swift/stitch-support.tar.xz" -MONGO_DIR="#{BUILD_DIR}/mongodb-macos-x86_64-#{MONGODB_VERSION}" - -# exit immediately if any subcommand fails -#class Object -# def `(command) -# ret = super -# exit 1 unless $?.success? -# ret -# end -#end - -def setup_mongod - if !File.exist?("#{BIN_DIR}/mongo") - `cd '#{BUILD_DIR}' && curl --silent '#{MONGODB_URL}' | tar xz` - FileUtils.cp("#{MONGO_DIR}/bin/mongo", BIN_DIR) - FileUtils.cp("#{MONGO_DIR}/bin/mongod", BIN_DIR) - end -end - -def run_mongod - puts "starting mongod..." - puts `mkdir -p '#{BUILD_DIR}'/db_files` - puts `#{MONGOD_EXE} --quiet \ - --dbpath '#{BUILD_DIR}'/db_files \ - --port 26000 \ - --replSet test \ - --fork \ - --logpath '#{BUILD_DIR}'/mongod.log` - puts "mongod starting" - - retries = 0 - begin - Net::HTTP.get(URI('http://localhost:26000')) - rescue => exception - sleep(1) - retries += 1 - if retries == 20 - abort('could not connect to mongod') - end - end - puts `#{MONGO_EXE} --port 26000 --eval 'rs.initiate()'` - puts "mongod started" -end - -def shutdown_mongod - puts 'shutting down mongod' - if File.exist?("#{BIN_DIR}/mongo") - puts `#{MONGO_EXE} --port 26000 admin --eval "db.shutdownServer({force: true})"` - end - puts 'mongod is down' -end - -def setup_stitch - puts "setting up stitch" - exports = [] - go_root = "#{BUILD_DIR}/go" - - if !File.exist?("#{go_root}/bin/go") - puts 'downloading go' - `cd #{BUILD_DIR} && curl --silent "https://dl.google.com/go/go#{GO_VERSION}.darwin-amd64.tar.gz" | tar xz` - puts `mkdir -p #{go_root}/src/github.com/10gen` - end - - stitch_dir = "#{BUILD_DIR}/stitch" - if !Dir.exist?(stitch_dir) - puts 'cloning stitch' - puts `git clone git@github.com:10gen/baas #{stitch_dir}` - else - puts 'stitch dir exists' - end - - puts 'checking out stitch' - stitch_worktree = "#{go_root}/src/github.com/10gen/stitch" - if Dir.exist?("#{stitch_dir}/.git") - # Fetch the BaaS version if we don't have it - puts `git -C '#{stitch_dir}' show-ref --verify --quiet #{STITCH_VERSION} || git -C '#{stitch_dir}' fetch` - # Set the worktree to the correct version - if Dir.exist?(stitch_worktree) - puts `git -C '#{stitch_worktree}' checkout #{STITCH_VERSION}` - else - puts `git -C '#{stitch_dir}' worktree add '#{stitch_worktree}' #{STITCH_VERSION}` - end - else - # We have a stitch directory with no .git directory, meaning we're - # running on CI and just need to copy the files into place - if !Dir.exist?(stitch_worktree) - puts `cp -Rc '#{stitch_dir}' '#{stitch_worktree}'` - end - end - - if !File.exist?("#{BUILD_DIR}/stitch-support.tar.xz") - puts 'downloading stitch support' - puts `cd #{BUILD_DIR} && curl --silent -O "#{STITCH_SUPPORT_URL}"` - end - - stitch_dir = stitch_worktree - if !File.exist?("#{LIB_DIR}/libstitch_support.dylib") - FileUtils.mkdir_p "#{stitch_dir}/etc/dylib/include/stitch_support/v1/stitch_support" - puts `tar --extract --strip-components=1 -C '#{stitch_dir}/etc/dylib' --file '#{BUILD_DIR}/stitch-support.tar.xz' stitch-support/lib stitch-support/include` - FileUtils.copy "#{stitch_dir}/etc/dylib/lib/libstitch_support.dylib", "#{LIB_DIR}" - FileUtils.copy "#{stitch_dir}/etc/dylib/include/stitch_support.h", "#{stitch_dir}/etc/dylib/include/stitch_support/v1/stitch_support" - end - - update_doc_filepath = "#{BIN_DIR}/update_doc" - if !File.exist?(update_doc_filepath) - puts `tar --extract --strip-components=2 -C '#{BIN_DIR}' --file '#{BUILD_DIR}/stitch-support.tar.xz' stitch-support/bin/update_doc` - puts `chmod +x '#{update_doc_filepath}'` - end - - assisted_agg_filepath = "#{BIN_DIR}/assisted_agg" - if !File.exist?(assisted_agg_filepath) - puts `tar --extract --strip-components=2 -C '#{BIN_DIR}' --file '#{BUILD_DIR}/stitch-support.tar.xz' stitch-support/bin/assisted_agg` - puts `chmod +x '#{assisted_agg_filepath}'` - end - - puts "Checking node installation #{`which node || true`.empty?}" - puts "Checking node local installation #{!Dir.exist?("#{BUILD_DIR}/node-v#{NODE_VERSION}-darwin-x64")}" - if `which node || true`.empty? && !Dir.exist?("#{BUILD_DIR}/node-v#{NODE_VERSION}-darwin-x64") - puts "downloading node 🚀" - puts `cd '#{BUILD_DIR}' && curl -O "https://nodejs.org/dist/v#{NODE_VERSION}/node-v#{NODE_VERSION}-darwin-x64.tar.gz" && tar xzf node-v#{NODE_VERSION}-darwin-x64.tar.gz` - exports << "export PATH=\"#{BUILD_DIR}/node-v#{NODE_VERSION}-darwin-x64/bin/:$PATH\"" - end - - if `which yarn || true`.empty? - `rm -rf "$HOME/.yarn"` - `export PATH=\"#{BUILD_DIR}/node-v#{NODE_VERSION}-darwin-x64/bin/:$PATH\" && curl -o- -L https://yarnpkg.com/install.sh | bash` - exports << "export PATH=\"$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH\"" - end - - puts 'building transpiler' - # Install a newer version of pkg that actually supports recent versions of node - puts `#{exports.length() == 0 ? "" : exports.join(' && ') + ' &&'} \ - cd '#{stitch_dir}/etc/transpiler' && yarn add pkg` - puts `#{exports.length() == 0 ? "" : exports.join(' && ') + ' &&'} \ - cd '#{stitch_dir}/etc/transpiler' && yarn install && yarn run build -t "#{TRANSPILER_TARGET}" && - cp -c bin/transpiler #{BUILD_DIR}/bin` - - puts "TRANSPILER SIZE" - puts `ls -l #{stitch_dir}/etc/transpiler/bin` - - exports << "export GOROOT=\"#{go_root}\"" - exports << "export PATH=\"$GOROOT/bin:$PATH\"" - - exports << "export STITCH_PATH=\"#{stitch_dir}\"" - exports << "export PATH=\"$PATH:$STITCH_PATH/etc/transpiler/bin\"" - exports << "export DYLD_LIBRARY_PATH='#{LIB_DIR}'" - exports << "export GOPRIVATE=\"github.com/10gen/*\"" - - puts 'build create_user binary' - - puts `#{exports.join(' && ')} && \ - cd '#{stitch_dir}' && \ - #{go_root}/bin/go build -o create_user cmd/auth/user.go && - cp -c create_user '#{BIN_DIR}'` - - puts 'create_user binary built' - - puts 'building server binary' - - puts `#{exports.join(' && ')} && \ - cd '#{stitch_dir}' && \ - #{go_root}/bin/go build -o stitch_server cmd/server/main.go - cp -c stitch_server '#{BIN_DIR}'` - - puts 'server binary built' -end - -def build_action - puts 'building baas' - begin - FileUtils.mkdir_p BIN_DIR - FileUtils.mkdir_p LIB_DIR - setup_mongod - run_mongod - shutdown_mongod - setup_stitch - rescue => exception - puts "error setting up: #{exception}" - end -end - -def clean_action - puts 'cleaning' - `rm -rf #{MONGO_DIR}` - `cd #{BUILD_DIR} && git rm -rf . && git clean -fxd` -end - -if ARGV.length < 1 - build_action -end - -case ARGV[0] -when "" - build_action -when "clean" - clean_action -end diff --git a/Realm/RLMAPIKeyAuth.h b/Realm/RLMAPIKeyAuth.h deleted file mode 100644 index 642bdd95d4..0000000000 --- a/Realm/RLMAPIKeyAuth.h +++ /dev/null @@ -1,95 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -@class RLMUserAPIKey, RLMObjectId; - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -/// Provider client for user API keys. -RLM_SWIFT_SENDABLE RLM_FINAL // immutable final class -@interface RLMAPIKeyAuth : RLMProviderClient - -/// A block type used to report an error -RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void(^RLMAPIKeyAuthOptionalErrorBlock)(NSError * _Nullable); - -/// A block type used to return an `RLMUserAPIKey` on success, or an `NSError` on failure -RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void(^RLMOptionalUserAPIKeyBlock)(RLMUserAPIKey * _Nullable, NSError * _Nullable); - -/// A block type used to return an array of `RLMUserAPIKey` on success, or an `NSError` on failure -RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void(^RLMUserAPIKeysBlock)(NSArray * _Nullable, NSError * _Nullable); - -/** - Creates a user API key that can be used to authenticate as the current user. - - @param name The name of the API key to be created. - @param completion A callback to be invoked once the call is complete. -*/ -- (void)createAPIKeyWithName:(NSString *)name - completion:(RLMOptionalUserAPIKeyBlock)completion NS_SWIFT_NAME(createAPIKey(named:completion:)); - -/** - Fetches a user API key associated with the current user. - - @param objectId The ObjectId of the API key to fetch. - @param completion A callback to be invoked once the call is complete. - */ -- (void)fetchAPIKey:(RLMObjectId *)objectId - completion:(RLMOptionalUserAPIKeyBlock)completion; - -/** - Fetches the user API keys associated with the current user. - - @param completion A callback to be invoked once the call is complete. - */ -- (void)fetchAPIKeysWithCompletion:(RLMUserAPIKeysBlock)completion; - -/** - Deletes a user API key associated with the current user. - - @param objectId The ObjectId of the API key to delete. - @param completion A callback to be invoked once the call is complete. - */ -- (void)deleteAPIKey:(RLMObjectId *)objectId - completion:(RLMAPIKeyAuthOptionalErrorBlock)completion; - -/** - Enables a user API key associated with the current user. - - @param objectId The ObjectId of the API key to enable. - @param completion A callback to be invoked once the call is complete. - */ -- (void)enableAPIKey:(RLMObjectId *)objectId - completion:(RLMAPIKeyAuthOptionalErrorBlock)completion; - -/** - Disables a user API key associated with the current user. - - @param objectId The ObjectId of the API key to disable. - @param completion A callback to be invoked once the call is complete. - */ -- (void)disableAPIKey:(RLMObjectId *)objectId - completion:(RLMAPIKeyAuthOptionalErrorBlock)completion; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMAPIKeyAuth.mm b/Realm/RLMAPIKeyAuth.mm deleted file mode 100644 index 6b683e46ac..0000000000 --- a/Realm/RLMAPIKeyAuth.mm +++ /dev/null @@ -1,90 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMAPIKeyAuth.h" -#import "RLMProviderClient_Private.hpp" - -#import "RLMApp_Private.hpp" -#import "RLMUserAPIKey_Private.hpp" -#import "RLMObjectId_Private.hpp" - -using namespace realm::app; - -@implementation RLMAPIKeyAuth - -- (App::UserAPIKeyProviderClient)client { - return self.app._realmApp->provider_client(); -} - -- (std::shared_ptr)currentUser { - return self.app._realmApp->current_user(); -} - -static realm::util::UniqueFunction)> -wrapCompletion(RLMOptionalUserAPIKeyBlock completion) { - return [completion](App::UserAPIKey userAPIKey, std::optional error) { - if (error) { - return completion(nil, makeError(*error)); - } - return completion([[RLMUserAPIKey alloc] initWithUserAPIKey:userAPIKey], nil); - }; -} - -- (void)createAPIKeyWithName:(NSString *)name - completion:(RLMOptionalUserAPIKeyBlock)completion { - self.client.create_api_key(name.UTF8String, self.currentUser, wrapCompletion(completion)); -} - -- (void)fetchAPIKey:(RLMObjectId *)objectId - completion:(RLMOptionalUserAPIKeyBlock)completion { - self.client.fetch_api_key(objectId.value, self.currentUser, wrapCompletion(completion)); -} - -- (void)fetchAPIKeysWithCompletion:(RLMUserAPIKeysBlock)completion { - self.client.fetch_api_keys(self.currentUser, - ^(const std::vector& userAPIKeys, - std::optional error) { - if (error) { - return completion(nil, makeError(*error)); - } - - NSMutableArray *apiKeys = [[NSMutableArray alloc] init]; - for (auto &userAPIKey : userAPIKeys) { - [apiKeys addObject:[[RLMUserAPIKey alloc] initWithUserAPIKey:userAPIKey]]; - } - - return completion(apiKeys, nil); - }); -} - -- (void)deleteAPIKey:(RLMObjectId *)objectId - completion:(RLMAPIKeyAuthOptionalErrorBlock)completion { - self.client.delete_api_key(objectId.value, self.currentUser, RLMWrapCompletion(completion)); -} - -- (void)enableAPIKey:(RLMObjectId *)objectId - completion:(RLMAPIKeyAuthOptionalErrorBlock)completion { - self.client.enable_api_key(objectId.value, self.currentUser, RLMWrapCompletion(completion)); -} - -- (void)disableAPIKey:(RLMObjectId *)objectId - completion:(RLMAPIKeyAuthOptionalErrorBlock)completion { - self.client.disable_api_key(objectId.value, self.currentUser, RLMWrapCompletion(completion)); -} - -@end diff --git a/Realm/RLMAnalytics.hpp b/Realm/RLMAnalytics.hpp deleted file mode 100644 index 76988ae44d..0000000000 --- a/Realm/RLMAnalytics.hpp +++ /dev/null @@ -1,61 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2015 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. -// -//////////////////////////////////////////////////////////////////////////// - -// Asynchronously submits build information to Realm if running in an iOS -// simulator or on OS X if a debugger is attached. Does nothing if running on an -// iOS / watchOS device or if a debugger is *not* attached. -// -// To be clear: this does *not* run when your app is in production or on -// your end-user’s devices; it will only run in the simulator or when a debugger -// is attached. -// -// Why are we doing this? In short, 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 language you use, what iOS -// versions you target, etc. Having this info will help prioritizing our time, -// adding new features and deprecating old features. Collecting an anonymized -// bundle & 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 Swift app -// 10 times would report 10 times more than a single Objective-C developer that -// only builds once, 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 signup, we feel this is a necessary step so we can collect -// relevant data to build a better product for you. If you truly, absolutely -// feel compelled to not send this data back to Realm, then you can set an env -// variable named REALM_DISABLE_ANALYTICS. Since Realm is free we believe -// letting these analytics run is a small price to pay for the product & support -// we give you. -// -// Currently the following information is reported: -// - What version of Realm and core is being used, and from which language (obj-c or Swift). -// - Which platform and version of OS X it's running on (in case Xcode aggressively drops -// support for older versions again, we need to know what we need to support). -// - The minimum iOS/OS X version that the application is targeting (again, to -// help us decide what versions we need to support). -// - An anonymous MAC address and bundle ID to aggregate the other information on. -// - The host platform OSX and version. -// - The XCode version. -// - Some info about the features been used when opening the realm for the first time. - -#import -#import - -void RLMSendAnalytics(RLMRealmConfiguration *configuration, RLMSchema *schema); -NSString *RLMHashBase16Data(const void *bytes, size_t length); diff --git a/Realm/RLMAnalytics.mm b/Realm/RLMAnalytics.mm deleted file mode 100644 index a29172c3d1..0000000000 --- a/Realm/RLMAnalytics.mm +++ /dev/null @@ -1,427 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2015 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. -// -//////////////////////////////////////////////////////////////////////////// - -// Asynchronously submits build information to Realm if running in an iOS -// simulator or on OS X if a debugger is attached. Does nothing if running on an -// iOS / watchOS device or if a debugger is *not* attached. -// -// To be clear: this does *not* run when your app is in production or on -// your end-user’s devices; it will only run in the simulator or when a debugger -// is attached. -// -// Why are we doing this? In short, 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 language you use, what iOS -// versions you target, etc. Having this info will help prioritizing our time, -// adding new features and deprecating old features. Collecting an anonymized -// bundle & 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 Swift app -// 10 times would report 10 times more than a single Objective-C developer that -// only builds once, 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 signup, we feel this is a necessary step so we can collect -// relevant data to build a better product for you. If you truly, absolutely -// feel compelled to not send this data back to Realm, then you can set an env -// variable named REALM_DISABLE_ANALYTICS. Since Realm is free we believe -// letting these analytics run is a small price to pay for the product & support -// we give you. -// -// Currently the following information is reported: -// - What version of Realm and core is being used, and from which language (obj-c or Swift). -// - Which platform and version of OS X it's running on (in case Xcode aggressively drops -// support for older versions again, we need to know what we need to support). -// - The minimum iOS/OS X version that the application is targeting (again, to -// help us decide what versions we need to support). -// - An anonymous MAC address and bundle ID to aggregate the other information on. -// - The host platform OSX and version. -// - The XCode version. -// - Some info about the features been used when opening the realm for the first time. - -#import "RLMAnalytics.hpp" - -#import - -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_MAC || (TARGET_OS_WATCH && TARGET_OS_SIMULATOR) || (TARGET_OS_TV && TARGET_OS_SIMULATOR) -#import "RLMObjectSchema_Private.h" -#import "RLMRealmConfiguration_Private.h" -#import "RLMSchema_Private.h" -#import "RLMSyncConfiguration.h" -#import "RLMUtil.hpp" - -#import -#import -#import -#import -#import - -#import - -#ifndef REALM_COCOA_VERSION -#import "RLMVersion.h" -#endif - -// Wrapper for sysctl() that handles the memory management stuff -static auto RLMSysCtl(int *mib, u_int mibSize, size_t *bufferSize) { - std::unique_ptr buffer(nullptr, &free); - - int ret = sysctl(mib, mibSize, nullptr, bufferSize, nullptr, 0); - if (ret != 0) { - return buffer; - } - - buffer.reset(malloc(*bufferSize)); - if (!buffer) { - return buffer; - } - - ret = sysctl(mib, mibSize, buffer.get(), bufferSize, nullptr, 0); - if (ret != 0) { - buffer.reset(); - } - - return buffer; -} - -// Get the version of OS X we're running on (even in the simulator this gives -// the OS X version and not the simulated iOS version) -static NSString *RLMHostOSVersion() { - size_t size; - sysctlbyname("kern.osproductversion", NULL, &size, NULL, 0); - char *model = (char*)malloc(size); - sysctlbyname("kern.osproductversion", model, &size, NULL, 0); - NSString *deviceModel = [NSString stringWithCString:model encoding:NSUTF8StringEncoding]; - free(model); - return deviceModel; -} - -static NSString *RLMTargetArch() { - NSString *targetArchitecture; -#if TARGET_CPU_X86_64 - targetArchitecture = @"x86_64"; -#elif TARGET_CPU_ARM64 - targetArchitecture = @"arm64"; -#endif - return targetArchitecture; -} - -// Hash the data in the given buffer and convert it to a hex-format string -NSString *RLMHashBase16Data(const void *bytes, size_t length) { - unsigned char buffer[CC_SHA256_DIGEST_LENGTH]; - CC_SHA256(bytes, static_cast(length), buffer); - - char formatted[CC_SHA256_DIGEST_LENGTH * 2 + 1]; - for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) { - snprintf(formatted + i * 2, sizeof(formatted) - i * 2, "%02x", buffer[i]); - } - - return [[NSString alloc] initWithBytes:formatted - length:CC_SHA256_DIGEST_LENGTH * 2 - encoding:NSUTF8StringEncoding]; -} - -static std::optional> getMacAddress(int id) { - char buff[] = "en0"; - snprintf(buff + 2, 2, "%d", id); - int index = static_cast(if_nametoindex(buff)); - if (!index) { - return std::nullopt; - } - - std::array mib = {{CTL_NET, PF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, index}}; - size_t bufferSize; - auto buffer = RLMSysCtl(&mib[0], mib.size(), &bufferSize); - if (!buffer) { - return std::nullopt; - } - - // sockaddr_dl struct is immediately after the if_msghdr struct in the buffer - auto sockaddr = reinterpret_cast(static_cast(buffer.get()) + 1); - std::array mac; - std::memcpy(&mac[0], sockaddr->sdl_data + sockaddr->sdl_nlen, 6); - - // Touch bar internal network interface, which is identical on all touch bar macs - if (mac == std::array{0xAC, 0xDE, 0x48, 0x00, 0x11, 0x22}) { - return std::nullopt; - } - - // The mac address reported on iOS. It's unclear how we're seeing this as - // this code doesn't run on iOS, but it somehow sometimes happens. - if (mac == std::array{2, 0, 0, 0, 0, 0}) { - return std::nullopt; - } - - return mac; -} - -// Returns the hash of the MAC address of the first network adaptor since the -// vendorIdentifier isn't constant between iOS simulators. -static NSString *RLMMACAddress() { - for (int i = 0; i < 9; ++i) { - if (auto mac = getMacAddress(i)) { - return RLMHashBase16Data(&(*mac)[0], 6); - } - } - return @"unknown"; - } - -static NSString *RLMBuilderId() { -#ifdef REALM_IOPLATFORMUUID - NSString *saltedId = [@"Realm is great" stringByAppendingString:REALM_IOPLATFORMUUID]; - NSData *data = [saltedId dataUsingEncoding:NSUTF8StringEncoding]; - - unsigned char buffer[CC_SHA256_DIGEST_LENGTH]; - CC_SHA256(data.bytes, static_cast(data.length), buffer); - NSData* hashedData = [NSData dataWithBytes:buffer length:CC_SHA256_DIGEST_LENGTH]; - - // Base64 Encoding - return [hashedData base64EncodedStringWithOptions:kNilOptions]; -#else - return nil; -#endif -} - -static NSDictionary *RLMBaseMetrics() { - static NSString *kUnknownString = @"unknown"; - NSBundle *appBundle = NSBundle.mainBundle; - NSString *hashedBundleID = appBundle.bundleIdentifier; - - // Main bundle isn't always the one of interest (e.g. when running tests - // it's xctest rather than the app's bundle), so look for one with a bundle ID - if (!hashedBundleID) { - for (NSBundle *bundle in NSBundle.allBundles) { - if ((hashedBundleID = bundle.bundleIdentifier)) { - appBundle = bundle; - break; - } - } - } - - // If we found a bundle ID anywhere, hash it as it could contain sensitive - // information (e.g. the name of an unannounced product) - if (hashedBundleID) { - NSData *data = [hashedBundleID dataUsingEncoding:NSUTF8StringEncoding]; - hashedBundleID = RLMHashBase16Data(data.bytes, data.length); - } - - Class swiftDecimal128 = NSClassFromString(@"RealmSwiftDecimal128"); - BOOL isSwift = swiftDecimal128 != nil; - - NSString *hashedDistinctId = RLMMACAddress(); - // We use the IOPlatformUUID if is available (Cocoapods, SPM), - // in case we cannot obtain it (Pre-built binaries) we use the hashed mac address. - NSString *hashedBuilderId = RLMBuilderId() ?: hashedDistinctId; - - NSDictionary *info = appBundle.infoDictionary; - - return @{ - // MixPanel properties - @"token": @"ce0fac19508f6c8f20066d345d360fd0", - - // Anonymous identifiers to deduplicate events - @"distinct_id": hashedDistinctId, - @"builder_id": hashedBuilderId, - - @"Anonymized MAC Address": hashedDistinctId, - @"Anonymized Bundle ID": hashedBundleID ?: kUnknownString, - - // SDK Info - @"Binding": @"cocoa", - // Which version of Realm is being used - @"Realm Version": REALM_COCOA_VERSION, - @"Core Version": @REALM_VERSION, - - // Language Info - @"Language": isSwift ? @"swift" : @"objc", - - // Target Info - // Current OS version the app is targeting - @"Target OS Version": [[NSProcessInfo processInfo] operatingSystemVersionString], - // Minimum OS version the app is targeting - @"Target OS Minimum Version": info[@"MinimumOSVersion"] ?: info[@"LSMinimumSystemVersion"] ?: kUnknownString, -#if defined(TARGET_OS_VISION) && TARGET_OS_VISION // TARGET_OS_VISION first defined in Xcode 15.2 - @"Target OS Type": @"visionos", -#elif TARGET_OS_WATCH - @"Target OS Type": @"watchos", -#elif TARGET_OS_TV - @"Target OS Type": @"tvos", -#elif TARGET_OS_IPHONE - @"Target OS Type": @"ios", -#else - @"Target OS Type": @"macos", -#endif - @"Target CPU Arch": RLMTargetArch() ?: kUnknownString, - - // Framework -#if TARGET_OS_MACCATALYST - @"Framework": @"maccatalyst", -#endif - - // Host Info - // Host OS version being built on - @"Host OS Type": @"macos", - @"Host OS Version": RLMHostOSVersion() ?: kUnknownString, - - // Installation method -#ifdef SWIFT_PACKAGE - @"Installation Method": @"spm", -#elif defined(COCOAPODS) - @"Installation Method": @"cocoapods", -#elif defined(CARTHAGE) - @"Installation Method": @"carthage", -#elif defined(REALM_IOS_STATIC) - @"Installation Method": @"static framework", -#else - @"Installation Method": @"other", -#endif - - // Compiler Info - @"Compiler": @"clang", - @"Clang Version": @__clang_version__, - @"Clang Major Version": @__clang_major__, - }; -} - -// This will only be executed once but depending on the number of objects, could take sometime -static NSDictionary *RLMSchemaMetrics(RLMSchema *schema) { - NSMutableDictionary *featuresDictionary = [@{@"Embedded_Object": @0, - @"Asymmetric_Object": @0, - @"Object_Link": @0, - @"Mixed": @0, - @"Primitive_List": @0, - @"Primitive_Set": @0, - @"Primitive_Dictionary": @0, - @"Object_List": @0, - @"Object_Set": @0, - @"Object_Dictionary": @0, - @"Backlink": @0, - } mutableCopy]; - - for (RLMObjectSchema *objectSchema in schema.objectSchema) { - if (objectSchema.isEmbedded) { - featuresDictionary[@"Embedded_Object"] = @1; - } - if (objectSchema.isAsymmetric) { - featuresDictionary[@"Asymmetric_Object"] = @1; - } - - for (RLMProperty *property in objectSchema.properties) { - if (property.array) { - if (property.type == RLMPropertyTypeObject) { - featuresDictionary[@"Object_List"] = @1; - } else { - featuresDictionary[@"Primitive_List"] = @1; - } - continue; - } - if (property.set) { - if (property.type == RLMPropertyTypeObject) { - featuresDictionary[@"Object_Set"] = @1; - } else { - featuresDictionary[@"Primitive_Set"] = @1; - } - continue; - } - if (property.dictionary) { - if (property.type == RLMPropertyTypeObject) { - featuresDictionary[@"Object_Dictionary"] = @1; - } else { - featuresDictionary[@"Primitive_Dictionary"] = @1; - } - continue; - } - - switch (property.type) { - case RLMPropertyTypeAny: - featuresDictionary[@"Mixed"] = @1; - break; - case RLMPropertyTypeObject: - featuresDictionary[@"Object_Link"] = @1; - break; - case RLMPropertyTypeLinkingObjects: - featuresDictionary[@"Backlink"] = @1; - break; - default: - break; - } - } - } - return featuresDictionary; -} - -static NSDictionary *RLMConfigurationMetrics(RLMRealmConfiguration *configuration) { - RLMSyncConfiguration *syncConfiguration = configuration.syncConfiguration; - bool isSync = syncConfiguration != nil; - bool isPBSSync = syncConfiguration.partitionValue != nil; - bool isFlexibleSync = (isSync && !isPBSSync); - auto resetMode = syncConfiguration.clientResetMode; - - bool isCompactOnLaunch = configuration.shouldCompactOnLaunch != nil; - bool migrationBlock = configuration.migrationBlock != nil; - - return @{ - // Sync - @"Sync Enabled": isSync ? @"true" : @"false", - @"Flx_Sync": isFlexibleSync ? @1 : @0, - @"Pbs_Sync": isPBSSync ? @1 : @0, - - // Client Reset - @"CR_Recover_Discard": (isSync && resetMode == RLMClientResetModeRecoverOrDiscardUnsyncedChanges) ? @1 : @0, - @"CR_Recover": (isSync && resetMode == RLMClientResetModeRecoverUnsyncedChanges) ? @1 : @0, - @"CR_Discard": (isSync && resetMode == RLMClientResetModeDiscardUnsyncedChanges) ? @1 : @0, - @"CR_Manual": (isSync && resetMode == RLMClientResetModeManual) ? @1 : @0, - - // Configuration - @"Compact_On_Launch": isCompactOnLaunch ? @1 : @0, - @"Schema_Migration_Block": migrationBlock ? @1 : @0, - }; -} - -void RLMSendAnalytics(RLMRealmConfiguration *configuration, RLMSchema *schema) { - if (getenv("REALM_DISABLE_ANALYTICS") || !RLMIsDebuggerAttached() || RLMIsRunningInPlayground()) { - return; - } - - id config = [configuration copy]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSDictionary *baseMetrics = RLMBaseMetrics(); - NSDictionary *schemaMetrics = RLMSchemaMetrics(schema); - NSDictionary *configurationMetrics = RLMConfigurationMetrics(config); - - NSMutableDictionary *metrics = [[NSMutableDictionary alloc] init]; - [metrics addEntriesFromDictionary:baseMetrics]; - [metrics addEntriesFromDictionary:schemaMetrics]; - [metrics addEntriesFromDictionary:configurationMetrics]; - - NSDictionary *payloadN = @{@"event": @"Run", @"properties": metrics}; - NSData *payload = [NSJSONSerialization dataWithJSONObject:payloadN options:0 error:nil]; - - NSString *url = @"https://data.mongodb-api.com/app/realmsdkmetrics-zmhtm/endpoint/metric_webhook/metric?data=%@"; - NSString *formatted = [NSString stringWithFormat:url, [payload base64EncodedStringWithOptions:0]]; - // No error handling or anything because logging errors annoyed people for no - // real benefit, and it's not clear what else we could do - [[NSURLSession.sharedSession dataTaskWithURL:[NSURL URLWithString:formatted]] resume]; - }); -} - -#else - -void RLMSendAnalytics() {} - -#endif diff --git a/Realm/RLMApp.h b/Realm/RLMApp.h deleted file mode 100644 index 6ad37d6eb0..0000000000 --- a/Realm/RLMApp.h +++ /dev/null @@ -1,239 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@protocol RLMNetworkTransport, RLMBSON; - -@class RLMUser, RLMCredentials, RLMSyncManager, RLMEmailPasswordAuth, RLMPushClient, RLMSyncTimeoutOptions; - -/// A block type used for APIs which asynchronously vend an `RLMUser`. -typedef void(^RLMUserCompletionBlock)(RLMUser * _Nullable, NSError * _Nullable); - -/// A block type used to report an error -typedef void(^RLMOptionalErrorBlock)(NSError * _Nullable); - -#pragma mark RLMAppConfiguration - -/// Properties representing the configuration of a client -/// that communicate with a particular Realm application. -/// -/// `RLMAppConfiguration` options cannot be modified once the `RLMApp` using it -/// is created. App's configuration values are cached when the App is created so any modifications after it -/// will not have any effect. -@interface RLMAppConfiguration : NSObject - -/// A custom base URL to request against. If not set or set to nil, the default base url for app services will be returned. -@property (nonatomic, strong, null_resettable) NSString *baseURL; - -/// The custom transport for network calls to the server. -@property (nonatomic, strong, nullable) id transport; - -/// :nodoc: -@property (nonatomic, strong, nullable) NSString *localAppName - __attribute__((deprecated("This field is not used"))); -/// :nodoc: -@property (nonatomic, strong, nullable) NSString *localAppVersion - __attribute__((deprecated("This field is not used"))); - -/// The default timeout for network requests. -@property (nonatomic, assign) NSUInteger defaultRequestTimeoutMS; - -/// If enabled (the default), a single connection is used for all Realms opened -/// with a single sync user. If disabled, a separate connection is used for each -/// Realm. -/// -/// Session multiplexing reduces resources used and typically improves -/// performance. When multiplexing is enabled, the connection is not immediately -/// closed when the last session is closed, and instead remains open for -/// ``RLMSyncTimeoutOptions.connectionLingerTime`` milliseconds (30 seconds by -/// default). -@property (nonatomic, assign) BOOL enableSessionMultiplexing; - -/** - Options for the assorted types of connection timeouts for sync connections. - - If nil default values for all timeouts are used instead. - */ -@property (nonatomic, nullable, copy) RLMSyncTimeoutOptions *syncTimeouts; - -/// :nodoc: -- (instancetype)initWithBaseURL:(nullable NSString *)baseURL - transport:(nullable id)transport - localAppName:(nullable NSString *)localAppName - localAppVersion:(nullable NSString *)localAppVersion -__attribute__((deprecated("localAppName and localAppVersion are unused"))); - -/// :nodoc: -- (instancetype)initWithBaseURL:(nullable NSString *) baseURL - transport:(nullable id)transport - localAppName:(nullable NSString *)localAppName - localAppVersion:(nullable NSString *)localAppVersion - defaultRequestTimeoutMS:(NSUInteger)defaultRequestTimeoutMS -__attribute__((deprecated("localAppName and localAppVersion are unused"))); - -/** -Create a new Realm App configuration. - -@param baseURL A custom base URL to request against. -@param transport A custom network transport. -*/ -- (instancetype)initWithBaseURL:(nullable NSString *)baseURL - transport:(nullable id)transport; - -/** - Create a new Realm App configuration. - - @param baseURL A custom base URL to request against. - @param transport A custom network transport. - @param defaultRequestTimeoutMS A custom default timeout for network requests. - */ -- (instancetype)initWithBaseURL:(nullable NSString *) baseURL - transport:(nullable id)transport - defaultRequestTimeoutMS:(NSUInteger)defaultRequestTimeoutMS; - -@end - -#pragma mark RLMApp - -/** - The `RLMApp` has the fundamental set of methods for communicating with a Realm - application backend. - - This interface provides access to login and authentication. - */ -RLM_SWIFT_SENDABLE RLM_FINAL // internally thread-safe -@interface RLMApp : NSObject - -/// The configuration for this Realm app. -@property (nonatomic, readonly) RLMAppConfiguration *configuration; - -/// The `RLMSyncManager` for this Realm app. -@property (nonatomic, readonly) RLMSyncManager *syncManager; - -/// Get a dictionary containing all users keyed on id. -@property (nonatomic, readonly) NSDictionary *allUsers; - -/// Get the current user logged into the Realm app. -@property (nonatomic, readonly, nullable) RLMUser *currentUser; - -/// The app ID for this Realm app. -@property (nonatomic, readonly) NSString *appId; - -/** - A client for the email/password authentication provider which - can be used to obtain a credential for logging in. - - Used to perform requests specifically related to the email/password provider. -*/ -@property (nonatomic, readonly) RLMEmailPasswordAuth *emailPasswordAuth; - -/** - Get an application with a given appId and configuration. - - @param appId The unique identifier of your Realm app. - */ -+ (instancetype)appWithId:(NSString *)appId; - -/** - Get an application with a given appId and configuration. - - @param appId The unique identifier of your Realm app. - @param configuration A configuration object to configure this client. - */ -+ (instancetype)appWithId:(NSString *)appId - configuration:(nullable RLMAppConfiguration *)configuration; - -/** - Login to a user for the Realm app. - - @param credentials The credentials identifying the user. - @param completion A callback invoked after completion. - */ -- (void)loginWithCredential:(RLMCredentials *)credentials - completion:(RLMUserCompletionBlock)completion NS_REFINED_FOR_SWIFT; - -/** - Switches the active user to the specified user. - - This sets which user is used by all RLMApp operations which require a user. This is a local operation which does not access the network. - An exception will be throw if the user is not valid. The current user will remain logged in. - - @param syncUser The user to switch to. - @returns The user you intend to switch to - */ -- (RLMUser *)switchToUser:(RLMUser *)syncUser; - -/** - A client which can be used to register devices with the server to receive push notificatons - */ -- (RLMPushClient *)pushClientWithServiceName:(NSString *)serviceName - NS_SWIFT_NAME(pushClient(serviceName:)); - -/** - RLMApp instances are cached internally by Realm and cannot be created directly. - - Use `+[RLMRealm appWithId]` or `+[RLMRealm appWithId:configuration:]` - to obtain a reference to an RLMApp. - */ -- (instancetype)init __attribute__((unavailable("Use +appWithId or appWithId:configuration:."))); - -/** -RLMApp instances are cached internally by Realm and cannot be created directly. - -Use `+[RLMRealm appWithId]` or `+[RLMRealm appWithId:configuration:]` -to obtain a reference to an RLMApp. -*/ -+ (instancetype)new __attribute__((unavailable("Use +appWithId or appWithId:configuration:."))); - -@end - -#pragma mark - Sign In With Apple Extension - -API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) -/// Use this delegate to be provided a callback once authentication has succeed or failed -@protocol RLMASLoginDelegate - -/// Callback that is invoked should the authentication fail. -/// @param error An error describing the authentication failure. -- (void)authenticationDidFailWithError:(NSError *)error NS_SWIFT_NAME(authenticationDidComplete(error:)); - -/// Callback that is invoked should the authentication succeed. -/// @param user The newly authenticated user. -- (void)authenticationDidCompleteWithUser:(RLMUser *)user NS_SWIFT_NAME(authenticationDidComplete(user:)); - -@end - -API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) -/// Category extension that deals with Sign In With Apple authentication. -/// This is only available on OS's that support `AuthenticationServices` -@interface RLMApp (ASLogin) - -/// Use this delegate to be provided a callback once authentication has succeed or failed. -@property (nonatomic, weak, nullable) id authorizationDelegate; - -/// Sets the ASAuthorizationControllerDelegate to be handled by `RLMApp` -/// @param controller The ASAuthorizationController in which you want `RLMApp` to consume its delegate. -- (void)setASAuthorizationControllerDelegateForController:(ASAuthorizationController *)controller NS_REFINED_FOR_SWIFT; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMApp.mm b/Realm/RLMApp.mm deleted file mode 100644 index 24db6b2001..0000000000 --- a/Realm/RLMApp.mm +++ /dev/null @@ -1,502 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMApp_Private.hpp" - -#import -#if __has_include() -#import -#define REALM_UIDEVICE_AVAILABLE -#endif - -#import "RLMAnalytics.hpp" -#import "RLMBSON_Private.hpp" -#import "RLMCredentials_Private.hpp" -#import "RLMEmailPasswordAuth.h" -#import "RLMLogger.h" -#import "RLMProviderClient_Private.hpp" -#import "RLMPushClient_Private.hpp" -#import "RLMSyncManager_Private.hpp" -#import "RLMUser_Private.hpp" -#import "RLMUtil.hpp" - -#import -#import -#import - -#if !defined(REALM_COCOA_VERSION) -#import "RLMVersion.h" -#endif - -using namespace realm; - -#pragma mark CocoaNetworkTransport -namespace { - /// Internal transport struct to bridge RLMNetworkingTransporting to the GenericNetworkTransport. - class CocoaNetworkTransport : public realm::app::GenericNetworkTransport { - public: - CocoaNetworkTransport(id transport) : m_transport(transport) {} - - void send_request_to_server(const app::Request& request, - util::UniqueFunction&& completion) override { - // Convert the app::Request to an RLMRequest - auto rlmRequest = [RLMRequest new]; - rlmRequest.url = @(request.url.data()); - rlmRequest.body = @(request.body.data()); - NSMutableDictionary *headers = [NSMutableDictionary new]; - for (auto&& header : request.headers) { - headers[@(header.first.data())] = @(header.second.data()); - } - rlmRequest.headers = headers; - rlmRequest.method = static_cast(request.method); - rlmRequest.timeout = request.timeout_ms / 1000.0; - - // Send the request through to the Cocoa level transport - auto completion_ptr = completion.release(); - [m_transport sendRequestToServer:rlmRequest completion:^(RLMResponse *response) { - util::UniqueFunction completion(completion_ptr); - std::map bridgingHeaders; - [response.headers enumerateKeysAndObjectsUsingBlock:[&](NSString *key, NSString *value, BOOL *) { - bridgingHeaders[key.UTF8String] = value.UTF8String; - }]; - - // Convert the RLMResponse to an app:Response and pass downstream to - // the object store - completion(app::Response{ - .http_status_code = static_cast(response.httpStatusCode), - .custom_status_code = static_cast(response.customStatusCode), - .headers = bridgingHeaders, - .body = response.body ? response.body.UTF8String : "" - }); - }]; - } - - id transport() const { - return m_transport; - } - private: - id m_transport; - }; -} - -#pragma mark RLMAppConfiguration -@implementation RLMAppConfiguration { - realm::app::AppConfig _config; -} - -- (instancetype)init { - if (self = [super init]) { - self.enableSessionMultiplexing = true; - self.encryptMetadata = !getenv("REALM_DISABLE_METADATA_ENCRYPTION") && !RLMIsRunningInPlayground(); - RLMNSStringToStdString(_config.base_file_path, RLMDefaultDirectoryForBundleIdentifier(nil)); - configureSyncConnectionParameters(_config); - } - return self; -} - -- (instancetype)initWithBaseURL:(nullable NSString *)baseURL - transport:(nullable id)transport - localAppName:(nullable NSString *)localAppName - localAppVersion:(nullable NSString *)localAppVersion { - return [self initWithBaseURL:baseURL - transport:transport - localAppName:localAppName - localAppVersion:localAppVersion - defaultRequestTimeoutMS:60000]; -} - -- (instancetype)initWithBaseURL:(nullable NSString *)baseURL - transport:(nullable id)transport - localAppName:(nullable NSString *)localAppName - localAppVersion:(nullable NSString *)localAppVersion - defaultRequestTimeoutMS:(NSUInteger)defaultRequestTimeoutMS { - if (self = [self init]) { - self.baseURL = baseURL; - self.transport = transport; - self.localAppName = localAppName; - self.localAppVersion = localAppVersion; - self.defaultRequestTimeoutMS = defaultRequestTimeoutMS; - } - return self; -} - -- (instancetype)initWithBaseURL:(nullable NSString *)baseURL - transport:(nullable id)transport { - return [self initWithBaseURL:baseURL - transport:transport - defaultRequestTimeoutMS:60000]; -} - -- (instancetype)initWithBaseURL:(nullable NSString *)baseURL - transport:(nullable id)transport - defaultRequestTimeoutMS:(NSUInteger)defaultRequestTimeoutMS { - if (self = [self init]) { - self.baseURL = baseURL; - self.transport = transport; - self.defaultRequestTimeoutMS = defaultRequestTimeoutMS; - } - return self; -} - -static void configureSyncConnectionParameters(realm::app::AppConfig& config) { - // Anonymized BundleId - NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier]; - NSData *bundleIdData = [bundleId dataUsingEncoding:NSUTF8StringEncoding]; - RLMNSStringToStdString(config.device_info.bundle_id, RLMHashBase16Data(bundleIdData.bytes, bundleIdData.length)); - - config.device_info.sdk = "Realm Swift"; - RLMNSStringToStdString(config.device_info.sdk_version, REALM_COCOA_VERSION); - - // Platform info isn't available when running via `swift test`. - // Non-Xcode SPM builds can't build for anything but macOS, so this is - // probably unimportant for now and we can just report "unknown" - auto processInfo = [NSProcessInfo processInfo]; - RLMNSStringToStdString(config.device_info.platform_version, - [processInfo operatingSystemVersionString] ?: @"unknown"); - - RLMNSStringToStdString(config.device_info.framework_version, @__clang_version__); - -#ifdef REALM_UIDEVICE_AVAILABLE - RLMNSStringToStdString(config.device_info.device_name, [UIDevice currentDevice].model); -#endif - struct utsname systemInfo; - uname(&systemInfo); - config.device_info.device_version = systemInfo.machine; -} - -- (const realm::app::AppConfig&)config { - if (!_config.transport) { - self.transport = nil; - } - return _config; -} - -- (id)copyWithZone:(NSZone *)zone { - RLMAppConfiguration *copy = [[RLMAppConfiguration alloc] init]; - copy->_config = _config; - return copy; -} - -- (NSString *)appId { - return RLMStringViewToNSString(_config.app_id); -} - -- (void)setAppId:(NSString *)appId { - if ([appId length] == 0) { - @throw RLMException(@"AppId cannot be an empty string"); - } - - RLMNSStringToStdString(_config.app_id, appId); -} - -static NSString *getOptionalString(const std::optional& str) { - return str ? RLMStringViewToNSString(*str) : nil; -} - -static void setOptionalString(std::optional& dst, NSString *src) { - if (src.length == 0) { - dst.reset(); - } - else { - dst.emplace(); - RLMNSStringToStdString(*dst, src); - } -} - -- (NSString *)baseURL { - return getOptionalString(_config.base_url) ?: RLMStringViewToNSString(app::App::default_base_url()) ?: @""; -} - -- (void)setBaseURL:(nullable NSString *)baseURL { - setOptionalString(_config.base_url, baseURL); -} - -- (id)transport { - return static_cast(*_config.transport).transport(); -} - -- (void)setTransport:(id)transport { - if (!transport) { - transport = [RLMNetworkTransport new]; - } - _config.transport = std::make_shared(transport); -} - -- (NSUInteger)defaultRequestTimeoutMS { - return _config.default_request_timeout_ms.value_or(60000U); -} - -- (void)setDefaultRequestTimeoutMS:(NSUInteger)defaultRequestTimeoutMS { - _config.default_request_timeout_ms = (uint64_t)defaultRequestTimeoutMS; -} - -- (BOOL)enableSessionMultiplexing { - return _config.sync_client_config.multiplex_sessions; -} - -- (void)setEnableSessionMultiplexing:(BOOL)enableSessionMultiplexing { - _config.sync_client_config.multiplex_sessions = enableSessionMultiplexing; -} - -- (BOOL)encryptMetadata { - return _config.metadata_mode == app::AppConfig::MetadataMode::Encryption; -} - -- (void)setEncryptMetadata:(BOOL)encryptMetadata { - _config.metadata_mode = encryptMetadata ? app::AppConfig::MetadataMode::Encryption - : app::AppConfig::MetadataMode::NoEncryption; -} - -- (NSURL *)rootDirectory { - return [NSURL fileURLWithPath:RLMStringViewToNSString(_config.base_file_path)]; -} - -- (void)setRootDirectory:(NSURL *)rootDirectory { - RLMNSStringToStdString(_config.base_file_path, rootDirectory.path); -} - -- (RLMSyncTimeoutOptions *)syncTimeouts { - return [[RLMSyncTimeoutOptions alloc] initWithOptions:_config.sync_client_config.timeouts]; -} - -- (void)setSyncTimeouts:(RLMSyncTimeoutOptions *)syncTimeouts { - _config.sync_client_config.timeouts = syncTimeouts->_options; -} - -@end - -#pragma mark RLMAppSubscriptionToken - -@implementation RLMAppSubscriptionToken { - std::shared_ptr _app; - std::optional _token; -} - -- (instancetype)initWithApp:(std::shared_ptr)app token:(app::App::Token&&)token { - if (self = [super init]) { - _app = std::move(app); - _token = std::move(token); - } - return self; -} - -- (void)unsubscribe { - _token.reset(); - _app.reset(); -} -@end - -#pragma mark RLMApp -@interface RLMApp() { - std::shared_ptr _app; - __weak id _authorizationDelegate API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)); -} - -@end - -@implementation RLMApp : NSObject - -+ (void)initialize { - [RLMRealm class]; - // Even though there is nothing to log when the App initialises, we want to - // be able to log anything happening after this e.g. login/register. - [RLMLogger class]; -} - -- (instancetype)initWithApp:(std::shared_ptr&&)app config:(RLMAppConfiguration *)config { - if (self = [super init]) { - _app = std::move(app); - _configuration = config; - _syncManager = [[RLMSyncManager alloc] initWithSyncManager:_app->sync_manager()]; - } - return self; -} - -- (instancetype)initWithConfiguration:(RLMAppConfiguration *)configuration { - if (self = [super init]) { - _app = RLMTranslateError([&] { - return app::App::get_app(app::App::CacheMode::Disabled, configuration.config); - }); - _configuration = configuration; - _syncManager = [[RLMSyncManager alloc] initWithSyncManager:_app->sync_manager()]; - } - return self; -} - -static RLMUnfairMutex s_appMutex; -static NSMutableDictionary *s_apps = [NSMutableDictionary new]; - -+ (NSArray *)allApps { - std::lock_guard lock(s_appMutex); - return s_apps.allValues; -} - -+ (void)resetAppCache { - std::lock_guard lock(s_appMutex); - [s_apps removeAllObjects]; - app::App::clear_cached_apps(); -} - -+ (instancetype)appWithConfiguration:(RLMAppConfiguration *)configuration { - std::lock_guard lock(s_appMutex); - NSString *appId = configuration.appId; - if (RLMApp *app = s_apps[appId]) { - return app; - } - return s_apps[appId] = [[RLMApp alloc] initWithConfiguration:configuration.copy]; -} - -+ (instancetype)appWithId:(NSString *)appId configuration:(RLMAppConfiguration *)configuration { - std::lock_guard lock(s_appMutex); - if (RLMApp *app = s_apps[appId]) { - return app; - } - configuration = configuration.copy; - configuration.appId = appId; - return s_apps[appId] = [[RLMApp alloc] initWithConfiguration:configuration]; -} - -+ (instancetype)appWithId:(NSString *)appId { - std::lock_guard lock(s_appMutex); - if (RLMApp *app = s_apps[appId]) { - return app; - } - auto config = [[RLMAppConfiguration alloc] init]; - config.appId = appId; - return s_apps[appId] = [[RLMApp alloc] initWithConfiguration:config]; -} - -+ (RLMApp *_Nullable)cachedAppWithId:(NSString *)appId { - std::lock_guard lock(s_appMutex); - return s_apps[appId]; -} - -- (NSString *)appId { - return @(_app->config().app_id.c_str()); -} - -- (std::shared_ptr)_realmApp { - return _app; -} - -- (NSDictionary *)allUsers { - NSMutableDictionary *buffer = [NSMutableDictionary new]; - for (auto&& user : _app->all_users()) { - NSString *user_id = @(user->user_id().c_str()); - buffer[user_id] = [[RLMUser alloc] initWithUser:std::move(user)]; - } - return buffer; -} - -- (RLMUser *)currentUser { - if (auto user = _app->current_user()) { - return [[RLMUser alloc] initWithUser:user]; - } - return nil; -} - -- (RLMEmailPasswordAuth *)emailPasswordAuth { - return [[RLMEmailPasswordAuth alloc] initWithApp:_app]; -} - -- (NSString *)baseURL { - return getOptionalString(_app->get_base_url()) ?: RLMStringViewToNSString(_app->default_base_url()); -} - -- (void)updateBaseURL:(NSString * _Nullable)baseURL completion:(nonnull RLMOptionalErrorBlock)completionHandler { - auto completion = ^(std::optional error) { - if (error) { - return completionHandler(makeError(*error)); - } - - completionHandler(nil); - }; - return RLMTranslateError([&] { - NSString *url = (baseURL ?: @""); - NSString *newUrl = [url stringByReplacingOccurrencesOfString:@"/" withString:@"" options:0 range:NSMakeRange(url.length-1, 1)]; - return _app->update_base_url(newUrl.UTF8String, completion); - }); -} - -- (void)loginWithCredential:(RLMCredentials *)credentials - completion:(RLMUserCompletionBlock)completionHandler { - auto completion = ^(std::shared_ptr user, std::optional error) { - if (error) { - return completionHandler(nil, makeError(*error)); - } - - completionHandler([[RLMUser alloc] initWithUser:user], nil); - }; - return RLMTranslateError([&] { - return _app->log_in_with_credentials(credentials.appCredentials, completion); - }); -} - -- (RLMUser *)switchToUser:(RLMUser *)syncUser { - RLMTranslateError([&] { - _app->switch_user(syncUser.user); - }); - return syncUser; -} - -- (RLMPushClient *)pushClientWithServiceName:(NSString *)serviceName { - return RLMTranslateError([&] { - return [[RLMPushClient alloc] initWithPushClient:_app->push_notification_client(serviceName.UTF8String)]; - }); -} - -#pragma mark - Sign In With Apple Extension - -- (void)setAuthorizationDelegate:(id)authorizationDelegate API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) { - _authorizationDelegate = authorizationDelegate; -} - -- (id)authorizationDelegate API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) { - return _authorizationDelegate; -} - -- (void)setASAuthorizationControllerDelegateForController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) { - controller.delegate = self; -} - -- (void)authorizationController:(__unused ASAuthorizationController *)controller - didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) { - NSString *jwt = [[NSString alloc] initWithData:((ASAuthorizationAppleIDCredential *)authorization.credential).identityToken - encoding:NSUTF8StringEncoding]; - [self loginWithCredential:[RLMCredentials credentialsWithAppleToken:jwt] - completion:^(RLMUser *user, NSError *error) { - if (user) { - [self.authorizationDelegate authenticationDidCompleteWithUser:user]; - } else { - [self.authorizationDelegate authenticationDidFailWithError:error]; - } - }]; -} - -- (void)authorizationController:(__unused ASAuthorizationController *)controller - didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) { - [self.authorizationDelegate authenticationDidFailWithError:error]; -} - -- (RLMAppSubscriptionToken *)subscribe:(RLMAppNotificationBlock)block { - return [[RLMAppSubscriptionToken alloc] initWithApp:_app token:_app->subscribe([block, self] (auto&) { - block(self); - })]; -} - -@end diff --git a/Realm/RLMApp_Private.h b/Realm/RLMApp_Private.h deleted file mode 100644 index 1f00557a64..0000000000 --- a/Realm/RLMApp_Private.h +++ /dev/null @@ -1,60 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -/// Observer block for app notifications. -typedef void(^RLMAppNotificationBlock)(RLMApp *); - -/// Token that identifies an observer. Unsubscribes when deconstructed to -/// avoid dangling observers, therefore this must be retained to hold -/// onto a subscription. -@interface RLMAppSubscriptionToken : NSObject -- (void)unsubscribe; -@end - -@interface RLMApp () -/// A custom base URL to request against. If not set or set to nil, the default base url for app services will be returned. -@property (nonatomic, readonly) NSString *baseURL; -/// Returns all currently cached Apps -+ (NSArray *)allApps; -/// Subscribe to notifications for this RLMApp. -- (RLMAppSubscriptionToken *)subscribe:(RLMAppNotificationBlock)block; - -+ (instancetype)appWithConfiguration:(RLMAppConfiguration *)configuration; -+ (RLMApp *_Nullable)cachedAppWithId:(NSString *)appId; - -+ (void)resetAppCache; - -/// Updates the base url used by Atlas device sync, in case the need to roam between servers (cloud and/or edge server). -/// @param baseURL The new base url to connect to. Setting `nil` will reset the base url to the default url. -/// @note Updating the base URL would trigger a client reset. -- (void)updateBaseURL:(NSString *_Nullable)baseURL - completion:(RLMOptionalErrorBlock)completionHandler NS_REFINED_FOR_SWIFT; - -@end - -@interface RLMAppConfiguration () -@property (nonatomic) NSString *appId; -@property (nonatomic) BOOL encryptMetadata; -@property (nonatomic) NSURL *rootDirectory; -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMApp_Private.hpp b/Realm/RLMApp_Private.hpp deleted file mode 100644 index c6d6cc82e7..0000000000 --- a/Realm/RLMApp_Private.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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#import - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -RLM_DIRECT_MEMBERS -@interface RLMAppConfiguration () -- (const realm::app::AppConfig&)config; -@end - -RLM_DIRECT_MEMBERS -@interface RLMApp () -- (std::shared_ptr)_realmApp; -@end - -NSError *makeError(realm::app::AppError const& appError); - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMAsymmetricObject.h b/Realm/RLMAsymmetricObject.h deleted file mode 100644 index 5bda8f9f67..0000000000 --- a/Realm/RLMAsymmetricObject.h +++ /dev/null @@ -1,98 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@class RLMObjectSchema, RLMPropertyDescriptor, RLMRealm; -/** - `RLMAsymmetricObject` is a base class used to define asymmetric Realm objects. - - Asymmetric objects can only be created using the `createInRealm:` - function, and cannot be added, removed or queried. - When created, asymmetric objects will be synced unidirectionally to the MongoDB - database and cannot be accessed locally. - - Linking an asymmetric object within an `Object` is not allowed and will throw an error. - - The property types supported on `RLMAsymmetricObject` are the same as for `RLMObject`, - except for that asymmetric objects can only link to embedded objects, so `RLMObject` - and `RLMArray` properties are not supported (`RLMEmbeddedObject` and - `RLMArray` *are*). - */ -@interface RLMAsymmetricObject : RLMObjectBase - -#pragma mark - Creating & Initializing Objects - -/** - Creates an unmanaged instance of a Realm object. - */ -- (instancetype)init NS_DESIGNATED_INITIALIZER; - -/** - Creates an unmanaged instance of a Realm object. - - Pass in an `NSArray` or `NSDictionary` instance to set the values of the object's properties. - */ -- (instancetype)initWithValue:(id)value; - -/** - Returns the class name for a Realm object subclass. - - @warning Do not override. Realm relies on this method returning the exact class - name. - - @return The class name for the model class. - */ -+ (NSString *)className; - -/** - Creates an Asymmetric object, which will be synced unidirectionally and - cannot be queried locally. - - Objects created using this method will not be added to the Realm. - - @warning This method may only be called during a write transaction. - @warning This method always returns nil. - - @param realm The Realm to be used to create the asymmetric object.. - @param value The value used to populate the object. - - @return Returns `nil` - */ -+ (nullable instancetype)createInRealm:(RLMRealm *)realm withValue:(id)value; - -#pragma mark - Properties - -/** - The object schema which lists the managed properties for the object. - */ -@property (nonatomic, readonly) RLMObjectSchema *objectSchema; - -#pragma mark - Dynamic Accessors - -/// :nodoc: -- (nullable id)objectForKeyedSubscript:(NSString *)key; - -/// :nodoc: -- (void)setObject:(nullable id)obj forKeyedSubscript:(NSString *)key; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMAsymmetricObject.mm b/Realm/RLMAsymmetricObject.mm deleted file mode 100644 index d6634a4042..0000000000 --- a/Realm/RLMAsymmetricObject.mm +++ /dev/null @@ -1,101 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMAsymmetricObject.h" - -#import "RLMObject_Private.hpp" -#import "RLMObjectStore.h" -#import "RLMSchema_Private.h" - -@implementation RLMAsymmetricObject -// synthesized in RLMObjectBase but redeclared here for documentation purposes -@dynamic objectSchema; - -#pragma mark - Designated Initializers - -- (instancetype)init { - return [super init]; -} - -#pragma mark - Convenience Initializers - -- (instancetype)initWithValue:(id)value { - if (!(self = [self init])) { - return nil; - } - RLMInitializeWithValue(self, value, RLMSchema.partialPrivateSharedSchema); - return self; -} - -#pragma mark - Class-based Object Creation - -+ (instancetype)createInRealm:(RLMRealm *)realm withValue:(id)value { - RLMCreateAsymmetricObjectInRealm(realm, [self className], value); - return nil; -} - -#pragma mark - Subscripting - -- (id)objectForKeyedSubscript:(NSString *)key { - return RLMObjectBaseObjectForKeyedSubscript(self, key); -} - -- (void)setObject:(id)obj forKeyedSubscript:(NSString *)key { - RLMObjectBaseSetObjectForKeyedSubscript(self, key, obj); -} - -#pragma mark - Other Instance Methods - -+ (NSString *)className { - return [super className]; -} - -#pragma mark - Default values for schema definition - -+ (NSString *)primaryKey { - return nil; -} - -+ (NSArray *)indexedProperties { - return @[]; -} - -+ (NSDictionary *)linkingObjectsProperties { - return @{}; -} - -+ (NSDictionary *)defaultPropertyValues { - return nil; -} - -+ (NSArray *)ignoredProperties { - return nil; -} - -+ (NSArray *)requiredProperties { - return @[]; -} - -+ (bool)_realmIgnoreClass { - return false; -} - -+ (bool)isAsymmetric { - return true; -} -@end diff --git a/Realm/RLMAsyncTask.h b/Realm/RLMAsyncTask.h index 4c015c5167..919b49eeb5 100644 --- a/Realm/RLMAsyncTask.h +++ b/Realm/RLMAsyncTask.h @@ -17,7 +17,6 @@ //////////////////////////////////////////////////////////////////////////// #import -#import RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @@ -31,51 +30,8 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) session as the sync session itself is created asynchronously, and may not exist yet when -[RLMRealm asyncOpenWithConfiguration:completion:] returns. */ -RLM_SWIFT_SENDABLE RLM_FINAL // is internally thread-safe +NS_SWIFT_SENDABLE RLM_FINAL // is internally thread-safe @interface RLMAsyncOpenTask : NSObject -/** - Register a progress notification block. - - Each registered progress notification block is called whenever the sync - subsystem has new progress data to report until the task is either cancelled - or the completion callback is called. Progress notifications are delivered on - the main queue. - */ -- (void)addProgressNotificationBlock:(RLMProgressNotificationBlock)block - __attribute__((deprecated("Use addSyncProgressNotificationBlock instead", "addSyncProgressNotificationBlock"))); - -/** - Register a progress notification block. - - Each registered progress notification block is called whenever the sync - subsystem has new progress data to report until the task is either cancelled - or the completion callback is called. Progress notifications are delivered on - the main queue. - */ -- (void)addSyncProgressNotificationBlock:(RLMSyncProgressNotificationBlock)block; - -/** - Register a progress notification block which is called on the given queue. - - Each registered progress notification block is called whenever the sync - subsystem has new progress data to report until the task is either cancelled - or the completion callback is called. Progress notifications are delivered on - the supplied queue. - */ -- (void)addProgressNotificationOnQueue:(dispatch_queue_t)queue - block:(RLMProgressNotificationBlock)block - __attribute__((deprecated("Use addSyncProgressNotificationOnQueue instead", "addSyncProgressNotificationOnQueue"))); - -/** - Register a progress notification block which is called on the given queue. - - Each registered progress notification block is called whenever the sync - subsystem has new progress data to report until the task is either cancelled - or the completion callback is called. Progress notifications are delivered on - the supplied queue. - */ -- (void)addSyncProgressNotificationOnQueue:(dispatch_queue_t)queue - block:(RLMSyncProgressNotificationBlock)block; /** Cancel the asynchronous open. diff --git a/Realm/RLMAsyncTask.mm b/Realm/RLMAsyncTask.mm index f601b2180d..42b11587c1 100644 --- a/Realm/RLMAsyncTask.mm +++ b/Realm/RLMAsyncTask.mm @@ -22,12 +22,9 @@ #import "RLMRealm_Private.hpp" #import "RLMRealmConfiguration_Private.hpp" #import "RLMScheduler.h" -#import "RLMSyncSubscription_Private.hpp" #import "RLMUtil.hpp" #import -#import -#import #import static dispatch_queue_t s_async_open_queue = dispatch_queue_create("io.realm.asyncOpenDispatchQueue", @@ -41,74 +38,25 @@ void RLMSetAsyncOpenQueue(dispatch_queue_t queue) { NSLocalizedDescriptionKey: @"Operation canceled" }]; -typedef void(^CoreProgressNotificationBlock)(NSUInteger transferredBytes, NSUInteger transferrableBytes, double estimate); - __attribute__((objc_direct_members)) @implementation RLMAsyncOpenTask { RLMUnfairMutex _mutex; - std::shared_ptr _task; - std::vector _progressBlocks; bool _cancel; RLMRealmConfiguration *_configuration; RLMScheduler *_scheduler; - bool _waitForDownloadCompletion; void (^_completion)(NSError *); RLMRealm *_backgroundRealm; } -- (void)addSyncProgressNotificationOnQueue:(dispatch_queue_t)queue block:(RLMSyncProgressNotificationBlock)block { - auto wrappedBlock = ^(NSUInteger transferred_bytes, NSUInteger transferrable_bytes, double estimate) { - dispatch_async(queue, ^{ - @autoreleasepool { - RLMSyncProgress progress = {.transferredBytes = transferred_bytes, .transferrableBytes = transferrable_bytes, .progressEstimate = estimate}; - block(progress); - } - }); - }; - - std::lock_guard lock(_mutex); - if (_task) { - _task->register_download_progress_notifier(wrappedBlock); - } - else if (!_cancel) { - _progressBlocks.push_back(wrappedBlock); - } -} - -- (void)addProgressNotificationOnQueue:(dispatch_queue_t)queue block:(RLMProgressNotificationBlock)block { - [self addSyncProgressNotificationOnQueue:queue block:^(RLMSyncProgress progress) { - block(progress.transferredBytes, progress.transferrableBytes); - }]; -} - -- (void)addProgressNotificationBlock:(RLMProgressNotificationBlock)block { - [self addProgressNotificationOnQueue:dispatch_get_main_queue() block:block]; -} - -- (void)addSyncProgressNotificationBlock:(RLMSyncProgressNotificationBlock)block { - [self addSyncProgressNotificationOnQueue:dispatch_get_main_queue() block:block]; -} - - (void)cancel { std::lock_guard lock(_mutex); _cancel = true; - _progressBlocks.clear(); - if (_task) { - _task->cancel(); - // Cancelling realm::AsyncOpenTask results in it never calling our callback, - // so if we're currently in that we have to just send the cancellation - // error immediately. In all other cases, though, we want to wait until - // we've actually cancelled and will send the error the next time we - // check for cancellation - [self reportError:s_canceledError]; - } } - (instancetype)initWithConfiguration:(RLMRealmConfiguration *)configuration - confinedTo:(RLMScheduler *)scheduler - download:(bool)waitForDownloadCompletion { + confinedTo:(RLMScheduler *)scheduler { if (!(self = [super init])) { return self; } @@ -117,17 +65,14 @@ - (instancetype)initWithConfiguration:(RLMRealmConfiguration *)configuration // the config after calling async open _configuration = configuration.copy; _scheduler = scheduler; - _waitForDownloadCompletion = waitForDownloadCompletion; return self; } - (instancetype)initWithConfiguration:(RLMRealmConfiguration *)configuration confinedTo:(RLMScheduler *)confinement - download:(bool)waitForDownloadCompletion completion:(RLMAsyncOpenRealmCallback)completion { - self = [self initWithConfiguration:configuration confinedTo:confinement - download:waitForDownloadCompletion]; + self = [self initWithConfiguration:configuration confinedTo:confinement]; [self waitForOpen:completion]; return self; } @@ -151,8 +96,6 @@ - (void)waitWithCompletion:(void (^)(NSError *))completion { return [self reportError:s_canceledError]; } - // get_synchronized_realm() synchronously opens the DB and performs file-format - // upgrades, so we want to dispatch to the background before invoking it. dispatch_async(s_async_open_queue, ^{ @autoreleasepool { [self startAsyncOpen]; @@ -160,86 +103,13 @@ - (void)waitWithCompletion:(void (^)(NSError *))completion { }); } -// The full async open flow is: -// 1. Dispatch to a background queue -// 2. Use Realm::get_synchronized_realm() to create the Realm file, run -// migrations and compactions, and download the latest data from the server. -// 3. Dispatch back to queue -// 4. Initialize a RLMRealm in the background queue to perform the SDK -// initialization (e.g. creating managed accessor classes). -// 5. Wait for initial flexible sync subscriptions to complete -// 6. Dispatch to the final scheduler -// 7. Open the final RLMRealm, release the previously opened background one, -// and then invoke the completion callback. -// -// Steps 2 and 5 are skipped for non-sync or non-flexible sync Realms, in which -// case step 4 will handle doing migrations and compactions etc. in the background. -// -// At any point `cancel` can be called from another thread. Cancellation is mostly -// cooperative rather than preemptive: we check at each step if we've been cancelled, -// and if so call the completion with the cancellation error rather than -// proceeding. Downloading the data from the server is the one exception to this. -// Ideally waiting for flexible sync subscriptions would also be preempted. - (void)startAsyncOpen { std::unique_lock lock(_mutex); if ([self checkCancellation]) { return; } - if (_waitForDownloadCompletion && _configuration.configRef.sync_config) { -#if REALM_ENABLE_SYNC - _task = realm::Realm::get_synchronized_realm(_configuration.config); - for (auto& block : _progressBlocks) { - _task->register_download_progress_notifier(block); - } - _progressBlocks.clear(); - _task->start([=](realm::ThreadSafeReference ref, std::exception_ptr err) { - std::lock_guard lock(_mutex); - if ([self checkCancellation]) { - return; - } - // Note that cancellation intentionally trumps reporting other kinds - // of errors - if (err) { - return [self reportException:err]; - } - - // Dispatch blocks can only capture copyable types, so we need to - // resolve the TSR to a shared_ptr - auto realm = ref.resolve>(nullptr); - // We're now running on the sync worker thread, so hop back - // to a more appropriate queue for the next stage of init. - dispatch_async(s_async_open_queue, ^{ - @autoreleasepool { - [self downloadCompleted]; - // Capture the Realm to keep the RealmCoordinator alive - // so that we don't have to reopen it. - static_cast(realm); - } - }); - }); -#else - @throw RLMException(@"Realm was not built with sync enabled"); -#endif - } - else { - // We're not downloading first, so just proceed directly to the next step. - lock.unlock(); - [self downloadCompleted]; - } -} - -- (void)downloadCompleted { - std::unique_lock lock(_mutex); - _task.reset(); - if ([self checkCancellation]) { - return; - } - NSError *error; - // We've now downloaded all data (if applicable) and done the object - // store initialization, and are back on our background queue. Next we - // want to do our own initialization while still in the background @autoreleasepool { // Holding onto the Realm so that opening the final Realm on the target // scheduler can hit the fast path @@ -249,37 +119,13 @@ - (void)downloadCompleted { return [self reportError:error]; } } - -#if REALM_ENABLE_SYNC - // If we're opening a flexible sync Realm, we now want to wait for the - // initial subscriptions to be ready - if (_waitForDownloadCompletion && _backgroundRealm.isFlexibleSync) { - auto subscriptions = _backgroundRealm.subscriptions; - if (subscriptions.state == RLMSyncSubscriptionStatePending) { - // FIXME: need cancellation for waiting for the subscription - return [subscriptions waitForSynchronizationOnQueue:nil - timeout:0 - completionBlock:^(NSError *error) { - if (error) { - std::lock_guard lock(_mutex); - return [self reportError:error]; - } - [self asyncOpenCompleted]; - }]; - } + if ([self checkCancellation]) { + return; } -#endif - lock.unlock(); - [self asyncOpenCompleted]; -} -- (void)asyncOpenCompleted { - std::lock_guard lock(_mutex); - if (![self checkCancellation]) { - [_scheduler invoke:^{ - [self openFinalRealmAndCallCompletion]; - }]; - } + [_scheduler invoke:^{ + [self openFinalRealmAndCallCompletion]; + }]; } - (void)openFinalRealmAndCallCompletion { @@ -349,42 +195,6 @@ - (void)releaseResources { } @end -@implementation RLMAsyncDownloadTask { - RLMUnfairMutex _mutex; - std::shared_ptr _session; - bool _started; -} - -- (instancetype)initWithRealm:(RLMRealm *)realm { - if (self = [super init]) { - _session = realm->_realm->sync_session(); - } - return self; -} - -- (void)waitWithCompletion:(void (^)(NSError *_Nullable))completion { - std::unique_lock lock(_mutex); - if (!_session) { - lock.unlock(); - return completion(nil); - } - - _started = true; - _session->revive_if_needed(); - _session->wait_for_download_completion([=](realm::Status status) { - completion(makeError(status)); - }); -} - -- (void)cancel { - std::unique_lock lock(_mutex); - if (_started) { - _session->force_close(); - } - _session = nullptr; -} -@end - __attribute__((objc_direct_members)) @implementation RLMAsyncRefreshTask { RLMUnfairMutex _mutex; @@ -488,84 +298,3 @@ - (void)wait:(void (^)())completion { completion(); } @end - -@implementation RLMAsyncSubscriptionTask { - RLMUnfairMutex _mutex; - - RLMSyncSubscriptionSet *_subscriptionSet; - dispatch_queue_t _queue; - NSTimeInterval _timeout; - void (^_completion)(NSError *); - - dispatch_block_t _worker; -} - -- (instancetype)initWithSubscriptionSet:(RLMSyncSubscriptionSet *)subscriptionSet - queue:(nullable dispatch_queue_t)queue - timeout:(NSTimeInterval)timeout - completion:(void(^)(NSError *))completion { - if (!(self = [super init])) { - return self; - } - - _subscriptionSet = subscriptionSet; - _queue = queue; - _timeout = timeout; - _completion = completion; - - return self; -} - -- (void)waitForSubscription { - if (_timeout != 0) { - std::lock_guard lock(_mutex); - // Setup timer - dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_timeout * NSEC_PER_SEC)); - // If the call below doesn't return after `time` seconds, the internal completion is called with an error. - _worker = dispatch_block_create(DISPATCH_BLOCK_ASSIGN_CURRENT, ^{ - NSString* errorMessage = [NSString stringWithFormat:@"Waiting for update timed out after %.01f seconds.", _timeout]; - NSError* error = [NSError errorWithDomain:NSPOSIXErrorDomain code:ETIMEDOUT userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; - [self invokeCompletionWithError:error]; - }); - - dispatch_after(time, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), _worker); - } - - [self waitForSync]; -} - -- (void)waitForSync { - std::lock_guard lock(_mutex); - if (_completion) { - _subscriptionSet->_subscriptionSet->get_state_change_notification(realm::sync::SubscriptionSet::State::Complete) - .get_async([self](realm::StatusWith state) noexcept { - NSError *error = makeError(state); - [self invokeCompletionWithError:error]; - }); - } -} - -- (void)invokeCompletionWithError:(NSError * _Nullable)error { - void (^completion)(NSError *); - { - std::lock_guard lock(_mutex); - std::swap(completion, _completion); - if (_worker) { - dispatch_block_cancel(_worker); - _worker = nil; - } - _subscriptionSet = nil; - } - - if (completion) { - if (_queue) { - dispatch_async(_queue, ^{ - completion(error); - }); - return; - } - - completion(error); - } -} -@end diff --git a/Realm/RLMAsyncTask_Private.h b/Realm/RLMAsyncTask_Private.h index 56d1e19163..2c25dc88f1 100644 --- a/Realm/RLMAsyncTask_Private.h +++ b/Realm/RLMAsyncTask_Private.h @@ -27,28 +27,18 @@ RLM_HEADER_AUDIT_BEGIN(nullability) - (instancetype)initWithConfiguration:(RLMRealmConfiguration *)configuration confinedTo:(RLMScheduler *)confinement - download:(bool)waitForDownloadCompletion completion:(RLMAsyncOpenRealmCallback)completion __attribute__((objc_direct)); - (instancetype)initWithConfiguration:(RLMRealmConfiguration *)configuration - confinedTo:(RLMScheduler *)confinement - download:(bool)waitForDownloadCompletion; + confinedTo:(RLMScheduler *)confinement; - (void)waitWithCompletion:(void (^)(NSError *_Nullable))completion; - (void)waitForOpen:(RLMAsyncOpenRealmCallback)completion __attribute__((objc_direct)); @end -// A cancellable task for waiting for downloads on an already-open Realm. -RLM_SWIFT_SENDABLE -@interface RLMAsyncDownloadTask : NSObject -- (instancetype)initWithRealm:(RLMRealm *)realm; -- (void)cancel; -- (void)waitWithCompletion:(void (^)(NSError *_Nullable))completion; -@end - // A cancellable task for beginning an async write -RLM_SWIFT_SENDABLE +NS_SWIFT_SENDABLE @interface RLMAsyncWriteTask : NSObject // Must only be called from within the Actor - (instancetype)initWithRealm:(RLMRealm *)realm; @@ -61,23 +51,11 @@ RLM_SWIFT_SENDABLE typedef void (^RLMAsyncRefreshCompletion)(bool); // A cancellable task for refreshing a Realm -RLM_SWIFT_SENDABLE +NS_SWIFT_SENDABLE @interface RLMAsyncRefreshTask : NSObject - (void)complete:(bool)didRefresh; - (void)wait:(RLMAsyncRefreshCompletion)completion; + (RLMAsyncRefreshTask *)completedRefresh; @end -// A cancellable task for refreshing a Realm -RLM_SWIFT_SENDABLE -@interface RLMAsyncSubscriptionTask : NSObject - -- (instancetype)initWithSubscriptionSet:(RLMSyncSubscriptionSet *)subscriptionSet - queue:(nullable dispatch_queue_t)queue - timeout:(NSTimeInterval)timeout - completion:(void(^)(NSError *))completion; - -- (void)waitForSubscription; -@end - RLM_HEADER_AUDIT_END(nullability) diff --git a/Realm/RLMBSON.h b/Realm/RLMBSON.h deleted file mode 100644 index 41750fd576..0000000000 --- a/Realm/RLMBSON.h +++ /dev/null @@ -1,174 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import -#import - -#pragma mark RLMBSONType - -/** - Allowed BSON types. - */ -typedef NS_ENUM(NSUInteger, RLMBSONType) { - /// BSON Null type - RLMBSONTypeNull, - /// BSON Int32 type - RLMBSONTypeInt32, - /// BSON Int64 type - RLMBSONTypeInt64, - /// BSON Bool type - RLMBSONTypeBool, - /// BSON Double type - RLMBSONTypeDouble, - /// BSON String type - RLMBSONTypeString, - /// BSON Binary type - RLMBSONTypeBinary, - /// BSON Timestamp type - RLMBSONTypeTimestamp, - /// BSON Datetime type - RLMBSONTypeDatetime, - /// BSON ObjectId type - RLMBSONTypeObjectId, - /// BSON Decimal128 type - RLMBSONTypeDecimal128, - /// BSON RegularExpression type - RLMBSONTypeRegularExpression, - /// BSON MaxKey type - RLMBSONTypeMaxKey, - /// BSON MinKey type - RLMBSONTypeMinKey, - /// BSON Document type - RLMBSONTypeDocument, - /// BSON Array type - RLMBSONTypeArray, - /// BSON UUID type - RLMBSONTypeUUID -}; - -#pragma mark RLMBSON - -/** - Protocol representing a BSON value. BSON is a computer data interchange format. - The name "BSON" is based on the term JSON and stands for "Binary JSON". - - The following types conform to RLMBSON: - - `NSNull` - `NSNumber` - `NSString` - `NSData` - `NSDateInterval` - `NSDate` - `RLMObjectId` - `RLMDecimal128` - `NSRegularExpression` - `RLMMaxKey` - `RLMMinKey` - `NSDictionary` - `NSArray` - `NSUUID` - - @see RLMBSONType - @see bsonspec.org - */ -@protocol RLMBSON - -/** - The BSON type for the conforming interface. - */ -@property (readonly) RLMBSONType bsonType NS_REFINED_FOR_SWIFT; - -/** - Whether or not this BSON is equal to another. - - @param other The BSON to compare to - */ -- (BOOL)isEqual:(_Nullable id)other; - -@end - -/// :nodoc: -@interface NSNull (RLMBSON) -@end - -/// :nodoc: -@interface NSNumber (RLMBSON) -@end - -/// :nodoc: -@interface NSString (RLMBSON) -@end - -/// :nodoc: -@interface NSData (RLMBSON) -@end - -/// :nodoc: -@interface NSDateInterval (RLMBSON) -@end - -/// :nodoc: -@interface NSDate (RLMBSON) -@end - -/// :nodoc: -@interface RLMObjectId (RLMBSON) -@end - -/// :nodoc: -@interface RLMDecimal128 (RLMBSON) -@end - -/// :nodoc: -@interface NSRegularExpression (RLMBSON) -@end - -/// MaxKey will always be the greatest value when comparing to other BSON types -RLM_SWIFT_SENDABLE RLM_FINAL -@interface RLMMaxKey : NSObject -@end - -/// MinKey will always be the smallest value when comparing to other BSON types -RLM_SWIFT_SENDABLE RLM_FINAL -@interface RLMMinKey : NSObject -@end - -/// :nodoc: -@interface RLMMaxKey (RLMBSON) -@end - -/// :nodoc: -@interface RLMMinKey (RLMBSON) -@end - -/// :nodoc: -@interface NSDictionary (RLMBSON) -@end - -/// :nodoc: -@interface NSMutableArray (RLMBSON) -@end - -/// :nodoc: -@interface NSArray (RLMBSON) -@end - -/// :nodoc: -@interface NSUUID (RLMBSON) -@end diff --git a/Realm/RLMBSON.mm b/Realm/RLMBSON.mm deleted file mode 100644 index 4abf8d8f6c..0000000000 --- a/Realm/RLMBSON.mm +++ /dev/null @@ -1,423 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMBSON_Private.hpp" - -#import "RLMDecimal128_Private.hpp" -#import "RLMObjectId_Private.hpp" -#import "RLMUUID_Private.hpp" -#import "RLMUtil.hpp" - -#import - -using namespace realm; -using namespace bson; - -#pragma mark NSNull - -@implementation NSNull (RLMBSON) - -- (RLMBSONType)bsonType { - return RLMBSONTypeNull; -} - -@end - -#pragma mark RLMObjectId - -@implementation RLMObjectId (RLMBSON) - -- (RLMBSONType)bsonType { - return RLMBSONTypeObjectId; -} - -@end - -#pragma mark RLMDecimal128 - -@implementation RLMDecimal128 (RLMBSON) - -- (RLMBSONType)bsonType { - return RLMBSONTypeDecimal128; -} - -@end - -#pragma mark NSString - -@implementation NSString (RLMBSON) - -- (RLMBSONType)bsonType { - return RLMBSONTypeString; -} - -@end - -#pragma mark NSNumber - -@implementation NSNumber (RLMBSON) - -- (RLMBSONType)bsonType { - char numberType = [self objCType][0]; - - if (numberType == *@encode(bool) || - numberType == *@encode(char)) { - return RLMBSONTypeBool; - } else if (numberType == *@encode(int) || - numberType == *@encode(short) || - numberType == *@encode(unsigned short) || - numberType == *@encode(unsigned int)) { - return RLMBSONTypeInt32; - } else if (numberType == *@encode(long) || - numberType == *@encode(long long) || - numberType == *@encode(unsigned long) || - numberType == *@encode(unsigned long long)) { - return RLMBSONTypeInt64; - } else { - return RLMBSONTypeDouble; - } -} - -@end - -#pragma mark NSMutableArray - -@implementation NSMutableArray (RLMBSON) - -- (RLMBSONType)bsonType { - return RLMBSONTypeArray; -} - -- (instancetype)initWithBsonArray:(BsonArray)bsonArray { - - if ((self = [self init])) { - for (auto& entry : bsonArray) { - [self addObject:RLMConvertBsonToRLMBSON(entry)]; - } - - return self; - } - - return nil; -} - -@end - -@implementation NSArray (RLMBSON) - -- (BsonArray)bsonArrayValue { - BsonArray bsonArray; - for (id value in self) { - bsonArray.push_back(RLMConvertRLMBSONToBson(value)); - } - return bsonArray; -} - -- (RLMBSONType)bsonType { - return RLMBSONTypeArray; -} - -@end - -#pragma mark NSDictionary - -@implementation NSMutableDictionary (RLMBSON) - -- (RLMBSONType)bsonType { - return RLMBSONTypeDocument; -} - -- (BsonDocument)bsonDocumentValue { - BsonDocument bsonDocument; - for (NSString *value in self) { - bsonDocument[value.UTF8String] = RLMConvertRLMBSONToBson(self[value]); - } - return bsonDocument; -} - -- (instancetype)initWithBsonDocument:(BsonDocument)bsonDocument { - if ((self = [self init])) { - for (auto it = bsonDocument.begin(); it != bsonDocument.end(); ++it) { - const auto& entry = (*it); - [self setObject:RLMConvertBsonToRLMBSON(entry.second) forKey:@(entry.first.data())]; - } - - return self; - } - - return nil; -} - -@end - -@implementation NSDictionary (RLMBSON) - -- (RLMBSONType)bsonType { - return RLMBSONTypeDocument; -} - -- (BsonDocument)bsonDocumentValue { - BsonDocument bsonDocument; - for (NSString *value in self) { - bsonDocument[value.UTF8String] = RLMConvertRLMBSONToBson(self[value]); - } - return bsonDocument; -} - -@end - -#pragma mark NSData - -@implementation NSData (RLMBSON) - -- (RLMBSONType)bsonType { - return RLMBSONTypeBinary; -} - -- (instancetype)initWithBsonBinary:(std::vector)bsonBinary { - if ((self = [NSData dataWithBytes:bsonBinary.data() length:bsonBinary.size()])) { - return self; - } - - return nil; -} - -@end - -#pragma mark NSDate - -@implementation NSDate (RLMBSON) - -- (RLMBSONType)bsonType { - return RLMBSONTypeDatetime; -} - -@end - -#pragma mark NSUUID - -@implementation NSUUID (RLMBSON) - -- (RLMBSONType)bsonType { - return RLMBSONTypeUUID; -} - -@end - -#pragma mark NSRegularExpression - -@implementation NSRegularExpression (RLMBSON) - -- (RLMBSONType)bsonType { - return RLMBSONTypeRegularExpression; -} - -- (RegularExpression)regularExpressionValue { - using Option = RegularExpression::Option; - std::string s; - - if ((_options & NSRegularExpressionCaseInsensitive) != 0) s += 'i'; - if ((_options & NSRegularExpressionUseUnixLineSeparators) != 0) s += 'm'; - if ((_options & NSRegularExpressionDotMatchesLineSeparators) != 0) s += 's'; - if ((_options & NSRegularExpressionUseUnicodeWordBoundaries) != 0) s += 'x'; - - return RegularExpression(_pattern.UTF8String, s); -} - -- (instancetype)initWithRegularExpression:(RegularExpression)regularExpression { - if ((self = [self init])) { - _pattern = @(regularExpression.pattern().data()); - switch (regularExpression.options()) { - case realm::bson::RegularExpression::Option::None: - _options = 0; - break; - case realm::bson::RegularExpression::Option::IgnoreCase: - _options = NSRegularExpressionCaseInsensitive; - break; - case realm::bson::RegularExpression::Option::Multiline: - _options = NSRegularExpressionUseUnixLineSeparators; - break; - case realm::bson::RegularExpression::Option::Dotall: - _options = NSRegularExpressionDotMatchesLineSeparators; - break; - case realm::bson::RegularExpression::Option::Extended: - _options = NSRegularExpressionUseUnicodeWordBoundaries; - break; - } - return self; - } - - return nil; -} - -@end - -#pragma mark RLMMaxKey - -@implementation RLMMaxKey - -- (BOOL)isEqual:(id)other { - return other == self || ([other class] == [self class]); -} - -- (NSUInteger)hash { - return 0; -} - -@end - -#pragma mark RLMMaxKey - -@implementation RLMMinKey - -- (BOOL)isEqual:(id)other { - return other == self || ([other class] == [self class]); -} - -- (NSUInteger)hash { - return 0; -} - -@end - -@implementation RLMMaxKey (RLMBSON) - -- (RLMBSONType)bsonType { - return RLMBSONTypeMaxKey; -} - -@end - -@implementation RLMMinKey (RLMBSON) - -- (RLMBSONType)bsonType { - return RLMBSONTypeMinKey; -} - -@end - -#pragma mark RLMBSONToBson - -Bson RLMConvertRLMBSONToBson(id b) { - switch ([b bsonType]) { - case RLMBSONTypeString: - return ((NSString *)b).UTF8String; - case RLMBSONTypeInt32: - return ((NSNumber *)b).intValue; - case RLMBSONTypeInt64: - return ((NSNumber *)b).longLongValue; - case RLMBSONTypeObjectId: - return [((RLMObjectId *)b) value]; - case RLMBSONTypeNull: - return util::none; - case RLMBSONTypeBool: - return (bool)((NSNumber *)b).boolValue; - case RLMBSONTypeDouble: - return ((NSNumber *)b).doubleValue; - case RLMBSONTypeBinary: - return std::vector((char*)((NSData *)b).bytes, - ((char*)((NSData *)b).bytes) + (int)((NSData *)b).length); - case RLMBSONTypeTimestamp: - // This represents a value of `Timestamp` in a MongoDB Collection. - return MongoTimestamp(((NSDate *)b).timeIntervalSince1970, 0); - case RLMBSONTypeDatetime: - // This represents a value of `Date` in a MongoDB Collection. - return RLMTimestampForNSDate((NSDate *)b); - case RLMBSONTypeDecimal128: - return [((RLMDecimal128 *)b) decimal128Value]; - case RLMBSONTypeRegularExpression: - return [((NSRegularExpression *)b) regularExpressionValue]; - case RLMBSONTypeMaxKey: - return max_key; - case RLMBSONTypeMinKey: - return min_key; - case RLMBSONTypeDocument: - return [((NSDictionary *)b) bsonDocumentValue]; - case RLMBSONTypeArray: - return [((NSArray *)b) bsonArrayValue]; - case RLMBSONTypeUUID: - return [((NSUUID *)b) rlm_uuidValue]; - } -} - -BsonDocument RLMConvertRLMBSONArrayToBsonDocument(NSArray> *array) { - BsonDocument bsonDocument = BsonDocument{}; - for (NSDictionary> *item in array) { - [item enumerateKeysAndObjectsUsingBlock:[&](NSString *key, id bson, BOOL *) { - bsonDocument[key.UTF8String] = RLMConvertRLMBSONToBson(bson); - }]; - } - return bsonDocument; -} - -#pragma mark BsonToRLMBSON - -id RLMConvertBsonToRLMBSON(const Bson& b) { - switch (b.type()) { - case realm::bson::Bson::Type::Null: - return [NSNull null]; - case realm::bson::Bson::Type::Int32: - return @(static_cast(b)); - case realm::bson::Bson::Type::Int64: - return @(static_cast(b)); - case realm::bson::Bson::Type::Bool: - return @(static_cast(b)); - case realm::bson::Bson::Type::Double: - return @(static_cast(b)); - case realm::bson::Bson::Type::String: - return @(static_cast(b).c_str()); - case realm::bson::Bson::Type::Binary: - return [[NSData alloc] initWithBsonBinary:static_cast>(b)]; - case realm::bson::Bson::Type::Timestamp: - return [[NSDate alloc] initWithTimeIntervalSince1970:static_cast(b).seconds]; - case realm::bson::Bson::Type::Datetime: - return [[NSDate alloc] initWithTimeIntervalSince1970:static_cast(b).get_seconds()]; - case realm::bson::Bson::Type::ObjectId: - return [[RLMObjectId alloc] initWithValue:static_cast(b)]; - case realm::bson::Bson::Type::Decimal128: - return [[RLMDecimal128 alloc] initWithDecimal128:static_cast(b)]; - case realm::bson::Bson::Type::RegularExpression: - return [[NSRegularExpression alloc] initWithRegularExpression:static_cast(b)]; - case realm::bson::Bson::Type::MaxKey: - return [RLMMaxKey new]; - case realm::bson::Bson::Type::MinKey: - return [RLMMinKey new]; - case realm::bson::Bson::Type::Document: - return [[NSMutableDictionary alloc] initWithBsonDocument:static_cast(b)]; - case realm::bson::Bson::Type::Array: - return [[NSMutableArray alloc] initWithBsonArray:static_cast(b)]; - case realm::bson::Bson::Type::Uuid: - return [[NSUUID alloc] initWithRealmUUID:static_cast(b)]; - } - return nil; -} - -id RLMConvertBsonDocumentToRLMBSON(std::optional b) { - return b ? RLMConvertBsonToRLMBSON(*b) : nil; -} - -NSArray> *RLMConvertBsonDocumentToRLMBSONArray(std::optional b) { - if (!b) { - return @[]; - } - NSMutableArray> *array = [[NSMutableArray alloc] init]; - for (const auto& [key, value] : *b) { - [array addObject:@{@(key.c_str()): RLMConvertBsonToRLMBSON(value)}]; - } - return array; -} diff --git a/Realm/RLMBSON_Private.hpp b/Realm/RLMBSON_Private.hpp deleted file mode 100644 index c1f6c05ae4..0000000000 --- a/Realm/RLMBSON_Private.hpp +++ /dev/null @@ -1,31 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import -#import - -namespace realm::bson { -class Bson; -class BsonDocument; -} - -realm::bson::Bson RLMConvertRLMBSONToBson(id b); -realm::bson::BsonDocument RLMConvertRLMBSONArrayToBsonDocument(NSArray> *array); -id RLMConvertBsonToRLMBSON(const realm::bson::Bson& b); -id RLMConvertBsonDocumentToRLMBSON(std::optional b); -NSArray> *RLMConvertBsonDocumentToRLMBSONArray(std::optional b); diff --git a/Realm/RLMClassInfo.hpp b/Realm/RLMClassInfo.hpp index 12644e0747..cbf77e3848 100644 --- a/Realm/RLMClassInfo.hpp +++ b/Realm/RLMClassInfo.hpp @@ -109,8 +109,6 @@ class RLMClassInfo { // KeyPathFromString converts a string keypath to a vector of key // pairs to be used for deep change checking across links. - // NEXT-MAJOR: This conflates a nil array and an empty array for backwards - // compatibility, but core now gives them different semantics std::optional>>> keyPathArrayFromStringArray(NSArray *keyPaths) const; diff --git a/Realm/RLMClassInfo.mm b/Realm/RLMClassInfo.mm index a9dff94edf..d7f00f2b78 100644 --- a/Realm/RLMClassInfo.mm +++ b/Realm/RLMClassInfo.mm @@ -146,7 +146,7 @@ throw RLMException(@"Invalid property name: property '%@' not found in object of std::optional RLMClassInfo::keyPathArrayFromStringArray(NSArray *keyPaths) const { std::optional keyPathArray; - if (keyPaths.count) { + if (keyPaths) { keyPathArray.emplace(); for (NSString *keyPath in keyPaths) { keyPathArray->push_back(keyPathFromString(realm, realm.schema, this, diff --git a/Realm/RLMCollection.h b/Realm/RLMCollection.h index eac117f17c..2278a08c38 100644 --- a/Realm/RLMCollection.h +++ b/Realm/RLMCollection.h @@ -23,7 +23,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @protocol RLMValue; @class RLMRealm, RLMResults, RLMSortDescriptor, RLMNotificationToken, RLMCollectionChange, RLMSectionedResults; -typedef RLM_CLOSED_ENUM(int32_t, RLMPropertyType); +typedef NS_CLOSED_ENUM(int32_t, RLMPropertyType); /// A callback which is invoked on each element in the Results collection which returns the section key. typedef id _Nullable(^RLMSectionedResultsKeyBlock)(id); @@ -530,7 +530,7 @@ __attribute__((warn_unused_result)); `RLMSortDescriptor` instances are immutable. */ -RLM_SWIFT_SENDABLE RLM_FINAL +NS_SWIFT_SENDABLE RLM_FINAL @interface RLMSortDescriptor : NSObject #pragma mark - Properties diff --git a/Realm/RLMCollection.mm b/Realm/RLMCollection.mm index 380a9d1484..66b3bdb0ce 100644 --- a/Realm/RLMCollection.mm +++ b/Realm/RLMCollection.mm @@ -533,7 +533,8 @@ - (bool)invalidate { if (!token->_realm) { return; } - RLMRealm *realm = token->_realm = [RLMRealm realmWithConfiguration:config queue:queue error:nil]; + RLMRealm *realm = [RLMRealm realmWithConfiguration:config queue:queue error:nil]; + token->_realm = realm; id collection = [realm resolveThreadSafeReference:tsr]; token->_token = [collection addNotificationCallback:block keyPaths:info->keyPathArrayFromStringArray(keyPaths)]; }); diff --git a/Realm/RLMCollection_Private.h b/Realm/RLMCollection_Private.h index 7d31bd3f0e..7e0eb76975 100644 --- a/Realm/RLMCollection_Private.h +++ b/Realm/RLMCollection_Private.h @@ -33,7 +33,7 @@ RLMNotificationToken *RLMAddNotificationBlock(id collection, id block, NSArray *_Nullable keyPaths, dispatch_queue_t _Nullable queue); -typedef RLM_CLOSED_ENUM(int32_t, RLMCollectionType) { +typedef NS_CLOSED_ENUM(int32_t, RLMCollectionType) { RLMCollectionTypeArray = 0, RLMCollectionTypeSet = 1, RLMCollectionTypeDictionary = 2 diff --git a/Realm/RLMConstants.h b/Realm/RLMConstants.h index b0b783313d..751abe2663 100644 --- a/Realm/RLMConstants.h +++ b/Realm/RLMConstants.h @@ -21,21 +21,10 @@ #define RLM_HEADER_AUDIT_BEGIN NS_HEADER_AUDIT_BEGIN #define RLM_HEADER_AUDIT_END NS_HEADER_AUDIT_END -#define RLM_SWIFT_SENDABLE NS_SWIFT_SENDABLE - #define RLM_FINAL __attribute__((objc_subclassing_restricted)) RLM_HEADER_AUDIT_BEGIN(nullability, sendability) -// Swift 5 considers NS_ENUM to be "open", meaning there could be values present -// other than the defined cases (which allows adding more cases later without -// it being a breaking change), while older versions consider it "closed". -#ifdef NS_CLOSED_ENUM -#define RLM_CLOSED_ENUM NS_CLOSED_ENUM -#else -#define RLM_CLOSED_ENUM NS_ENUM -#endif - #if __has_attribute(ns_error_domain) && (!defined(__cplusplus) || !__cplusplus || __cplusplus >= 201103L) #define RLM_ERROR_ENUM(type, name, domain) \ _Pragma("clang diagnostic push") \ @@ -60,7 +49,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) For more information, see [Realm Models](https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/model-data/object-models/). */ -typedef RLM_CLOSED_ENUM(int32_t, RLMPropertyType) { +typedef NS_CLOSED_ENUM(int32_t, RLMPropertyType) { #pragma mark - Primitive types /** Integers: `NSInteger`, `int`, `long`, `Int` (Swift) */ @@ -100,7 +89,7 @@ typedef RLM_CLOSED_ENUM(int32_t, RLMPropertyType) { For more information, see [Realm Models](https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/model-data/supported-types/#std-label-ios-anyrealmvalue-data-type). */ -typedef RLM_CLOSED_ENUM(int32_t, RLMAnyValueType) { +typedef NS_CLOSED_ENUM(int32_t, RLMAnyValueType) { #pragma mark - Primitive types /** Integers: `NSInteger`, `int`, `long`, `Int` (Swift) */ RLMAnyValueTypeInt = 0, diff --git a/Realm/RLMCredentials.h b/Realm/RLMCredentials.h deleted file mode 100644 index adff4e7e94..0000000000 --- a/Realm/RLMCredentials.h +++ /dev/null @@ -1,126 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) -@protocol RLMBSON; - -/// A token representing an identity provider's credentials. -typedef NSString *RLMCredentialsToken; - -/// A type representing the unique identifier of an Atlas App Services identity provider. -typedef NSString *RLMIdentityProvider NS_EXTENSIBLE_STRING_ENUM; - -/// The username/password identity provider. User accounts are handled by Atlas App Services directly without the -/// involvement of a third-party identity provider. -extern RLMIdentityProvider const RLMIdentityProviderUsernamePassword; - -/// A Facebook account as an identity provider. -extern RLMIdentityProvider const RLMIdentityProviderFacebook; - -/// A Google account as an identity provider. -extern RLMIdentityProvider const RLMIdentityProviderGoogle; - -/// An Apple account as an identity provider. -extern RLMIdentityProvider const RLMIdentityProviderApple; - -/// A JSON Web Token as an identity provider. -extern RLMIdentityProvider const RLMIdentityProviderJWT; - -/// An Anonymous account as an identity provider. -extern RLMIdentityProvider const RLMIdentityProviderAnonymous; - -/// An Realm Cloud function as an identity provider. -extern RLMIdentityProvider const RLMIdentityProviderFunction; - -/// A user api key as an identity provider. -extern RLMIdentityProvider const RLMIdentityProviderUserAPIKey; - -/// A server api key as an identity provider. -extern RLMIdentityProvider const RLMIdentityProviderServerAPIKey; - -/** - Opaque credentials representing a specific Realm App user. - */ -RLM_SWIFT_SENDABLE RLM_FINAL // immutable final class -@interface RLMCredentials : NSObject - -/// The name of the identity provider which generated the credentials token. -@property (nonatomic, readonly) RLMIdentityProvider provider; - -/** - Construct and return credentials from a Facebook account token. - */ -+ (instancetype)credentialsWithFacebookToken:(RLMCredentialsToken)token; - -/** - Construct and return credentials from a Google account token. - */ -+ (instancetype)credentialsWithGoogleAuthCode:(RLMCredentialsToken)token; - -/** - Construct and return credentials from a Google id token. - */ -+ (instancetype)credentialsWithGoogleIdToken:(RLMCredentialsToken)token; - -/** - Construct and return credentials from an Apple account token. - */ -+ (instancetype)credentialsWithAppleToken:(RLMCredentialsToken)token; - -/** - Construct and return credentials for an Atlas App Services function using a mongodb document as a json payload. -*/ -+ (instancetype)credentialsWithFunctionPayload:(NSDictionary> *)payload; - -/** - Construct and return credentials from a user api key. -*/ -+ (instancetype)credentialsWithUserAPIKey:(NSString *)apiKey; - -/** - Construct and return credentials from a server api key. -*/ -+ (instancetype)credentialsWithServerAPIKey:(NSString *)apiKey; - -/** - Construct and return Atlas App Services credentials from an email and password. - */ -+ (instancetype)credentialsWithEmail:(NSString *)email - password:(NSString *)password; - -/** - Construct and return credentials from a JSON Web Token. - */ -+ (instancetype)credentialsWithJWT:(NSString *)token; - -/** - Construct and return anonymous credentials - */ -+ (instancetype)anonymousCredentials; - -/// :nodoc: -- (instancetype)init __attribute__((unavailable("RLMAppCredentials cannot be created directly"))); - -/// :nodoc: -+ (instancetype)new __attribute__((unavailable("RLMAppCredentials cannot be created directly"))); - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMCredentials.mm b/Realm/RLMCredentials.mm deleted file mode 100644 index 94d2370271..0000000000 --- a/Realm/RLMCredentials.mm +++ /dev/null @@ -1,87 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMCredentials_Private.hpp" - -#import "RLMBSON_Private.hpp" -#import "RLMUtil.hpp" - -#import - -using namespace realm::app; - -@implementation RLMCredentials -- (instancetype)initWithAppCredentials:(AppCredentials&&)credentials { - if (self = [super init]) { - _appCredentials = std::move(credentials); - _provider = @(_appCredentials.provider_as_string().data()); - return self; - } - return nil; -} - -+ (instancetype)credentialsWithFacebookToken:(RLMCredentialsToken)token { - return [[self alloc] initWithAppCredentials:AppCredentials::facebook(token.UTF8String)]; -} - -+ (instancetype)credentialsWithGoogleAuthCode:(RLMCredentialsToken)token { - return [[self alloc] initWithAppCredentials:AppCredentials::google(AuthCode(token.UTF8String))]; -} - -+ (instancetype)credentialsWithGoogleIdToken:(RLMCredentialsToken)token { - return [[self alloc] initWithAppCredentials:AppCredentials::google(IdToken(token.UTF8String))]; -} - -+ (instancetype)credentialsWithAppleToken:(RLMCredentialsToken)token { - return [[self alloc] initWithAppCredentials:AppCredentials::apple(token.UTF8String)]; -} - -+ (instancetype)credentialsWithEmail:(NSString *)username - password:(NSString *)password { - return [[self alloc] initWithAppCredentials:AppCredentials::username_password(username.UTF8String, - password.UTF8String)]; -} - -+ (instancetype)credentialsWithJWT:(NSString *)token { - return [[self alloc] initWithAppCredentials:AppCredentials::custom(token.UTF8String)]; -} - -+ (instancetype)credentialsWithFunctionPayload:(NSDictionary> *)payload { - return [[self alloc] initWithAppCredentials:AppCredentials::function(static_cast(RLMConvertRLMBSONToBson(payload)))]; -} - -+ (instancetype)credentialsWithUserAPIKey:(NSString *)apiKey { - return [[self alloc] initWithAppCredentials:AppCredentials::api_key(apiKey.UTF8String)]; -} - -+ (instancetype)credentialsWithServerAPIKey:(NSString *)apiKey { - return [[self alloc] initWithAppCredentials:AppCredentials::api_key(apiKey.UTF8String)]; -} - -+ (instancetype)anonymousCredentials { - return [[self alloc] initWithAppCredentials:AppCredentials::anonymous()]; -} - -- (BOOL)isEqual:(id)object { - if (auto that = RLMDynamicCast(object)) { - return [self.provider isEqualToString:that.provider] - && self.appCredentials.serialize_as_json() == that.appCredentials.serialize_as_json(); - } - return NO; -} -@end diff --git a/Realm/RLMCredentials_Private.hpp b/Realm/RLMCredentials_Private.hpp deleted file mode 100644 index 71d6a76b99..0000000000 --- a/Realm/RLMCredentials_Private.hpp +++ /dev/null @@ -1,25 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMCredentials.h" - -#import - -@interface RLMCredentials() -@property (nonatomic, direct) realm::app::AppCredentials& appCredentials; -@end diff --git a/Realm/RLMDecimal128.h b/Realm/RLMDecimal128.h index f6224bbf28..74ce72ebee 100644 --- a/Realm/RLMDecimal128.h +++ b/Realm/RLMDecimal128.h @@ -29,7 +29,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) this type stores up to 34 digits of significand and an exponent from -6143 to 6144. */ -RLM_SWIFT_SENDABLE // immutable +NS_SWIFT_SENDABLE // immutable @interface RLMDecimal128 : NSObject /// Creates a new zero-initialized decimal128. - (instancetype)init; @@ -50,9 +50,8 @@ RLM_SWIFT_SENDABLE // immutable /// Parses the given string to a RLMDecimal128. /// -/// Returns a decimal where `isNaN` is `YES` if the string cannot be parsed as a decimal. `error` is never set -/// and this will never actually return `nil`. -- (nullable instancetype)initWithString:(NSString *)string error:(NSError **)error; +/// Returns a decimal where `isNaN` is `YES` if the string cannot be parsed as a decimal. +- (instancetype)initWithString:(NSString *)string; /// Converts the given number to a RLMDecimal128. + (instancetype)decimalWithNumber:(NSNumber *)number; diff --git a/Realm/RLMDecimal128.mm b/Realm/RLMDecimal128.mm index 3a70174e0b..6fc019d031 100644 --- a/Realm/RLMDecimal128.mm +++ b/Realm/RLMDecimal128.mm @@ -73,7 +73,7 @@ - (instancetype)initWithNumber:(NSNumber *)number { return self; } -- (instancetype)initWithString:(NSString *)string error:(__unused NSError **)error { +- (instancetype)initWithString:(NSString *)string { if ((self = [self init])) { _value = realm::Decimal128(string.UTF8String); } @@ -85,7 +85,7 @@ + (instancetype)decimalWithNumber:(NSNumber *)number { } + (instancetype)decimalWithNSDecimal:(NSDecimalNumber *)number { - return [[self alloc] initWithString:number.stringValue error:nil]; + return [[self alloc] initWithString:number.stringValue]; } - (id)copyWithZone:(NSZone *)zone { diff --git a/Realm/RLMEmailPasswordAuth.h b/Realm/RLMEmailPasswordAuth.h deleted file mode 100644 index 748eb26afa..0000000000 --- a/Realm/RLMEmailPasswordAuth.h +++ /dev/null @@ -1,120 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -@protocol RLMBSON; - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -/// A block type used to report an error -RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void(^RLMEmailPasswordAuthOptionalErrorBlock)(NSError * _Nullable); - -/** - A client for the email/password authentication provider which - can be used to obtain a credential for logging in, - and to perform requests specifically related to the email/password provider. -*/ -RLM_SWIFT_SENDABLE RLM_FINAL // is internally thread-safe -@interface RLMEmailPasswordAuth : RLMProviderClient - -/** - Registers a new email identity with the email/password provider, - and sends a confirmation email to the provided address. - - @param email The email address of the user to register. - @param password The password that the user created for the new email/password identity. - @param completionHandler A callback to be invoked once the call is complete. -*/ - -- (void)registerUserWithEmail:(NSString *)email - password:(NSString *)password - completion:(RLMEmailPasswordAuthOptionalErrorBlock)completionHandler NS_SWIFT_NAME(registerUser(email:password:completion:)); - -/** - Confirms an email identity with the email/password provider. - - @param token The confirmation token that was emailed to the user. - @param tokenId The confirmation token id that was emailed to the user. - @param completionHandler A callback to be invoked once the call is complete. -*/ -- (void)confirmUser:(NSString *)token - tokenId:(NSString *)tokenId - completion:(RLMEmailPasswordAuthOptionalErrorBlock)completionHandler; - -/** - Re-sends a confirmation email to a user that has registered but - not yet confirmed their email address. - - @param email The email address of the user to re-send a confirmation for. - @param completionHandler A callback to be invoked once the call is complete. -*/ -- (void)resendConfirmationEmail:(NSString *)email - completion:(RLMEmailPasswordAuthOptionalErrorBlock)completionHandler; - -/** - Retries custom confirmation function for a given email address. - - @param email The email address of the user to retry custom confirmation logic. - @param completionHandler A callback to be invoked once the call is complete. - */ -- (void)retryCustomConfirmation:(NSString *)email - completion:(RLMEmailPasswordAuthOptionalErrorBlock)completionHandler; - -/** - Sends a password reset email to the given email address. - - @param email The email address of the user to send a password reset email for. - @param completionHandler A callback to be invoked once the call is complete. -*/ -- (void)sendResetPasswordEmail:(NSString *)email - completion:(RLMEmailPasswordAuthOptionalErrorBlock)completionHandler; - -/** - Resets the password of an email identity using the - password reset token emailed to a user. - - @param password The new password. - @param token The password reset token that was emailed to the user. - @param tokenId The password reset token id that was emailed to the user. - @param completionHandler A callback to be invoked once the call is complete. -*/ -- (void)resetPasswordTo:(NSString *)password - token:(NSString *)token - tokenId:(NSString *)tokenId - completion:(RLMEmailPasswordAuthOptionalErrorBlock)completionHandler; - -/** - Resets the password of an email identity using the - password reset function set up in the application. - - @param email The email address of the user. - @param password The desired new password. - @param args A list of arguments passed in as a BSON array. - @param completionHandler A callback to be invoked once the call is complete. -*/ -- (void)callResetPasswordFunction:(NSString *)email - password:(NSString *)password - args:(NSArray> *)args - completion:(RLMEmailPasswordAuthOptionalErrorBlock)completionHandler NS_REFINED_FOR_SWIFT; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) - diff --git a/Realm/RLMEmailPasswordAuth.mm b/Realm/RLMEmailPasswordAuth.mm deleted file mode 100644 index 890fb20f1a..0000000000 --- a/Realm/RLMEmailPasswordAuth.mm +++ /dev/null @@ -1,78 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMEmailPasswordAuth.h" - -#import "RLMApp_Private.hpp" -#import "RLMBSON_Private.hpp" -#import "RLMProviderClient_Private.hpp" - -#import - -@implementation RLMEmailPasswordAuth - -- (realm::app::App::UsernamePasswordProviderClient)client { - return self.app._realmApp->provider_client(); -} - -- (void)registerUserWithEmail:(NSString *)email - password:(NSString *)password - completion:(RLMEmailPasswordAuthOptionalErrorBlock)completion { - self.client.register_email(email.UTF8String, password.UTF8String, RLMWrapCompletion(completion)); -} - -- (void)confirmUser:(NSString *)token - tokenId:(NSString *)tokenId - completion:(RLMEmailPasswordAuthOptionalErrorBlock)completion { - self.client.confirm_user(token.UTF8String, tokenId.UTF8String, RLMWrapCompletion(completion)); -} - -- (void)retryCustomConfirmation:(NSString *)email - completion:(RLMEmailPasswordAuthOptionalErrorBlock)completion { - self.client.retry_custom_confirmation(email.UTF8String, RLMWrapCompletion(completion)); -} - -- (void)resendConfirmationEmail:(NSString *)email - completion:(RLMEmailPasswordAuthOptionalErrorBlock)completion { - self.client.resend_confirmation_email(email.UTF8String, RLMWrapCompletion(completion)); -} - -- (void)sendResetPasswordEmail:(NSString *)email - completion:(RLMEmailPasswordAuthOptionalErrorBlock)completion { - self.client.send_reset_password_email(email.UTF8String, RLMWrapCompletion(completion)); -} - -- (void)resetPasswordTo:(NSString *)password - token:(NSString *)token - tokenId:(NSString *)tokenId - completion:(RLMEmailPasswordAuthOptionalErrorBlock)completion { - self.client.reset_password(password.UTF8String, token.UTF8String, tokenId.UTF8String, - RLMWrapCompletion(completion)); -} - -- (void)callResetPasswordFunction:(NSString *)email - password:(NSString *)password - args:(NSArray> *)args - completion:(RLMEmailPasswordAuthOptionalErrorBlock)completion { - self.client.call_reset_password_function(email.UTF8String, - password.UTF8String, - static_cast(RLMConvertRLMBSONToBson(args)), - RLMWrapCompletion(completion)); -} - -@end diff --git a/Realm/RLMError.h b/Realm/RLMError.h index c006ea4b8e..8cf222e9e9 100644 --- a/Realm/RLMError.h +++ b/Realm/RLMError.h @@ -28,47 +28,12 @@ extern NSString *const RLMErrorDomain; /** An error domain identifying non-specific system errors. */ extern NSString *const RLMUnknownSystemErrorDomain; -/** - The error domain string for all SDK errors related to errors reported - by the synchronization manager error handler, as well as general sync - errors that don't fall into any of the other categories. - */ -extern NSString *const RLMSyncErrorDomain; - -/** - The error domain string for all SDK errors related to the authentication - endpoint. - */ -extern NSString *const RLMSyncAuthErrorDomain; - -/** -The error domain string for all SDK errors related to the Atlas App Services -endpoint. -*/ -extern NSString *const RLMAppErrorDomain; - #pragma mark - RLMError -/// A user info key containing the error code. This is provided for backwards -/// compatibility only and should not be used. -extern NSString *const RLMErrorCodeKey __attribute((deprecated("use -[NSError code]"))); - /// A user info key containing the name of the error code. This is for /// debugging purposes only and should not be relied on. extern NSString *const RLMErrorCodeNameKey; -/// A user info key present in sync errors which originate from the server, -/// containing the URL of the server-side logs associated with the error. -extern NSString * const RLMServerLogURLKey; - -/// A user info key containing a HTTP status code. Some ``RLMAppError`` codes -/// include this, most notably ``RLMAppErrorHttpRequestFailed``. -extern NSString * const RLMHTTPStatusCodeKey; - -/// A user info key containing a `RLMCompensatingWriteInfo` which includes -/// further details about what was reverted by the server. -extern NSString *const RLMCompensatingWriteInfoKey; - /** `RLMError` is an enumeration representing all recoverable errors. It is associated with the Realm error domain specified in `RLMErrorDomain`. @@ -162,12 +127,6 @@ typedef RLM_ERROR_ENUM(NSInteger, RLMError, RLMErrorDomain) { */ RLMErrorUnsupportedFileFormatVersion = 16, - /** - Denotes an error that occurs if a synchronized Realm is opened in more - than one process at once. - */ - RLMErrorMultipleSyncAgents = 17, - /// A subscription was rejected by the server. RLMErrorSubscriptionFailed = 18, @@ -191,259 +150,4 @@ typedef RLM_ERROR_ENUM(NSInteger, RLMError, RLMErrorDomain) { synchronized Realm or vice versa. */ RLMErrorIncompatibleHistories = 21, - - /** - Denotes an error that occurs if objects were written to a flexible sync - Realm without any active subscriptions for that object type. All objects - created in flexible sync Realms must match at least one active - subscription or the server will reject the write. - */ - RLMErrorNoSubscriptionForWrite = 22, }; - -#pragma mark - RLMSyncError - -/// A user info key for use with `RLMSyncErrorClientResetError`. -extern NSString *const kRLMSyncPathOfRealmBackupCopyKey; - -/// A user info key for use with certain error types. -extern NSString *const kRLMSyncErrorActionTokenKey; - -/** - An error related to a problem that might be reported by the synchronization manager - error handler, or a callback on a sync-related API that performs asynchronous work. - */ -typedef RLM_ERROR_ENUM(NSInteger, RLMSyncError, RLMSyncErrorDomain) { - /// An error that indicates a problem with the session (a specific Realm opened for sync). - RLMSyncErrorClientSessionError = 4, - - /// An error that indicates a problem with a specific user. - RLMSyncErrorClientUserError = 5, - - /** - An error that indicates an internal, unrecoverable problem - with the underlying synchronization engine. - */ - RLMSyncErrorClientInternalError = 6, - - /** - An error that indicates the Realm needs to be reset. - - A synced Realm may need to be reset because Atlas App Services encountered an - error and had to be restored from a backup. If the backup copy of the remote Realm - is of an earlier version than the local copy of the Realm, the server will ask the - client to reset the Realm. - - The reset process is as follows: the local copy of the Realm is copied into a recovery - directory for safekeeping, and then deleted from the original location. The next time - the Realm for that partition value is opened, the Realm will automatically be re-downloaded from - Atlas App Services, and can be used as normal. - - Data written to the Realm after the local copy of the Realm diverged from the backup - remote copy will be present in the local recovery copy of the Realm file. The - re-downloaded Realm will initially contain only the data present at the time the Realm - was backed up on the server. - - The client reset process can be initiated in one of two ways. - - The `userInfo` dictionary contains an opaque token object under the key - `kRLMSyncErrorActionTokenKey`. This token can be passed into - `+[RLMSyncSession immediatelyHandleError:]` in order to immediately perform the client - reset process. This should only be done after your app closes and invalidates every - instance of the offending Realm on all threads (note that autorelease pools may make this - difficult to guarantee). - - If `+[RLMSyncSession immediatelyHandleError:]` is not called, the client reset process - will be automatically carried out the next time the app is launched and the `App` is initialized. - - The value for the `kRLMSyncPathOfRealmBackupCopyKey` key in the `userInfo` dictionary - describes the path of the recovered copy of the Realm. This copy will not actually be - created until the client reset process is initiated. - - @see `-[NSError rlmSync_errorActionToken]`, `-[NSError rlmSync_clientResetBackedUpRealmPath]` - */ - RLMSyncErrorClientResetError = 7, - - /// :nodoc: - RLMSyncErrorUnderlyingAuthError = 8, - - /** - An error that indicates the user does not have permission to perform an operation - upon a synced Realm. For example, a user may receive this error if they attempt to - open a Realm they do not have at least read access to, or write to a Realm they only - have read access to. - - This error may also occur if a user incorrectly opens a Realm they have read-only - permissions to without using the `asyncOpen()` APIs. - - A Realm that suffers a permission denied error is, by default, flagged so that its - local copy will be deleted the next time the application starts. - - The `userInfo` dictionary contains an opaque token object under the key - `kRLMSyncErrorActionTokenKey`. This token can be passed into - `+[RLMSyncSession immediatelyHandleError:]` in order to immediately delete the local - copy. This should only be done after your app closes and invalidates every instance - of the offending Realm on all threads (note that autorelease pools may make this - difficult to guarantee). - - @warning It is strongly recommended that, if a Realm has encountered a permission denied - error, its files be deleted before attempting to re-open it. - - @see `-[NSError rlmSync_errorActionToken]` - */ - RLMSyncErrorPermissionDeniedError = 9, - - /** - An error that indicates that the server has rejected the requested flexible sync subscriptions. - */ - RLMSyncErrorInvalidFlexibleSyncSubscriptions = 10, - - /** - An error that indicates that the server has reverted a write made by this - client. This can happen due to not having write permission, or because an - object was created in a flexible sync Realm which does not match any - active subscriptions. - - This error is informational and does not require any explicit handling. - */ - RLMSyncErrorWriteRejected = 11, - - /** - A connection error without a more specific error code occurred. - - Realm internally handles retrying connections with appropriate backoffs, - so connection errors are normally logged and not reported to the error - handler. The exception is if - ``RLMSyncConfiguration.cancelAsyncOpenOnNonFatalErrors`` is set to `true`, - in which case async opens will be canceled on connection failures and the - error will be reported to the completion handler. - - Note that connection timeouts are reported as - (errorDomain: NSPosixErrorDomain, error: ETIMEDOUT) - and not as one of these error codes. - */ - RLMSyncErrorConnectionFailed = 12, - - /** - Connecting to the server failed due to a TLS issue such as an invalid certificate. - */ - RLMSyncErrorTLSHandshakeFailed = 13, - /** - The server has encountered an error that it wants the user to know about, - but is not necessarily fatal. - - An error with this code may indicate that either sync is not enabled or it's trying to connect to - an edge server app. - */ - RLMSyncErrorServerWarning = 14, -}; - -#pragma mark - RLMSyncAuthError - -// NEXT-MAJOR: This was a ROS thing and should have been removed in v10 -/// :nodoc: -typedef RLM_ERROR_ENUM(NSInteger, RLMSyncAuthError, RLMSyncAuthErrorDomain) { - RLMSyncAuthErrorBadResponse = 1, - RLMSyncAuthErrorBadRemoteRealmPath = 2, - RLMSyncAuthErrorHTTPStatusCodeError = 3, - RLMSyncAuthErrorClientSessionError = 4, - RLMSyncAuthErrorInvalidParameters = 601, - RLMSyncAuthErrorMissingPath = 602, - RLMSyncAuthErrorInvalidCredential = 611, - RLMSyncAuthErrorUserDoesNotExist = 612, - RLMSyncAuthErrorUserAlreadyExists = 613, - RLMSyncAuthErrorAccessDeniedOrInvalidPath = 614, - RLMSyncAuthErrorInvalidAccessToken = 615, - RLMSyncAuthErrorFileCannotBeShared = 703, -} __attribute__((deprecated("Errors of this type are no longer reported"))); - -#pragma mark - RLMSyncAppError - -/// An error which occurred when making a request to Atlas App Services. -typedef RLM_ERROR_ENUM(NSInteger, RLMAppError, RLMAppErrorDomain) { - /// An unknown error has occurred - RLMAppErrorUnknown = -1, - - /// A HTTP request completed with an error status code. The failing status - /// code can be found in the ``RLMHTTPStatusCodeKey`` key of the userInfo - /// dictionary. - RLMAppErrorHttpRequestFailed = 1, - - /// A user's session is in an invalid state. Logging out and back in may rectify this. - RLMAppErrorInvalidSession, - /// A request sent to the server was malformed in some way. - RLMAppErrorBadRequest, - /// A request was made using a nonexistent user. - RLMAppErrorUserNotFound, - /// A request was made against an App using a User which does not belong to that App. - RLMAppErrorUserAppDomainMismatch, - /// The auth provider has limited the domain names which can be used for email addresses, and the given one is not allowed. - RLMAppErrorDomainNotAllowed, - /// The request body size exceeded a server-configured limit. - RLMAppErrorReadSizeLimitExceeded, - /// A request had an invalid parameter. - RLMAppErrorInvalidParameter, - /// A request was missing a required parameter. - RLMAppErrorMissingParameter, - /// Executing the requested server function failed with an error. - RLMAppErrorFunctionExecutionError, - /// The server encountered an internal error. - RLMAppErrorInternalServerError, - /// Authentication failed due to the request auth provider not existing. - RLMAppErrorAuthProviderNotFound, - /// The requested value does not exist. - RLMAppErrorValueNotFound, - /// The value being created already exists. - RLMAppErrorValueAlreadyExists, - /// A value with the same name as the value being created already exists. - RLMAppErrorValueDuplicateName, - /// The called server function does not exist. - RLMAppErrorFunctionNotFound, - /// The called server function has a syntax error. - RLMAppErrorFunctionSyntaxError, - /// The called server function is invalid in some way. - RLMAppErrorFunctionInvalid, - /// Registering an API key with the auth provider failed due to it already existing. - RLMAppErrorAPIKeyAlreadyExists, - /// The operation failed due to exceeding the server-configured time limit. - RLMAppErrorExecutionTimeLimitExceeded, - /// The body of the called function does not define a callable thing. - RLMAppErrorNotCallable, - /// Email confirmation failed for a user because the user has already confirmed their email. - RLMAppErrorUserAlreadyConfirmed, - /// The user cannot be used because it has been disabled. - RLMAppErrorUserDisabled, - /// An auth error occurred which does not have a more specific error code. - RLMAppErrorAuthError, - /// Account registration failed due to the user name already being taken. - RLMAppErrorAccountNameInUse, - /// A login request failed due to an invalid password. - RLMAppErrorInvalidPassword, - /// Operation failed due to server-side maintenance. - RLMAppErrorMaintenanceInProgress, - /// Operation failed due to an error reported by MongoDB. - RLMAppErrorMongoDBError, -}; - -/// Extended information about a write which was rejected by the server. -/// -/// The server will sometimes reject writes made by the client for reasons such -/// as permissions, additional server-side validation failing, or because the -/// object didn't match any flexible sync subscriptions. When this happens, a -/// ``RLMSyncErrorWriteRejected`` error is reported which contains an array of -/// `RLMCompensatingWriteInfo` objects in the ``RLMCompensatingWriteInfoKey`` -/// userInfo key with information about what writes were rejected and why. -/// -/// This information is intended for debugging and logging purposes only. The -/// `reason` strings are generated by the server and are not guaranteed to be -/// stable, so attempting to programmatically do anything with them will break -/// without warning. -RLM_SWIFT_SENDABLE RLM_FINAL -@interface RLMCompensatingWriteInfo : NSObject -/// The class name of the object being written to. -@property (nonatomic, readonly) NSString *objectType; -/// The primary key of the object being written to. -@property (nonatomic, readonly) id primaryKey NS_REFINED_FOR_SWIFT; -/// A human-readable string describing why the write was rejected. -@property (nonatomic, readonly) NSString *reason; -@end diff --git a/Realm/RLMError.mm b/Realm/RLMError.mm index 72cc0b8388..7a661fe757 100644 --- a/Realm/RLMError.mm +++ b/Realm/RLMError.mm @@ -19,27 +19,14 @@ #import "RLMError_Private.hpp" #import "RLMUtil.hpp" -#import "RLMSyncSession_Private.hpp" -#import #import -#import -// NEXT-MAJOR: we should merge these all into a single error domain/error enum NSString *const RLMErrorDomain = @"io.realm"; NSString *const RLMUnknownSystemErrorDomain = @"io.realm.unknown"; -NSString *const RLMSyncErrorDomain = @"io.realm.sync"; -NSString *const RLMSyncAuthErrorDomain = @"io.realm.sync.auth"; -NSString *const RLMAppErrorDomain = @"io.realm.app"; -NSString *const kRLMSyncPathOfRealmBackupCopyKey = @"recovered_realm_location_path"; -NSString *const kRLMSyncErrorActionTokenKey = @"error_action_token"; NSString *const RLMErrorCodeKey = @"Error Code"; NSString *const RLMErrorCodeNameKey = @"Error Name"; -NSString *const RLMServerLogURLKey = @"Server Log URL"; -NSString *const RLMCompensatingWriteInfoKey = @"Compensating Write Info"; -NSString *const RLMHTTPStatusCodeKey = @"HTTP Status Code"; -static NSString *const RLMDeprecatedErrorCodeKey = @"Error Code"; namespace { NSInteger translateFileError(realm::ErrorCodes::Error code) { @@ -56,99 +43,21 @@ NSInteger translateFileError(realm::ErrorCodes::Error code) { case ec::IncompatibleLockFile: return RLMErrorIncompatibleLockFile; case ec::IncompatibleSession: return RLMErrorIncompatibleSession; case ec::InvalidDatabase: return RLMErrorInvalidDatabase; - case ec::MultipleSyncAgents: return RLMErrorMultipleSyncAgents; - case ec::NoSubscriptionForWrite: return RLMErrorNoSubscriptionForWrite; case ec::OutOfDiskSpace: return RLMErrorOutOfDiskSpace; case ec::PermissionDenied: return RLMErrorFilePermissionDenied; case ec::SchemaMismatch: return RLMErrorSchemaMismatch; - case ec::SubscriptionFailed: return RLMErrorSubscriptionFailed; case ec::UnsupportedFileFormatVersion: return RLMErrorUnsupportedFileFormatVersion; - // Sync errors - case ec::AuthError: return RLMSyncErrorClientUserError; - case ec::SyncPermissionDenied: return RLMSyncErrorPermissionDeniedError; - case ec::SyncCompensatingWrite: return RLMSyncErrorWriteRejected; - case ec::SyncConnectFailed: return RLMSyncErrorConnectionFailed; - case ec::TlsHandshakeFailed: return RLMSyncErrorTLSHandshakeFailed; - case ec::SyncConnectTimeout: return ETIMEDOUT; - - // App errors - case ec::APIKeyAlreadyExists: return RLMAppErrorAPIKeyAlreadyExists; - case ec::AccountNameInUse: return RLMAppErrorAccountNameInUse; - case ec::AppUnknownError: return RLMAppErrorUnknown; - case ec::AuthProviderNotFound: return RLMAppErrorAuthProviderNotFound; - case ec::DomainNotAllowed: return RLMAppErrorDomainNotAllowed; - case ec::ExecutionTimeLimitExceeded: return RLMAppErrorExecutionTimeLimitExceeded; - case ec::FunctionExecutionError: return RLMAppErrorFunctionExecutionError; - case ec::FunctionInvalid: return RLMAppErrorFunctionInvalid; - case ec::FunctionNotFound: return RLMAppErrorFunctionNotFound; - case ec::FunctionSyntaxError: return RLMAppErrorFunctionSyntaxError; - case ec::InvalidPassword: return RLMAppErrorInvalidPassword; - case ec::InvalidSession: return RLMAppErrorInvalidSession; - case ec::MaintenanceInProgress: return RLMAppErrorMaintenanceInProgress; - case ec::MissingParameter: return RLMAppErrorMissingParameter; - case ec::MongoDBError: return RLMAppErrorMongoDBError; - case ec::NotCallable: return RLMAppErrorNotCallable; - case ec::ReadSizeLimitExceeded: return RLMAppErrorReadSizeLimitExceeded; - case ec::UserAlreadyConfirmed: return RLMAppErrorUserAlreadyConfirmed; - case ec::UserAppDomainMismatch: return RLMAppErrorUserAppDomainMismatch; - case ec::UserDisabled: return RLMAppErrorUserDisabled; - case ec::UserNotFound: return RLMAppErrorUserNotFound; - case ec::ValueAlreadyExists: return RLMAppErrorValueAlreadyExists; - case ec::ValueDuplicateName: return RLMAppErrorValueDuplicateName; - case ec::ValueNotFound: return RLMAppErrorValueNotFound; - - case ec::AWSError: - case ec::GCMError: - case ec::HTTPError: - case ec::InternalServerError: - case ec::TwilioError: - return RLMAppErrorInternalServerError; - - case ec::ArgumentsNotAllowed: - case ec::BadRequest: - case ec::InvalidParameter: - return RLMAppErrorBadRequest; - default: { auto category = realm::ErrorCodes::error_categories(code); if (category.test(realm::ErrorCategory::file_access)) { return RLMErrorFileAccess; } - if (category.test(realm::ErrorCategory::app_error)) { - return RLMAppErrorUnknown; - } - if (category.test(realm::ErrorCategory::sync_error)) { - return RLMSyncErrorClientInternalError; - } return RLMErrorFail; } } } -NSString *errorDomain(realm::ErrorCodes::Error error) { - // Special-case errors where our error domain doesn't match core's category - // NEXT-MAJOR: we should unify everything into RLMErrorDomain - using ec = realm::ErrorCodes::Error; - switch (error) { - case ec::SubscriptionFailed: - return RLMErrorDomain; - case ec::SyncConnectTimeout: - return NSPOSIXErrorDomain; - default: - break; - } - - auto category = realm::ErrorCodes::error_categories(error); - if (category.test(realm::ErrorCategory::sync_error)) { - return RLMSyncErrorDomain; - } - if (category.test(realm::ErrorCategory::app_error)) { - return RLMAppErrorDomain; - } - return RLMErrorDomain; -} - NSString *errorString(realm::ErrorCodes::Error error) { return RLMStringViewToNSString(realm::ErrorCodes::error_string(error)); } @@ -161,10 +70,6 @@ NSInteger translateFileError(realm::ErrorCodes::Error code) { NSMutableDictionary *userInfo = [NSMutableDictionary new]; userInfo[NSLocalizedDescriptionKey] = @(msg); - // FIXME: remove these in v11 - userInfo[@"Error Code"] = @(code); - userInfo[@"Category"] = @(ec.category().name()); - return [NSError errorWithDomain:errorDomain code:code userInfo:userInfo.copy]; } } // anonymous namespace @@ -174,30 +79,27 @@ NSInteger translateFileError(realm::ErrorCodes::Error code) { return nil; } auto code = translateFileError(status.code()); - return [NSError errorWithDomain:errorDomain(status.code()) + return [NSError errorWithDomain:RLMErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: @(status.reason().c_str()), - RLMDeprecatedErrorCodeKey: @(code), RLMErrorCodeNameKey: errorString(status.code())}]; } NSError *makeError(realm::Exception const& exception) { NSInteger code = translateFileError(exception.code()); - return [NSError errorWithDomain:errorDomain(exception.code()) + return [NSError errorWithDomain:RLMErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: @(exception.what()), - RLMDeprecatedErrorCodeKey: @(code), RLMErrorCodeNameKey: errorString(exception.code())}]; } NSError *makeError(realm::FileAccessError const& exception) { NSInteger code = translateFileError(exception.code()); - return [NSError errorWithDomain:errorDomain(exception.code()) + return [NSError errorWithDomain:RLMErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: @(exception.what()), NSFilePathErrorKey: @(exception.get_path().data()), - RLMDeprecatedErrorCodeKey: @(code), RLMErrorCodeNameKey: errorString(exception.code())}]; } @@ -210,115 +112,3 @@ NSInteger translateFileError(realm::ErrorCodes::Error code) { NSError *makeError(std::system_error const& exception) { return translateSystemError(exception.code(), exception.what()); } - -__attribute__((objc_direct_members)) -@implementation RLMCompensatingWriteInfo { - realm::sync::CompensatingWriteErrorInfo _info; -} - -- (instancetype)initWithInfo:(realm::sync::CompensatingWriteErrorInfo&&)info { - if ((self = [super init])) { - _info = std::move(info); - } - return self; -} - -- (NSString *)objectType { - return @(_info.object_name.c_str()); -} - -- (NSString *)reason { - return @(_info.reason.c_str()); -} - -- (id)primaryKey { - return RLMMixedToObjc(_info.primary_key); -} -@end - -NSError *makeError(realm::SyncError&& error, const std::shared_ptr& app) { - auto& status = error.status; - if (status.is_ok()) { - return nil; - } - - NSMutableDictionary *userInfo = [NSMutableDictionary new]; - userInfo[NSLocalizedDescriptionKey] = RLMStringViewToNSString(error.simple_message); - if (!error.logURL.empty()) { - userInfo[RLMServerLogURLKey] = RLMStringViewToNSString(error.logURL); - } - if (!error.compensating_writes_info.empty()) { - NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:error.compensating_writes_info.size()]; - for (auto& info : error.compensating_writes_info) { - [array addObject:[[RLMCompensatingWriteInfo alloc] initWithInfo:std::move(info)]]; - } - userInfo[RLMCompensatingWriteInfoKey] = [array copy]; - } - for (auto& pair : error.user_info) { - if (pair.first == realm::SyncError::c_original_file_path_key) { - userInfo[kRLMSyncErrorActionTokenKey] = - [[RLMSyncErrorActionToken alloc] initWithOriginalPath:pair.second app:app]; - } - else if (pair.first == realm::SyncError::c_recovery_file_path_key) { - userInfo[kRLMSyncPathOfRealmBackupCopyKey] = @(pair.second.c_str()); - } - } - - int errorCode = RLMSyncErrorClientInternalError; - NSString *errorDomain = RLMSyncErrorDomain; - using enum realm::ErrorCodes::Error; - auto code = error.status.code(); - bool isSyncError = realm::ErrorCodes::error_categories(code).test(realm::ErrorCategory::sync_error); - switch (code) { - case SyncPermissionDenied: - errorCode = RLMSyncErrorPermissionDeniedError; - break; - case AuthError: - errorCode = RLMSyncErrorClientUserError; - break; - case SyncCompensatingWrite: - errorCode = RLMSyncErrorWriteRejected; - break; - case SyncConnectFailed: - errorCode = RLMSyncErrorConnectionFailed; - break; - case SyncConnectTimeout: - errorCode = ETIMEDOUT; - errorDomain = NSPOSIXErrorDomain; - break; - - default: - if (error.is_client_reset_requested()) - errorCode = RLMSyncErrorClientResetError; - else if (isSyncError) - errorCode = RLMSyncErrorClientSessionError; - else if (error.server_requests_action == realm::sync::ProtocolErrorInfo::Action::Warning) - errorCode = RLMSyncErrorServerWarning; - else if (!error.is_fatal) - return nil; - break; - } - - return [NSError errorWithDomain:errorDomain code:errorCode userInfo:userInfo.copy]; -} - -NSError *makeError(realm::app::AppError const& appError) { - auto& status = appError.to_status(); - if (status.is_ok()) { - return nil; - } - - // Core uses the same error code for both sync and app auth errors, but we - // have separate ones - auto code = translateFileError(status.code()); - auto domain = errorDomain(status.code()); - if (domain == RLMSyncErrorDomain && code == RLMSyncErrorClientUserError) { - domain = RLMAppErrorDomain; - code = RLMAppErrorAuthError; - } - return [NSError errorWithDomain:domain code:code - userInfo:@{NSLocalizedDescriptionKey: @(status.reason().c_str()), - RLMDeprecatedErrorCodeKey: @(code), - RLMErrorCodeNameKey: errorString(status.code()), - RLMServerLogURLKey: @(appError.link_to_server_logs.c_str())}]; -} diff --git a/Realm/RLMError_Private.hpp b/Realm/RLMError_Private.hpp index e8e8878d6a..d5afb3ea08 100644 --- a/Realm/RLMError_Private.hpp +++ b/Realm/RLMError_Private.hpp @@ -23,14 +23,6 @@ RLM_HIDDEN_BEGIN -namespace realm { -struct SyncError; -namespace app { -class App; -struct AppError; -} -} - NSError *makeError(realm::Status const& status); template @@ -42,8 +34,5 @@ NSError *makeError(realm::Exception const& exception); NSError *makeError(realm::FileAccessError const& exception); NSError *makeError(std::exception const& exception); NSError *makeError(std::system_error const& exception); -NSError *makeError(realm::app::AppError const& error); -NSError *makeError(realm::SyncError&& error, const std::shared_ptr& app); -NSError *makeError(realm::SyncError const& error) = delete; RLM_HIDDEN_END diff --git a/Realm/RLMEvent.h b/Realm/RLMEvent.h deleted file mode 100644 index 9a6acb1a6e..0000000000 --- a/Realm/RLMEvent.h +++ /dev/null @@ -1,64 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#ifdef __cplusplus -#include - -namespace realm { -struct AuditConfig; -} -#endif - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@class RLMRealm, RLMUser, RLMRealmConfiguration; -typedef RLM_CLOSED_ENUM(NSUInteger, RLMSyncLogLevel); - -struct RLMEventContext; -typedef void (^RLMEventCompletion)(NSError *_Nullable); - -FOUNDATION_EXTERN struct RLMEventContext *_Nullable RLMEventGetContext(RLMRealm *realm); -FOUNDATION_EXTERN uint64_t RLMEventBeginScope(struct RLMEventContext *context, NSString *activity); -FOUNDATION_EXTERN void RLMEventCommitScope(struct RLMEventContext *context, uint64_t scope_id, - RLMEventCompletion _Nullable completion); -FOUNDATION_EXTERN void RLMEventCancelScope(struct RLMEventContext *context, uint64_t scope_id); -FOUNDATION_EXTERN bool RLMEventIsActive(struct RLMEventContext *context, uint64_t scope_id); -FOUNDATION_EXTERN void RLMEventRecordEvent(struct RLMEventContext *context, NSString *activity, - NSString *_Nullable event, NSString *_Nullable data, - RLMEventCompletion _Nullable completion); -FOUNDATION_EXTERN void RLMEventUpdateMetadata(struct RLMEventContext *context, - NSDictionary *newMetadata); - -@interface RLMEventConfiguration : NSObject -@property (nonatomic) NSString *partitionPrefix; -@property (nonatomic, nullable) RLMUser *syncUser; -@property (nonatomic, nullable) NSDictionary *metadata; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -@property (nonatomic, nullable) void (^logger)(RLMSyncLogLevel, NSString *); -#pragma clang diagnostic pop -@property (nonatomic, nullable) RLM_SWIFT_SENDABLE void (^errorHandler)(NSError *); - -#ifdef __cplusplus -- (std::shared_ptr)auditConfigWithRealmConfiguration:(RLMRealmConfiguration *)realmConfig; -#endif -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMEvent.mm b/Realm/RLMEvent.mm deleted file mode 100644 index bfcc578da4..0000000000 --- a/Realm/RLMEvent.mm +++ /dev/null @@ -1,242 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#import "RLMError_Private.hpp" -#import "RLMObjectSchema_Private.hpp" -#import "RLMObjectStore.h" -#import "RLMObject_Private.hpp" -#import "RLMRealmConfiguration_Private.hpp" -#import "RLMRealmUtil.hpp" -#import "RLMRealm_Private.hpp" -#import "RLMSyncConfiguration_Private.hpp" -#import "RLMSyncManager_Private.hpp" -#import "RLMUser_Private.hpp" -#import "RLMUtil.hpp" - -#import -#import -#import -#import -#import - -using namespace realm; - -@interface RLMObjectBase () -- (NSString *)customEventRepresentation; -@end - -namespace { -util::UniqueFunction wrapCompletion(void (^completion)(NSError *)) { - if (!completion) { - return nullptr; - } - return [=](std::exception_ptr err) { - @autoreleasepool { - if (!err) { - return completion(nil); - } - try { - std::rethrow_exception(err); - } - catch (NSException *e) { - auto info = @{@"ExceptionName": e.name ?: NSNull.null, - @"ExceptionReason": e.reason ?: NSNull.null, - @"ExceptionCallStackReturnAddresses": e.callStackReturnAddresses, - @"ExceptionCallStackSymbols": e.callStackSymbols, - @"ExceptionUserInfo": e.userInfo ?: NSNull.null}; - completion([NSError errorWithDomain:RLMErrorDomain code:RLMErrorFail userInfo:info]); - } - catch (...) { - NSError *error; - RLMRealmTranslateException(&error); - completion(error); - } - } - }; -} - -realm::AuditInterface *auditContext(RLMEventContext *context) { - return reinterpret_cast(context); -} - -std::vector> convertMetadata(NSDictionary *metadata) { - std::vector> ret; - ret.reserve(metadata.count); - [metadata enumerateKeysAndObjectsUsingBlock:[&](NSString *key, NSString *value, BOOL *) { - ret.emplace_back(key.UTF8String, value.UTF8String); - }]; - return ret; -} - -std::optional nsStringToOptionalString(NSString *str) { - if (!str) { - return util::none; - } - - std::string ret; - RLMNSStringToStdString(ret, str); - return ret; -} -} // anonymous namespace - -uint64_t RLMEventBeginScope(RLMEventContext *context, NSString *activity) { - return auditContext(context)->begin_scope(activity.UTF8String); -} - -void RLMEventCommitScope(RLMEventContext *context, uint64_t scope_id, RLMEventCompletion completion) { - auditContext(context)->end_scope(scope_id, wrapCompletion(completion)); -} - -void RLMEventCancelScope(RLMEventContext *context, uint64_t scope_id) { - auditContext(context)->cancel_scope(scope_id); -} - -bool RLMEventIsActive(RLMEventContext *context, uint64_t scope_id) { - return auditContext(context)->is_scope_valid(scope_id); -} - -void RLMEventRecordEvent(RLMEventContext *context, NSString *activity, NSString *event, - NSString *data, RLMEventCompletion completion) { - auditContext(context)->record_event(activity.UTF8String, nsStringToOptionalString(event), - nsStringToOptionalString(data), wrapCompletion(completion)); -} - -void RLMEventUpdateMetadata(RLMEventContext *context, NSDictionary *newMetadata) { - auditContext(context)->update_metadata(convertMetadata(newMetadata)); -} - -RLMEventContext *RLMEventGetContext(RLMRealm *realm) { - return reinterpret_cast(realm->_realm->audit_context()); -} - -namespace { -class RLMEventSerializer : public realm::AuditObjectSerializer { -public: - RLMEventSerializer(RLMRealmConfiguration *c) : _config(c.copy) { - auto& config = _config.configRef; - config.cache = false; - config.audit_config = nullptr; - config.automatic_change_notifications = false; - } - - ~RLMEventSerializer() { - scope_complete(); - } - - void scope_complete() final { - for (auto& [_, acc] : _accessorMap) { - if (acc) { - acc->_realm = nil; - acc->_objectSchema = nil; - } - } - if (_realm) { - _realm->_realm->close(); - _realm = nil; - } - } - - void to_json(nlohmann::json& out, const Obj& obj) final { - @autoreleasepool { - auto tableKey = obj.get_table()->get_key(); - RLMObjectBase *acc = getAccessor(tableKey); - if (!acc) { - return AuditObjectSerializer::to_json(out, obj); - } - - if (!acc->_realm) { - acc->_realm = realm(); - acc->_info = acc->_realm->_info[tableKey]; - acc->_objectSchema = acc->_info->rlmObjectSchema; - } - - acc->_row = obj; - RLMInitializeSwiftAccessor(acc, false); - NSString *customRepresentation = [acc customEventRepresentation]; - out = nlohmann::json::parse(customRepresentation.UTF8String); - } - } - -private: - RLMRealmConfiguration *_config; - RLMRealm *_realm; - std::unordered_map _accessorMap; - - RLMRealm *realm() { - if (!_realm) { - _realm = [RLMRealm realmWithConfiguration:_config error:nil]; - } - return _realm; - } - - RLMObjectBase *getAccessor(TableKey tableKey) { - auto it = _accessorMap.find(tableKey.value); - if (it != _accessorMap.end()) { - return it->second; - } - - RLMClassInfo *info = realm()->_info[tableKey]; - if (!info || !info->rlmObjectSchema.hasCustomEventSerialization) { - _accessorMap.insert({tableKey.value, nil}); - return nil; - } - - RLMObjectBase *acc = [[info->rlmObjectSchema.accessorClass alloc] init]; - acc->_realm = realm(); - acc->_objectSchema = info->rlmObjectSchema; - acc->_info = info; - _accessorMap.insert({tableKey.value, acc}); - return acc; - } -}; -} // anonymous namespace - -@implementation RLMEventConfiguration -- (std::shared_ptr)auditConfigWithRealmConfiguration:(RLMRealmConfiguration *)realmConfig { - auto config = std::make_shared(); - config->audit_user = self.syncUser.user; - config->partition_value_prefix = self.partitionPrefix.UTF8String; - config->metadata = convertMetadata(self.metadata); - config->serializer = std::make_shared(realmConfig); - if (_logger) { - config->logger = RLMWrapLogFunction(_logger); - } - if (_errorHandler) { - config->sync_error_handler = [eh = _errorHandler](realm::SyncError e) { - if (auto error = makeError(std::move(e), nullptr)) { - eh(error); - } - }; - } - - std::shared_ptr app; - if (config->audit_user) { - app = static_cast(*config->audit_user).app(); - } - else if (auto user = realmConfig.syncConfiguration.user) { - app = user.user->app(); - } - if (app) { - config->base_file_path = app->config().base_file_path; - } - - return config; -} -@end diff --git a/Realm/RLMFindOneAndModifyOptions.h b/Realm/RLMFindOneAndModifyOptions.h deleted file mode 100644 index 3cce99f23e..0000000000 --- a/Realm/RLMFindOneAndModifyOptions.h +++ /dev/null @@ -1,80 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) -@protocol RLMBSON; -@class RLMSortDescriptor; - -/// Options to use when executing a `findOneAndUpdate`, `findOneAndReplace`, -/// or `findOneAndDelete` command on a `RLMMongoCollection`. -@interface RLMFindOneAndModifyOptions : NSObject - -/// Limits the fields to return for all matching documents. -@property (nonatomic, nullable) id projection NS_REFINED_FOR_SWIFT; - -/// The order in which to return matching documents. -@property (nonatomic, nullable) id sort NS_REFINED_FOR_SWIFT -__attribute__((deprecated("Use `sorting` instead, which correctly sort more than one sort attribute", "sorting"))); - -/// The order in which to return matching documents. -@property (nonatomic) NSArray> *sorting NS_REFINED_FOR_SWIFT; - - -/// Whether or not to perform an upsert, default is false -/// (only available for find_one_and_replace and find_one_and_update) -@property (nonatomic) BOOL upsert; - -/// When true then the new document is returned, -/// Otherwise the old document is returned (default) -/// (only available for findOneAndReplace and findOneAndUpdate) -@property (nonatomic) BOOL shouldReturnNewDocument; - -/// Options to use when executing a `findOneAndUpdate`, `findOneAndReplace`, -/// or `findOneAndDelete` command on a `RLMMongoCollection`. -/// @param projection Limits the fields to return for all matching documents. -/// @param sort The order in which to return matching documents. -/// @param upsert Whether or not to perform an upsert, default is false -/// (only available for findOneAndReplace and findOneAndUpdate) -/// @param shouldReturnNewDocument When true then the new document is returned, -/// Otherwise the old document is returned (default), -/// (only available for findOneAndReplace and findOneAndUpdate) -- (instancetype)initWithProjection:(id _Nullable)projection - sort:(id _Nullable)sort - upsert:(BOOL)upsert - shouldReturnNewDocument:(BOOL)shouldReturnNewDocument -__attribute__((deprecated("Please use `initWithProjection:sorting:upsert:shouldReturnNewDocument:`"))) - NS_SWIFT_UNAVAILABLE("Please see FindOneAndModifyOptions"); - -/// Options to use when executing a `findOneAndUpdate`, `findOneAndReplace`, -/// or `findOneAndDelete` command on a `RLMMongoCollection`. -/// @param projection Limits the fields to return for all matching documents. -/// @param sorting The order in which to return matching documents. -/// @param upsert Whether or not to perform an upsert, default is false -/// (only available for findOneAndReplace and findOneAndUpdate) -/// @param shouldReturnNewDocument When true then the new document is returned, -/// Otherwise the old document is returned (default), -/// (only available for findOneAndReplace and findOneAndUpdate) -- (instancetype)initWithProjection:(id _Nullable)projection - sorting:(NSArray> *)sorting - upsert:(BOOL)upsert - shouldReturnNewDocument:(BOOL)shouldReturnNewDocument; -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMFindOneAndModifyOptions.mm b/Realm/RLMFindOneAndModifyOptions.mm deleted file mode 100644 index d21ba1062a..0000000000 --- a/Realm/RLMFindOneAndModifyOptions.mm +++ /dev/null @@ -1,110 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMFindOneAndModifyOptions_Private.hpp" -#import "RLMBSON_Private.hpp" -#import "RLMCollection.h" - -@interface RLMFindOneAndModifyOptions() { - realm::app::MongoCollection::FindOneAndModifyOptions _options; -}; -@end - -@implementation RLMFindOneAndModifyOptions - -- (instancetype)initWithProjection:(id _Nullable)projection - sort:(id _Nullable)sort - upsert:(BOOL)upsert - shouldReturnNewDocument:(BOOL)shouldReturnNewDocument { - if (self = [super init]) { - self.upsert = upsert; - self.shouldReturnNewDocument = shouldReturnNewDocument; - self.projection = projection; - self.sort = sort; - } - return self; -} - -- (instancetype)initWithProjection:(id _Nullable)projection - sorting:(NSArray> *)sorting - upsert:(BOOL)upsert - shouldReturnNewDocument:(BOOL)shouldReturnNewDocument { - if (self = [super init]) { - self.upsert = upsert; - self.shouldReturnNewDocument = shouldReturnNewDocument; - self.projection = projection; - self.sorting = sorting; - } - return self; -} - -- (realm::app::MongoCollection::FindOneAndModifyOptions)_findOneAndModifyOptions { - return _options; -} - -- (id)projection { - return RLMConvertBsonDocumentToRLMBSON(_options.projection_bson); -} - -- (id)sort { - return RLMConvertBsonDocumentToRLMBSON(_options.sort_bson); -} - -- (NSArray> *)sorting { - return RLMConvertBsonDocumentToRLMBSONArray(_options.sort_bson); -} - -- (BOOL)upsert { - return _options.upsert; -} - -- (BOOL)shouldReturnNewDocument { - return _options.return_new_document; -} - -- (void)setProjection:(id)projection { - if (projection) { - auto bson = realm::bson::BsonDocument(RLMConvertRLMBSONToBson(projection)); - _options.projection_bson = std::optional(bson); - } else { - _options.projection_bson = realm::util::none; - } -} - -- (void)setSort:(id)sort { - if (sort) { - auto bson = realm::bson::BsonDocument(RLMConvertRLMBSONToBson(sort)); - _options.sort_bson = std::optional(bson); - } else { - _options.sort_bson = realm::util::none; - } -} - -- (void)setSorting:(NSArray> *)sorting { - _options.sort_bson = RLMConvertRLMBSONArrayToBsonDocument(sorting); -} - -- (void)setUpsert:(BOOL)upsert { - _options.upsert = upsert; -} - -- (void)setShouldReturnNewDocument:(BOOL)returnNewDocument { - _options.return_new_document = returnNewDocument; -} - -@end diff --git a/Realm/RLMFindOneAndModifyOptions_Private.hpp b/Realm/RLMFindOneAndModifyOptions_Private.hpp deleted file mode 100644 index 1cda193842..0000000000 --- a/Realm/RLMFindOneAndModifyOptions_Private.hpp +++ /dev/null @@ -1,25 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#import - -@interface RLMFindOneAndModifyOptions () -- (realm::app::MongoCollection::FindOneAndModifyOptions)_findOneAndModifyOptions; -@end diff --git a/Realm/RLMFindOptions.h b/Realm/RLMFindOptions.h deleted file mode 100644 index 043a853740..0000000000 --- a/Realm/RLMFindOptions.h +++ /dev/null @@ -1,77 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@protocol RLMBSON; - -/// Options to use when executing a `find` command on a `RLMMongoCollection`. -@interface RLMFindOptions : NSObject - -/// The maximum number of documents to return. Specifying 0 will return all documents. -@property (nonatomic) NSInteger limit; - -/// Limits the fields to return for all matching documents. -@property (nonatomic, nullable) id projection NS_REFINED_FOR_SWIFT; - -/// The order in which to return matching documents. -@property (nonatomic, nullable) id sort NS_REFINED_FOR_SWIFT -__attribute__((deprecated("Use `sorting` instead, which correctly sort more than one sort attribute", "sorting"))); - -/// The order in which to return matching documents. -@property (nonatomic) NSArray> *sorting NS_REFINED_FOR_SWIFT; - -/// Options to use when executing a `find` command on a `RLMMongoCollection`. -/// @param limit The maximum number of documents to return. Specifying 0 will return all documents. -/// @param projection Limits the fields to return for all matching documents. -/// @param sort The order in which to return matching documents. -- (instancetype)initWithLimit:(NSInteger)limit - projection:(id _Nullable)projection - sort:(id _Nullable)sort -__attribute__((deprecated("Please use `initWithLimit:projection:sorting:`"))) - NS_SWIFT_UNAVAILABLE("Please see FindOption"); - - -/// Options to use when executing a `find` command on a `RLMMongoCollection`. -/// @param projection Limits the fields to return for all matching documents. -/// @param sort The order in which to return matching documents. -- (instancetype)initWithProjection:(id _Nullable)projection - sort:(id _Nullable)sort __deprecated -__attribute__((deprecated("Please use `initWithProjection:sorting:`"))) - NS_SWIFT_UNAVAILABLE("Please see FindOption"); - - -/// Options to use when executing a `find` command on a `RLMMongoCollection`. -/// @param limit The maximum number of documents to return. Specifying 0 will return all documents. -/// @param projection Limits the fields to return for all matching documents. -/// @param sorting The order in which to return matching documents. -- (instancetype)initWithLimit:(NSInteger)limit - projection:(id _Nullable)projection - sorting:(NSArray> *)sorting; - -/// Options to use when executing a `find` command on a `RLMMongoCollection`. -/// @param projection Limits the fields to return for all matching documents. -/// @param sorting The order in which to return matching documents. -- (instancetype)initWithProjection:(id _Nullable)projection - sorting:(NSArray> *)sorting; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMFindOptions.mm b/Realm/RLMFindOptions.mm deleted file mode 100644 index 37a2bc403e..0000000000 --- a/Realm/RLMFindOptions.mm +++ /dev/null @@ -1,116 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMFindOptions_Private.hpp" -#import "RLMBSON_Private.hpp" -#import "RLMCollection.h" - -@interface RLMFindOptions() { - realm::app::MongoCollection::FindOptions _options; -}; -@end - -@implementation RLMFindOptions - -- (instancetype)initWithLimit:(NSInteger)limit - projection:(id _Nullable)projection - sort:(id _Nullable)sort { - if (self = [super init]) { - self.projection = projection; - self.sort = sort; - self.limit = limit; - } - return self; -} - -- (instancetype)initWithProjection:(id _Nullable)projection - sort:(id _Nullable)sort { - if (self = [super init]) { - self.projection = projection; - self.sort = sort; - } - return self; -} - -- (instancetype)initWithLimit:(NSInteger)limit - projection:(id _Nullable)projection - sorting:(NSArray> *)sorting { - if (self = [super init]) { - self.projection = projection; - self.sorting = sorting; - self.limit = limit; - } - return self; -} - -- (instancetype)initWithProjection:(id _Nullable)projection - sorting:(NSArray> *)sorting { - if (self = [super init]) { - self.projection = projection; - self.sorting = sorting; - } - return self; -} - -- (realm::app::MongoCollection::FindOptions)_findOptions { - return _options; -} - -- (id)projection { - return RLMConvertBsonDocumentToRLMBSON(_options.projection_bson); -} - -- (id)sort { - return RLMConvertBsonDocumentToRLMBSON(_options.sort_bson); -} - -- (NSArray> *)sorting { - return RLMConvertBsonDocumentToRLMBSONArray(_options.sort_bson); -} - -- (void)setProjection:(id)projection { - if (projection) { - auto bson = realm::bson::BsonDocument(RLMConvertRLMBSONToBson(projection)); - _options.projection_bson = std::optional(bson); - } else { - _options.projection_bson = realm::util::none; - } -} - -- (void)setSort:(id)sort { - if (sort) { - auto bson = realm::bson::BsonDocument(RLMConvertRLMBSONToBson(sort)); - _options.sort_bson = std::optional(bson); - } else { - _options.sort_bson = realm::util::none; - } -} - -- (void)setSorting:(NSArray> *)sorting { - _options.sort_bson = RLMConvertRLMBSONArrayToBsonDocument(sorting); -} - -- (NSInteger)limit { - return static_cast(_options.limit.value_or(0)); -} - -- (void)setLimit:(NSInteger)limit { - _options.limit = std::optional(limit); -} - -@end diff --git a/Realm/RLMFindOptions_Private.hpp b/Realm/RLMFindOptions_Private.hpp deleted file mode 100644 index af1264b5c1..0000000000 --- a/Realm/RLMFindOptions_Private.hpp +++ /dev/null @@ -1,29 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@interface RLMFindOptions () -- (realm::app::MongoCollection::FindOptions)_findOptions; -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMGeospatial.h b/Realm/RLMGeospatial.h index 451ac0c9ad..35d7bfcd32 100644 --- a/Realm/RLMGeospatial.h +++ b/Realm/RLMGeospatial.h @@ -45,7 +45,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability) @warning Altitude is not used in any of the query calculations. */ -RLM_SWIFT_SENDABLE +NS_SWIFT_SENDABLE @interface RLMGeospatialPoint : NSObject /// Latitude in degrees. @property (readonly) double latitude; @@ -83,7 +83,7 @@ Returns `nil` if the values of latitude and longitude are not within the ranges - warning: This class cannot be persisted and can only be use within a geospatial `geoWithin` query. */ -RLM_SWIFT_SENDABLE +NS_SWIFT_SENDABLE @interface RLMGeospatialBox : NSObject /// The bottom left corner of the rectangle. @property (readonly, strong) RLMGeospatialPoint *bottomLeft; @@ -120,7 +120,7 @@ RLM_SWIFT_SENDABLE @warning This class cannot be persisted and can only be use within a geospatial `geoWithin` query. */ -RLM_SWIFT_SENDABLE +NS_SWIFT_SENDABLE @interface RLMGeospatialPolygon : NSObject /// The polygon's external (outer) ring. @property (readonly, strong) NSArray *outerRing; @@ -155,7 +155,7 @@ Returns `nil` if the first and the last `RLMGeospatialPoint` in a polygon are no - warning: This structure cannot be persisted and can only be used to build other geospatial shapes */ -RLM_SWIFT_SENDABLE +NS_SWIFT_SENDABLE @interface RLMDistance : NSObject /// The distance in radians. @property (readonly) double radians; @@ -226,7 +226,7 @@ A class that represents a circle, that can be used in a geospatial `geoWithin`qu @warning This class cannot be persisted and can only be use within a geospatial `geoWithin` query. */ -RLM_SWIFT_SENDABLE +NS_SWIFT_SENDABLE @interface RLMGeospatialCircle : NSObject /// Center of the circle. @property (readonly, strong) RLMGeospatialPoint *center; diff --git a/Realm/RLMInitialSubscriptionsConfiguration.h b/Realm/RLMInitialSubscriptionsConfiguration.h deleted file mode 100644 index e4b37cab18..0000000000 --- a/Realm/RLMInitialSubscriptionsConfiguration.h +++ /dev/null @@ -1,70 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -/** - A block which receives a subscription set instance, that can be used to add an initial set of subscriptions which will be executed - when the Realm is first opened. - */ -RLM_SWIFT_SENDABLE -typedef void(^RLMFlexibleSyncInitialSubscriptionsBlock)(RLMSyncSubscriptionSet * _Nonnull subscriptions); - -/** - A configuration controlling how the initial subscriptions are populated when a Realm file is first opened. - - @see `RLMSubscriptionSet` - */ -RLM_SWIFT_SENDABLE RLM_FINAL // immutable final class -@interface RLMInitialSubscriptionsConfiguration : NSObject - -/** - A callback that's executed in an update block to populate the initial subscriptions for that Realm. - - This callback will only be executed when the Realm is first created, unless `rerunOnOpen` is `true`, in which case it will be executed every time - the Realm is opened. - */ -@property (nonatomic, readonly) RLMFlexibleSyncInitialSubscriptionsBlock callback; - -/** - Controls whether to re-run the `callback` every time the Realm is opened. - */ -@property (nonatomic, readonly) BOOL rerunOnOpen; - -/** - Create a new initial subscriptions configuration. - - @param callback Callback that will be invoked to update the subscriptions for this Realm file when it's first created or every time it's opened if `rerunOnOpen` is `true`. - @param rerunOnOpen A flag controlling whether to run the subscription callback every time the Realm is opened or only the first time. - */ -- (instancetype)initWithCallback:(RLMFlexibleSyncInitialSubscriptionsBlock)callback rerunOnOpen:(BOOL)rerunOnOpen; - - -/** - Create a new initial subscriptions configuration. - - @param callback Callback that will be invoked to update the subscriptions for this Realm file when it's first created. - */ -- (instancetype)initWithCallback:(RLMFlexibleSyncInitialSubscriptionsBlock)callback; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMInitialSubscriptionsConfiguration.m b/Realm/RLMInitialSubscriptionsConfiguration.m deleted file mode 100644 index 0bd9d29702..0000000000 --- a/Realm/RLMInitialSubscriptionsConfiguration.m +++ /dev/null @@ -1,35 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -@implementation RLMInitialSubscriptionsConfiguration - --(id)initWithCallback:(RLMFlexibleSyncInitialSubscriptionsBlock)callback -{ - return [self initWithCallback:callback rerunOnOpen:false]; -} - --(id)initWithCallback:(RLMFlexibleSyncInitialSubscriptionsBlock)callback rerunOnOpen:(BOOL)rerunOnOpen -{ - _callback = callback; - _rerunOnOpen = rerunOnOpen; - return self; -} - -@end diff --git a/Realm/RLMLogger.h b/Realm/RLMLogger.h index 96ab919671..7fea2a964a 100644 --- a/Realm/RLMLogger.h +++ b/Realm/RLMLogger.h @@ -21,7 +21,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability) /// An enum representing different levels of sync-related logging that can be configured. -typedef RLM_CLOSED_ENUM(NSUInteger, RLMLogLevel) { +typedef NS_CLOSED_ENUM(NSUInteger, RLMLogLevel) { /// Nothing will ever be logged. RLMLogLevelOff, /// Only fatal errors will be logged. @@ -52,7 +52,7 @@ typedef RLM_CLOSED_ENUM(NSUInteger, RLMLogLevel) { /// /// The log function may be called from multiple threads simultaneously, and is /// responsible for performing its own synchronization if any is required. -RLM_SWIFT_SENDABLE // invoked on a background thread +NS_SWIFT_SENDABLE // invoked on a background thread typedef void (^RLMLogFunction)(RLMLogLevel level, NSString *message); /** diff --git a/Realm/RLMMongoClient.h b/Realm/RLMMongoClient.h deleted file mode 100644 index 50a1c226dc..0000000000 --- a/Realm/RLMMongoClient.h +++ /dev/null @@ -1,47 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@class RLMApp; - -/// The `RLMMongoClient` enables reading and writing on a MongoDB database via the Realm Cloud service. -/// -/// It provides access to instances of `RLMMongoDatabase`, which in turn provide access to specific -/// `RLMMongoCollection`s that hold your data. -/// -/// - Note: -/// Before you can read or write data, a user must log in. -/// -/// - SeeAlso: -/// `RLMApp`, `RLMMongoDatabase`, `RLMMongoCollection` -RLM_SWIFT_SENDABLE RLM_FINAL // immutable final class -@interface RLMMongoClient : NSObject - -/// The name of the client -@property (nonatomic, readonly) NSString *name; - -/// Gets a `RLMMongoDatabase` instance for the given database name. -/// @param name the name of the database to retrieve -- (RLMMongoDatabase *)databaseWithName:(NSString *)name NS_SWIFT_NAME(database(named:)); - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMMongoClient.mm b/Realm/RLMMongoClient.mm deleted file mode 100644 index 1867a5429e..0000000000 --- a/Realm/RLMMongoClient.mm +++ /dev/null @@ -1,67 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMMongoClient_Private.hpp" - -#import "RLMMongoDatabase_Private.hpp" -#import "RLMMongoCollection_Private.h" -#import "RLMApp_Private.hpp" - -#import -#import -#import - -@implementation RLMMongoClient - -- (instancetype)initWithUser:(RLMUser *)user serviceName:(NSString *)serviceName { - if (self = [super init]) { - _user = user; - _name = serviceName; - } - return self; -} - -- (RLMMongoDatabase *)databaseWithName:(NSString *)name { - return [[RLMMongoDatabase alloc] initWithUser:self.user - serviceName:self.name - databaseName:name]; -} - -@end - -@implementation RLMMongoDatabase - -- (instancetype)initWithUser:(RLMUser *)user - serviceName:(NSString *)serviceName - databaseName:(NSString *)databaseName { - if (self = [super init]) { - _user = user; - _serviceName = serviceName; - _name = databaseName; - } - return self; -} - -- (RLMMongoCollection *)collectionWithName:(NSString *)name { - return [[RLMMongoCollection alloc] initWithUser:self.user - serviceName:self.serviceName - databaseName:self.name - collectionName:name]; -} - -@end diff --git a/Realm/RLMMongoClient_Private.hpp b/Realm/RLMMongoClient_Private.hpp deleted file mode 100644 index e5c4e76db7..0000000000 --- a/Realm/RLMMongoClient_Private.hpp +++ /dev/null @@ -1,34 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@class RLMUser; - -@interface RLMMongoClient () - -@property (nonatomic, strong) RLMUser *user; - -- (instancetype)initWithUser:(RLMUser *)user serviceName:(NSString *)serviceName; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) - diff --git a/Realm/RLMMongoCollection.h b/Realm/RLMMongoCollection.h deleted file mode 100644 index 0feae60acb..0000000000 --- a/Realm/RLMMongoCollection.h +++ /dev/null @@ -1,330 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) -@protocol RLMBSON; - -@class RLMFindOptions, RLMFindOneAndModifyOptions, RLMUpdateResult, RLMChangeStream, RLMObjectId; - -/// Delegate which is used for subscribing to changes on a `[RLMMongoCollection watch]` stream. -@protocol RLMChangeEventDelegate -/// The stream was opened. -/// @param changeStream The RLMChangeStream subscribing to the stream changes. -- (void)changeStreamDidOpen:(RLMChangeStream *)changeStream; -/// The stream has been closed. -/// @param error If an error occured when closing the stream, an error will be passed. -- (void)changeStreamDidCloseWithError:(nullable NSError *)error; -/// A error has occured while streaming. -/// @param error The streaming error. -- (void)changeStreamDidReceiveError:(NSError *)error; -/// Invoked when a change event has been received. -/// @param changeEvent The change event in BSON format. -- (void)changeStreamDidReceiveChangeEvent:(id)changeEvent; -@end - -/// Acts as a middleman and processes events with WatchStream -RLM_SWIFT_SENDABLE RLM_FINAL // is internally thread-safe -@interface RLMChangeStream : NSObject -/// Stops a watch streaming session. -- (void)close; -/// :nodoc: -- (instancetype)init NS_UNAVAILABLE; -@end - -/// The `RLMMongoCollection` represents a MongoDB collection. -/// -/// You can get an instance from a `RLMMongoDatabase`. -/// -/// Create, read, update, and delete methods are available. -/// -/// Operations against the Realm Cloud server are performed asynchronously. -/// -/// - Note: -/// Before you can read or write data, a user must log in. -/// - Usage: -/// RLMMongoClient *client = [self.app mongoClient:@"mongodb1"]; -/// RLMMongoDatabase *database = [client databaseWithName:@"test_data"]; -/// RLMMongoCollection *collection = [database collectionWithName:@"Dog"]; -/// [collection insertOneDocument:@{@"name": @"fido", @"breed": @"cane corso"} completion:...]; -/// -/// - SeeAlso: -/// `RLMMongoClient`, `RLMMongoDatabase` -RLM_SWIFT_SENDABLE RLM_FINAL // is internally thread-safe -@interface RLMMongoCollection : NSObject -/// Block which returns an object id on a successful insert, or an error should one occur. -RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void(^RLMMongoInsertBlock)(id _Nullable, NSError * _Nullable); -/// Block which returns an array of object ids on a successful insertMany, or an error should one occur. -RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void(^RLMMongoInsertManyBlock)(NSArray> * _Nullable, NSError * _Nullable); -/// Block which returns an array of Documents on a successful find operation, or an error should one occur. -RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void(^RLMMongoFindBlock)(NSArray> *> * _Nullable, - NSError * _Nullable); -/// Block which returns a Document on a successful findOne operation, or an error should one occur. -RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void(^RLMMongoFindOneBlock)(NSDictionary> * _Nullable_result, - NSError * _Nullable); -/// Block which returns the number of Documents in a collection on a successful count operation, or an error should one occur. -RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void(^RLMMongoCountBlock)(NSInteger, NSError * _Nullable); -/// Block which returns an RLMUpdateResult on a successful update operation, or an error should one occur. -RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void(^RLMMongoUpdateBlock)(RLMUpdateResult * _Nullable, NSError * _Nullable); -/// Block which returns the deleted Document on a successful delete operation, or an error should one occur. -RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void(^RLMMongoDeleteBlock)(NSDictionary> * _Nullable_result, - NSError * _Nullable); - -/// The name of this mongodb collection. -@property (nonatomic, readonly) NSString *name; - -/// Encodes the provided value to BSON and inserts it. If the value is missing an identifier, one will be -/// generated for it. -/// @param document A `Document` value to insert. -/// @param completion The result of attempting to perform the insert. An Id will be returned for the inserted object on sucess -- (void)insertOneDocument:(NSDictionary> *)document - completion:(RLMMongoInsertBlock)completion NS_REFINED_FOR_SWIFT; - -/// Encodes the provided values to BSON and inserts them. If any values are missing identifiers, -/// they will be generated. -/// @param documents The `Document` values in a bson array to insert. -/// @param completion The result of the insert, returns an array inserted document ids in order -- (void)insertManyDocuments:(NSArray> *> *)documents - completion:(RLMMongoInsertManyBlock)completion NS_REFINED_FOR_SWIFT; - -/// Finds the documents in this collection which match the provided filter. -/// @param filterDocument A `Document` as bson that should match the query. -/// @param options `RLMFindOptions` to use when executing the command. -/// @param completion The resulting bson array of documents or error if one occurs -- (void)findWhere:(NSDictionary> *)filterDocument - options:(RLMFindOptions *)options - completion:(RLMMongoFindBlock)completion NS_REFINED_FOR_SWIFT; - -/// Finds the documents in this collection which match the provided filter. -/// @param filterDocument A `Document` as bson that should match the query. -/// @param completion The resulting bson array as a string or error if one occurs -- (void)findWhere:(NSDictionary> *)filterDocument - completion:(RLMMongoFindBlock)completion NS_REFINED_FOR_SWIFT; - -/// Returns one document from a collection or view which matches the -/// provided filter. If multiple documents satisfy the query, this method -/// returns the first document according to the query's sort order or natural -/// order. -/// @param filterDocument A `Document` as bson that should match the query. -/// @param options `RLMFindOptions` to use when executing the command. -/// @param completion The resulting bson or error if one occurs -- (void)findOneDocumentWhere:(NSDictionary> *)filterDocument - options:(RLMFindOptions *)options - completion:(RLMMongoFindOneBlock)completion NS_REFINED_FOR_SWIFT; - -/// Returns one document from a collection or view which matches the -/// provided filter. If multiple documents satisfy the query, this method -/// returns the first document according to the query's sort order or natural -/// order. -/// @param filterDocument A `Document` as bson that should match the query. -/// @param completion The resulting bson or error if one occurs -- (void)findOneDocumentWhere:(NSDictionary> *)filterDocument - completion:(RLMMongoFindOneBlock)completion NS_REFINED_FOR_SWIFT; - -/// Runs an aggregation framework pipeline against this collection. -/// @param pipeline A bson array made up of `Documents` containing the pipeline of aggregation operations to perform. -/// @param completion The resulting bson array of documents or error if one occurs -- (void)aggregateWithPipeline:(NSArray> *> *)pipeline - completion:(RLMMongoFindBlock)completion NS_REFINED_FOR_SWIFT; - -/// Counts the number of documents in this collection matching the provided filter. -/// @param filterDocument A `Document` as bson that should match the query. -/// @param limit The max amount of documents to count -/// @param completion Returns the count of the documents that matched the filter. -- (void)countWhere:(NSDictionary> *)filterDocument - limit:(NSInteger)limit - completion:(RLMMongoCountBlock)completion NS_REFINED_FOR_SWIFT; - -/// Counts the number of documents in this collection matching the provided filter. -/// @param filterDocument A `Document` as bson that should match the query. -/// @param completion Returns the count of the documents that matched the filter. -- (void)countWhere:(NSDictionary> *)filterDocument - completion:(RLMMongoCountBlock)completion NS_REFINED_FOR_SWIFT; - -/// Deletes a single matching document from the collection. -/// @param filterDocument A `Document` as bson that should match the query. -/// @param completion The result of performing the deletion. Returns the count of deleted objects -- (void)deleteOneDocumentWhere:(NSDictionary> *)filterDocument - completion:(RLMMongoCountBlock)completion NS_REFINED_FOR_SWIFT; - -/// Deletes multiple documents -/// @param filterDocument Document representing the match criteria -/// @param completion The result of performing the deletion. Returns the count of the deletion -- (void)deleteManyDocumentsWhere:(NSDictionary> *)filterDocument - completion:(RLMMongoCountBlock)completion NS_REFINED_FOR_SWIFT; - -/// Updates a single document matching the provided filter in this collection. -/// @param filterDocument A bson `Document` representing the match criteria. -/// @param updateDocument A bson `Document` representing the update to be applied to a matching document. -/// @param upsert When true, creates a new document if no document matches the query. -/// @param completion The result of the attempt to update a document. -- (void)updateOneDocumentWhere:(NSDictionary> *)filterDocument - updateDocument:(NSDictionary> *)updateDocument - upsert:(BOOL)upsert - completion:(RLMMongoUpdateBlock)completion NS_REFINED_FOR_SWIFT; - -/// Updates a single document matching the provided filter in this collection. -/// @param filterDocument A bson `Document` representing the match criteria. -/// @param updateDocument A bson `Document` representing the update to be applied to a matching document. -/// @param completion The result of the attempt to update a document. -- (void)updateOneDocumentWhere:(NSDictionary> *)filterDocument - updateDocument:(NSDictionary> *)updateDocument - completion:(RLMMongoUpdateBlock)completion NS_REFINED_FOR_SWIFT; - -/// Updates multiple documents matching the provided filter in this collection. -/// @param filterDocument A bson `Document` representing the match criteria. -/// @param updateDocument A bson `Document` representing the update to be applied to a matching document. -/// @param upsert When true, creates a new document if no document matches the query. -/// @param completion The result of the attempt to update a document. -- (void)updateManyDocumentsWhere:(NSDictionary> *)filterDocument - updateDocument:(NSDictionary> *)updateDocument - upsert:(BOOL)upsert - completion:(RLMMongoUpdateBlock)completion NS_REFINED_FOR_SWIFT; - -/// Updates multiple documents matching the provided filter in this collection. -/// @param filterDocument A bson `Document` representing the match criteria. -/// @param updateDocument A bson `Document` representing the update to be applied to a matching document. -/// @param completion The result of the attempt to update a document. -- (void)updateManyDocumentsWhere:(NSDictionary> *)filterDocument - updateDocument:(NSDictionary> *)updateDocument - completion:(RLMMongoUpdateBlock)completion NS_REFINED_FOR_SWIFT; - -/// Updates a single document in a collection based on a query filter and -/// returns the document in either its pre-update or post-update form. Unlike -/// `updateOneDocument`, this action allows you to atomically find, update, and -/// return a document with the same command. This avoids the risk of other -/// update operations changing the document between separate find and update -/// operations. -/// @param filterDocument A bson `Document` representing the match criteria. -/// @param updateDocument A bson `Document` representing the update to be applied to a matching document. -/// @param options `RemoteFindOneAndModifyOptions` to use when executing the command. -/// @param completion The result of the attempt to update a document. -- (void)findOneAndUpdateWhere:(NSDictionary> *)filterDocument - updateDocument:(NSDictionary> *)updateDocument - options:(RLMFindOneAndModifyOptions *)options - completion:(RLMMongoFindOneBlock)completion NS_REFINED_FOR_SWIFT; - -/// Updates a single document in a collection based on a query filter and -/// returns the document in either its pre-update or post-update form. Unlike -/// `updateOneDocument`, this action allows you to atomically find, update, and -/// return a document with the same command. This avoids the risk of other -/// update operations changing the document between separate find and update -/// operations. -/// @param filterDocument A bson `Document` representing the match criteria. -/// @param updateDocument A bson `Document` representing the update to be applied to a matching document. -/// @param completion The result of the attempt to update a document. -- (void)findOneAndUpdateWhere:(NSDictionary> *)filterDocument - updateDocument:(NSDictionary> *)updateDocument - completion:(RLMMongoFindOneBlock)completion NS_REFINED_FOR_SWIFT; - -/// Overwrites a single document in a collection based on a query filter and -/// returns the document in either its pre-replacement or post-replacement -/// form. Unlike `updateOneDocument`, this action allows you to atomically find, -/// replace, and return a document with the same command. This avoids the -/// risk of other update operations changing the document between separate -/// find and update operations. -/// @param filterDocument A `Document` that should match the query. -/// @param replacementDocument A `Document` describing the replacement. -/// @param options `RLMFindOneAndModifyOptions` to use when executing the command. -/// @param completion The result of the attempt to replace a document. -- (void)findOneAndReplaceWhere:(NSDictionary> *)filterDocument - replacementDocument:(NSDictionary> *)replacementDocument - options:(RLMFindOneAndModifyOptions *)options - completion:(RLMMongoFindOneBlock)completion NS_REFINED_FOR_SWIFT; - -/// Overwrites a single document in a collection based on a query filter and -/// returns the document in either its pre-replacement or post-replacement -/// form. Unlike `updateOneDocument`, this action allows you to atomically find, -/// replace, and return a document with the same command. This avoids the -/// risk of other update operations changing the document between separate -/// find and update operations. -/// @param filterDocument A `Document` that should match the query. -/// @param replacementDocument A `Document` describing the update. -/// @param completion The result of the attempt to replace a document. -- (void)findOneAndReplaceWhere:(NSDictionary> *)filterDocument - replacementDocument:(NSDictionary> *)replacementDocument - completion:(RLMMongoFindOneBlock)completion NS_REFINED_FOR_SWIFT; - -/// Removes a single document from a collection based on a query filter and -/// returns a document with the same form as the document immediately before -/// it was deleted. Unlike `deleteOneDocument`, this action allows you to atomically -/// find and delete a document with the same command. This avoids the risk of -/// other update operations changing the document between separate find and -/// delete operations. -/// @param filterDocument A `Document` that should match the query. -/// @param options `RLMFindOneAndModifyOptions` to use when executing the command. -/// @param completion The result of the attempt to delete a document. -- (void)findOneAndDeleteWhere:(NSDictionary> *)filterDocument - options:(RLMFindOneAndModifyOptions *)options - completion:(RLMMongoDeleteBlock)completion NS_REFINED_FOR_SWIFT; - -/// Removes a single document from a collection based on a query filter and -/// returns a document with the same form as the document immediately before -/// it was deleted. Unlike `deleteOneDocument`, this action allows you to atomically -/// find and delete a document with the same command. This avoids the risk of -/// other update operations changing the document between separate find and -/// delete operations. -/// @param filterDocument A `Document` that should match the query. -/// @param completion The result of the attempt to delete a document. -- (void)findOneAndDeleteWhere:(NSDictionary> *)filterDocument - completion:(RLMMongoDeleteBlock)completion NS_REFINED_FOR_SWIFT; - -/// Opens a MongoDB change stream against the collection to watch for changes. The resulting stream will be notified -/// of all events on this collection that the active user is authorized to see based on the configured MongoDB -/// rules. -/// @param delegate The delegate that will react to events and errors from the resulting change stream. -/// @param queue Dispatches streaming events to an optional queue, if no queue is provided the main queue is used -- (RLMChangeStream *)watchWithDelegate:(id)delegate - delegateQueue:(nullable dispatch_queue_t)queue NS_REFINED_FOR_SWIFT; - -/// Opens a MongoDB change stream against the collection to watch for changes -/// made to specific documents. The documents to watch must be explicitly -/// specified by their _id. -/// @param filterIds The list of _ids in the collection to watch. -/// @param delegate The delegate that will react to events and errors from the resulting change stream. -/// @param queue Dispatches streaming events to an optional queue, if no queue is provided the main queue is used -- (RLMChangeStream *)watchWithFilterIds:(NSArray *)filterIds - delegate:(id)delegate - delegateQueue:(nullable dispatch_queue_t)queue NS_REFINED_FOR_SWIFT; - -/// Opens a MongoDB change stream against the collection to watch for changes. The provided BSON document will be -/// used as a match expression filter on the change events coming from the stream. -/// -/// See https://docs.mongodb.com/manual/reference/operator/aggregation/match/ for documentation around how to define -/// a match filter. -/// -/// Defining the match expression to filter ChangeEvents is similar to defining the match expression for triggers: -/// https://docs.mongodb.com/realm/triggers/database-triggers/ -/// @param matchFilter The $match filter to apply to incoming change events -/// @param delegate The delegate that will react to events and errors from the resulting change stream. -/// @param queue Dispatches streaming events to an optional queue, if no queue is provided the main queue is used -- (RLMChangeStream *)watchWithMatchFilter:(NSDictionary> *)matchFilter - delegate:(id)delegate - delegateQueue:(nullable dispatch_queue_t)queue NS_REFINED_FOR_SWIFT; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMMongoCollection.mm b/Realm/RLMMongoCollection.mm deleted file mode 100644 index 5e5d98426c..0000000000 --- a/Realm/RLMMongoCollection.mm +++ /dev/null @@ -1,445 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMMongoCollection_Private.h" - -#import "RLMApp_Private.hpp" -#import "RLMBSON_Private.hpp" -#import "RLMError_Private.hpp" -#import "RLMFindOneAndModifyOptions_Private.hpp" -#import "RLMFindOptions_Private.hpp" -#import "RLMNetworkTransport_Private.hpp" -#import "RLMUpdateResult_Private.hpp" -#import "RLMUser_Private.hpp" - -#import -#import -#import -#import - -__attribute__((objc_direct_members)) -@implementation RLMChangeStream { -@public - realm::app::WatchStream _watchStream; - id _subscriber; - __weak NSURLSession *_session; - void (^_schedule)(dispatch_block_t); -} - -- (instancetype)initWithChangeEventSubscriber:(id)subscriber - scheduler:(void (^)(dispatch_block_t))scheduler { - if (self = [super init]) { - _subscriber = subscriber; - _schedule = scheduler; - } - return self; -} - -- (void)didCloseWithError:(NSError *)error { - _schedule(^{ - [_subscriber changeStreamDidCloseWithError:error]; - }); -} - -- (void)didOpen { - _schedule(^{ - [_subscriber changeStreamDidOpen:self]; - }); -} - -- (void)didReceiveError:(nonnull NSError *)error { - _schedule(^{ - [_subscriber changeStreamDidReceiveError:error]; - }); -} - -- (void)didReceiveEvent:(nonnull NSData *)event { - if (_watchStream.state() == realm::app::WatchStream::State::NEED_DATA) { - [event enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *) { - _watchStream.feed_buffer(std::string_view(static_cast(bytes), byteRange.length)); - }]; - } - - while (_watchStream.state() == realm::app::WatchStream::State::HAVE_EVENT) { - id event = RLMConvertBsonToRLMBSON(_watchStream.next_event()); - _schedule(^{ - [_subscriber changeStreamDidReceiveChangeEvent:event]; - }); - } - - if (_watchStream.state() == realm::app::WatchStream::State::HAVE_ERROR) { - [self didReceiveError:makeError(_watchStream.error())]; - } -} - -- (void)attachURLSession:(NSURLSession *)urlSession { - _session = urlSession; -} - -- (void)close { - [_session invalidateAndCancel]; -} -@end - -static realm::bson::BsonDocument toBsonDocument(id bson) { - return realm::bson::BsonDocument(RLMConvertRLMBSONToBson(bson)); -} -static realm::bson::BsonArray toBsonArray(id bson) { - return realm::bson::BsonArray(RLMConvertRLMBSONToBson(bson)); -} - -__attribute__((objc_direct_members)) -@interface RLMMongoCollection () -@property (nonatomic, strong) RLMUser *user; -@property (nonatomic, strong) NSString *serviceName; -@property (nonatomic, strong) NSString *databaseName; -@end - -__attribute__((objc_direct_members)) -@implementation RLMMongoCollection -- (instancetype)initWithUser:(RLMUser *)user - serviceName:(NSString *)serviceName - databaseName:(NSString *)databaseName - collectionName:(NSString *)collectionName { - if (self = [super init]) { - _user = user; - _serviceName = serviceName; - _databaseName = databaseName; - _name = collectionName; - } - return self; -} - -- (realm::app::MongoCollection)collection:(NSString *)name { - return _user.user->mongo_client(self.serviceName.UTF8String) - .db(self.databaseName.UTF8String).collection(name.UTF8String); -} - -- (realm::app::MongoCollection)collection { - return [self collection:self.name]; -} - -- (void)findWhere:(NSDictionary> *)document - options:(RLMFindOptions *)options - completion:(RLMMongoFindBlock)completion { - self.collection.find(toBsonDocument(document), [options _findOptions], - [completion](std::optional documents, - std::optional error) { - if (error) { - return completion(nil, makeError(*error)); - } - completion((NSArray> *> *)RLMConvertBsonToRLMBSON(*documents), nil); - }); -} - -- (void)findWhere:(NSDictionary> *)document - completion:(RLMMongoFindBlock)completion { - [self findWhere:document options:[[RLMFindOptions alloc] init] completion:completion]; -} - -- (void)findOneDocumentWhere:(NSDictionary> *)document - options:(RLMFindOptions *)options - completion:(RLMMongoFindOneBlock)completion { - self.collection.find_one(toBsonDocument(document), [options _findOptions], - [completion](std::optional document, - std::optional error) { - if (error) { - return completion(nil, makeError(*error)); - } - if (document) { - completion((NSDictionary> *)RLMConvertBsonToRLMBSON(*document), nil); - } else { - completion(nil, nil); - } - }); -} - -- (void)findOneDocumentWhere:(NSDictionary> *)document - completion:(RLMMongoFindOneBlock)completion { - [self findOneDocumentWhere:document options:[[RLMFindOptions alloc] init] completion:completion]; -} - -- (void)insertOneDocument:(NSDictionary> *)document - completion:(RLMMongoInsertBlock)completion { - self.collection.insert_one(toBsonDocument(document), - [completion](std::optional objectId, - std::optional error) { - if (error) { - return completion(nil, makeError(*error)); - } - completion(RLMConvertBsonToRLMBSON(*objectId), nil); - }); -} - -- (void)insertManyDocuments:(NSArray> *> *)documents - completion:(RLMMongoInsertManyBlock)completion { - self.collection.insert_many(toBsonArray(documents), - [completion](realm::bson::BsonArray insertedIds, - std::optional error) { - if (error) { - return completion(nil, makeError(*error)); - } - NSMutableArray *insertedArr = [[NSMutableArray alloc] initWithCapacity:insertedIds.size()]; - for (auto& objectId : insertedIds) { - [insertedArr addObject:RLMConvertBsonToRLMBSON(objectId)]; - } - completion(insertedArr, nil); - }); -} - -- (void)aggregateWithPipeline:(NSArray> *> *)pipeline - completion:(RLMMongoFindBlock)completion { - self.collection.aggregate(toBsonArray(pipeline), - [completion](std::optional documents, - std::optional error) { - if (error) { - return completion(nil, makeError(*error)); - } - completion((NSArray *)RLMConvertBsonToRLMBSON(*documents), nil); - }); -} - -- (void)countWhere:(NSDictionary> *)document - limit:(NSInteger)limit - completion:(RLMMongoCountBlock)completion { - self.collection.count_bson(toBsonDocument(document), limit, - [completion](std::optional&& value, - std::optional&& error) { - if (error) { - return completion(0, makeError(*error)); - } - if (value->type() == realm::bson::Bson::Type::Int64) { - return completion(static_cast(static_cast(*value)), nil); - } - // If the collection does not exist the call returns undefined - return completion(0, nil); - }); -} - -- (void)countWhere:(NSDictionary> *)document - completion:(RLMMongoCountBlock)completion { - [self countWhere:document limit:0 completion:completion]; -} - -- (void)deleteOneDocumentWhere:(NSDictionary> *)document - completion:(RLMMongoCountBlock)completion { - self.collection.delete_one(toBsonDocument(document), - [completion](uint64_t count, - std::optional error) { - if (error) { - return completion(0, makeError(*error)); - } - completion(static_cast(count), nil); - }); -} - -- (void)deleteManyDocumentsWhere:(NSDictionary> *)document - completion:(RLMMongoCountBlock)completion { - self.collection.delete_many(toBsonDocument(document), - [completion](uint64_t count, - std::optional error) { - if (error) { - return completion(0, makeError(*error)); - } - completion(static_cast(count), nil); - }); -} - -- (void)updateOneDocumentWhere:(NSDictionary> *)filterDocument - updateDocument:(NSDictionary> *)updateDocument - upsert:(BOOL)upsert - completion:(RLMMongoUpdateBlock)completion { - self.collection.update_one(toBsonDocument(filterDocument), toBsonDocument(updateDocument), - upsert, - [completion](realm::app::MongoCollection::UpdateResult result, - std::optional error) { - if (error) { - return completion(nil, makeError(*error)); - } - completion([[RLMUpdateResult alloc] initWithUpdateResult:result], nil); - }); -} - -- (void)updateOneDocumentWhere:(NSDictionary> *)filterDocument - updateDocument:(NSDictionary> *)updateDocument - completion:(RLMMongoUpdateBlock)completion { - [self updateOneDocumentWhere:filterDocument - updateDocument:updateDocument - upsert:NO - completion:completion]; -} - -- (void)updateManyDocumentsWhere:(NSDictionary> *)filterDocument - updateDocument:(NSDictionary> *)updateDocument - upsert:(BOOL)upsert - completion:(RLMMongoUpdateBlock)completion { - self.collection.update_many(toBsonDocument(filterDocument), toBsonDocument(updateDocument), - upsert, - [completion](realm::app::MongoCollection::UpdateResult result, - std::optional error) { - if (error) { - return completion(nil, makeError(*error)); - } - completion([[RLMUpdateResult alloc] initWithUpdateResult:result], nil); - }); -} - -- (void)updateManyDocumentsWhere:(NSDictionary> *)filterDocument - updateDocument:(NSDictionary> *)updateDocument - completion:(RLMMongoUpdateBlock)completion { - [self updateManyDocumentsWhere:filterDocument - updateDocument:updateDocument - upsert:NO - completion:completion]; -} - -- (void)findOneAndUpdateWhere:(NSDictionary> *)filterDocument - updateDocument:(NSDictionary> *)updateDocument - options:(RLMFindOneAndModifyOptions *)options - completion:(RLMMongoFindOneBlock)completion { - self.collection.find_one_and_update(toBsonDocument(filterDocument), toBsonDocument(updateDocument), - [options _findOneAndModifyOptions], - [completion](std::optional document, - std::optional error) { - if (error) { - return completion(nil, makeError(*error)); - } - - return completion((NSDictionary *)RLMConvertBsonDocumentToRLMBSON(document), nil); - }); -} - -- (void)findOneAndUpdateWhere:(NSDictionary> *)filterDocument - updateDocument:(NSDictionary> *)updateDocument - completion:(RLMMongoFindOneBlock)completion { - [self findOneAndUpdateWhere:filterDocument - updateDocument:updateDocument - options:[[RLMFindOneAndModifyOptions alloc] init] - completion:completion]; -} - -- (void)findOneAndReplaceWhere:(NSDictionary> *)filterDocument - replacementDocument:(NSDictionary> *)replacementDocument - options:(RLMFindOneAndModifyOptions *)options - completion:(RLMMongoFindOneBlock)completion { - self.collection.find_one_and_replace(toBsonDocument(filterDocument), toBsonDocument(replacementDocument), - [options _findOneAndModifyOptions], - [completion](std::optional document, - std::optional error) { - if (error) { - return completion(nil, makeError(*error)); - } - - return completion((NSDictionary *)RLMConvertBsonDocumentToRLMBSON(document), nil); - }); -} - -- (void)findOneAndReplaceWhere:(NSDictionary> *)filterDocument - replacementDocument:(NSDictionary> *)replacementDocument - completion:(RLMMongoFindOneBlock)completion { - [self findOneAndReplaceWhere:filterDocument - replacementDocument:replacementDocument - options:[[RLMFindOneAndModifyOptions alloc] init] - completion:completion]; -} - -- (void)findOneAndDeleteWhere:(NSDictionary> *)filterDocument - options:(RLMFindOneAndModifyOptions *)options - completion:(RLMMongoDeleteBlock)completion { - self.collection.find_one_and_delete(toBsonDocument(filterDocument), - [options _findOneAndModifyOptions], - [completion](std::optional document, - std::optional error) { - if (error) { - return completion(nil, makeError(*error)); - } - - return completion((NSDictionary *)RLMConvertBsonDocumentToRLMBSON(document), nil); - }); -} - -- (void)findOneAndDeleteWhere:(NSDictionary> *)filterDocument - completion:(RLMMongoDeleteBlock)completion { - [self findOneAndDeleteWhere:filterDocument - options:[[RLMFindOneAndModifyOptions alloc] init] - completion:completion]; -} - -- (RLMChangeStream *)watchWithDelegate:(id)delegate - delegateQueue:(nullable dispatch_queue_t)delegateQueue { - return [self watchWithMatchFilter:nil - idFilter:nil - delegate:delegate - delegateQueue:delegateQueue]; -} - -- (RLMChangeStream *)watchWithFilterIds:(NSArray *)filterIds - delegate:(id)delegate - delegateQueue:(nullable dispatch_queue_t)delegateQueue { - return [self watchWithMatchFilter:nil - idFilter:filterIds - delegate:delegate - delegateQueue:delegateQueue]; -} - -- (RLMChangeStream *)watchWithMatchFilter:(NSDictionary> *)matchFilter - delegate:(id)delegate - delegateQueue:(nullable dispatch_queue_t)delegateQueue { - return [self watchWithMatchFilter:matchFilter - idFilter:nil - delegate:delegate - delegateQueue:delegateQueue]; -} - -- (RLMChangeStream *)watchWithMatchFilter:(nullable id)matchFilter - idFilter:(nullable id)idFilter - delegate:(id)delegate - delegateQueue:(nullable dispatch_queue_t)queue { - queue = queue ?: dispatch_get_main_queue(); - return [self watchWithMatchFilter:matchFilter - idFilter:idFilter - delegate:delegate - scheduler:^(dispatch_block_t block) { dispatch_async(queue, block); }]; -} - -- (RLMChangeStream *)watchWithMatchFilter:(nullable id)matchFilter - idFilter:(nullable id)idFilter - delegate:(id)delegate - scheduler:(void (^)(dispatch_block_t))scheduler { - realm::bson::BsonDocument baseArgs = { - {"database", self.databaseName.UTF8String}, - {"collection", self.name.UTF8String} - }; - - if (matchFilter) { - baseArgs["filter"] = RLMConvertRLMBSONToBson(matchFilter); - } - if (idFilter) { - baseArgs["ids"] = RLMConvertRLMBSONToBson(idFilter); - } - auto args = realm::bson::BsonArray{baseArgs}; - auto app = self.user.user->app(); - auto request = app->make_streaming_request(app->current_user(), "watch", args, - std::optional(self.serviceName.UTF8String)); - auto changeStream = [[RLMChangeStream alloc] initWithChangeEventSubscriber:delegate scheduler:scheduler]; - RLMNetworkTransport *transport = self.user.app.configuration.transport; - RLMRequest *rlmRequest = RLMRequestFromRequest(request); - changeStream->_session = [transport doStreamRequest:rlmRequest eventSubscriber:changeStream]; - return changeStream; -} -@end diff --git a/Realm/RLMMongoCollection_Private.h b/Realm/RLMMongoCollection_Private.h deleted file mode 100644 index 77bbf9752a..0000000000 --- a/Realm/RLMMongoCollection_Private.h +++ /dev/null @@ -1,37 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability) - -@class RLMUser; - -@interface RLMMongoCollection () -- (instancetype)initWithUser:(RLMUser *)user - serviceName:(NSString *)serviceName - databaseName:(NSString *)databaseName - collectionName:(NSString *)collectionName; - -- (RLMChangeStream *)watchWithMatchFilter:(nullable id)matchFilter - idFilter:(nullable id)idFilter - delegate:(id)delegate - scheduler:(void (^)(dispatch_block_t))scheduler; -@end - -RLM_HEADER_AUDIT_END(nullability) diff --git a/Realm/RLMMongoDatabase.h b/Realm/RLMMongoDatabase.h deleted file mode 100644 index 177fbc1b8c..0000000000 --- a/Realm/RLMMongoDatabase.h +++ /dev/null @@ -1,51 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@class RLMMongoCollection; - -/// The `RLMMongoDatabase` represents a MongoDB database, which holds a group -/// of collections that contain your data. -/// -/// It can be retrieved from the `RLMMongoClient`. -/// -/// Use it to get `RLMMongoCollection`s for reading and writing data. -/// -/// - Note: -/// Before you can read or write data, a user must log in`. -/// -/// - SeeAlso: -/// `RLMMongoClient`, `RLMMongoCollection` -RLM_SWIFT_SENDABLE RLM_FINAL // immutable final class -@interface RLMMongoDatabase : NSObject - -/// The name of this database -@property (nonatomic, readonly) NSString *name; - -/// Gets a collection. -/// @param name The name of the collection to return -/// @returns The collection -- (RLMMongoCollection *)collectionWithName:(NSString *)name; -// NEXT-MAJOR: NS_SWIFT_NAME(collection(named:)) - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMMongoDatabase_Private.hpp b/Realm/RLMMongoDatabase_Private.hpp deleted file mode 100644 index c22241d890..0000000000 --- a/Realm/RLMMongoDatabase_Private.hpp +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@class RLMUser; - -@interface RLMMongoDatabase () - -@property (nonatomic, strong) RLMUser *user; -@property (nonatomic, strong) NSString *serviceName; - -- (instancetype)initWithUser:(RLMUser *)user - serviceName:(NSString *)serviceName - databaseName:(NSString *)databaseName; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMNetworkTransport.h b/Realm/RLMNetworkTransport.h deleted file mode 100644 index 2884b42966..0000000000 --- a/Realm/RLMNetworkTransport.h +++ /dev/null @@ -1,132 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -/// Allowed HTTP methods to be used with `RLMNetworkTransport`. -typedef RLM_CLOSED_ENUM(int32_t, RLMHTTPMethod) { - /// GET is used to request data from a specified resource. - RLMHTTPMethodGET = 0, - /// POST is used to send data to a server to create/update a resource. - RLMHTTPMethodPOST = 1, - /// PATCH is used to send data to a server to update a resource. - RLMHTTPMethodPATCH = 2, - /// PUT is used to send data to a server to create/update a resource. - RLMHTTPMethodPUT = 3, - /// The DELETE method deletes the specified resource. - RLMHTTPMethodDELETE = 4 -}; - -/// An HTTP request that can be made to an arbitrary server. -@interface RLMRequest : NSObject - -/// The HTTP method of this request. -@property (nonatomic, assign) RLMHTTPMethod method; - -/// The URL to which this request will be made. -@property (nonatomic, strong) NSString *url; - -/// The number of milliseconds that the underlying transport should spend on an -/// HTTP round trip before failing with an error. -@property (nonatomic, assign) NSTimeInterval timeout; - -/// The HTTP headers of this request. -@property (nonatomic, strong) NSDictionary* headers; - -/// The body of the request. -@property (nonatomic, strong) NSString* body; - -@end - -/// The contents of an HTTP response. -@interface RLMResponse : NSObject - -/// The status code of the HTTP response. -@property (nonatomic, assign) NSInteger httpStatusCode; - -/// A custom status code provided by the SDK. -@property (nonatomic, assign) NSInteger customStatusCode; - -/// The headers of the HTTP response. -@property (nonatomic, strong) NSDictionary* headers; - -/// The body of the HTTP response. -@property (nonatomic, strong) NSString *body; - -@end - -/// Delegate which is used for subscribing to changes. -@protocol RLMEventDelegate -/// Invoked when a change event has been received. -/// @param event The change event encoded as NSData -- (void)didReceiveEvent:(NSData *)event; -/// A error has occurred while subscribing to changes. -/// @param error The error that has occurred. -- (void)didReceiveError:(NSError *)error; -/// The stream was opened. -- (void)didOpen; -/// The stream has been closed. -/// @param error The error that has occurred. -- (void)didCloseWithError:(NSError *_Nullable)error; -@end - -/// A block for receiving an `RLMResponse` from the `RLMNetworkTransport`. -RLM_SWIFT_SENDABLE // invoked on a backgroun thread -typedef void(^RLMNetworkTransportCompletionBlock)(RLMResponse *); - -/// Transporting protocol for foreign interfaces. Allows for custom -/// request/response handling. -RLM_SWIFT_SENDABLE // used from multiple threads so must be internally thread-safe -@protocol RLMNetworkTransport - -/** - Sends a request to a given endpoint. - - @param request The request to send. - @param completionBlock A callback invoked on completion of the request. -*/ -- (void)sendRequestToServer:(RLMRequest *)request - completion:(RLMNetworkTransportCompletionBlock)completionBlock; - -/// Starts an event stream request. -/// @param request The RLMRequest to start. -/// @param subscriber The RLMEventDelegate which will subscribe to changes from the server. -- (NSURLSession *)doStreamRequest:(RLMRequest *)request - eventSubscriber:(id)subscriber; - -@end - -/// Transporting protocol for foreign interfaces. Allows for custom -/// request/response handling. -RLM_SWIFT_SENDABLE // is internally thread-safe -@interface RLMNetworkTransport : NSObject - -/** - Sends a request to a given endpoint. - - @param request The request to send. - @param completionBlock A callback invoked on completion of the request. -*/ -- (void)sendRequestToServer:(RLMRequest *) request - completion:(RLMNetworkTransportCompletionBlock)completionBlock; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMNetworkTransport.mm b/Realm/RLMNetworkTransport.mm deleted file mode 100644 index d1af28e9d0..0000000000 --- a/Realm/RLMNetworkTransport.mm +++ /dev/null @@ -1,240 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMNetworkTransport_Private.hpp" - -#import "RLMApp.h" -#import "RLMError.h" -#import "RLMRealmConfiguration.h" -#import "RLMSyncUtil_Private.hpp" -#import "RLMSyncManager_Private.hpp" -#import "RLMUtil.hpp" - -#import -#import - -using namespace realm; - -static_assert((int)RLMHTTPMethodGET == (int)app::HttpMethod::get); -static_assert((int)RLMHTTPMethodPOST == (int)app::HttpMethod::post); -static_assert((int)RLMHTTPMethodPUT == (int)app::HttpMethod::put); -static_assert((int)RLMHTTPMethodPATCH == (int)app::HttpMethod::patch); -static_assert((int)RLMHTTPMethodDELETE == (int)app::HttpMethod::del); - -#pragma mark RLMSessionDelegate - -@interface RLMSessionDelegate : NSObject -+ (instancetype)delegateWithCompletion:(RLMNetworkTransportCompletionBlock)completion; -@end - -NSString * const RLMHTTPMethodToNSString[] = { - [RLMHTTPMethodGET] = @"GET", - [RLMHTTPMethodPOST] = @"POST", - [RLMHTTPMethodPUT] = @"PUT", - [RLMHTTPMethodPATCH] = @"PATCH", - [RLMHTTPMethodDELETE] = @"DELETE" -}; - -@implementation RLMRequest -@end - -@implementation RLMResponse -@end - -@interface RLMEventSessionDelegate : NSObject -+ (instancetype)delegateWithEventSubscriber:(id)subscriber; -@end; - -@implementation RLMNetworkTransport - -- (void)sendRequestToServer:(RLMRequest *)request - completion:(RLMNetworkTransportCompletionBlock)completionBlock { - // Create the request - NSURL *requestURL = [[NSURL alloc] initWithString: request.url]; - NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:requestURL]; - urlRequest.HTTPMethod = RLMHTTPMethodToNSString[request.method]; - if (![urlRequest.HTTPMethod isEqualToString:@"GET"]) { - urlRequest.HTTPBody = [request.body dataUsingEncoding:NSUTF8StringEncoding]; - } - urlRequest.timeoutInterval = request.timeout; - - for (NSString *key in request.headers) { - [urlRequest addValue:request.headers[key] forHTTPHeaderField:key]; - } - id delegate = [RLMSessionDelegate delegateWithCompletion:completionBlock]; - auto session = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration - delegate:delegate delegateQueue:nil]; - - // Add the request to a task and start it - [[session dataTaskWithRequest:urlRequest] resume]; - // Tell the session to destroy itself once it's done with the request - [session finishTasksAndInvalidate]; -} - -- (NSURLSession *)doStreamRequest:(nonnull RLMRequest *)request - eventSubscriber:(nonnull id)subscriber { - NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; - sessionConfig.timeoutIntervalForRequest = 30; - sessionConfig.timeoutIntervalForResource = INT_MAX; - sessionConfig.HTTPAdditionalHeaders = @{ - @"Content-Type": @"text/event-stream", - @"Cache": @"no-cache", - @"Accept": @"text/event-stream" - }; - id delegate = [RLMEventSessionDelegate delegateWithEventSubscriber:subscriber]; - auto session = [NSURLSession sessionWithConfiguration:sessionConfig - delegate:delegate - delegateQueue:nil]; - NSURL *url = [[NSURL alloc] initWithString:request.url]; - [[session dataTaskWithURL:url] resume]; - return session; -} - -RLMRequest *RLMRequestFromRequest(realm::app::Request const& request) { - RLMRequest *rlmRequest = [RLMRequest new]; - NSMutableDictionary *headersDict = [NSMutableDictionary new]; - for (auto &[key, value] : request.headers) { - headersDict[@(key.c_str())] = @(value.c_str()); - } - rlmRequest.headers = headersDict; - rlmRequest.method = static_cast(request.method); - rlmRequest.timeout = request.timeout_ms; - rlmRequest.url = @(request.url.c_str()); - rlmRequest.body = @(request.body.c_str()); - return rlmRequest; -} - -@end - -#pragma mark RLMSessionDelegate - -@implementation RLMSessionDelegate { - NSData *_data; - RLMNetworkTransportCompletionBlock _completionBlock; -} - -+ (instancetype)delegateWithCompletion:(RLMNetworkTransportCompletionBlock)completion { - RLMSessionDelegate *delegate = [RLMSessionDelegate new]; - delegate->_completionBlock = completion; - return delegate; -} - -- (void)URLSession:(__unused NSURLSession *)session - dataTask:(__unused NSURLSessionDataTask *)dataTask - didReceiveData:(NSData *)data { - if (!_data) { - _data = data; - return; - } - if (![_data respondsToSelector:@selector(appendData:)]) { - _data = [_data mutableCopy]; - } - [(id)_data appendData:data]; -} - -- (void)URLSession:(__unused NSURLSession *)session - task:(NSURLSessionTask *)task -didCompleteWithError:(NSError *)error -{ - RLMResponse *response = [RLMResponse new]; - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) task.response; - response.headers = httpResponse.allHeaderFields; - response.httpStatusCode = httpResponse.statusCode; - - if (error) { - response.body = error.localizedDescription; - response.customStatusCode = error.code; - return _completionBlock(response); - } - - response.body = [[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding]; - - _completionBlock(response); -} - -@end - -@implementation RLMEventSessionDelegate { - id _subscriber; - bool _hasOpened; -} - -+ (instancetype)delegateWithEventSubscriber:(id)subscriber { - RLMEventSessionDelegate *delegate = [RLMEventSessionDelegate new]; - delegate->_subscriber = subscriber; - return delegate; -} - -- (void)URLSession:(__unused NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask - didReceiveData:(NSData *)data { - if (!_hasOpened) { - _hasOpened = true; - [_subscriber didOpen]; - } - - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)dataTask.response; - if (httpResponse.statusCode == 200) { - return [_subscriber didReceiveEvent:data]; - } - - NSString *errorStatus = [NSString stringWithFormat:@"URLSession HTTP error code: %ld", - (long)httpResponse.statusCode]; - NSError *error = [NSError errorWithDomain:RLMAppErrorDomain - code:RLMAppErrorHttpRequestFailed - userInfo:@{NSLocalizedDescriptionKey: errorStatus, - RLMHTTPStatusCodeKey: @(httpResponse.statusCode), - NSURLErrorFailingURLErrorKey: dataTask.currentRequest.URL}]; - return [_subscriber didCloseWithError:error]; -} - -- (void)URLSession:(__unused NSURLSession *)session - task:(NSURLSessionTask *)task -didCompleteWithError:(NSError *)error -{ - RLMResponse *response = [RLMResponse new]; - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response; - response.headers = httpResponse.allHeaderFields; - response.httpStatusCode = httpResponse.statusCode; - - // -999 indicates that the session was cancelled. - if (error && (error.code != -999)) { - response.body = [error localizedDescription]; - return [_subscriber didCloseWithError:error]; - } - if (error && (error.code == -999)) { - return [_subscriber didCloseWithError:nil]; - } - if (response.httpStatusCode == 200) { - return; - } - - NSString *errorStatus = [NSString stringWithFormat:@"URLSession HTTP error code: %ld", - (long)httpResponse.statusCode]; - - // error may be nil when the http status code is 401/403 - replace it with [NSNull null] in that case - NSError *wrappedError = [NSError errorWithDomain:RLMAppErrorDomain - code:RLMAppErrorHttpRequestFailed - userInfo:@{NSLocalizedDescriptionKey: errorStatus, - RLMHTTPStatusCodeKey: @(httpResponse.statusCode), - NSURLErrorFailingURLErrorKey: task.currentRequest.URL, - NSUnderlyingErrorKey: error?: [NSNull null]}]; - return [_subscriber didCloseWithError:wrappedError]; -} - -@end diff --git a/Realm/RLMNetworkTransport_Private.hpp b/Realm/RLMNetworkTransport_Private.hpp deleted file mode 100644 index 27751bf7fd..0000000000 --- a/Realm/RLMNetworkTransport_Private.hpp +++ /dev/null @@ -1,25 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMNetworkTransport.h" - -namespace realm::app { -struct Request; -} - -RLMRequest *RLMRequestFromRequest(realm::app::Request const& request); diff --git a/Realm/RLMObjectBase.mm b/Realm/RLMObjectBase.mm index 0cc1b5a114..4d21cbf27e 100644 --- a/Realm/RLMObjectBase.mm +++ b/Realm/RLMObjectBase.mm @@ -717,14 +717,15 @@ - (void)addNotificationBlock:(RLMObjectNotificationCallback)block } NSError *error; - _realm = [RLMRealm realmWithConfiguration:config queue:queue error:&error]; - if (!_realm) { + auto realm = [RLMRealm realmWithConfiguration:config queue:queue error:&error]; + _realm = realm; + if (!realm) { block(nil, nil, nil, nil, error); return; } - RLMObjectBase *obj = [_realm resolveThreadSafeReference:tsr]; + RLMObjectBase *obj = [realm resolveThreadSafeReference:tsr]; - _object = realm::Object(_realm->_realm, *obj->_info->objectSchema, obj->_row); + _object = realm::Object(realm->_realm, *obj->_info->objectSchema, obj->_row); _token = _object.add_notification_callback(ObjectChangeCallbackWrapper{block, obj}, obj->_info->keyPathArrayFromStringArray(keyPaths)); } diff --git a/Realm/RLMObjectId.h b/Realm/RLMObjectId.h index 32ef7b0471..2d4afa5c0b 100644 --- a/Realm/RLMObjectId.h +++ b/Realm/RLMObjectId.h @@ -34,7 +34,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) ObjectIds are intended to be fast to generate. Sorting by an ObjectId field will typically result in the objects being sorted in creation order. */ -RLM_SWIFT_SENDABLE // immutable +NS_SWIFT_SENDABLE // immutable @interface RLMObjectId : NSObject /// Creates a new randomly-initialized ObjectId. + (nonnull instancetype)objectId NS_SWIFT_NAME(generate()); diff --git a/Realm/RLMObjectSchema.h b/Realm/RLMObjectSchema.h index 9c4666b7c3..462cb98f93 100644 --- a/Realm/RLMObjectSchema.h +++ b/Realm/RLMObjectSchema.h @@ -30,7 +30,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) Object schemas map to tables in the core database. */ -RLM_SWIFT_SENDABLE RLM_FINAL // not actually immutable, but the public API kinda is +NS_SWIFT_SENDABLE RLM_FINAL // not actually immutable, but the public API kinda is @interface RLMObjectSchema : NSObject #pragma mark - Properties diff --git a/Realm/RLMProperty.h b/Realm/RLMProperty.h index 7f2594d3b3..418ce78705 100644 --- a/Realm/RLMProperty.h +++ b/Realm/RLMProperty.h @@ -55,7 +55,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) These property instances map to columns in the core database. */ -RLM_SWIFT_SENDABLE RLM_FINAL // not actually immutable, but the public API kinda is +NS_SWIFT_SENDABLE RLM_FINAL // not actually immutable, but the public API kinda is @interface RLMProperty : NSObject #pragma mark - Properties @@ -127,7 +127,7 @@ RLM_SWIFT_SENDABLE RLM_FINAL // not actually immutable, but the public API kinda /** An `RLMPropertyDescriptor` instance represents a specific property on a given class. */ -RLM_SWIFT_SENDABLE RLM_FINAL +NS_SWIFT_SENDABLE RLM_FINAL @interface RLMPropertyDescriptor : NSObject /** diff --git a/Realm/RLMProviderClient.h b/Realm/RLMProviderClient.h deleted file mode 100644 index 11f4396f5f..0000000000 --- a/Realm/RLMProviderClient.h +++ /dev/null @@ -1,32 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@class RLMApp; - -/// Base provider client interface. -RLM_SWIFT_SENDABLE -@interface RLMProviderClient : NSObject -/// The app associated with this provider client. -@property (nonatomic, strong, readonly) RLMApp *app; -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMProviderClient.mm b/Realm/RLMProviderClient.mm deleted file mode 100644 index 1ac679b6af..0000000000 --- a/Realm/RLMProviderClient.mm +++ /dev/null @@ -1,48 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMProviderClient_Private.hpp" - -#import "RLMApp_Private.hpp" - -#import -#import - -@implementation RLMProviderClient - -- (instancetype)initWithApp:(std::shared_ptr)app { - if (self = [super init]) { - _app = std::move(app); - } - return self; -} - -- (RLMApp *)app { - return [RLMApp appWithId:@(_app->config().app_id.c_str())]; -} - -realm::util::UniqueFunction)> -RLMWrapCompletion(RLMProviderClientOptionalErrorBlock completion) { - return [completion](std::optional error) { - if (error) { - return completion(makeError(*error)); - } - completion(nil); - }; -} -@end diff --git a/Realm/RLMProviderClient_Private.hpp b/Realm/RLMProviderClient_Private.hpp deleted file mode 100644 index e8653e6e25..0000000000 --- a/Realm/RLMProviderClient_Private.hpp +++ /dev/null @@ -1,34 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#import - -@interface RLMProviderClient () { - @public - std::shared_ptr _app; -} -- (instancetype _Nonnull)initWithApp:(std::shared_ptr)app; - -/// A block type used to report an error -typedef void(^RLMProviderClientOptionalErrorBlock)(NSError * _Nullable); - -realm::util::UniqueFunction)> -RLMWrapCompletion(_Nonnull RLMProviderClientOptionalErrorBlock); -@end diff --git a/Realm/RLMPushClient.h b/Realm/RLMPushClient.h deleted file mode 100644 index 0628c3adaf..0000000000 --- a/Realm/RLMPushClient.h +++ /dev/null @@ -1,47 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@class RLMApp, RLMUser; - -/// A block type used to report an error -RLM_SWIFT_SENDABLE // invoked on a backgroun thread -typedef void(^RLMOptionalErrorBlock)(NSError * _Nullable); - -/// A client which can be used to register devices with the server to receive push notificatons -RLM_SWIFT_SENDABLE RLM_FINAL // immutable final class -@interface RLMPushClient : NSObject - -/// The push notification service name the device will be registered with on the server -@property (nonatomic, readonly, nonnull) NSString *serviceName; - -/// Request to register device token to the server -- (void)registerDeviceWithToken:(NSString *)token - user:(RLMUser *)user - completion:(RLMOptionalErrorBlock)completion NS_SWIFT_NAME(registerDevice(token:user:completion:)); - -/// Request to deregister a device for a user -- (void)deregisterDeviceForUser:(RLMUser *)user - completion:(RLMOptionalErrorBlock)completion NS_SWIFT_NAME(deregisterDevice(user:completion:)); - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMPushClient.mm b/Realm/RLMPushClient.mm deleted file mode 100644 index f860a71c3e..0000000000 --- a/Realm/RLMPushClient.mm +++ /dev/null @@ -1,57 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMPushClient_Private.hpp" - -#import "RLMApp_Private.hpp" -#import "RLMUser_Private.hpp" - -#import - -@implementation RLMPushClient { - std::optional _pushClient; -} - -- (instancetype)initWithPushClient:(realm::app::PushClient&&)pushClient { - if (self = [super init]) { - _pushClient = std::move(pushClient); - return self; - } - return nil; -} - -- (void)registerDeviceWithToken:(NSString *)token user:(RLMUser *)user completion:(RLMOptionalErrorBlock)completion { - _pushClient->register_device(token.UTF8String, user.user, ^(std::optional error) { - if (error) { - return completion(makeError(*error)); - } - completion(nil); - }); -} - - -- (void)deregisterDeviceForUser:(RLMUser *)user completion:(RLMOptionalErrorBlock)completion { - _pushClient->deregister_device(user.user, ^(std::optional error) { - if (error) { - return completion(makeError(*error)); - } - completion(nil); - }); -} - -@end diff --git a/Realm/RLMPushClient_Private.hpp b/Realm/RLMPushClient_Private.hpp deleted file mode 100644 index 44304c9c30..0000000000 --- a/Realm/RLMPushClient_Private.hpp +++ /dev/null @@ -1,28 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMPushClient.h" - -namespace realm::app { -class PushClient; -} - -RLM_DIRECT_MEMBERS -@interface RLMPushClient () -- (instancetype)initWithPushClient:(realm::app::PushClient&&)pushClient; -@end diff --git a/Realm/RLMQueryUtil.mm b/Realm/RLMQueryUtil.mm index 3985317481..82ada24f8a 100644 --- a/Realm/RLMQueryUtil.mm +++ b/Realm/RLMQueryUtil.mm @@ -625,17 +625,16 @@ void add_collection_operation_constraint(NSPredicateOperatorType operatorType, template void QueryBuilder::add_substring_constraint(const T& value, Query condition) { // Foundation always returns false for substring operations with a RHS of null or "". - m_query.and_query(value.size() - ? std::move(condition) - : std::unique_ptr(new FalseExpression)); + m_query.and_query(value.size() ? std::move(condition) + : std::unique_ptr(new FalseExpression)); } template<> void QueryBuilder::add_substring_constraint(const Mixed& value, Query condition) { // Foundation always returns false for substring operations with a RHS of null or "". - m_query.and_query(value.get_string().size() - ? std::move(condition) - : std::unique_ptr(new FalseExpression)); + bool empty = value.is_type(type_String) ? value.get_string().size() : value.get_binary().size(); + m_query.and_query(empty ? std::move(condition) + : std::unique_ptr(new FalseExpression)); } @@ -760,29 +759,6 @@ Query make_diacritic_sensitive_constraint(NSPredicateOperatorType operatorType, bool caseSensitive = !(predicateOptions & NSCaseInsensitivePredicateOption); Query condition = make_diacritic_sensitive_constraint(operatorType, caseSensitive, column, value); - // Queries on Mixed used to coerce Strings to Binary and vice-versa. Core - // no longer does this, but we can maintain compatibility by doing the - // coercion and checking both - // NEXT-MAJOR: we should remove this and realign with core's behavior - if constexpr (is_any_v, Columns>, Columns>, Columns>) { - Mixed m = value; - if (!m.is_null()) { - if (m.get_type() == type_String) { - m = m.export_to_type(); - } - else { - m = m.export_to_type(); - } - - // Equality and substring operations need (col == strValue OR col == binValue), - // but not equals needs (col != strValue AND col != binValue) - if (operatorType != NSNotEqualToPredicateOperatorType) { - condition.Or(); - } - - condition.and_query(make_diacritic_sensitive_constraint(operatorType, caseSensitive, column, m)); - } - } switch (operatorType) { case NSBeginsWithPredicateOperatorType: case NSEndsWithPredicateOperatorType: diff --git a/Realm/RLMRealm+Sync.h b/Realm/RLMRealm+Sync.h deleted file mode 100644 index ff20e43c2d..0000000000 --- a/Realm/RLMRealm+Sync.h +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#import - -@class RLMResults, RLMSyncSession; - -RLM_HEADER_AUDIT_BEGIN(nullability) - -/// -@interface RLMRealm (Sync) - -/** - Get the RLMSyncSession used by this Realm. Will be nil if this is not a - synchronized Realm. -*/ -@property (nonatomic, nullable, readonly) RLMSyncSession *syncSession; - -@end - -RLM_HEADER_AUDIT_END(nullability) diff --git a/Realm/RLMRealm+Sync.mm b/Realm/RLMRealm+Sync.mm deleted file mode 100644 index 9ee8b0afef..0000000000 --- a/Realm/RLMRealm+Sync.mm +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMRealm+Sync.h" - -#import "RLMObjectBase.h" -#import "RLMQueryUtil.hpp" -#import "RLMObjectSchema.h" -#import "RLMRealm_Private.hpp" -#import "RLMResults_Private.hpp" -#import "RLMSchema.h" -#import "RLMSyncSession.h" - -#import -#import - -using namespace realm; - -@implementation RLMRealm (Sync) - -- (RLMSyncSession *)syncSession { - return [RLMSyncSession sessionForRealm:self]; -} - -@end diff --git a/Realm/RLMRealm.h b/Realm/RLMRealm.h index 39c7d4a2e9..998beae74b 100644 --- a/Realm/RLMRealm.h +++ b/Realm/RLMRealm.h @@ -18,7 +18,7 @@ #import -@class RLMRealmConfiguration, RLMRealm, RLMObject, RLMSchema, RLMMigration, RLMNotificationToken, RLMThreadSafeReference, RLMAsyncOpenTask, RLMSyncSubscriptionSet; +@class RLMRealmConfiguration, RLMRealm, RLMObject, RLMSchema, RLMMigration, RLMNotificationToken, RLMThreadSafeReference, RLMAsyncOpenTask; /** A callback block for opening Realms asynchronously. @@ -109,7 +109,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @return An `RLMRealm` instance. */ -+ (nullable instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error; ++ (nullable instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error NS_RETURNS_RETAINED; /** Obtains an `RLMRealm` instance with the given configuration bound to the given queue. @@ -134,7 +134,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) */ + (nullable instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration queue:(nullable dispatch_queue_t)queue - error:(NSError **)error; + error:(NSError **)error NS_RETURNS_RETAINED; /** Obtains an `RLMRealm` instance persisted at a specified file URL. @@ -143,7 +143,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @return An `RLMRealm` instance. */ -+ (instancetype)realmWithURL:(NSURL *)fileURL; ++ (instancetype)realmWithURL:(NSURL *)fileURL NS_RETURNS_RETAINED; /** Asynchronously open a Realm and deliver it to a block on the given queue. @@ -244,9 +244,6 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) /** Writes a copy of the Realm to a given location specified by a given configuration. - If the configuration supplied is derived from a `RLMUser` then this Realm will be copied with - sync functionality enabled. - The destination file cannot already exist. @param configuration A Realm Configuration. @@ -861,16 +858,6 @@ NS_REFINED_FOR_SWIFT; */ - (void)deleteAllObjects; -#pragma mark - Sync Subscriptions - -/** - Represents the active subscriptions for this realm, which can be used to add/remove/update - and search flexible sync subscriptions. - Getting the subscriptions from a local or partition-based configured realm will thrown an exception. - */ -@property (nonatomic, readonly, nonnull) RLMSyncSubscriptionSet *subscriptions; - - #pragma mark - Migrations /** @@ -882,7 +869,7 @@ NS_REFINED_FOR_SWIFT; @param oldSchemaVersion The schema version of the Realm being migrated. */ -RLM_SWIFT_SENDABLE +NS_SWIFT_SENDABLE typedef void (^RLMMigrationBlock)(RLMMigration *migration, uint64_t oldSchemaVersion); /** @@ -948,7 +935,7 @@ NS_REFINED_FOR_SWIFT; When you wish to stop, call the `-invalidate` method. Notifications are also stopped if the token is deallocated. */ -RLM_SWIFT_SENDABLE // is internally thread-safe +NS_SWIFT_SENDABLE // is internally thread-safe @interface RLMNotificationToken : NSObject /// Stops notifications for the change subscription that returned this token. /// diff --git a/Realm/RLMRealm.mm b/Realm/RLMRealm.mm index 54a75ae12d..9c406c22e3 100644 --- a/Realm/RLMRealm.mm +++ b/Realm/RLMRealm.mm @@ -18,7 +18,6 @@ #import "RLMRealm_Private.hpp" -#import "RLMAnalytics.hpp" #import "RLMAsyncTask_Private.h" #import "RLMArray_Private.hpp" #import "RLMDictionary_Private.hpp" @@ -37,11 +36,8 @@ #import "RLMRealmUtil.hpp" #import "RLMScheduler.h" #import "RLMSchema_Private.hpp" -#import "RLMSyncConfiguration.h" -#import "RLMSyncConfiguration_Private.hpp" #import "RLMSet_Private.hpp" #import "RLMThreadSafeReference_Private.hpp" -#import "RLMUpdateChecker.hpp" #import "RLMUtil.hpp" #import @@ -51,16 +47,7 @@ #import #import #import -#import -#if REALM_ENABLE_SYNC -#import "RLMSyncManager_Private.hpp" -#import "RLMSyncSession_Private.hpp" -#import "RLMSyncUtil_Private.hpp" -#import "RLMSyncSubscription_Private.hpp" - -#import -#endif using namespace realm; using util::File; @@ -178,16 +165,6 @@ REALM_NOINLINE void RLMRealmTranslateException(NSError **error) { } namespace { -// ARC tries to eliminate calls to autorelease when the value is then immediately -// returned, but this results in significantly different semantics between debug -// and release builds for RLMRealm, so force it to always autorelease. -// NEXT-MAJOR: we should switch to NS_RETURNS_RETAINED, which did not exist yet -// when we wrote this but is the correct thing. -id autorelease(__unsafe_unretained id value) { - // +1 __bridge_retained, -1 CFAutorelease - return value ? (__bridge id)CFAutorelease((__bridge_retained CFTypeRef)value) : nil; -} - RLMRealm *getCachedRealm(RLMRealmConfiguration *configuration, RLMScheduler *options) NS_RETURNS_RETAINED { auto& config = configuration.configRef; if (!configuration.cache && !configuration.dynamic) { @@ -212,7 +189,7 @@ id autorelease(__unsafe_unretained id value) { if (oldConfig.encryption_key != config.encryption_key) { @throw RLMException(@"Realm at path '%@' already opened with different encryption key", configuration.fileURL.path); } - return autorelease(realm); + return realm; } bool copySeedFile(RLMRealmConfiguration *configuration, NSError **error) { @@ -248,18 +225,6 @@ + (void)initialize { [RLMLogger class]; } -+ (void)runFirstCheckForConfiguration:(RLMRealmConfiguration *)configuration schema:(RLMSchema *)schema { - static bool initialized; - if (initialized) { - return; - } - initialized = true; - - // Run Analytics on the very first any Realm open. - RLMSendAnalytics(configuration, schema); - RLMCheckForUpdates(); -} - - (instancetype)initPrivate { self = [super init]; return self; @@ -319,7 +284,7 @@ + (RLMAsyncOpenTask *)asyncOpenWithConfiguration:(RLMRealmConfiguration *)config callback:(RLMAsyncOpenRealmCallback)callback { return [[RLMAsyncOpenTask alloc] initWithConfiguration:configuration confinedTo:[RLMScheduler dispatchQueue:callbackQueue] - download:true completion:callback]; + completion:callback]; } + (instancetype)realmWithSharedRealm:(SharedRealm)sharedRealm @@ -333,7 +298,7 @@ + (instancetype)realmWithSharedRealm:(SharedRealm)sharedRealm realm->_realm->set_schema_subset(schema.objectStoreCopy); } realm->_info = RLMSchemaInfo(realm); - return autorelease(realm); + return realm; } + (instancetype)realmWithSharedRealm:(std::shared_ptr)osRealm @@ -384,17 +349,17 @@ + (instancetype)realmWithSharedRealm:(std::shared_ptr)osRealm } + (instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error { - return autorelease([self realmWithConfiguration:configuration - confinedTo:RLMScheduler.currentRunLoop - error:error]); + return [self realmWithConfiguration:configuration + confinedTo:RLMScheduler.currentRunLoop + error:error]; } + (instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration queue:(dispatch_queue_t)queue error:(NSError **)error { - return autorelease([self realmWithConfiguration:configuration - confinedTo:[RLMScheduler dispatchQueue:queue] - error:error]); + return [self realmWithConfiguration:configuration + confinedTo:[RLMScheduler dispatchQueue:queue] + error:error]; } + (instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration @@ -512,9 +477,6 @@ + (instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration realm->_realm->m_binding_context->realm = realm->_realm; } - // Run Analytics and Update checker, this will be run only the first any realm open - [self runFirstCheckForConfiguration:configuration schema:realm.schema]; - return realm; } @@ -1120,39 +1082,4 @@ - (void)detachAllEnumerators { } _collectionEnumerators = nil; } - -- (bool)isFlexibleSync { -#if REALM_ENABLE_SYNC - return _realm->config().sync_config && _realm->config().sync_config->flx_sync_requested; -#else - return false; -#endif -} - -- (RLMSyncSubscriptionSet *)subscriptions { -#if REALM_ENABLE_SYNC - if (!self.isFlexibleSync) { - @throw RLMException(@"This Realm was not configured with flexible sync"); - } - return [[RLMSyncSubscriptionSet alloc] initWithSubscriptionSet:_realm->get_latest_subscription_set() realm:self]; -#else - @throw RLMException(@"Realm was not compiled with sync enabled"); -#endif -} - -void RLMRealmSubscribeToAll(RLMRealm *realm) { - if (!realm.isFlexibleSync) { - return; - } - - auto subs = realm->_realm->get_latest_subscription_set().make_mutable_copy(); - auto& group = realm->_realm->read_group(); - for (auto key : group.get_table_keys()) { - if (!std::string_view(group.get_table_name(key)).starts_with("class_")) { - continue; - } - subs.insert_or_assign(group.get_table(key)->where()); - } - subs.commit(); -} @end diff --git a/Realm/RLMRealmConfiguration.h b/Realm/RLMRealmConfiguration.h index 55b4689a7c..b084962afb 100644 --- a/Realm/RLMRealmConfiguration.h +++ b/Realm/RLMRealmConfiguration.h @@ -18,8 +18,6 @@ #import -@class RLMEventConfiguration, RLMSyncConfiguration; - RLM_HEADER_AUDIT_BEGIN(nullability, sendability) /** @@ -31,7 +29,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) Return `YES` to indicate that an attempt to compact the file should be made. The compaction will be skipped if another process is accessing it. */ -RLM_SWIFT_SENDABLE +NS_SWIFT_SENDABLE typedef BOOL (^RLMShouldCompactOnLaunchBlock)(NSUInteger totalBytes, NSUInteger bytesUsed); /** @@ -174,17 +172,6 @@ typedef BOOL (^RLMShouldCompactOnLaunchBlock)(NSUInteger totalBytes, NSUInteger */ @property (nonatomic, copy, nullable) NSURL *seedFilePath; -/** - A configuration object representing configuration state for Realms intended - to sync with Atlas Device Sync. - - This property is mutually exclusive with both `inMemoryIdentifier` and `fileURL`; - setting any one of the three properties will automatically nil out the other two. - - @see `RLMSyncConfiguration` - */ -@property (nullable, nonatomic) RLMSyncConfiguration *syncConfiguration; - @end RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMRealmConfiguration.mm b/Realm/RLMRealmConfiguration.mm index 99cb539d59..61ddec089e 100644 --- a/Realm/RLMRealmConfiguration.mm +++ b/Realm/RLMRealmConfiguration.mm @@ -18,7 +18,6 @@ #import "RLMRealmConfiguration_Private.h" -#import "RLMEvent.h" #import "RLMObjectSchema_Private.hpp" #import "RLMRealm_Private.h" #import "RLMSchema_Private.hpp" @@ -27,17 +26,6 @@ #import #import -#if REALM_ENABLE_SYNC -#import "RLMSyncConfiguration_Private.hpp" -#import "RLMUser_Private.hpp" - -#import -#import -#import -#else -@class RLMSyncConfiguration; -#endif - static NSString *const c_RLMRealmConfigurationProperties[] = { @"fileURL", @"inMemoryIdentifier", @@ -66,7 +54,6 @@ @implementation RLMRealmConfiguration { realm::Realm::Config _config; - RLMSyncErrorReportingBlock _manualClientResetHandler; } - (realm::Realm::Config&)configRef { @@ -128,7 +115,6 @@ - (instancetype)copyWithZone:(NSZone *)zone { configuration->_migrationBlock = _migrationBlock; configuration->_shouldCompactOnLaunch = _shouldCompactOnLaunch; configuration->_customSchema = _customSchema; - configuration->_eventConfiguration = _eventConfiguration; configuration->_migrationObjectClass = _migrationObjectClass; configuration->_seedFilePath = _seedFilePath; return configuration; @@ -204,29 +190,9 @@ - (BOOL)readOnly { return _config.immutable() || _config.read_only(); } -static bool isSync(realm::Realm::Config const& config) { -#if REALM_ENABLE_SYNC - return !!config.sync_config; -#endif - return false; -} - - (void)updateSchemaMode { - if (self.deleteRealmIfMigrationNeeded) { - if (isSync(_config)) { - @throw RLMException(@"Cannot set 'deleteRealmIfMigrationNeeded' when sync is enabled ('syncConfig' is set)."); - } - } - else if (self.readOnly) { - _config.schema_mode = isSync(_config) ? realm::SchemaMode::ReadOnly : realm::SchemaMode::Immutable; - } - else if (isSync(_config)) { - if (_customSchema) { - _config.schema_mode = realm::SchemaMode::AdditiveExplicit; - } - else { - _config.schema_mode = realm::SchemaMode::AdditiveDiscovered; - } + if (self.readOnly) { + _config.schema_mode = realm::SchemaMode::Immutable; } else { _config.schema_mode = realm::SchemaMode::Automatic; @@ -240,7 +206,7 @@ - (void)setReadOnly:(BOOL)readOnly { } else if (self.shouldCompactOnLaunch) { @throw RLMException(@"Cannot set `readOnly` when `shouldCompactOnLaunch` is set."); } - _config.schema_mode = isSync(_config) ? realm::SchemaMode::ReadOnly : realm::SchemaMode::Immutable; + _config.schema_mode = realm::SchemaMode::Immutable; } else if (self.readOnly) { _config.schema_mode = realm::SchemaMode::Automatic; @@ -268,9 +234,6 @@ - (void)setDeleteRealmIfMigrationNeeded:(BOOL)deleteRealmIfMigrationNeeded { if (self.readOnly) { @throw RLMException(@"Cannot set `deleteRealmIfMigrationNeeded` when `readOnly` is set."); } - if (isSync(_config)) { - @throw RLMException(@"Cannot set 'deleteRealmIfMigrationNeeded' when sync is enabled ('syncConfig' is set)."); - } _config.schema_mode = realm::SchemaMode::SoftResetFile; } else if (self.deleteRealmIfMigrationNeeded) { @@ -353,56 +316,8 @@ - (void)setDisableAutomaticChangeNotifications:(bool)disableAutomaticChangeNotif _config.automatic_change_notifications = !disableAutomaticChangeNotifications; } -#if REALM_ENABLE_SYNC -- (void)setSyncConfiguration:(RLMSyncConfiguration *)syncConfiguration { - if (syncConfiguration == nil) { - _config.sync_config = nullptr; - return; - } - auto& rawConfig = syncConfiguration.rawConfiguration; - if (rawConfig.user->state() == realm::SyncUser::State::Removed) { - @throw RLMException(@"Cannot set a sync configuration which has a removed user."); - } - - _config.sync_config = std::make_shared(rawConfig); - _config.path = syncConfiguration.path; - - // The manual client reset handler doesn't exist on the raw config, - // so assign it here. - _manualClientResetHandler = syncConfiguration.manualClientResetHandler; - - [self updateSchemaMode]; -} - -- (RLMSyncConfiguration *)syncConfiguration { - if (!_config.sync_config) { - return nil; - } - RLMSyncConfiguration* syncConfig = [[RLMSyncConfiguration alloc] initWithRawConfig:*_config.sync_config path:_config.path]; - syncConfig.manualClientResetHandler = _manualClientResetHandler; - return syncConfig; -} - -#else // REALM_ENABLE_SYNC -- (RLMSyncConfiguration *)syncConfiguration { - return nil; -} -#endif // REALM_ENABLE_SYNC - - (realm::Realm::Config)config { - auto config = _config; - if (config.sync_config) { - config.sync_config = std::make_shared(*config.sync_config); - } -#if REALM_ENABLE_SYNC - if (config.sync_config) { - RLMSetConfigInfoForClientResetCallbacks(*config.sync_config, self); - } - if (_eventConfiguration) { - config.audit_config = [_eventConfiguration auditConfigWithRealmConfiguration:self]; - } -#endif - return config; + return _config; } @end diff --git a/Realm/RLMRealm_Private.h b/Realm/RLMRealm_Private.h index e2bfa264fc..d6d0244681 100644 --- a/Realm/RLMRealm_Private.h +++ b/Realm/RLMRealm_Private.h @@ -56,14 +56,11 @@ FOUNDATION_EXTERN RLMRealm *_Nullable RLMGetAnyCachedRealm(RLMRealmConfiguration // Scheduler an async refresh for the given Realm FOUNDATION_EXTERN RLMAsyncRefreshTask *_Nullable RLMRealmRefreshAsync(RLMRealm *rlmRealm) NS_RETURNS_RETAINED; -FOUNDATION_EXTERN void RLMRealmSubscribeToAll(RLMRealm *); - // RLMRealm private members @interface RLMRealm () @property (nonatomic, readonly) BOOL dynamic; @property (nonatomic, readwrite) RLMSchema *schema; @property (nonatomic, readonly, nullable) id actor; -@property (nonatomic, readonly) bool isFlexibleSync; // `-configuration` does a deep copy of the schema as if the user mutates the // RLMSchema in use by a RLMRealm things will break horribly. When we know that @@ -84,7 +81,7 @@ FOUNDATION_EXTERN void RLMRealmSubscribeToAll(RLMRealm *); + (nullable instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration confinedTo:(RLMScheduler *)options - error:(NSError **)error; + error:(NSError **)error NS_RETURNS_RETAINED; - (RLMAsyncWriteTask *)beginAsyncWrite NS_RETURNS_RETAINED; - (void)commitAsyncWriteWithGrouping:(bool)allowGrouping diff --git a/Realm/RLMResults.h b/Realm/RLMResults.h index 554cae7c82..4f176e713f 100644 --- a/Realm/RLMResults.h +++ b/Realm/RLMResults.h @@ -23,22 +23,6 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) /// A block type used for APIs which asynchronously return a `Results`. typedef void(^RLMResultsCompletionBlock)(RLMResults * _Nullable, NSError * _Nullable); -/** - Determines wait for download behavior when subscribing on RLMResults. - @see ``[RLMResults subscribeWithName:waitForSync:onQueue:completion:]`` -*/ -typedef NS_ENUM(NSUInteger, RLMWaitForSyncMode) { - /// `subscribeWithName`'s callback will be invoked once matching objects are downloaded - /// from the server only when the subscription is created the first time. If the - /// subscription already exists, the callback is invoked without waiting for new downloads. - RLMWaitForSyncModeOnCreation, - /// `subscribeWithName`'s callback will wait for downloads before being invoked. - /// The callback can't be invoked in this mode unless an internet connection is established or a timeout is set. - RLMWaitForSyncModeAlways, - /// `subscribeWithName`'s callback is always invoked without waiting for downloads. - RLMWaitForSyncModeNever -} NS_SWIFT_NAME(WaitForSyncMode); - @class RLMObject; /** @@ -428,163 +412,6 @@ __attribute__((warn_unused_result)); keyPaths:(nullable NSArray *)keyPaths __attribute__((warn_unused_result)); -#pragma mark - Flexible Sync - -/** - Creates a RLMSyncSubscription matching the RLMResults's local filter. - - After committing the subscription to the realm's local subscription set, the method - will wait for downloads according to ``RLMWaitForSyncMode`` behavior. - - ### Unnamed subscriptions ### - If `subscribeWithCompletion:` is called without a name whose query matches an unnamed subscription, another subscription is not created. - - If `subscribeWithCompletion:` is called without a name whose query matches a named subscription, an additional unnamed subscription is created. - ### Named Subscriptions ### - If `subscribeWithCompletion:` is called with a name whose query matches an unnamed subscription, an additional named subscription is created. - ### Existing name and query ### - If `subscribeWithCompletion:` is called with a name whose name is taken on a different query, the old subscription is updated with the new query. - - @note This method opens an update block transaction that creates or updates a subscription. - It's advised to *not* loop over this method in order to create multiple subscriptions at once. - This could create a performance bottleneck by opening multiple unnecessary write transactions. - @see: `[RLMSyncSubscription update:queue:onComplete:]` in order to create multiple subscriptions. - - @param queue The queue where the completion dispatches. - @param completion The completion block called after the subscription completes. The callback - will wait for downloads according to the value in `waitForSyncMode`. - @see ``RLMWaitForSyncMode`` - @warning This API is currently in `Preview` and may be subject to changes in the future. - */ -- (void)subscribeWithCompletionOnQueue:(dispatch_queue_t _Nullable)queue - completion:(RLMResultsCompletionBlock)completion; - -/** - Creates a RLMSyncSubscription matching the RLMResults's local filter. - - After committing the subscription to the realm's local subscription set, the method - will wait for downloads according to ``RLMWaitForSyncMode`` behavior. - - ### Unnamed subscriptions ### - If `subscribeWithCompletion:` is called without a name whose query matches an unnamed subscription, another subscription is not created. - - If `subscribeWithCompletion:` is called without a name whose query matches a named subscription, an additional unnamed subscription is created. - ### Named Subscriptions ### - If `subscribeWithCompletion:` is called with a name whose query matches an unnamed subscription, an additional named subscription is created. - ### Existing name and query ### - If `subscribeWithCompletion:` is called with a name whose name is taken on a different query, the old subscription is updated with the new query. - - @note This method opens an update block transaction that creates or updates a subscription. - It's advised to *not* loop over this method in order to create multiple subscriptions at once. - This could create a performance bottleneck by opening multiple unnecessary write transactions. - @see: `[RLMSyncSubscription update:queue:onComplete:]` in order to create multiple subscriptions. - - @param name The name used to identify the subscription. - @param queue The queue where the completion dispatches. - @param completion The completion block called after the subscription completes. The callback - will wait for downloads according to the value in `waitForSyncMode`. - @see ``RLMWaitForSyncMode`` - @warning This API is currently in `Preview` and may be subject to changes in the future. - */ -- (void)subscribeWithName:(NSString *_Nullable)name - onQueue:(dispatch_queue_t _Nullable)queue - completion:(RLMResultsCompletionBlock)completion; - -/** - Creates a RLMSyncSubscription matching the RLMResults's local filter. - - After committing the subscription to the realm's local subscription set, the method - will wait for downloads according to the ``RLMWaitForSyncMode``. - - ### Unnamed subscriptions ### - If `subscribeWithCompletion:` is called without a name whose query matches an unnamed subscription, another subscription is not created. - - If `subscribeWithCompletion:` is called without a name whose query matches a named subscription, an additional unnamed subscription is created. - ### Named Subscriptions ### - If `subscribeWithCompletion:` is called with a name whose query matches an unnamed subscription, an additional named subscription is created. - ### Existing name and query ### - If `subscribeWithCompletion:` is called with a name whose name is taken on a different query, the old subscription is updated with the new query. - - @note This method opens an update block transaction that creates or updates a subscription. - It's advised to *not* loop over this method in order to create multiple subscriptions at once. - This could create a performance bottleneck by opening multiple unnecessary write transactions. - @see: `[RLMSyncSubscription update:queue:onComplete:]` in order to create multiple subscriptions. - - @param name The name used to identify the subscription. - @param waitForSyncMode Dictates when the completion handler is called - @param queue The queue where the completion dispatches. - @param completion The completion block called after the subscription completes. The callback - will wait for downloads according to the value in `waitForSyncMode`. - @see ``RLMWaitForSyncMode`` - @warning This API is currently in `Preview` and may be subject to changes in the future. - */ -- (void)subscribeWithName:(NSString *_Nullable)name - waitForSync:(RLMWaitForSyncMode)waitForSyncMode - onQueue:(dispatch_queue_t _Nullable)queue - completion:(RLMResultsCompletionBlock)completion -__attribute__((swift_attr("@_unsafeInheritExecutor"))); - -/** - Creates a RLMSyncSubscription matching the RLMResults's local filter. - - After committing the subscription to the realm's local subscription set, the method - will wait for downloads according to the ``RLMWaitForSyncMode``. - - ### Unnamed subscriptions ### - If `subscribeWithCompletion:` is called without a name whose query matches an unnamed subscription, another subscription is not created. - - If `subscribeWithCompletion:` is called without a name whose query matches a named subscription, an additional unnamed subscription is created. - ### Named Subscriptions ### - If `subscribeWithCompletion:` is called with a name whose query matches an unnamed subscription, an additional named subscription is created. - ### Existing name and query ### - If `subscribeWithCompletion:` is called with a name whose name is taken on a different query, the old subscription is updated with the new query. - - @note This method opens an update block transaction that creates or updates a subscription. - It's advised to *not* loop over this method in order to create multiple subscriptions at once. - This could create a performance bottleneck by opening multiple unnecessary write transactions. - @see: `[RLMSyncSubscription update:queue:onComplete:]` in order to create multiple subscriptions. - - @param name The name used to identify the subscription. - @param waitForSyncMode Dictates when the completion handler is called - @param queue The queue where the completion dispatches. - @param timeout A timeout which ends waiting for downloads - via the completion handler. If the timeout is exceeded the completion - handler returns an error. - @param completion The completion block called after the subscription completes. The callback - will wait for downloads according to the value in `waitForSyncMode`. - @see ``RLMWaitForSyncMode`` - @warning This API is currently in `Preview` and may be subject to changes in the future. - */ -- (void)subscribeWithName:(NSString *_Nullable)name - waitForSync:(RLMWaitForSyncMode)waitForSyncMode - onQueue:(dispatch_queue_t _Nullable)queue - timeout:(NSTimeInterval)timeout - completion:(RLMResultsCompletionBlock)completion -__attribute__((swift_attr("@_unsafeInheritExecutor"))); - -/** - Removes a RLMSubscription matching the RLMResults'slocal filter. - - The method returns after committing the subscription removal to the - realm's local subscription set. Calling this method will not wait for objects to - be removed from the realm. - - Calling unsubscribe on a RLMResults does not remove the local filter from the RLMResults. - After calling unsubscribe, RLMResults may still contain objects because - other subscriptions may exist in the RLMRealm's subscription set. - - @note In order for a named subscription to be removed, the RLMResults - must have previously created the subscription. - The `RLMResults` returned in the completion block when calling `subscribe` can be used to unsubscribe from the same subscription. - - @note This method opens an update block transaction that creates or updates a subscription. - It's advised to *not* loop over this method in order to create multiple subscriptions at once. - This could create a performance bottleneck by opening multiple unnecessary write transactions. - @see: ``[RLMSyncSubscription update:queue:onComplete:]`` in order to create multiple subscriptions. - @warning This API is currently in `Preview` and may be subject to changes in the future. - */ -- (void)unsubscribe; - #pragma mark - Aggregating Property Values /** diff --git a/Realm/RLMResults.mm b/Realm/RLMResults.mm index eac9566e7b..32c3b30f9a 100644 --- a/Realm/RLMResults.mm +++ b/Realm/RLMResults.mm @@ -31,7 +31,6 @@ #import "RLMScheduler.h" #import "RLMSchema_Private.h" #import "RLMSectionedResults_Private.hpp" -#import "RLMSyncSubscription_Private.hpp" #import "RLMThreadSafeReference_Private.hpp" #import "RLMUtil.hpp" @@ -570,138 +569,6 @@ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMCollec return _results.add_notification_callback(RLMWrapCollectionChangeCallback(block, self, true), std::move(keyPaths)); } -- (void)completionWithThreadSafeReference:(RLMThreadSafeReference * _Nullable)reference - confinedTo:(RLMScheduler *)confinement - completion:(RLMResultsCompletionBlock)completion - error:(NSError *_Nullable)error { - RLMRealmConfiguration *configuration = _realm.configurationSharingSchema; - [confinement invoke:^{ - if (error) { - return completion(nil, error); - } - - NSError *err; - RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:&err]; - RLMResults *collection = [realm resolveThreadSafeReference:reference]; - collection.associatedSubscriptionId = self.associatedSubscriptionId; - completion(collection, err); - }]; -} - -// Returns true if the calling method should call immediately the completion block, this can happen if the subscription -// was already created in case of `onCreation` or we have selected `never` as sync mode (which doesn't require the subscription to complete to return) -- (bool)shouldNotWaitForSubscriptionToComplete:(RLMWaitForSyncMode)waitForSyncMode - name:(NSString *)name { - RLMSyncSubscriptionSet *subscriptions = self.realm.subscriptions; - switch(waitForSyncMode) { - case RLMWaitForSyncModeOnCreation: - // If an existing named subscription matches the provided name and local query, return. - if (name) { - RLMSyncSubscription *sub = [subscriptions subscriptionWithName:name query:_results.get_query()]; - if (sub != nil) { - return true; - } - } else { - // otherwise check if an unnamed subscription already exists. Return if it does exist. - RLMSyncSubscription *sub = [subscriptions subscriptionWithQuery:_results.get_query()]; - if (sub != nil && sub.name == nil) { - return true; - } - } - // If no name was provided and no existing unnamed subscription matches. - // break and create new subscription later. - break; - case RLMWaitForSyncModeAlways: - // never returns early - break; - case RLMWaitForSyncModeNever: - // commit subscription synchronously and return. - [subscriptions update:^{ - self.associatedSubscriptionId = [subscriptions addSubscriptionWithClassName:self.objectClassName - subscriptionName:name - query:_results.get_query() - updateExisting:true]; - }]; - return true; - } - return false; -} - -- (void)subscribeWithName:(NSString *_Nullable)name - waitForSync:(RLMWaitForSyncMode)waitForSyncMode - confinedTo:(RLMScheduler *)confinement - timeout:(NSTimeInterval)timeout - completion:(RLMResultsCompletionBlock)completion { - RLMThreadSafeReference *reference = [RLMThreadSafeReference referenceWithThreadConfined:self]; - if ([self shouldNotWaitForSubscriptionToComplete:waitForSyncMode name:name]) { - [self completionWithThreadSafeReference:reference confinedTo:confinement completion:completion error:nil]; - } else { - RLMThreadSafeReference *reference = [RLMThreadSafeReference referenceWithThreadConfined:self]; - RLMSyncSubscriptionSet *subscriptions = _realm.subscriptions; - [subscriptions update:^{ - // associated subscription id is nil when no name is provided. - self.associatedSubscriptionId = [subscriptions addSubscriptionWithClassName:self.objectClassName - subscriptionName:name - query:_results.get_query() - updateExisting:true]; - } queue:nil timeout:timeout onComplete:^(NSError *error) { - [self completionWithThreadSafeReference:reference confinedTo:confinement completion:completion error:error]; - }]; - } -} - -- (void)subscribeWithCompletionOnQueue:(dispatch_queue_t _Nullable)queue - completion:(RLMResultsCompletionBlock)completion { - return [self subscribeWithName:nil onQueue:queue completion:completion]; -}; - -- (void)subscribeWithName:(NSString *_Nullable)name - onQueue:(dispatch_queue_t _Nullable)queue - completion:(RLMResultsCompletionBlock)completion { - return [self subscribeWithName:name waitForSync:RLMWaitForSyncModeOnCreation onQueue:queue completion:completion]; -} - -- (void)subscribeWithName:(NSString *_Nullable)name - waitForSync:(RLMWaitForSyncMode)waitForSyncMode - onQueue:(dispatch_queue_t _Nullable)queue - completion:(RLMResultsCompletionBlock)completion { - [self subscribeWithName:name - waitForSync:waitForSyncMode - onQueue:queue - timeout:0 - completion:completion]; -} - -- (void)subscribeWithName:(NSString *_Nullable)name - waitForSync:(RLMWaitForSyncMode)waitForSyncMode - onQueue:(dispatch_queue_t _Nullable)queue - timeout:(NSTimeInterval)timeout - completion:(RLMResultsCompletionBlock)completion { - [self subscribeWithName:name - waitForSync:waitForSyncMode - confinedTo:[RLMScheduler dispatchQueue:queue] - timeout:timeout - completion:completion]; -} - -- (void)unsubscribe { - RLMSyncSubscriptionSet *subscriptions = self.realm.subscriptions; - - if (self.associatedSubscriptionId) { - [subscriptions update:^{ - [subscriptions removeSubscriptionWithId:self.associatedSubscriptionId]; - }]; - } else { - RLMSyncSubscription *sub = [subscriptions subscriptionWithQuery:_results.get_query()]; - if (sub.name == nil) { - [subscriptions update:^{ - [subscriptions removeSubscriptionWithClassName:self.objectClassName - query:_results.get_query()]; - }]; - } - } -} - - (BOOL)isAttached { return !!_realm; } diff --git a/Realm/RLMResults_Private.h b/Realm/RLMResults_Private.h index 224a39908d..1fc25dce7e 100644 --- a/Realm/RLMResults_Private.h +++ b/Realm/RLMResults_Private.h @@ -29,13 +29,6 @@ RLM_HEADER_AUDIT_BEGIN(nullability) + (instancetype)emptyDetachedResults; - (RLMResults *)snapshot; - -- (void)subscribeWithName:(NSString *_Nullable)name - waitForSync:(RLMWaitForSyncMode)waitForSyncMode - confinedTo:(RLMScheduler *)confinement - timeout:(NSTimeInterval)timeout - completion:(RLMResultsCompletionBlock)completion; - @end RLM_HEADER_AUDIT_END(nullability) diff --git a/Realm/RLMScheduler.h b/Realm/RLMScheduler.h index 3c1b9874ba..9456e670d5 100644 --- a/Realm/RLMScheduler.h +++ b/Realm/RLMScheduler.h @@ -33,7 +33,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) // an actor. The scheduler ensures that the Realm is only used on one thread at // a time, and allows us to dispatch work to the thread where we can access the // Realm safely. -RLM_SWIFT_SENDABLE // is immutable +NS_SWIFT_SENDABLE // is immutable @interface RLMScheduler : NSObject + (RLMScheduler *)mainRunLoop __attribute__((objc_direct)); + (RLMScheduler *)currentRunLoop __attribute__((objc_direct)); diff --git a/Realm/RLMSchema.h b/Realm/RLMSchema.h index fe46f6d479..a8bd9635d6 100644 --- a/Realm/RLMSchema.h +++ b/Realm/RLMSchema.h @@ -30,7 +30,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) Schemas map to collections of tables in the core database. */ -RLM_SWIFT_SENDABLE // not actually immutable, but the public API kinda is +NS_SWIFT_SENDABLE // not actually immutable, but the public API kinda is @interface RLMSchema : NSObject #pragma mark - Properties diff --git a/Realm/RLMSyncConfiguration.h b/Realm/RLMSyncConfiguration.h deleted file mode 100644 index 1262fa1349..0000000000 --- a/Realm/RLMSyncConfiguration.h +++ /dev/null @@ -1,203 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#import -#import - -@class RLMApp; -@class RLMRealm; -@class RLMRealmConfiguration; -@class RLMUser; -@class RLMInitialSubscriptionsConfiguration; -@protocol RLMBSON; - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -/** Determines file behavior during a client reset. - - @see: https://docs.mongodb.com/realm/sync/error-handling/client-resets/ -*/ -typedef NS_ENUM(NSUInteger, RLMClientResetMode) { - /// The local copy of the Realm is copied into a recovery - /// directory for safekeeping, and then deleted from the original location. The next time - /// the Realm for that partition value is opened, the Realm will automatically be re-downloaded from - /// Atlas App Services, and can be used as normal. - - /// Data written to the Realm after the local copy of the Realm diverged from the backup - /// remote copy will be present in the local recovery copy of the Realm file. The - /// re-downloaded Realm will initially contain only the data present at the time the Realm - /// was backed up on the server. - /// - /// @see: ``rlmSync_clientResetBackedUpRealmPath`` and ``RLMSyncErrorActionToken`` for more information on accessing the recovery directory and error information. - /// - /// The manual client reset mode handler can be set in two places: - /// 1. As an ErrorReportingBlock argument at ``RLMSyncConfiguration.manualClientResetHandler``. - /// 2. As an ErrorReportingBlock in the ``RLMSyncManager.errorHandler`` property. - /// @see: ``RLMSyncManager.errorHandler`` - /// - /// When an ``RLMSyncErrorClientResetError`` is thrown, the following rules determine which block is executed: - /// - If an error reporting block is set in ``.manualClientResetHandler`` and the ``RLMSyncManager.errorHandler``, the ``.manualClientResetHandler`` block will be executed. - /// - If an error reporting block is set in either the ``.manualClientResetHandler`` or the ``RLMSyncManager``, but not both, the single block will execute. - /// - If no block is set in either location, the client reset will not be handled. The application will likely need to be restarted and unsynced local changes may be lost. - /// @note: The ``RLMSyncManager.errorHandler`` is still invoked under all ``RLMSyncError``s *other than* ``RLMSyncErrorClientResetError``. - /// @see ``RLMSyncError`` for an exhaustive list. - RLMClientResetModeManual = 0, - /// All unsynchronized local changes are automatically discarded and the local state is - /// automatically reverted to the most recent state from the server. Unsynchronized changes - /// can then be recovered in the post-client-reset callback block. - /// - /// If ``RLMClientResetModeDiscardLocal`` is enabled but the client reset operation is unable to complete - /// then the client reset process reverts to manual mode. Example) During a destructive schema change this - /// mode will fail and invoke the manual client reset handler. - /// - /// The RLMClientResetModeDiscardLocal mode supports two client reset callbacks -- ``RLMClientResetBeforeBlock``, ``RLMClientResetAfterBlock`` -- which can be passed as arguments when creating the ``RLMSyncConfiguration``. - /// @see: ``RLMClientResetAfterBlock`` and ``RLMClientResetBeforeBlock`` - RLMClientResetModeDiscardLocal __deprecated_enum_msg("Use RLMClientResetModeDiscardUnsyncedChanges") = 1, - /// All unsynchronized local changes are automatically discarded and the local state is - /// automatically reverted to the most recent state from the server. Unsynchronized changes - /// can then be recovered in the post-client-reset callback block. - /// - /// If ``RLMClientResetModeDiscardUnsyncedChanges`` is enabled but the client reset operation is unable to complete - /// then the client reset process reverts to manual mode. Example) During a destructive schema change this - /// mode will fail and invoke the manual client reset handler. - /// - /// The RLMClientResetModeDiscardUnsyncedChanges mode supports two client reset callbacks -- ``RLMClientResetBeforeBlock``, ``RLMClientResetAfterBlock`` -- which can be passed as arguments when creating the ``RLMSyncConfiguration``. - /// @see: ``RLMClientResetAfterBlock`` and ``RLMClientResetBeforeBlock`` - RLMClientResetModeDiscardUnsyncedChanges = 1, - /// The client device will download a realm realm which reflects the latest - /// state of the server after a client reset. A recovery process is run locally in - /// an attempt to integrate the server version with any local changes from - /// before the client reset occurred. - /// - /// The changes are integrated with the following rules: - /// 1. Objects created locally that were not synced before client reset will be integrated. - /// 2. If an object has been deleted on the server, but was modified on the client, the delete takes precedence and the update is discarded - /// 3. If an object was deleted on the client, but not the server, then the client delete instruction is applied. - /// 4. In the case of conflicting updates to the same field, the client update is applied. - /// - /// If the recovery integration fails, the client reset process falls back to ``RLMClientResetModeManual``. - /// The recovery integration will fail if the "Client Recovery" setting is not enabled on the server. - /// Integration may also fail in the event of an incompatible schema change. - /// - /// The RLMClientResetModeRecoverUnsyncedChanges mode supports two client reset callbacks -- ``RLMClientResetBeforeBlock``, ``RLMClientResetAfterBlock`` -- which can be passed as arguments when creating the ``RLMSyncConfiguration``. - /// @see: ``RLMClientResetAfterBlock`` and ``RLMClientResetBeforeBlock`` - RLMClientResetModeRecoverUnsyncedChanges = 2, - /// The client device will download a realm with objects reflecting the latest version of the server. A recovery - /// process is run locally in an attempt to integrate the server version with any local changes from before the - /// client reset occurred. - /// - /// The changes are integrated with the following rules: - /// 1. Objects created locally that were not synced before client reset will be integrated. - /// 2. If an object has been deleted on the server, but was modified on the client, the delete takes precedence and the update is discarded - /// 3. If an object was deleted on the client, but not the server, then the client delete instruction is applied. - /// 4. In the case of conflicting updates to the same field, the client update is applied. - /// - /// If the recovery integration fails, the client reset process falls back to ``RLMClientResetModeDiscardUnsyncedChanges``. - /// The recovery integration will fail if the "Client Recovery" setting is not enabled on the server. - /// Integration may also fail in the event of an incompatible schema change. - /// - /// The RLMClientResetModeRecoverOrDiscardUnsyncedChanges mode supports two client reset callbacks -- ``RLMClientResetBeforeBlock``, ``RLMClientResetAfterBlock`` -- which can be passed as arguments when creating the ``RLMSyncConfiguration``. - /// @see: ``RLMClientResetAfterBlock`` and ``RLMClientResetBeforeBlock`` - RLMClientResetModeRecoverOrDiscardUnsyncedChanges = 3 -}; - -/** - A block type used to report before a client reset will occur. - The `beforeFrozen` is a frozen copy of the local state prior to client reset. - */ -RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void(^RLMClientResetBeforeBlock)(RLMRealm * _Nonnull beforeFrozen); - -/** - A block type used to report after a client reset occurred. - The `beforeFrozen` argument is a frozen copy of the local state prior to client reset. - The `after` argument contains the local database state after the client reset occurred. - */ -RLM_SWIFT_SENDABLE // invoked on a backgroun thread -typedef void(^RLMClientResetAfterBlock)(RLMRealm * _Nonnull beforeFrozen, RLMRealm * _Nonnull after); - -/** - A configuration object representing configuration state for a Realm which is intended to sync with a Realm Object - Server. - */ -@interface RLMSyncConfiguration : NSObject - -/// The user to which the remote Realm belongs. -@property (nonatomic, readonly) RLMUser *user; - -/** - The value this Realm is partitioned on. The partition key is a property defined in - Atlas App Services. All classes with a property with this value will be synchronized to the - Realm. - */ -@property (nonatomic, readonly, nullable) id partitionValue; - -/** - An enum which determines file recovery behavior in the event of a client reset. - @note: Defaults to `RLMClientResetModeRecoverUnsyncedChanges` - - @see: `RLMClientResetMode` - @see: https://docs.mongodb.com/realm/sync/error-handling/client-resets/ -*/ -@property (nonatomic) RLMClientResetMode clientResetMode; - -/** - A callback which notifies prior to prior to a client reset occurring. - @see: `RLMClientResetBeforeBlock` - */ -@property (nonatomic, nullable) RLMClientResetBeforeBlock beforeClientReset; - -/** - A callback which notifies after a client reset has occurred. - @see: `RLMClientResetAfterBlock` - */ -@property (nonatomic, nullable) RLMClientResetAfterBlock afterClientReset; - -/** - A callback that's executed when an `RLMSyncErrorClientResetError` is encountered. - @See RLMSyncErrorReportingBlock and RLMSyncErrorClientResetError for more - details on handling a client reset manually. - */ -@property (nonatomic, nullable) RLMSyncErrorReportingBlock manualClientResetHandler; - -/** - A configuration that controls how initial subscriptions are populated when the Realm is opened. - @see `RLMInitialSubscriptionsConfiguration` - */ -@property (nonatomic, readwrite, nullable) RLMInitialSubscriptionsConfiguration *initialSubscriptions; - -/** - Whether nonfatal connection errors should cancel async opens. - - By default, if a nonfatal connection error such as a connection timing out occurs, any currently pending asyncOpen operations will ignore the error and continue to retry until it succeeds. If this is set to true, the open will instead fail and report the error. - - NEXT-MAJOR: This should be true by default. - */ -@property (nonatomic) bool cancelAsyncOpenOnNonFatalErrors; - -/// :nodoc: -- (instancetype)init __attribute__((unavailable("This type cannot be created directly"))); - -/// :nodoc: -+ (instancetype)new __attribute__((unavailable("This type cannot be created directly"))); - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMSyncConfiguration.mm b/Realm/RLMSyncConfiguration.mm deleted file mode 100644 index 14400ef9c2..0000000000 --- a/Realm/RLMSyncConfiguration.mm +++ /dev/null @@ -1,327 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncConfiguration_Private.hpp" - -#import - -#import "RLMApp_Private.hpp" -#import "RLMBSON_Private.hpp" -#import "RLMError_Private.hpp" -#import "RLMRealm_Private.hpp" -#import "RLMRealmConfiguration_Private.h" -#import "RLMRealmConfiguration_Private.hpp" -#import "RLMRealmUtil.hpp" -#import "RLMSchema_Private.hpp" -#import "RLMSyncManager_Private.hpp" -#import "RLMSyncSession_Private.hpp" -#import "RLMSyncSubscription.h" -#import "RLMSyncUtil_Private.hpp" -#import "RLMUser_Private.hpp" -#import "RLMUtil.hpp" - -#import -#import -#import -#import -#import -#import -#import - -using namespace realm; - -namespace { -using ProtocolError = realm::sync::ProtocolError; - -struct CallbackSchema { - bool dynamic; - RLMSchema *customSchema; -}; - -struct BeforeClientResetWrapper : CallbackSchema { - RLMClientResetBeforeBlock block; - void operator()(std::shared_ptr local) { - @autoreleasepool { - if (local->schema_version() != RLMNotVersioned) { - block([RLMRealm realmWithSharedRealm:local schema:customSchema dynamic:dynamic freeze:true]); - } - } - } -}; - -struct AfterClientResetWrapper : CallbackSchema { - RLMClientResetAfterBlock block; - void operator()(std::shared_ptr local, ThreadSafeReference remote, bool) { - @autoreleasepool { - if (local->schema_version() == RLMNotVersioned) { - return; - } - - RLMRealm *localRealm = [RLMRealm realmWithSharedRealm:local - schema:customSchema - dynamic:dynamic - freeze:true]; - RLMRealm *remoteRealm = [RLMRealm realmWithSharedRealm:Realm::get_shared_realm(std::move(remote)) - schema:customSchema - dynamic:dynamic - freeze:false]; - block(localRealm, remoteRealm); - } - } -}; - -struct InitialSubscriptionsWrapper : CallbackSchema { - RLMFlexibleSyncInitialSubscriptionsBlock block; - void operator()(std::shared_ptr local) { - @autoreleasepool { - RLMRealm *realm = [RLMRealm realmWithSharedRealm:local - schema:customSchema - dynamic:dynamic - freeze:false]; - - RLMSyncSubscriptionSet* subscriptions = realm.subscriptions; - [subscriptions update:^{ - block(subscriptions); - }]; - } - } -}; -} // anonymous namespace - -@interface RLMSyncConfiguration () { - std::unique_ptr _config; - RLMSyncErrorReportingBlock _manualClientResetHandler; -} - -@end - -@implementation RLMSyncConfiguration - -@dynamic stopPolicy; - -- (instancetype)initWithRawConfig:(realm::SyncConfig)config path:(std::string const&)path { - if (self = [super init]) { - _config = std::make_unique(std::move(config)); - _path = path; - } - return self; -} - -- (BOOL)isEqual:(id)object { - if (![object isKindOfClass:[RLMSyncConfiguration class]]) { - return NO; - } - RLMSyncConfiguration *that = (RLMSyncConfiguration *)object; - return [self.partitionValue isEqual:that.partitionValue] - && [self.user isEqual:that.user] - && self.stopPolicy == that.stopPolicy; -} - -- (realm::SyncConfig&)rawConfiguration { - return *_config; -} - -- (RLMUser *)user { - return [[RLMUser alloc] initWithUser:_config->user]; -} - -- (RLMSyncStopPolicy)stopPolicy { - return translateStopPolicy(_config->stop_policy); -} - -- (void)setStopPolicy:(RLMSyncStopPolicy)stopPolicy { - _config->stop_policy = translateStopPolicy(stopPolicy); -} - -- (RLMClientResetMode)clientResetMode { - return RLMClientResetMode(_config->client_resync_mode); -} - -- (void)setClientResetMode:(RLMClientResetMode)clientResetMode { - _config->client_resync_mode = realm::ClientResyncMode(clientResetMode); -} - -- (RLMClientResetBeforeBlock)beforeClientReset { - if (_config->notify_before_client_reset) { - auto wrapper = _config->notify_before_client_reset.target(); - return wrapper->block; - } else { - return nil; - } -} - -- (void)setBeforeClientReset:(RLMClientResetBeforeBlock)beforeClientReset { - if (!beforeClientReset) { - _config->notify_before_client_reset = nullptr; - } else if (self.clientResetMode == RLMClientResetModeManual) { - @throw RLMException(@"RLMClientResetBeforeBlock reset notifications are not supported in Manual mode. Use RLMSyncConfiguration.manualClientResetHandler or RLMSyncManager.ErrorHandler"); - } else { - _config->freeze_before_reset_realm = false; - _config->notify_before_client_reset = BeforeClientResetWrapper{.block = beforeClientReset}; - } -} - -- (RLMClientResetAfterBlock)afterClientReset { - if (_config->notify_after_client_reset) { - auto wrapper = _config->notify_after_client_reset.target(); - return wrapper->block; - } else { - return nil; - } -} - -- (void)setAfterClientReset:(RLMClientResetAfterBlock)afterClientReset { - if (!afterClientReset) { - _config->notify_after_client_reset = nullptr; - } else if (self.clientResetMode == RLMClientResetModeManual) { - @throw RLMException(@"RLMClientResetAfterBlock reset notifications are not supported in Manual mode. Use RLMSyncConfiguration.manualClientResetHandler or RLMSyncManager.ErrorHandler"); - } else { - _config->notify_after_client_reset = AfterClientResetWrapper{.block = afterClientReset}; - } -} - -- (RLMSyncErrorReportingBlock)manualClientResetHandler { - return _manualClientResetHandler; -} - -- (void)setManualClientResetHandler:(RLMSyncErrorReportingBlock)manualClientReset { - if (!manualClientReset) { - _manualClientResetHandler = nil; - } else if (self.clientResetMode != RLMClientResetModeManual) { - @throw RLMException(@"A manual client reset handler can only be set with RLMClientResetModeManual"); - } else { - _manualClientResetHandler = manualClientReset; - } - [self assignConfigErrorHandler:self.user]; -} - -- (RLMInitialSubscriptionsConfiguration *)initialSubscriptions { - if (_config->subscription_initializer) { - auto wrapper = _config->subscription_initializer.target(); - - return [[RLMInitialSubscriptionsConfiguration alloc] initWithCallback:wrapper->block - rerunOnOpen:_config->rerun_init_subscription_on_open]; - } - - return nil; -} - -- (void)setInitialSubscriptions:(RLMInitialSubscriptionsConfiguration *)initialSubscriptions { - if (initialSubscriptions) { - _config->subscription_initializer = InitialSubscriptionsWrapper{.block = initialSubscriptions.callback}; - _config->rerun_init_subscription_on_open = initialSubscriptions.rerunOnOpen; - } else { - _config->subscription_initializer = nil; - } -} - -void RLMSetConfigInfoForClientResetCallbacks(realm::SyncConfig& syncConfig, RLMRealmConfiguration *config) { - if (syncConfig.notify_before_client_reset) { - auto before = syncConfig.notify_before_client_reset.target(); - before->dynamic = config.dynamic; - before->customSchema = config.customSchema; - } - if (syncConfig.notify_after_client_reset) { - auto after = syncConfig.notify_after_client_reset.target(); - after->dynamic = config.dynamic; - after->customSchema = config.customSchema; - } - if (syncConfig.subscription_initializer) { - auto initializer = syncConfig.subscription_initializer.target(); - initializer->dynamic = config.dynamic; - initializer->customSchema = config.customSchema; - } -} - -- (id)partitionValue { - if (!_config->partition_value.empty()) { - return RLMConvertBsonToRLMBSON(realm::bson::parse(_config->partition_value)); - } - return nil; -} - -- (bool)cancelAsyncOpenOnNonFatalErrors { - return _config->cancel_waits_on_nonfatal_error; -} - -- (void)setCancelAsyncOpenOnNonFatalErrors:(bool)cancelAsyncOpenOnNonFatalErrors { - _config->cancel_waits_on_nonfatal_error = cancelAsyncOpenOnNonFatalErrors; -} - -- (void)assignConfigErrorHandler:(RLMUser *)user { - RLMSyncManager *manager = user.app.syncManager; - __weak RLMSyncManager *weakManager = manager; - RLMSyncErrorReportingBlock resetHandler = self.manualClientResetHandler; - _config->error_handler = [weakManager, resetHandler](std::shared_ptr errored_session, SyncError error) { - RLMSyncErrorReportingBlock errorHandler; - if (error.is_client_reset_requested()) { - errorHandler = resetHandler; - } - if (!errorHandler) { - @autoreleasepool { - errorHandler = weakManager.errorHandler; - } - } - if (!errorHandler) { - return; - } - NSError *nsError = makeError(std::move(error), - static_cast(errored_session->user().get())->app()); - if (!nsError) { - return; - } - RLMSyncSession *session = [[RLMSyncSession alloc] initWithSyncSession:errored_session]; - dispatch_async(dispatch_get_main_queue(), ^{ - // Keep the SyncSession alive until the callback completes as - // RLMSyncSession only holds a weak reference - static_cast(errored_session); - errorHandler(nsError, session); - }); - }; -}; - -static void setDefaults(SyncConfig& config, RLMUser *user) { - config.client_resync_mode = ClientResyncMode::Recover; - config.stop_policy = SyncSessionStopPolicy::AfterChangesUploaded; - [user.app.syncManager populateConfig:config]; -} - -- (instancetype)initWithUser:(RLMUser *)user - partitionValue:(nullable id)partitionValue { - if (self = [super init]) { - std::stringstream s; - s << RLMConvertRLMBSONToBson(partitionValue); - _config = std::make_unique(user.user, s.str()); - _path = [user pathForPartitionValue:_config->partition_value]; - setDefaults(*_config, user); - [self assignConfigErrorHandler:user]; - } - return self; -} - -- (instancetype)initWithUser:(RLMUser *)user { - if (self = [super init]) { - _config = std::make_unique(user.user, SyncConfig::FLXSyncEnabled{}); - _path = [user pathForFlexibleSync]; - setDefaults(*_config, user); - [self assignConfigErrorHandler:user]; - } - return self; -} - -@end diff --git a/Realm/RLMSyncConfiguration_Private.h b/Realm/RLMSyncConfiguration_Private.h deleted file mode 100644 index 62d0e1f1f0..0000000000 --- a/Realm/RLMSyncConfiguration_Private.h +++ /dev/null @@ -1,47 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -typedef RLM_CLOSED_ENUM(NSUInteger, RLMSyncStopPolicy) { - RLMSyncStopPolicyImmediately, - RLMSyncStopPolicyLiveIndefinitely, - RLMSyncStopPolicyAfterChangesUploaded, -}; - - -@class RLMSchema; - -@interface RLMSyncConfiguration () - -// Flexible sync -- (instancetype)initWithUser:(RLMUser *)user; -// Partition-based sync -- (instancetype)initWithUser:(RLMUser *)user - partitionValue:(nullable id)partitionValue; - -// Internal-only APIs -@property (nonatomic, readwrite) RLMSyncStopPolicy stopPolicy; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMSyncConfiguration_Private.hpp b/Realm/RLMSyncConfiguration_Private.hpp deleted file mode 100644 index d74c21764b..0000000000 --- a/Realm/RLMSyncConfiguration_Private.hpp +++ /dev/null @@ -1,43 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncConfiguration_Private.h" - -#import - -namespace realm { -class SyncSession; -struct SyncConfig; -struct SyncError; -using SyncSessionErrorHandler = void(std::shared_ptr, SyncError); -} - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@interface RLMSyncConfiguration () -- (instancetype)initWithRawConfig:(realm::SyncConfig)config path:(std::string const&)path; -- (realm::SyncConfig&)rawConfiguration; - -// Pass the RLMRealmConfiguration to it's sync configuration so client reset callbacks -// can access schema, dynamic, and path properties. -void RLMSetConfigInfoForClientResetCallbacks(realm::SyncConfig& syncConfig, RLMRealmConfiguration *config); - -@property (nonatomic, direct) std::string path; -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMSyncManager.h b/Realm/RLMSyncManager.h deleted file mode 100644 index 1dee453d0d..0000000000 --- a/Realm/RLMSyncManager.h +++ /dev/null @@ -1,223 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -@class RLMSyncSession, RLMSyncTimeoutOptions, RLMAppConfiguration; - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -// NEXT-MAJOR: This enum needs to be removed when access to the logger is removed -// from the sync manager. -/// An enum representing different levels of sync-related logging that can be configured. -typedef RLM_CLOSED_ENUM(NSUInteger, RLMSyncLogLevel) { - /// Nothing will ever be logged. - RLMSyncLogLevelOff, - /// Only fatal errors will be logged. - RLMSyncLogLevelFatal, - /// Only errors will be logged. - RLMSyncLogLevelError, - /// Warnings and errors will be logged. - RLMSyncLogLevelWarn, - /// Information about sync events will be logged. Fewer events will be logged in order to avoid overhead. - RLMSyncLogLevelInfo, - /// Information about sync events will be logged. More events will be logged than with `RLMSyncLogLevelInfo`. - RLMSyncLogLevelDetail, - /// Log information that can aid in debugging. - /// - /// - warning: Will incur a measurable performance impact. - RLMSyncLogLevelDebug, - /// Log information that can aid in debugging. More events will be logged than with `RLMSyncLogLevelDebug`. - /// - /// - warning: Will incur a measurable performance impact. - RLMSyncLogLevelTrace, - /// Log information that can aid in debugging. More events will be logged than with `RLMSyncLogLevelTrace`. - /// - /// - warning: Will incur a measurable performance impact. - RLMSyncLogLevelAll -}; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -/// A log callback function which can be set on RLMSyncManager. -/// -/// The log function may be called from multiple threads simultaneously, and is -/// responsible for performing its own synchronization if any is required. -RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void (^RLMSyncLogFunction)(RLMSyncLogLevel level, NSString *message); -#pragma clang diagnostic pop - -/// A block type representing a block which can be used to report a sync-related error to the application. If the error -/// pertains to a specific session, that session will also be passed into the block. -RLM_SWIFT_SENDABLE // invoked on a background thread -typedef void(^RLMSyncErrorReportingBlock)(NSError *, RLMSyncSession * _Nullable); - -/** - A manager which serves as a central point for sync-related configuration. - */ -RLM_SWIFT_SENDABLE RLM_FINAL // is internally thread-safe -@interface RLMSyncManager : NSObject - -/** - A block which can optionally be set to report sync-related errors to your application. - - Any error reported through this block will be of the `RLMSyncError` type, and marked - with the `RLMSyncErrorDomain` domain. - - Errors reported through this mechanism are fatal, with several exceptions. Please consult - `RLMSyncError` for information about the types of errors that can be reported through - the block, and for for suggestions on handling recoverable error codes. - - @see `RLMSyncError` - */ -@property (nullable, atomic, copy) RLMSyncErrorReportingBlock errorHandler; - -/// :nodoc: -@property (nonatomic, copy) NSString *appID -__attribute__((deprecated("This property is not used for anything"))); - -/** - A string identifying this application which is included in the User-Agent - header of sync connections. By default, this will be the application's bundle - identifier. - - This property must be set prior to opening a synchronized Realm for the first - time. Any modifications made after opening a Realm will be ignored. - */ -@property (atomic, copy) NSString *userAgent; - -/** - The logging threshold which newly opened synced Realms will use. Defaults to - `RLMSyncLogLevelInfo`. - - By default logging strings are output to Apple System Logger. Set `logger` to - perform custom logging logic instead. - - @warning This property must be set before any synced Realms are opened. Setting it after - opening any synced Realm will do nothing. - */ -@property (atomic) RLMSyncLogLevel logLevel -__attribute__((deprecated("Use `RLMLogger.default.level`/`Logger.shared.level` to set/get the default logger threshold level."))); - -/** - The function which will be invoked whenever the sync client has a log message. - - If nil, log strings are output to Apple System Logger instead. - - @warning This property must be set before any synced Realms are opened. Setting - it after opening any synced Realm will do nothing. - */ -@property (atomic, nullable) RLMSyncLogFunction logger -__attribute__((deprecated("Use `RLMLogger.default`/`Logger.shared` to set/get the default logger."))); - -/** - The name of the HTTP header to send authorization data in when making requests to Atlas App Services which has - been configured to expect a custom authorization header. - */ -@property (nullable, atomic, copy) NSString *authorizationHeaderName; - -/** - Extra HTTP headers to append to every request to Atlas App Services. - - Modifying this property while sync sessions are active will result in all - sessions disconnecting and reconnecting using the new headers. - */ -@property (nullable, atomic, copy) NSDictionary *customRequestHeaders; - -/** - Options for the assorted types of connection timeouts for sync connections. - - If nil default values for all timeouts are used instead. - - @warning This property must be set before any synced Realms are opened. Setting - it after opening any synced Realm will do nothing. - */ -@property (nullable, atomic, copy) RLMSyncTimeoutOptions *timeoutOptions; - -/// :nodoc: -- (instancetype)init __attribute__((unavailable("RLMSyncManager cannot be created directly"))); - -/// :nodoc: -+ (instancetype)new __attribute__((unavailable("RLMSyncManager cannot be created directly"))); - -@end - -/** - Options for configuring timeouts and intervals in the sync client. - */ -@interface RLMSyncTimeoutOptions : NSObject -/// The maximum number of milliseconds 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. -@property (nonatomic) NSUInteger connectTimeout; - -/// The number of milliseconds 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. -@property (nonatomic) NSUInteger connectionLingerTime; - -/// The number of milliseconds 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. -@property (nonatomic) NSUInteger pingKeepalivePeriod; - -/// How long in milliseconds to wait for a reponse 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. -@property (nonatomic) NSUInteger pongKeepaliveTimeout; - -/// The maximum amount of time, in milliseconds, 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. -@property (nonatomic) NSUInteger fastReconnectLimit; -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMSyncManager.mm b/Realm/RLMSyncManager.mm deleted file mode 100644 index 6a76b62b87..0000000000 --- a/Realm/RLMSyncManager.mm +++ /dev/null @@ -1,269 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncManager_Private.hpp" - -#import "RLMApp_Private.hpp" -#import "RLMSyncSession_Private.hpp" -#import "RLMUser_Private.hpp" -#import "RLMSyncUtil_Private.hpp" -#import "RLMUtil.hpp" - -#import -#import -#import - -#if !defined(REALM_COCOA_VERSION) -#import "RLMVersion.h" -#endif - -#include - -using namespace realm; - -// NEXT-MAJOR: All the code associated to the logger from sync manager should be removed. -using Level = realm::util::Logger::Level; - -namespace { -Level levelForSyncLogLevel(RLMSyncLogLevel logLevel) { - switch (logLevel) { - case RLMSyncLogLevelOff: return Level::off; - case RLMSyncLogLevelFatal: return Level::fatal; - case RLMSyncLogLevelError: return Level::error; - case RLMSyncLogLevelWarn: return Level::warn; - case RLMSyncLogLevelInfo: return Level::info; - case RLMSyncLogLevelDetail: return Level::detail; - case RLMSyncLogLevelDebug: return Level::debug; - case RLMSyncLogLevelTrace: return Level::trace; - case RLMSyncLogLevelAll: return Level::all; - } - REALM_UNREACHABLE(); // Unrecognized log level. -} - -RLMSyncLogLevel logLevelForLevel(Level logLevel) { - switch (logLevel) { - case Level::off: return RLMSyncLogLevelOff; - case Level::fatal: return RLMSyncLogLevelFatal; - case Level::error: return RLMSyncLogLevelError; - case Level::warn: return RLMSyncLogLevelWarn; - case Level::info: return RLMSyncLogLevelInfo; - case Level::detail: return RLMSyncLogLevelDetail; - case Level::debug: return RLMSyncLogLevelDebug; - case Level::trace: return RLMSyncLogLevelTrace; - case Level::all: return RLMSyncLogLevelAll; - } - REALM_UNREACHABLE(); // Unrecognized log level. -} - -#pragma mark - Loggers - -struct CocoaSyncLogger : public realm::util::Logger { - void do_log(const realm::util::LogCategory&, Level, const std::string& message) override { - NSLog(@"Sync: %@", RLMStringDataToNSString(message)); - } -}; - -static std::unique_ptr defaultSyncLogger(realm::util::Logger::Level level) { - auto logger = std::make_unique(); - logger->set_level_threshold(level); - return std::move(logger); -} - -struct CallbackLogger : public realm::util::Logger { - RLMSyncLogFunction logFn; - void do_log(const realm::util::LogCategory&, Level level, const std::string& message) override { - @autoreleasepool { - logFn(logLevelForLevel(level), RLMStringDataToNSString(message)); - } - } -}; - -} // anonymous namespace - -std::shared_ptr RLMWrapLogFunction(RLMSyncLogFunction fn) { - auto logger = std::make_shared(); - logger->logFn = fn; - logger->set_level_threshold(Level::all); - return logger; -} - -#pragma mark - RLMSyncManager - -@implementation RLMSyncManager { - RLMUnfairMutex _mutex; - std::shared_ptr _syncManager; - NSDictionary *_customRequestHeaders; - RLMSyncLogFunction _logger; -} - -- (instancetype)initWithSyncManager:(std::shared_ptr)syncManager { - if (self = [super init]) { - _syncManager = syncManager; - return self; - } - return nil; -} - -- (NSDictionary *)customRequestHeaders { - std::lock_guard lock(_mutex); - return _customRequestHeaders; -} - -- (void)setCustomRequestHeaders:(NSDictionary *)customRequestHeaders { - { - std::lock_guard lock(_mutex); - _customRequestHeaders = customRequestHeaders.copy; - } - - for (auto&& session : _syncManager->get_all_sessions()) { - auto config = session->config(); - config.custom_http_headers.clear(); - for (NSString *key in customRequestHeaders) { - config.custom_http_headers.emplace(key.UTF8String, customRequestHeaders[key].UTF8String); - } - session->update_configuration(std::move(config)); - } -} - -- (RLMSyncLogFunction)logger { - std::lock_guard lock(_mutex); - return _logger; -} - -- (void)setLogger:(RLMSyncLogFunction)logFn { - { - std::lock_guard lock(_mutex); - _logger = logFn; - } - if (logFn) { - _syncManager->set_logger_factory([logFn](realm::util::Logger::Level level) { - auto logger = std::make_unique(); - logger->logFn = logFn; - logger->set_level_threshold(level); - return logger; - }); - } - else { - _syncManager->set_logger_factory(defaultSyncLogger); - } -} - -#pragma mark - Passthrough properties - -- (NSString *)userAgent { - return @(_syncManager->config().user_agent_application_info.c_str()); -} - -- (void)setUserAgent:(NSString *)userAgent { - _syncManager->set_user_agent(RLMStringDataWithNSString(userAgent)); -} - -- (RLMSyncTimeoutOptions *)timeoutOptions { - return [[RLMSyncTimeoutOptions alloc] initWithOptions:_syncManager->config().timeouts]; -} - -- (void)setTimeoutOptions:(RLMSyncTimeoutOptions *)timeoutOptions { - _syncManager->set_timeouts(timeoutOptions->_options); -} - -- (RLMSyncLogLevel)logLevel { - return logLevelForLevel(_syncManager->log_level()); -} - -- (void)setLogLevel:(RLMSyncLogLevel)logLevel { - _syncManager->set_log_level(levelForSyncLogLevel(logLevel)); -} - -#pragma mark - Private API - -- (void)resetForTesting { - _errorHandler = nil; - _logger = nil; - _syncManager->tear_down_for_testing(); -} - -- (std::shared_ptr const&)syncManager { - return _syncManager; -} - -- (void)waitForSessionTermination { - _syncManager->wait_for_sessions_to_terminate(); -} - -- (void)populateConfig:(realm::SyncConfig&)config { - @synchronized (self) { - if (_authorizationHeaderName) { - config.authorization_header_name.emplace(_authorizationHeaderName.UTF8String); - } - [_customRequestHeaders enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *header, BOOL *) { - config.custom_http_headers.emplace(key.UTF8String, header.UTF8String); - }]; - } -} - -- (bool)hasAnySessions { - return _syncManager->get_all_sessions().size() > 0; -} -@end - -#pragma mark - RLMSyncTimeoutOptions - -@implementation RLMSyncTimeoutOptions -- (instancetype)initWithOptions:(realm::SyncClientTimeouts)options { - if (self = [super init]) { - _options = options; - } - return self; -} - -- (NSUInteger)connectTimeout { - return static_cast(_options.connect_timeout); -} -- (void)setConnectTimeout:(NSUInteger)connectTimeout { - _options.connect_timeout = connectTimeout; -} - -- (NSUInteger)connectLingerTime { - return static_cast(_options.connection_linger_time); -} -- (void)setConnectionLingerTime:(NSUInteger)connectionLingerTime { - _options.connection_linger_time = connectionLingerTime; -} - -- (NSUInteger)pingKeepalivePeriod { - return static_cast(_options.ping_keepalive_period); -} -- (void)setPingKeepalivePeriod:(NSUInteger)pingKeepalivePeriod { - _options.ping_keepalive_period = pingKeepalivePeriod; -} - -- (NSUInteger)pongKeepaliveTimeout { - return static_cast(_options.pong_keepalive_timeout); -} -- (void)setPongKeepaliveTimeout:(NSUInteger)pongKeepaliveTimeout { - _options.pong_keepalive_timeout = pongKeepaliveTimeout; -} - -- (NSUInteger)fastReconnectLimit { - return static_cast(_options.fast_reconnect_limit); -} -- (void)setFastReconnectLimit:(NSUInteger)fastReconnectLimit { - _options.fast_reconnect_limit = fastReconnectLimit; -} - -@end diff --git a/Realm/RLMSyncManager_Private.hpp b/Realm/RLMSyncManager_Private.hpp deleted file mode 100644 index 855e8f93a0..0000000000 --- a/Realm/RLMSyncManager_Private.hpp +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#import "RLMNetworkTransport.h" - -#import -#import - -namespace realm { -struct SyncConfig; -class SyncManager; -namespace app { -class App; -} -namespace util { -class Logger; -} -} - -@class RLMAppConfiguration, RLMUser, RLMSyncConfiguration; - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@interface RLMSyncManager () -- (std::shared_ptr const&)syncManager; -- (instancetype)initWithSyncManager:(std::shared_ptr)syncManager; - -- (bool)hasAnySessions; -- (void)resetForTesting; -- (void)waitForSessionTermination; -- (void)populateConfig:(realm::SyncConfig&)config; -@end - -RLM_DIRECT_MEMBERS -@interface RLMSyncTimeoutOptions () { - @public - realm::SyncClientTimeouts _options; -} -- (instancetype)initWithOptions:(realm::SyncClientTimeouts)options; -@end - -std::shared_ptr RLMWrapLogFunction(RLMSyncLogFunction); - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMSyncSession.h b/Realm/RLMSyncSession.h deleted file mode 100644 index 6b28046d8f..0000000000 --- a/Realm/RLMSyncSession.h +++ /dev/null @@ -1,326 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -/** - The current state of the session represented by a session object. - */ -typedef NS_ENUM(NSUInteger, RLMSyncSessionState) { - /// The sync session is actively communicating or attempting to communicate - /// with Atlas App Services. A session is considered Active even if - /// it is not currently connected. Check the connection state instead if you - /// wish to know if the connection is currently online. - RLMSyncSessionStateActive, - /// The sync session is not attempting to communicate with MongoDB - /// Realm due to the user logging out or synchronization being paused. - RLMSyncSessionStateInactive, - /// The sync session encountered a fatal error and is permanently invalid; it should be discarded. - RLMSyncSessionStateInvalid -}; - -/** - The current state of a sync session's connection. Sessions which are not in - the Active state will always be Disconnected. - */ -typedef NS_ENUM(NSUInteger, RLMSyncConnectionState) { - /// The sync session is not connected to the server, and is not attempting - /// to connect, either because the session is inactive or because it is - /// waiting to retry after a failed connection. - RLMSyncConnectionStateDisconnected, - /// The sync session is attempting to connect to Atlas App Services. - RLMSyncConnectionStateConnecting, - /// The sync session is currently connected to Atlas App Services. - RLMSyncConnectionStateConnected, -}; - -/** - The transfer direction (upload or download) tracked by a given progress notification block. - - Progress notification blocks can be registered on sessions if your app wishes to be informed - how many bytes have been uploaded or downloaded, for example to show progress indicator UIs. - */ -typedef RLM_CLOSED_ENUM(NSUInteger, RLMSyncProgressDirection) { - /// For monitoring upload progress. - RLMSyncProgressDirectionUpload, - /// For monitoring download progress. - RLMSyncProgressDirectionDownload, -}; - -/** - The desired behavior of a progress notification block. - - Progress notification blocks can be registered on sessions if your app wishes to be informed - how many bytes have been uploaded or downloaded, for example to show progress indicator UIs. - */ -typedef NS_ENUM(NSUInteger, RLMSyncProgressMode) { - /** - The block will be called indefinitely, or until it is unregistered by calling - `-[RLMProgressNotificationToken invalidate]`. - - Notifications will always report the latest number of transferred bytes, and the - most up-to-date number of total transferrable bytes. - */ - RLMSyncProgressModeReportIndefinitely, - /** - The block will, upon registration, store the total number of bytes - to be transferred. When invoked, it will always report the most up-to-date number - of transferrable bytes out of that original number of transferrable bytes. - - When the number of transferred bytes reaches or exceeds the - number of transferrable bytes, the block will be unregistered. - */ - RLMSyncProgressModeForCurrentlyOutstandingWork, -}; - -@class RLMUser, RLMSyncConfiguration, RLMSyncErrorActionToken, RLMSyncManager; - -/** - The type of a progress notification block intended for reporting a session's network - activity to the user. - - `transferredBytes` refers to the number of bytes that have been uploaded or downloaded. - `transferrableBytes` refers to the total number of bytes transferred, and pending transfer. - */ -typedef void(^RLMProgressNotificationBlock)(NSUInteger transferredBytes, NSUInteger transferrableBytes) __attribute__((deprecated("Use RLMSyncProgressNotificationBlock instead", "RLMSyncProgressNotificationBlock"))); - -/** - A struct encapsulating progress information. - */ -typedef struct RLMSyncProgress { - /// The number of bytes that have been transferred. - NSUInteger transferredBytes; - - /** - The total number of transferrable bytes (bytes that have been transferred, - plus bytes pending transfer). - */ - NSUInteger transferrableBytes; - - /** - A value between 0.0 and 1.0 representing the estimated transfer progress. This value is precise for - uploads, but will be based on historical data and certain heuristics applied by the server for downloads. - - Whenever the progress reporting mode is `forCurrentlyOutstandingWork`, that value - will monotonically increase until it reaches 1.0. If the progress mode is `reportIndefinitely`, the - value may either increase or decrease as new data needs to be transferred. - */ - double progressEstimate; -} RLMSyncProgress; - -/** - The type of a progress notification block intended for reporting a session's network - activity to the user. - */ -typedef void(^RLMSyncProgressNotificationBlock)(RLMSyncProgress progress); - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -/** - A token object corresponding to a progress notification block on a session object. - - To stop notifications manually, call `-invalidate` on it. Notifications should be stopped before - the token goes out of scope or is destroyed. - */ -RLM_SWIFT_SENDABLE RLM_FINAL // is internally thread-safe -@interface RLMProgressNotificationToken : RLMNotificationToken -@end - -/** - An object encapsulating an Atlas App Services "session". Sessions represent the - communication between the client (and a local Realm file on disk), and the server - (and a remote Realm with a given partition value stored on Atlas App Services). - - Sessions are always created by the SDK and vended out through various APIs. The - lifespans of sessions associated with Realms are managed automatically. Session - objects can be accessed from any thread. - */ -RLM_SWIFT_SENDABLE RLM_FINAL // is internally thread-safe -@interface RLMSyncSession : NSObject - -/// The session's current state. -/// -/// This property is not KVO-compliant. -@property (nonatomic, readonly) RLMSyncSessionState state; - -/// The session's current connection state. -/// -/// This property is KVO-compliant and can be observed to be notified of changes. -/// Be warned that KVO observers for this property may be called on a background -/// thread. -@property (atomic, readonly) RLMSyncConnectionState connectionState; - -/// The user that owns this session. -- (nullable RLMUser *)parentUser; - -/** - If the session is valid, return a sync configuration that can be used to open the Realm - associated with this session. - */ -- (nullable RLMSyncConfiguration *)configuration; - -/** - Temporarily suspend syncronization and disconnect from the server. - - The session will not attempt to connect to Atlas App Services until `resume` - is called or the Realm file is closed and re-opened. - */ -- (void)suspend; - -/** - Resume syncronization and reconnect to Atlas App Services after suspending. - - This is a no-op if the session was already active or if the session is invalid. - Newly created sessions begin in the Active state and do not need to be resumed. - */ -- (void)resume; - -/** - Request an immediate reconnection to the server if the session is disconnected. - - Realm automatically reconnects after disconnects with an exponential backoff, - which is reset when the reachability handler reports a network status change. - In some scenarios an application may wish to skip the reconnect delay, such as - when an application receives the DidBecomeActive notification, which can be - done by calling this method. Calling this method is never required. - - This method is asynchronous and merely skips the current reconnect delay, so - the connection state will still normally be disconnected immediately after - calling it. - - Has no effect if the session is currently connected. - */ -- (void)reconnect; - -/** - Register a progress notification block. - - Multiple blocks can be registered with the same session at once. Each block - will be invoked on a side queue devoted to progress notifications. - - If the session has already received progress information from the - synchronization subsystem, the block will be called immediately. Otherwise, it - will be called as soon as progress information becomes available. - - The token returned by this method must be retained as long as progress - notifications are desired, and the `-invalidate` method should be called on it - when notifications are no longer needed and before the token is destroyed. - - If no token is returned, the notification block will never be called again. - There are a number of reasons this might be true. If the session has previously - experienced a fatal error it will not accept progress notification blocks. If - the block was configured in the `RLMSyncProgressForCurrentlyOutstandingWork` - mode but there is no additional progress to report (for example, the number - of transferrable bytes and transferred bytes are equal), the block will not be - called again. - - @param direction The transfer direction (upload or download) to track in this progress notification block. - @param mode The desired behavior of this progress notification block. - @param block The block to invoke when notifications are available. - - @return A token which must be held for as long as you want notifications to be delivered. - - @see `RLMSyncProgressDirection`, `RLMSyncProgress`, `RLMProgressNotificationBlock`, `RLMProgressNotificationToken` - */ -- (nullable RLMProgressNotificationToken *)addProgressNotificationForDirection:(RLMSyncProgressDirection)direction - mode:(RLMSyncProgressMode)mode -block:(RLMProgressNotificationBlock)block __attribute__((deprecated("Use addSyncProgressNotificationForDirection instead", "addSyncProgressNotificationForDirection"))) - NS_REFINED_FOR_SWIFT; - - -/** - Register a progress notification block. - - Multiple blocks can be registered with the same session at once. Each block - will be invoked on a side queue devoted to progress notifications. - - If the session has already received progress information from the - synchronization subsystem, the block will be called immediately. Otherwise, it - will be called as soon as progress information becomes available. - - The token returned by this method must be retained as long as progress - notifications are desired, and the `-invalidate` method should be called on it - when notifications are no longer needed and before the token is destroyed. - - If no token is returned, the notification block will never be called again. - There are a number of reasons this might be true. If the session has previously - experienced a fatal error it will not accept progress notification blocks. If - the block was configured in the `RLMSyncProgressForCurrentlyOutstandingWork` - mode but there is no additional progress to report (for example, the number - of transferrable bytes and transferred bytes are equal), the block will not be - called again. - - @param direction The transfer direction (upload or download) to track in this progress notification block. - @param mode The desired behavior of this progress notification block. - @param block The block to invoke when notifications are available. - - @return A token which must be held for as long as you want notifications to be delivered. - - @see ``RLMSyncProgressDirection``, ``RLMSyncProgress``, ``RLMSyncProgressNotificationBlock``, ``RLMProgressNotificationToken`` - */ -- (nullable RLMProgressNotificationToken *)addSyncProgressNotificationForDirection:(RLMSyncProgressDirection)direction - mode:(RLMSyncProgressMode)mode - block:(RLMSyncProgressNotificationBlock)block - NS_REFINED_FOR_SWIFT; - - -/// Wait for pending uploads to complete or the session to expire, and dispatch the callback onto the specified queue. -- (BOOL)waitForUploadCompletionOnQueue:(nullable dispatch_queue_t)queue callback:(void(^)(NSError * _Nullable))callback NS_REFINED_FOR_SWIFT; - -/// Wait for pending downloads to complete or the session to expire, and dispatch the callback onto the specified queue. -- (BOOL)waitForDownloadCompletionOnQueue:(nullable dispatch_queue_t)queue callback:(void(^)(NSError * _Nullable))callback NS_REFINED_FOR_SWIFT; - -/// :nodoc: -+ (void)immediatelyHandleError:(RLMSyncErrorActionToken *)token syncManager:(RLMSyncManager *)syncManager - __attribute__((deprecated("The syncManager: parameter is no longer required"))); - -/** - Given an error action token, immediately handle the corresponding action. - - @see ```RLMSyncErrorClientResetError``, ``RLMSyncErrorPermissionDeniedError`` - */ -+ (void)immediatelyHandleError:(RLMSyncErrorActionToken *)token; - -/** - Get the sync session for the given Realm if it is a synchronized Realm, or `nil` - if it is not. - */ -+ (nullable RLMSyncSession *)sessionForRealm:(RLMRealm *)realm; - -@end - -// MARK: - Error action token - -#pragma mark - Error action token - -/** - An opaque token returned as part of certain errors. It can be - passed into certain APIs to perform certain actions. - - @see `RLMSyncErrorClientResetError`, `RLMSyncErrorPermissionDeniedError` - */ -RLM_SWIFT_SENDABLE RLM_FINAL -@interface RLMSyncErrorActionToken : NSObject -/// :nodoc: -- (instancetype)init __attribute__((unavailable("This type cannot be created directly"))); - -/// :nodoc: -+ (instancetype)new __attribute__((unavailable("This type cannot be created directly"))); -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMSyncSession.mm b/Realm/RLMSyncSession.mm deleted file mode 100644 index b5f02c551f..0000000000 --- a/Realm/RLMSyncSession.mm +++ /dev/null @@ -1,289 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncSession_Private.hpp" - -#import "RLMApp.h" -#import "RLMRealm_Private.hpp" -#import "RLMError_Private.hpp" -#import "RLMSyncConfiguration_Private.hpp" -#import "RLMUser_Private.hpp" -#import "RLMSyncManager_Private.hpp" -#import "RLMSyncUtil_Private.hpp" - -#import -#import - -using namespace realm; - -@interface RLMSyncErrorActionToken () { -@public - std::string _originalPath; - std::shared_ptr _app; -} -@end - -@interface RLMProgressNotificationToken() { - uint64_t _token; - std::shared_ptr _session; -} -@end - -@implementation RLMProgressNotificationToken - -- (void)suppressNextNotification { - // No-op, but implemented in case this token is passed to - // `-[RLMRealm commitWriteTransactionWithoutNotifying:]`. -} - -- (bool)invalidate { - if (_session) { - _session->unregister_progress_notifier(_token); - _session.reset(); - _token = 0; - return true; - } - return false; -} - -- (nullable instancetype)initWithTokenValue:(uint64_t)token - session:(std::shared_ptr)session { - if (token == 0) { - return nil; - } - if (self = [super init]) { - _token = token; - _session = session; - return self; - } - return nil; -} - -@end - -@interface RLMSyncSession () -@property (class, nonatomic, readonly) dispatch_queue_t notificationsQueue; -@property (atomic, readwrite) RLMSyncConnectionState connectionState; -@end - -@implementation RLMSyncSession - -+ (dispatch_queue_t)notificationsQueue { - static auto queue = dispatch_queue_create("io.realm.sync.sessionsNotificationQueue", DISPATCH_QUEUE_SERIAL); - return queue; -} - -static RLMSyncConnectionState convertConnectionState(SyncSession::ConnectionState state) { - switch (state) { - case SyncSession::ConnectionState::Disconnected: return RLMSyncConnectionStateDisconnected; - case SyncSession::ConnectionState::Connecting: return RLMSyncConnectionStateConnecting; - case SyncSession::ConnectionState::Connected: return RLMSyncConnectionStateConnected; - } -} - -- (instancetype)initWithSyncSession:(std::shared_ptr const&)session { - if (self = [super init]) { - _session = session; - _connectionState = convertConnectionState(session->connection_state()); - // No need to save the token as RLMSyncSession always outlives the - // underlying SyncSession - session->register_connection_change_callback([=](auto, auto newState) { - dispatch_async(dispatch_get_main_queue(), ^{ - self.connectionState = convertConnectionState(newState); - }); - }); - return self; - } - return nil; -} - -- (RLMSyncConfiguration *)configuration { - if (auto session = _session.lock()) { - return [[RLMSyncConfiguration alloc] initWithRawConfig:session->config() path:session->path()]; - } - return nil; -} - -- (NSURL *)realmURL { - if (auto session = _session.lock()) { - auto url = session->full_realm_url(); - if (!url.empty() && session->state() == SyncSession::State::Active) { - return [NSURL URLWithString:@(url.c_str())]; - } - } - return nil; -} - -- (RLMUser *)parentUser { - if (auto session = _session.lock()) { - return [[RLMUser alloc] initWithUser:session->user()]; - } - return nil; -} - -- (RLMSyncSessionState)state { - if (auto session = _session.lock()) { - if (session->state() == SyncSession::State::Inactive) { - return RLMSyncSessionStateInactive; - } - return RLMSyncSessionStateActive; - } - return RLMSyncSessionStateInvalid; -} - -- (void)suspend { - if (auto session = _session.lock()) { - session->force_close(); - } -} - -- (void)resume { - if (auto session = _session.lock()) { - session->revive_if_needed(); - } -} - -- (void)pause { - // NEXT-MAJOR: this is what suspend should be - if (auto session = _session.lock()) { - session->pause(); - } -} - -- (void)unpause { - // NEXT-MAJOR: this is what resume should be - if (auto session = _session.lock()) { - session->resume(); - } -} - -- (void)reconnect { - if (auto session = _session.lock()) { - session->handle_reconnect(); - } -} - -static util::UniqueFunction wrapCompletion(dispatch_queue_t queue, - void (^callback)(NSError *)) { - queue = queue ?: dispatch_get_main_queue(); - return [=](Status status) { - NSError *error = makeError(status); - dispatch_async(queue, ^{ - callback(error); - }); - }; -} - -- (BOOL)waitForUploadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback { - if (auto session = _session.lock()) { - session->wait_for_upload_completion(wrapCompletion(queue, callback)); - return YES; - } - return NO; -} - -- (BOOL)waitForDownloadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback { - if (auto session = _session.lock()) { - session->wait_for_download_completion(wrapCompletion(queue, callback)); - return YES; - } - return NO; -} - -- (RLMProgressNotificationToken *)addSyncProgressNotificationForDirection:(RLMSyncProgressDirection)direction - mode:(RLMSyncProgressMode)mode - block:(RLMSyncProgressNotificationBlock)block { - if (auto session = _session.lock()) { - dispatch_queue_t queue = RLMSyncSession.notificationsQueue; - auto notifier_direction = (direction == RLMSyncProgressDirectionUpload - ? SyncSession::ProgressDirection::upload - : SyncSession::ProgressDirection::download); - bool is_streaming = (mode == RLMSyncProgressModeReportIndefinitely); - uint64_t token = session->register_progress_notifier([=](uint64_t transferred, uint64_t transferrable, double estimate) { - dispatch_async(queue, ^{ - RLMSyncProgress progress = { - .transferredBytes = (NSUInteger)transferred, - .transferrableBytes = (NSUInteger)transferrable, - .progressEstimate = estimate - }; - block(progress); - }); - }, notifier_direction, is_streaming); - return [[RLMProgressNotificationToken alloc] initWithTokenValue:token session:session]; - } - return nil; -} - -- (RLMProgressNotificationToken *)addProgressNotificationForDirection:(RLMSyncProgressDirection)direction - mode:(RLMSyncProgressMode)mode - block:(RLMProgressNotificationBlock)block { - return [self addSyncProgressNotificationForDirection:direction mode:mode block:([=](RLMSyncProgress progress) { - block(progress.transferredBytes, progress.transferrableBytes); - })]; -} - -+ (void)immediatelyHandleError:(RLMSyncErrorActionToken *)token { - if (token->_app) { - token->_app->immediately_run_file_actions(token->_originalPath); - token->_app.reset(); - } -} - -+ (void)immediatelyHandleError:(RLMSyncErrorActionToken *)token - syncManager:(__unused RLMSyncManager *)syncManager { - [self immediatelyHandleError:token]; -} - -+ (nullable RLMSyncSession *)sessionForRealm:(RLMRealm *)realm { - if (auto session = realm->_realm->sync_session()) { - return [[RLMSyncSession alloc] initWithSyncSession:session]; - } - return nil; -} - -- (NSString *)description { - return [NSString stringWithFormat: - @" {\n" - "\tstate = %d;\n" - "\tconnectionState = %d;\n" - "\trealmURL = %@;\n" - "\tuser = %@;\n" - "}", - (__bridge void *)self, - static_cast(self.state), - static_cast(self.connectionState), - self.realmURL, - self.parentUser.identifier]; -} - -@end - -// MARK: - Error action token - -@implementation RLMSyncErrorActionToken - -- (instancetype)initWithOriginalPath:(std::string)originalPath app:(std::shared_ptr)app { - if (self = [super init]) { - _originalPath = std::move(originalPath); - _app = std::move(app); - return self; - } - return nil; -} - -@end diff --git a/Realm/RLMSyncSession_Private.hpp b/Realm/RLMSyncSession_Private.hpp deleted file mode 100644 index 1838eca991..0000000000 --- a/Realm/RLMSyncSession_Private.hpp +++ /dev/null @@ -1,49 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncSession.h" - -#import - -namespace realm { -class AsyncOpenTask; -class SyncSession; -namespace app { -class App; -} -} - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@interface RLMSyncSession () { -@public // So it's visible to tests - std::weak_ptr _session; -} - -- (instancetype)init __attribute__((unavailable("This type cannot be created directly"))); -+ (instancetype)new __attribute__((unavailable("This type cannot be created directly"))); - -- (instancetype)initWithSyncSession:(std::shared_ptr const&)session; - -@end - -@interface RLMSyncErrorActionToken () -- (instancetype)initWithOriginalPath:(std::string)originalPath app:(std::shared_ptr)app; -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMSyncSubscription.h b/Realm/RLMSyncSubscription.h deleted file mode 100644 index 314e9392c7..0000000000 --- a/Realm/RLMSyncSubscription.h +++ /dev/null @@ -1,369 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -@class RLMObjectId; - -#pragma mark - Subscription States - -/// The current state of the subscription. This can be used for ensuring that -/// the subscriptions are not errored and that it has been successfully -/// synced to the server. -typedef NS_ENUM(NSUInteger, RLMSyncSubscriptionState) { - /// The subscription is complete and the server has sent all the data that matched the subscription - /// queries at the time the subscription set was updated. The server is now in a steady-state - /// synchronization mode where it will stream update as they come. - RLMSyncSubscriptionStateComplete, - /// The subscription encountered an error and synchronization is paused for this Realm. You can - /// find the error calling error in the subscription set to get a description of the error. You can - /// still use the current subscription set to write a subscription. - RLMSyncSubscriptionStateError, - /// The subscription is persisted locally but not yet processed by the server, which means - /// the server hasn't yet returned all the data that matched the updated subscription queries. - RLMSyncSubscriptionStatePending, - /// The subscription set has been super-ceded by an updated one, this typically means that - /// someone is trying to write a subscription on a different instance of the subscription set. - /// You should not use a superseded subscription set and instead obtain a new instance of - /// the subscription set to write a subscription. - RLMSyncSubscriptionStateSuperseded -}; - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -/** - `RLMSyncSubscription` is used to define a Flexible Sync subscription obtained from querying a - subscription set, which can be used to read or remove/update a committed subscription. - */ -@interface RLMSyncSubscription : NSObject - -/// Name of the subscription. If not specified it will return nil. -@property (nonatomic, readonly, nullable) NSString *name; - -/// When the subscription was created. Recorded automatically. -@property (nonatomic, readonly) NSDate *createdAt; - -/// When the subscription was last updated. Recorded automatically. -@property (nonatomic, readonly) NSDate *updatedAt; - -/** - Updates a Flexible Sync's subscription query with an allowed query which will be used to bootstrap data - from the server when committed. - - @warning This method may only be called during a write subscription block. - - @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. - */ -- (void)updateSubscriptionWhere:(NSString *)predicateFormat, ...; - -/// :nodoc: -- (void)updateSubscriptionWhere:(NSString *)predicateFormat - args:(va_list)args; - -/** - Updates a Flexible Sync's subscription query with an allowed query which will be used to bootstrap data - from the server when committed. - - @warning This method may only be called during a write subscription block. - - @param predicate The predicate with which to filter the objects on the server. - */ -- (void)updateSubscriptionWithPredicate:(NSPredicate *)predicate; - -@end - -/** - `RLMSyncSubscriptionSet` is a collection of `RLMSyncSubscription`s. This is the entry point - for adding and removing `RLMSyncSubscription`s. - */ -@interface RLMSyncSubscriptionSet : NSObject - -/// The number of subscriptions in the subscription set. -@property (readonly) NSUInteger count; - -/// Gets the error associated to the subscription set. This will be non-nil in case the current -/// state of the subscription set is `RLMSyncSubscriptionStateError`. -@property (nonatomic, readonly, nullable) NSError *error; - -/// Gets the state associated to the subscription set. -@property (nonatomic, readonly) RLMSyncSubscriptionState state; - -#pragma mark - Batch Update subscriptions - -/** - Synchronously performs any transactions (add/remove/update) to the subscription set within the block, - this will not wait for the server to acknowledge and see all the data associated with this collection of subscriptions, - and will return after committing the subscription transactions. - - @param block The block containing actions to perform to the subscription set. - */ -- (void)update:(__attribute__((noescape)) void(^)(void))block; -/// :nodoc: -- (void)write:(__attribute__((noescape)) void(^)(void))block __attribute__((unavailable("Renamed to -update"))); - -/** - Synchronously performs any transactions (add/remove/update) to the subscription set within the block. The `onComplete` block is executed after waiting for associated data to be downloaded from the server. - - @param block The block containing actions to perform to the subscription set. - @param onComplete A block which is called upon synchronization of - data from the server. The block will be passed `nil` - if the update succeeded, and an error describing the problem - otherwise. - */ -- (void)update:(__attribute__((noescape)) void(^)(void))block - onComplete:(nullable void(^RLM_SWIFT_SENDABLE)(NSError *_Nullable))onComplete - __attribute__((swift_async(not_swift_private, 2))) - __attribute__((swift_attr("@_unsafeInheritExecutor"))); -/// :nodoc: -- (void)write:(__attribute__((noescape)) void(^)(void))block - onComplete:(void(^)(NSError * _Nullable))onComplete __attribute__((unavailable("Renamed to -update:onComplete."))); - -/** - Synchronously performs any transactions (add/remove/update) to the subscription set within the block. The `onComplete` block is executed after waiting for associated data to be downloaded from the server. - - @param block The block containing actions to perform to the subscription set. - @param queue The serial queue to deliver notifications to. - @param onComplete A block which is called upon synchronization of - data from the server. The block will be passed `nil` - if the update succeeded, and an error describing the problem - otherwise. - */ - - (void)update:(__attribute__((noescape)) void(^)(void))block - queue:(nullable dispatch_queue_t)queue - onComplete:(void(^)(NSError *))onComplete -__attribute__((swift_attr("@_unsafeInheritExecutor"))); - - -#pragma mark - Find subscription - -/** - Finds a subscription by the specified name. - - @param name The name used to identify the subscription. - - @return A subscription for the given name. - */ -- (nullable RLMSyncSubscription *)subscriptionWithName:(NSString *)name; - -/** - Finds a subscription by the query for the specified object class name. - - @param objectClassName The class name for the model class to be queried. - @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. - - @return A subscription for the given query.. - */ -- (nullable RLMSyncSubscription *)subscriptionWithClassName:(NSString *)objectClassName - where:(NSString *)predicateFormat, ...; - -/// :nodoc: -- (nullable RLMSyncSubscription *)subscriptionWithClassName:(NSString *)objectClassName - where:(NSString *)predicateFormat - args:(va_list)args; - -/** - Finds a subscription by the query for the specified object class name. - - @param objectClassName The class name for the model class to be queried. - @param predicate The predicate used to to filter the objects on the server. - - @return A subscription for the given query.. - */ -- (nullable RLMSyncSubscription *)subscriptionWithClassName:(NSString *)objectClassName - predicate:(NSPredicate *)predicate; - -#pragma mark - Add a Subscription - -/** - Adds a new subscription to the subscription set which will be sent to the server when - committed at the end of a write subscription block. - - @warning This method may only be called during a write subscription block. - - @param objectClassName The class name for the model class to be queried. - @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. - */ -- (void)addSubscriptionWithClassName:(NSString *)objectClassName - where:(NSString *)predicateFormat, ...; - -/// :nodoc: -- (void)addSubscriptionWithClassName:(NSString *)objectClassName - where:(NSString *)predicateFormat - args:(va_list)args; - -/** - Adds a new subscription to the subscription set which will be sent to the server when - committed at the end of a write subscription block. - - @warning This method may only be called during a write subscription block. - - @param objectClassName The class name for the model class to be queried. - @param name The name used the identify the subscription. - @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. - */ -- (void)addSubscriptionWithClassName:(NSString *)objectClassName - subscriptionName:(NSString *)name - where:(NSString *)predicateFormat, ...; - -/// :nodoc: -- (void)addSubscriptionWithClassName:(NSString *)objectClassName - subscriptionName:(NSString *)name - where:(NSString *)predicateFormat - args:(va_list)args; - -/** - Adds a new subscription to the subscription set which will be sent to the server when - committed at the end of a write subscription block. - - @warning This method may only be called during a write subscription block. - - @param objectClassName The class name for the model class to be queried. - @param predicate The predicate defining the query for the subscription. - */ -- (void)addSubscriptionWithClassName:(NSString *)objectClassName - predicate:(NSPredicate *)predicate; - -/** - Adds a new subscription to the subscription set which will be sent to the server when - committed at the end of a write subscription block. - - @warning This method may only be called during a write subscription block. - - @param objectClassName The class name for the model class to be queried. - @param name The name used to identify the subscription. - @param predicate The predicate defining the query for the subscription. - */ -- (void)addSubscriptionWithClassName:(NSString *)objectClassName - subscriptionName:(nullable NSString *)name - predicate:(NSPredicate *)predicate; - -#pragma mark - Remove Subscription - -/** - Removes a subscription with the specified name from the subscription set. - - @warning This method may only be called during a write subscription block. - - @param name The name used the identify the subscription. - */ -- (void)removeSubscriptionWithName:(NSString *)name; - -/** - Removes a subscription with the specified query for the object class from the subscription set. - - @warning This method may only be called during a write subscription block. - - @param objectClassName The class name for the model class to be queried. - @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. - */ -- (void)removeSubscriptionWithClassName:(NSString *)objectClassName - where:(NSString *)predicateFormat, ...; - -/// :nodoc: -- (void)removeSubscriptionWithClassName:(NSString *)objectClassName - where:(NSString *)predicateFormat - args:(va_list)args; - -/** - Removes a subscription with the specified query for the object class from the subscription set. - - @warning This method may only be called during a write subscription block. - - @param objectClassName The class name for the model class to be queried. - @param predicate The predicate which will be used to identify the subscription to be removed. - */ -- (void)removeSubscriptionWithClassName:(NSString *)objectClassName - predicate:(NSPredicate *)predicate; - -/** - Removes the subscription from the subscription set. - - @warning This method may only be called during a write subscription block. - - @param subscription An instance of the subscription to be removed. - */ -- (void)removeSubscription:(RLMSyncSubscription *)subscription; - -#pragma mark - Remove Subscriptions - -/** - Removes all subscription from the subscription set. - - @warning This method may only be called during a write subscription block. - @warning Removing all subscriptions will result in an error if no new subscription is added. Server should - acknowledge at least one subscription. - */ -- (void)removeAllSubscriptions; - -/** - Removes all subscriptions without a name from the subscription set. - - @warning This method may only be called during a write subscription block. - @warning Removing all subscriptions will result in an error if no new subscription is added. Server should - acknowledge at least one subscription. - */ - - (void)removeAllUnnamedSubscriptions; - -/** - Removes all subscription with the specified class name. - - @param className The class name for the model class to be queried. - - @warning This method may only be called during a write subscription block. - */ -- (void)removeAllSubscriptionsWithClassName:(NSString *)className; - -#pragma mark - SubscriptionSet Collection - -/** - Returns the subscription at the given `index`. - - @param index The index. - - @return A subscription for the given index in the subscription set. - */ -- (nullable RLMSyncSubscription *)objectAtIndex:(NSUInteger)index; - -/** - Returns the first object in the subscription set list, or `nil` if the subscriptions are empty. - - @return A subscription. - */ -- (nullable RLMSyncSubscription *)firstObject; - -/** - Returns the last object in the subscription set, or `nil` if the subscriptions are empty. - - @return A subscription. - */ -- (nullable RLMSyncSubscription *)lastObject; - -#pragma mark - Subscript - -/** - Returns the subscription at the given `index`. - - @param index The index. - - @return A subscription for the given index in the subscription set. - */ -- (id)objectAtIndexedSubscript:(NSUInteger)index; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMSyncSubscription.mm b/Realm/RLMSyncSubscription.mm deleted file mode 100644 index 75e57e5fdf..0000000000 --- a/Realm/RLMSyncSubscription.mm +++ /dev/null @@ -1,600 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncSubscription_Private.hpp" - -#import "RLMAsyncTask_Private.h" -#import "RLMError_Private.hpp" -#import "RLMObjectId_Private.hpp" -#import "RLMQueryUtil.hpp" -#import "RLMRealm_Private.hpp" -#import "RLMScheduler.h" -#import "RLMUtil.hpp" - -#import -#import -#import - -#pragma mark - Subscription - -@interface RLMSyncSubscription () { - std::unique_ptr _subscription; - RLMSyncSubscriptionSet *_subscriptionSet; -} -@end - -@implementation RLMSyncSubscription - -- (instancetype)initWithSubscription:(realm::sync::Subscription)subscription subscriptionSet:(RLMSyncSubscriptionSet *)subscriptionSet { - if (self = [super init]) { - _subscription = std::make_unique(subscription); - _subscriptionSet = subscriptionSet; - return self; - } - return nil; -} - -- (RLMObjectId *)identifier { - return [[RLMObjectId alloc] initWithValue:_subscription->id]; -} - -- (nullable NSString *)name { - auto name = _subscription->name; - if (name) { - return @(name->c_str()); - } - return nil; -} - -- (NSDate *)createdAt { - return RLMTimestampToNSDate(_subscription->created_at); -} - -- (NSDate *)updatedAt { - return RLMTimestampToNSDate(_subscription->updated_at); -} - -- (NSString *)queryString { - return @(_subscription->query_string.c_str()); -} - -- (NSString *)objectClassName { - return @(_subscription->object_class_name.c_str()); -} - -- (void)updateSubscriptionWhere:(NSString *)predicateFormat, ... { - va_list args; - va_start(args, predicateFormat); - [self updateSubscriptionWhere:predicateFormat - args:args]; - va_end(args); -} - -- (void)updateSubscriptionWhere:(NSString *)predicateFormat - args:(va_list)args { - [self updateSubscriptionWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; -} - -- (void)updateSubscriptionWithPredicate:(NSPredicate *)predicate { - if (self.name != nil) { - [_subscriptionSet addSubscriptionWithClassName:self.objectClassName - subscriptionName:self.name - predicate:predicate - updateExisting:true]; - } - else { - RLMSyncSubscription *foundSubscription = [_subscriptionSet subscriptionWithClassName:self.objectClassName where:self.queryString]; - if (foundSubscription) { - [_subscriptionSet removeSubscription:foundSubscription]; - [_subscriptionSet addSubscriptionWithClassName:self.objectClassName - predicate:predicate]; - } else { - @throw RLMException(@"Cannot update a non-existent subscription."); - } - } -} - -@end - -#pragma mark - SubscriptionSet - -@interface RLMSyncSubscriptionSet () { - std::unique_ptr _mutableSubscriptionSet; - NSHashTable *_enumerators; -} -@end - -@interface RLMSyncSubscriptionEnumerator() { - // The buffer supplied by fast enumeration does not retain the objects given - // to it, but because we create objects on-demand and don't want them - // autoreleased (a table can have more rows than the device has memory for - // accessor objects) we need a thing to retain them. - id _strongBuffer[16]; -} -@end - -@implementation RLMSyncSubscriptionEnumerator { - RLMSyncSubscriptionSet *_subscriptionSet; -} - -- (instancetype)initWithSubscriptionSet:(RLMSyncSubscriptionSet *)subscriptionSet { - if (self = [super init]) { - _subscriptionSet = subscriptionSet; - return self; - } - return nil; -} -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state - count:(NSUInteger)len { - NSUInteger batchCount = 0, count = [_subscriptionSet count]; - for (NSUInteger index = state->state; index < count && batchCount < len; ++index) { - auto subscription = [_subscriptionSet objectAtIndex:index]; - _strongBuffer[batchCount] = subscription; - batchCount++; - } - - for (NSUInteger i = batchCount; i < len; ++i) { - _strongBuffer[i] = nil; - } - - if (batchCount == 0) { - // Release our data if we're done, as we're autoreleased and so may - // stick around for a while - if (_subscriptionSet) { - _subscriptionSet = nil; - } - } - - - state->itemsPtr = (__unsafe_unretained id *)(void *)_strongBuffer; - state->state += batchCount; - state->mutationsPtr = state->extra+1; - - return batchCount; -} - -@end - -NSUInteger RLMFastEnumerate(NSFastEnumerationState *state, - NSUInteger len, - RLMSyncSubscriptionSet *collection) { - __autoreleasing RLMSyncSubscriptionEnumerator *enumerator; - if (state->state == 0) { - enumerator = collection.fastEnumerator; - state->extra[0] = (long)enumerator; - state->extra[1] = collection.count; - } - else { - enumerator = (__bridge id)(void *)state->extra[0]; - } - - return [enumerator countByEnumeratingWithState:state count:len]; -} - -@implementation RLMSyncSubscriptionSet { - std::mutex _collectionEnumeratorMutex; - RLMRealm *_realm; -} - -- (instancetype)initWithSubscriptionSet:(realm::sync::SubscriptionSet)subscriptionSet - realm:(RLMRealm *)realm { - if (self = [super init]) { - _subscriptionSet = std::make_unique(subscriptionSet); - _realm = realm; - return self; - } - return nil; -} - -- (RLMSyncSubscriptionEnumerator *)fastEnumerator { - return [[RLMSyncSubscriptionEnumerator alloc] initWithSubscriptionSet:self]; -} - -- (NSUInteger)count { - return _subscriptionSet->size(); -} - -- (nullable NSError *)error { - _subscriptionSet->refresh(); - NSString *errorMessage = RLMStringDataToNSString(_subscriptionSet->error_str()); - if (errorMessage.length == 0) { - return nil; - } - return [[NSError alloc] initWithDomain:RLMSyncErrorDomain - code:RLMSyncErrorInvalidFlexibleSyncSubscriptions - userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; -} - -- (RLMSyncSubscriptionState)state { - _subscriptionSet->refresh(); - switch (_subscriptionSet->state()) { - case realm::sync::SubscriptionSet::State::Uncommitted: - case realm::sync::SubscriptionSet::State::Pending: - case realm::sync::SubscriptionSet::State::Bootstrapping: - case realm::sync::SubscriptionSet::State::AwaitingMark: - return RLMSyncSubscriptionStatePending; - case realm::sync::SubscriptionSet::State::Complete: - return RLMSyncSubscriptionStateComplete; - case realm::sync::SubscriptionSet::State::Error: - return RLMSyncSubscriptionStateError; - case realm::sync::SubscriptionSet::State::Superseded: - return RLMSyncSubscriptionStateSuperseded; - } -} - -#pragma mark - Batch Update subscriptions - -- (void)update:(__attribute__((noescape)) void(^)(void))block { - [self update:block onComplete:nil]; -} - -- (void)update:(__attribute__((noescape)) void(^)(void))block onComplete:(void(^)(NSError *))completionBlock { - [self update:block queue:nil onComplete:completionBlock]; -} - -- (void)update:(__attribute__((noescape)) void(^)(void))block - queue:(nullable dispatch_queue_t)queue - onComplete:(void(^)(NSError *))completionBlock { - [self update:block queue:queue timeout:0 onComplete:completionBlock]; -} - -- (void)update:(__attribute__((noescape)) void(^)(void))block - queue:(nullable dispatch_queue_t)queue - timeout:(NSTimeInterval)timeout - onComplete:(void(^)(NSError *))completionBlock { - if (_mutableSubscriptionSet) { - @throw RLMException(@"Cannot initiate a write transaction on subscription set that is already being updated."); - } - _mutableSubscriptionSet = std::make_unique(_subscriptionSet->make_mutable_copy()); - realm::util::ScopeExit cleanup([&]() noexcept { - if (_mutableSubscriptionSet) { - _mutableSubscriptionSet = nullptr; - _subscriptionSet->refresh(); - } - }); - - block(); - - try { - _subscriptionSet = std::make_unique(std::move(*_mutableSubscriptionSet).commit()); - _mutableSubscriptionSet = nullptr; - } - catch (realm::Exception const& ex) { - @throw RLMException(ex); - } - catch (std::exception const& ex) { - @throw RLMException(ex); - } - - if (completionBlock) { - [self waitForSynchronizationOnQueue:queue - timeout:timeout - completionBlock:completionBlock]; - } -} - -- (void)waitForSynchronizationOnQueue:(nullable dispatch_queue_t)queue - timeout:(NSTimeInterval)timeout - completionBlock:(void(^)(NSError *))completionBlock { - RLMAsyncSubscriptionTask *syncSubscriptionTask = [[RLMAsyncSubscriptionTask alloc] initWithSubscriptionSet:self - queue:queue - timeout:timeout - completion:completionBlock]; - [syncSubscriptionTask waitForSubscription]; -} - -#pragma mark - Find subscription - -- (nullable RLMSyncSubscription *)subscriptionWithName:(NSString *)name { - auto subscription = _subscriptionSet->find([name UTF8String]); - if (subscription) { - return [[RLMSyncSubscription alloc] initWithSubscription:*subscription - subscriptionSet:self]; - } - return nil; -} - -- (nullable RLMSyncSubscription *)subscriptionWithClassName:(NSString *)objectClassName - where:(NSString *)predicateFormat, ... { - va_list args; - va_start(args, predicateFormat); - return [self subscriptionWithClassName:objectClassName - where:predicateFormat - args:args]; - va_end(args); -} - -- (nullable RLMSyncSubscription *)subscriptionWithClassName:(NSString *)objectClassName - where:(NSString *)predicateFormat - args:(va_list)args { - return [self subscriptionWithClassName:objectClassName - predicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; -} - -- (nullable RLMSyncSubscription *)subscriptionWithClassName:(NSString *)objectClassName - predicate:(NSPredicate *)predicate { - RLMClassInfo& info = _realm->_info[objectClassName]; - auto query = RLMPredicateToQuery(predicate, info.rlmObjectSchema, _realm.schema, _realm.group); - return [self subscriptionWithQuery:query]; -} - -- (nullable RLMSyncSubscription *)subscriptionWithQuery:(realm::Query)query { - auto subscription = _subscriptionSet->find(query); - if (subscription) { - return [[RLMSyncSubscription alloc] initWithSubscription:*subscription - subscriptionSet:self]; - } - return nil; -} - -- (nullable RLMSyncSubscription *)subscriptionWithName:(NSString *)name - query:(realm::Query)query { - auto subscription = _subscriptionSet->find([name UTF8String]); - if (subscription && subscription->query_string == query.get_description()) { - return [[RLMSyncSubscription alloc] initWithSubscription:*subscription - subscriptionSet:self]; - } else { - return nil; - } -} - - -#pragma mark - Add a Subscription - -- (void)addSubscriptionWithClassName:(NSString *)objectClassName - where:(NSString *)predicateFormat, ... { - va_list args; - va_start(args, predicateFormat); - return [self addSubscriptionWithClassName:objectClassName - where:predicateFormat - args:args]; - va_end(args); -} - -- (void)addSubscriptionWithClassName:(NSString *)objectClassName - where:(NSString *)predicateFormat - args:(va_list)args { - [self addSubscriptionWithClassName:objectClassName - subscriptionName:nil - predicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; -} - -- (void)addSubscriptionWithClassName:(NSString *)objectClassName - subscriptionName:(NSString *)name - where:(NSString *)predicateFormat, ... { - va_list args; - va_start(args, predicateFormat); - return [self addSubscriptionWithClassName:objectClassName - subscriptionName:name - where:predicateFormat - args:args]; - va_end(args); -} - -- (void)addSubscriptionWithClassName:(NSString *)objectClassName - subscriptionName:(NSString *)name - where:(NSString *)predicateFormat - args:(va_list)args { - [self addSubscriptionWithClassName:objectClassName - subscriptionName:name - predicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; - -} - -- (void)addSubscriptionWithClassName:(NSString *)objectClassName - predicate:(NSPredicate *)predicate { - return [self addSubscriptionWithClassName:objectClassName - subscriptionName:nil - predicate:predicate]; -} - -- (void)addSubscriptionWithClassName:(NSString *)objectClassName - subscriptionName:(nullable NSString *)name - predicate:(NSPredicate *)predicate { - return [self addSubscriptionWithClassName:objectClassName - subscriptionName:name - predicate:predicate - updateExisting:false]; -} - -- (void)addSubscriptionWithClassName:(NSString *)objectClassName - subscriptionName:(nullable NSString *)name - predicate:(NSPredicate *)predicate - updateExisting:(BOOL)updateExisting { - [self verifyInWriteTransaction]; - - RLMClassInfo& info = _realm->_info[objectClassName]; - auto query = RLMPredicateToQuery(predicate, info.rlmObjectSchema, _realm.schema, _realm.group); - - [self addSubscriptionWithClassName:objectClassName - subscriptionName:name - query:query - updateExisting:updateExisting]; -} - -- (RLMObjectId *)addSubscriptionWithClassName:(NSString *)objectClassName - subscriptionName:(nullable NSString *)name - query:(realm::Query)query - updateExisting:(BOOL)updateExisting { - [self verifyInWriteTransaction]; - - if (name) { - if (updateExisting || !_mutableSubscriptionSet->find(name.UTF8String)) { - auto it = _mutableSubscriptionSet->insert_or_assign(name.UTF8String, query); - return [[RLMObjectId alloc] initWithValue:it.first->id]; - } - else { - @throw RLMException(@"A subscription named '%@' already exists. If you meant to update the existing subscription please use the `update` method.", name); - } - } - else { - auto it = _mutableSubscriptionSet->insert_or_assign(query); - return [[RLMObjectId alloc] initWithValue:it.first->id]; - } -} - -#pragma mark - Remove Subscription - -- (void)removeSubscriptionWithName:(NSString *)name { - [self verifyInWriteTransaction]; - - auto subscription = _subscriptionSet->find([name UTF8String]); - if (subscription) { - _mutableSubscriptionSet->erase(subscription->name); - } -} - -- (void)removeSubscriptionWithClassName:(NSString *)objectClassName - where:(NSString *)predicateFormat, ... { - va_list args; - va_start(args, predicateFormat); - [self removeSubscriptionWithClassName:objectClassName - where:predicateFormat - args:args]; - va_end(args); -} - -- (void)removeSubscriptionWithClassName:(NSString *)objectClassName - where:(NSString *)predicateFormat - args:(va_list)args { - [self removeSubscriptionWithClassName:objectClassName - predicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; -} - -- (void)removeSubscriptionWithClassName:(NSString *)objectClassName - predicate:(NSPredicate *)predicate { - RLMClassInfo& info = _realm->_info[objectClassName]; - auto query = RLMPredicateToQuery(predicate, info.rlmObjectSchema, _realm.schema, _realm.group); - [self removeSubscriptionWithClassName:objectClassName query:query]; -} - -- (void)removeSubscriptionWithClassName:(NSString *)objectClassName - query:(realm::Query)query { - [self verifyInWriteTransaction]; - - auto subscription = _subscriptionSet->find(query); - if (subscription) { - _mutableSubscriptionSet->erase(query); - } -} - -- (void)removeSubscription:(RLMSyncSubscription *)subscription { - [self removeSubscriptionWithId:subscription.identifier]; -} - -- (void)removeSubscriptionWithId:(RLMObjectId *)objectId { - [self verifyInWriteTransaction]; - - for (auto it = _mutableSubscriptionSet->begin(); it != _mutableSubscriptionSet->end();) { - if (it->id == objectId.value) { - it = _mutableSubscriptionSet->erase(it); - return; - } - it++; - } -} - -#pragma mark - Remove Subscriptions - -- (void)removeAllSubscriptions { - [self verifyInWriteTransaction]; - _mutableSubscriptionSet->clear(); -} - -- (void)removeAllUnnamedSubscriptions { - [self verifyInWriteTransaction]; - - for (auto it = _mutableSubscriptionSet->begin(); it != _mutableSubscriptionSet->end();) { - if (!it->name) { - it = _mutableSubscriptionSet->erase(it); - } else { - it++; - } - } -} - -- (void)removeAllSubscriptionsWithClassName:(NSString *)className { - [self verifyInWriteTransaction]; - - for (auto it = _mutableSubscriptionSet->begin(); it != _mutableSubscriptionSet->end();) { - if (it->object_class_name == [className UTF8String]) { - it = _mutableSubscriptionSet->erase(it); - } - else { - it++; - } - } -} - -#pragma mark - NSFastEnumerator - -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state - objects:(__unused __unsafe_unretained id [])buffer - count:(NSUInteger)len { - return RLMFastEnumerate(state, len, self); -} - -#pragma mark - SubscriptionSet Collection - -- (RLMSyncSubscription *)objectAtIndex:(NSUInteger)index { - auto size = _subscriptionSet->size(); - if (index >= size) { - @throw RLMException(@"Index %llu is out of bounds (must be less than %llu).", - (unsigned long long)index, (unsigned long long)size); - } - - return [[RLMSyncSubscription alloc]initWithSubscription:_subscriptionSet->at(size_t(index)) - subscriptionSet:self]; -} - -- (RLMSyncSubscription *)firstObject { - if (_subscriptionSet->size() < 1) { - return nil; - } - return [[RLMSyncSubscription alloc]initWithSubscription:_subscriptionSet->at(size_t(0)) - subscriptionSet:self]; -} - -- (RLMSyncSubscription *)lastObject { - if (_subscriptionSet->size() < 1) { - return nil; - } - - return [[RLMSyncSubscription alloc]initWithSubscription:_subscriptionSet->at(_subscriptionSet->size()-1) - subscriptionSet:self]; -} - -#pragma mark - Subscript - -- (id)objectAtIndexedSubscript:(NSUInteger)index { - return [self objectAtIndex:index]; -} - -#pragma mark - Private API - -- (uint64_t)version { - return _subscriptionSet->version(); -} - -- (void)verifyInWriteTransaction { - if (_mutableSubscriptionSet == nil) { - @throw RLMException(@"Can only add, remove, or update subscriptions within a write subscription block."); - } -} - -@end diff --git a/Realm/RLMSyncSubscription_Private.h b/Realm/RLMSyncSubscription_Private.h deleted file mode 100644 index 45bdba7167..0000000000 --- a/Realm/RLMSyncSubscription_Private.h +++ /dev/null @@ -1,72 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -#pragma mark - Subscription - -@interface RLMSyncSubscription () - -@property (nonatomic, readonly) RLMObjectId *identifier; - -@property (nonatomic, readonly) NSString *queryString; - -@property (nonatomic, readonly) NSString *objectClassName; - -@end - -#pragma mark - SubscriptionSet - -@interface RLMSyncSubscriptionEnumerator : NSObject -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state - count:(NSUInteger)len; - -- (instancetype)initWithSubscriptionSet:(RLMSyncSubscriptionSet *)subscriptionSet; -@end - -@interface RLMSyncSubscriptionSet () - -@property (readonly) uint64_t version; - -- (void)addSubscriptionWithClassName:(NSString *)objectClassName - subscriptionName:(nullable NSString *)name - predicate:(NSPredicate *)predicate - updateExisting:(BOOL)updateExisting; - -- (void)update:(__attribute__((noescape)) void(^)(void))block - queue:(nullable dispatch_queue_t)queue - timeout:(NSTimeInterval)timeout - onComplete:(void(^)(NSError *))completionBlock; - -- (void)waitForSynchronizationOnQueue:(nullable dispatch_queue_t)queue - timeout:(NSTimeInterval)timeout - completionBlock:(void(^)(NSError *))completionBlock; - -- (RLMSyncSubscriptionEnumerator *)fastEnumerator; - -NSUInteger RLMFastEnumerate(NSFastEnumerationState *state, - NSUInteger len, - RLMSyncSubscriptionSet *collection); - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMSyncSubscription_Private.hpp b/Realm/RLMSyncSubscription_Private.hpp deleted file mode 100644 index fee2eddc1f..0000000000 --- a/Realm/RLMSyncSubscription_Private.hpp +++ /dev/null @@ -1,66 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncSubscription_Private.h" - -#import - -namespace realm::sync { -class Subscription; -class SubscriptionSet; -} -namespace realm { -class Query; -} - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@interface RLMSyncSubscription () -- (instancetype)initWithSubscription:(realm::sync::Subscription)subscription subscriptionSet:(RLMSyncSubscriptionSet *)subscriptionSet; -@end - -@interface RLMSyncSubscriptionSet () { -@public - std::unique_ptr _subscriptionSet; -} - -- (instancetype)initWithSubscriptionSet:(realm::sync::SubscriptionSet)subscriptionSet realm:(RLMRealm *)realm; - -- (void)update:(__attribute__((noescape)) void(^)(void))block - queue:(nullable dispatch_queue_t)queue - timeout:(NSTimeInterval)timeout - onComplete:(void(^)(NSError *))completionBlock; - -- (RLMObjectId *)addSubscriptionWithClassName:(NSString *)objectClassName - subscriptionName:(nullable NSString *)name - query:(realm::Query)query - updateExisting:(BOOL)updateExisting; - -- (nullable RLMSyncSubscription *)subscriptionWithQuery:(realm::Query)query; - -// Return subscription that matches name *and* query -- (nullable RLMSyncSubscription *)subscriptionWithName:(NSString *)name - query:(realm::Query)query; - -- (void)removeSubscriptionWithClassName:(NSString *)objectClassName - query:(realm::Query)query; - -- (void)removeSubscriptionWithId:(RLMObjectId *)objectId; -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMSyncUtil.mm b/Realm/RLMSyncUtil.mm deleted file mode 100644 index 7741400fd2..0000000000 --- a/Realm/RLMSyncUtil.mm +++ /dev/null @@ -1,49 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncUtil_Private.hpp" - -#import "RLMUser_Private.hpp" - -NSString *const kRLMSyncPathOfRealmBackupCopyKey = @"recovered_realm_location_path"; -NSString *const kRLMSyncErrorActionTokenKey = @"error_action_token"; - -#pragma mark - C++ APIs - -using namespace realm; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -static_assert((int)RLMClientResetModeDiscardLocal == (int)realm::ClientResyncMode::DiscardLocal); -#pragma clang diagnostic pop -static_assert((int)RLMClientResetModeDiscardUnsyncedChanges == (int)realm::ClientResyncMode::DiscardLocal); -static_assert((int)RLMClientResetModeRecoverUnsyncedChanges == (int)realm::ClientResyncMode::Recover); -static_assert((int)RLMClientResetModeRecoverOrDiscardUnsyncedChanges == (int)realm::ClientResyncMode::RecoverOrDiscard); -static_assert((int)RLMClientResetModeManual == (int)realm::ClientResyncMode::Manual); - -static_assert(int(RLMSyncStopPolicyImmediately) == int(SyncSessionStopPolicy::Immediately)); -static_assert(int(RLMSyncStopPolicyLiveIndefinitely) == int(SyncSessionStopPolicy::LiveIndefinitely)); -static_assert(int(RLMSyncStopPolicyAfterChangesUploaded) == int(SyncSessionStopPolicy::AfterChangesUploaded)); - -SyncSessionStopPolicy translateStopPolicy(RLMSyncStopPolicy stopPolicy) { - return static_cast(stopPolicy); -} - -RLMSyncStopPolicy translateStopPolicy(SyncSessionStopPolicy stopPolicy) { - return static_cast(stopPolicy); -} diff --git a/Realm/RLMSyncUtil_Private.hpp b/Realm/RLMSyncUtil_Private.hpp deleted file mode 100644 index f531e9e8b1..0000000000 --- a/Realm/RLMSyncUtil_Private.hpp +++ /dev/null @@ -1,28 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncConfiguration_Private.h" - -#import - -realm::SyncSessionStopPolicy translateStopPolicy(RLMSyncStopPolicy stopPolicy); -RLMSyncStopPolicy translateStopPolicy(realm::SyncSessionStopPolicy stop_policy); - -typedef NS_ENUM(NSUInteger, RLMClientResetMode); -RLMClientResetMode translateClientResetMode(realm::ClientResyncMode mode); -realm::ClientResyncMode translateClientResetMode(RLMClientResetMode mode); diff --git a/Realm/RLMThreadSafeReference.h b/Realm/RLMThreadSafeReference.h index 9472942ed3..13732e3af7 100644 --- a/Realm/RLMThreadSafeReference.h +++ b/Realm/RLMThreadSafeReference.h @@ -67,7 +67,7 @@ RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @see `RLMThreadConfined` @see `-[RLMRealm resolveThreadSafeReference:]` */ -RLM_SWIFT_SENDABLE RLM_FINAL // is internally thread-safe +NS_SWIFT_SENDABLE RLM_FINAL // is internally thread-safe @interface RLMThreadSafeReference<__covariant Confined : id> : NSObject /** diff --git a/Realm/RLMUpdateChecker.hpp b/Realm/RLMUpdateChecker.hpp deleted file mode 100644 index 7f01ac7351..0000000000 --- a/Realm/RLMUpdateChecker.hpp +++ /dev/null @@ -1,20 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2014 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. -// -//////////////////////////////////////////////////////////////////////////// - -// Asynchronously check for updates to Realm if running on a simulator -void RLMCheckForUpdates(); diff --git a/Realm/RLMUpdateChecker.mm b/Realm/RLMUpdateChecker.mm deleted file mode 100644 index 6e547462f3..0000000000 --- a/Realm/RLMUpdateChecker.mm +++ /dev/null @@ -1,60 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2014 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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMUpdateChecker.hpp" - -#import "RLMRealm.h" -#import "RLMUtil.hpp" - -#if TARGET_IPHONE_SIMULATOR && !defined(REALM_COCOA_VERSION) -#import "RLMVersion.h" -#endif - -void RLMCheckForUpdates() { -#if TARGET_IPHONE_SIMULATOR - if (getenv("REALM_DISABLE_UPDATE_CHECKER") || RLMIsRunningInPlayground()) { - return; - } - - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"alpha|beta|rc" - options:(NSRegularExpressionOptions)0 - error:nil]; - NSUInteger numberOfMatches = [regex numberOfMatchesInString:REALM_COCOA_VERSION - options:(NSMatchingOptions)0 - range:NSMakeRange(0, REALM_COCOA_VERSION.length)]; - - if (numberOfMatches > 0) { - // pre-release version, skip update checking - return; - } - - auto handler = ^(NSData *data, NSURLResponse *response, NSError *error) { - if (error || ((NSHTTPURLResponse *)response).statusCode != 200) { - return; - } - - NSString *latestVersion = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - if (![REALM_COCOA_VERSION isEqualToString:latestVersion]) { - NSLog(@"Version %@ of Realm is now available: https://github.com/realm/realm-swift/blob/v%@/CHANGELOG.md", latestVersion, latestVersion); - } - }; - - NSString *url = [NSString stringWithFormat:@"https://static.realm.io/update/cocoa?%@", REALM_COCOA_VERSION]; - [[NSURLSession.sharedSession dataTaskWithURL:[NSURL URLWithString:url] completionHandler:handler] resume]; -#endif -} diff --git a/Realm/RLMUpdateResult.h b/Realm/RLMUpdateResult.h deleted file mode 100644 index df0b237e30..0000000000 --- a/Realm/RLMUpdateResult.h +++ /dev/null @@ -1,45 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@class RLMObjectId; -@protocol RLMBSON; - -/// The result of an `updateOne` or `updateMany` operation a `RLMMongoCollection`. -RLM_SWIFT_SENDABLE RLM_FINAL // immutable final class -@interface RLMUpdateResult : NSObject - -/// The number of documents that matched the filter. -@property (nonatomic, readonly) NSUInteger matchedCount; - -/// The number of documents modified. -@property (nonatomic, readonly) NSUInteger modifiedCount; - -/// The identifier of the inserted document if an upsert took place and the document's primary key is an `ObjectId`. -@property (nonatomic, nullable, readonly) RLMObjectId *objectId -__attribute__((deprecated("Use documentId instead, which support all BSON types", "documentId"))); - -/// The identifier of the inserted document if an upsert took place. -@property (nonatomic, nullable, readonly) id documentId; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMUpdateResult.mm b/Realm/RLMUpdateResult.mm deleted file mode 100644 index cd73ec45c7..0000000000 --- a/Realm/RLMUpdateResult.mm +++ /dev/null @@ -1,38 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMUpdateResult_Private.hpp" - -#import "RLMBSON_Private.hpp" -#import "RLMUtil.hpp" - -@implementation RLMUpdateResult - -- (instancetype)initWithUpdateResult:(realm::app::MongoCollection::UpdateResult)updateResult { - if (self = [super init]) { - _matchedCount = updateResult.matched_count; - _modifiedCount = updateResult.modified_count; - if (updateResult.upserted_id) { - _documentId = RLMConvertBsonToRLMBSON(*updateResult.upserted_id); - _objectId = RLMDynamicCast(_documentId); - } - } - return self; -} - -@end diff --git a/Realm/RLMUpdateResult_Private.hpp b/Realm/RLMUpdateResult_Private.hpp deleted file mode 100644 index 0ec46c61a1..0000000000 --- a/Realm/RLMUpdateResult_Private.hpp +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) -@interface RLMUpdateResult () -- (instancetype)initWithUpdateResult:(realm::app::MongoCollection::UpdateResult)UpdateResult; -@end -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMUser.h b/Realm/RLMUser.h deleted file mode 100644 index db0a43ad6e..0000000000 --- a/Realm/RLMUser.h +++ /dev/null @@ -1,447 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import -#import -#import -#import - -@class RLMUser, RLMSyncSession, RLMRealm, RLMUserIdentity, RLMAPIKeyAuth, RLMMongoClient, RLMMongoDatabase, RLMMongoCollection, RLMUserProfile; -@protocol RLMBSON; - -/** - The state of the user object. - */ -typedef NS_ENUM(NSUInteger, RLMUserState) { - /// The user is logged out. Call `logInWithCredentials:...` with valid credentials to log the user back in. - RLMUserStateLoggedOut, - /// The user is logged in, and any Realms associated with it are syncing with Atlas App Services. - RLMUserStateLoggedIn, - /// The user has been removed, and cannot be used. - RLMUserStateRemoved -}; - -/// A block type used to report an error related to a specific user. -RLM_SWIFT_SENDABLE -typedef void(^RLMOptionalUserBlock)(RLMUser * _Nullable, NSError * _Nullable); - -/// A block type used to report an error on a network request from the user. -RLM_SWIFT_SENDABLE -typedef void(^RLMUserOptionalErrorBlock)(NSError * _Nullable); - -/// A block which returns a dictionary should there be any custom data set for a user -RLM_SWIFT_SENDABLE -typedef void(^RLMUserCustomDataBlock)(NSDictionary * _Nullable, NSError * _Nullable); - -/// A block type for returning from function calls. -RLM_SWIFT_SENDABLE -typedef void(^RLMCallFunctionCompletionBlock)(id _Nullable, NSError * _Nullable); - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -/** - A `RLMUser` instance represents a single Realm App user account. - - A user may have one or more credentials associated with it. These credentials - uniquely identify the user to the authentication provider, and are used to sign - into an Atlas App Services user account. - - Note that user objects are only vended out via SDK APIs, and cannot be directly - initialized. User objects can be accessed from any thread. - */ -RLM_SWIFT_SENDABLE // internally thread-safe -@interface RLMUser : NSObject - -/** - The unique Atlas App Services string identifying this user. - Note this is different from an identity: A user may have multiple identities but has a single identifier. See RLMUserIdentity. - */ -@property (nonatomic, readonly) NSString *identifier NS_SWIFT_NAME(id); - -/// Returns an array of identities currently linked to a user. -@property (nonatomic, readonly) NSArray *identities; - -/** - The user's refresh token used to access App Services. - - By default, refresh tokens expire 60 days after they are issued. - You can configure this time for your App's refresh tokens to be - anywhere between 30 minutes and 180 days. - - You can configure the refresh token expiration time for all sessions in - an App from the Admin UI or Admin API. -*/ -@property (nullable, nonatomic, readonly) NSString *refreshToken; - -/** - The user's access token used to access App Services. - - This is required to make HTTP requests to Atlas App Services like the Data API or GraphQL. - It should be treated as sensitive data. - - The Realm SDK automatically manages access tokens and refreshes them - when they expire. - */ -@property (nullable, nonatomic, readonly) NSString *accessToken; - -/** - The current state of the user. - */ -@property (nonatomic, readonly) RLMUserState state; - -/** - Indicates if the user is logged in or not. Returns true if the access token and refresh token are not empty. - */ -@property (nonatomic, readonly) BOOL isLoggedIn; - -#pragma mark - Lifecycle - -/** - Create a partition-based sync configuration instance for the given partition value. - - @param partitionValue The `RLMBSON` value the Realm is partitioned on. - @return A default configuration object with the sync configuration set to use the given partition value. - */ -- (RLMRealmConfiguration *)configurationWithPartitionValue:(nullable id)partitionValue NS_REFINED_FOR_SWIFT; - -/** - Create a partition-based sync configuration instance for the given partition value. - - @param partitionValue The `RLMBSON` value the Realm is partitioned on. - @param clientResetMode Determines file recovery behavior in the event of a client reset. - See: https://docs.mongodb.com/realm/sync/error-handling/client-resets/ - - @return A configuration object with the sync configuration set to use the given partition value. - */ -- (RLMRealmConfiguration *)configurationWithPartitionValue:(nullable id)partitionValue - clientResetMode:(RLMClientResetMode)clientResetMode NS_REFINED_FOR_SWIFT; - -/** - Create a partition-based sync configuration instance for the given partition value. - - @param partitionValue The `RLMBSON` value the Realm is partitioned on. - @param clientResetMode Determines file recovery behavior in the event of a client reset. - See: https://docs.mongodb.com/realm/sync/error-handling/client-resets/ - @param beforeResetBlock A callback which notifies prior to a client reset occurring. See: `RLMClientResetBeforeBlock` - @param afterResetBlock A callback which notifies after a client reset has occurred. See: `RLMClientResetAfterBlock` - - @return A configuration object with the sync configuration set to use the given partition value. - */ -- (RLMRealmConfiguration *)configurationWithPartitionValue:(nullable id)partitionValue - clientResetMode:(RLMClientResetMode)clientResetMode - notifyBeforeReset:(nullable RLMClientResetBeforeBlock)beforeResetBlock - notifyAfterReset:(nullable RLMClientResetAfterBlock)afterResetBlock NS_REFINED_FOR_SWIFT; - -/** - Create a partition-based sync configuration instance for the given partition value. - - @param partitionValue The `RLMBSON` value the Realm is partitioned on. - @param clientResetMode Determines file recovery behavior in the event of a client reset. - See: https://docs.mongodb.com/realm/sync/error-handling/client-resets/ - @param manualClientResetHandler An error reporting block that is invoked during a client reset. - @See ``RLMSyncErrorReportingBlock`` and ``RLMClientResetInfo`` - - @return A configuration object with the sync configuration set to use the given partition value. - */ -- (RLMRealmConfiguration *)configurationWithPartitionValue:(nullable id)partitionValue - clientResetMode:(RLMClientResetMode)clientResetMode - manualClientResetHandler:(nullable RLMSyncErrorReportingBlock)manualClientResetHandler NS_REFINED_FOR_SWIFT; - -/** - Create a flexible sync configuration instance, which can be used to open a Realm that - supports flexible sync. - - @note A single server-side Device Sync App can sync data with either partition-based realms or flexible sync based realms. - In order for an application to contain both partition-based and flexible sync realms, more than one - server-side Device Sync App must be used. - - @return A ``RLMRealmConfiguration`` instance with a flexible sync configuration. - */ -- (RLMRealmConfiguration *)flexibleSyncConfiguration NS_REFINED_FOR_SWIFT; - -/** - Create a flexible sync configuration instance, which can be used to open a Realm that - supports flexible sync. - - @note A single server-side Device Sync App can sync data with either partition-based realms or flexible sync based realms. - In order for an application to contain both partition-based and flexible sync realms, more than one - server-side Device Sync App must be used. - - @param clientResetMode Determines file recovery behavior in the event of a client reset. - See: https://docs.mongodb.com/realm/sync/error-handling/client-resets/ - @param beforeResetBlock A callback which notifies prior to a client reset occurring. See: `RLMClientResetBeforeBlock` - @param afterResetBlock A callback which notifies after a client reset has occurred. See: `RLMClientResetAfterBlock` - - @return A `RLMRealmConfiguration` instance with a flexible sync configuration. - */ -- (RLMRealmConfiguration *)flexibleSyncConfigurationWithClientResetMode:(RLMClientResetMode)clientResetMode - notifyBeforeReset:(nullable RLMClientResetBeforeBlock)beforeResetBlock - notifyAfterReset:(nullable RLMClientResetAfterBlock)afterResetBlock NS_REFINED_FOR_SWIFT; -/** - Create a flexible sync configuration instance, which can be used to open a Realm that - supports flexible sync. - - @note A single server-side Device Sync App can sync data with either partition-based realms or flexible sync based realms. - In order for an application to contain both partition-based and flexible sync realms, more than one - server-side Device Sync App must be used. - - @param clientResetMode Determines file recovery behavior in the event of a client reset. - See: https://docs.mongodb.com/realm/sync/error-handling/client-resets/ - @param manualClientResetHandler An error reporting block that is invoked during a client reset. - @See `RLMSyncErrorReportingBlock` and `RLMClientResetInfo` - - @return A `RLMRealmConfiguration` instance with a flexible sync configuration. - */ -- (RLMRealmConfiguration *)flexibleSyncConfigurationWithClientResetMode:(RLMClientResetMode)clientResetMode - manualClientResetHandler:(nullable RLMSyncErrorReportingBlock)manualClientResetHandler NS_REFINED_FOR_SWIFT; - -/** - Create a flexible sync configuration instance, which can be used to open a Realm that - supports flexible sync. - - @note A single server-side Device Sync App can sync data with either partition-based realms or flexible sync based realms. - In order for an application to contain both partition-based and flexible sync realms, more than one - server-side Device Sync App must be used. - - @param initialSubscriptions A block which receives a subscription set instance, that can be - used to add an initial set of subscriptions which will be executed - when the Realm is first opened. - @param rerunOnOpen If true, allows to run the initial set of subscriptions specified, on every app startup. - This can be used to re-run dynamic time ranges and other queries that require a - re-computation of a static variable. - - @return A `RLMRealmConfiguration` instance with a flexible sync configuration. - */ -- (RLMRealmConfiguration *)flexibleSyncConfigurationWithInitialSubscriptions:(RLMFlexibleSyncInitialSubscriptionsBlock)initialSubscriptions - rerunOnOpen:(BOOL)rerunOnOpen NS_REFINED_FOR_SWIFT; -/** - Create a flexible sync configuration instance, which can be used to open a Realm that - supports flexible sync. - - @note A single server-side Device Sync App can sync data with either partition-based realms or flexible sync based realms. - In order for an application to contain both partition-based and flexible sync realms, more than one - server-side Device Sync App must be used. - - @param initialSubscriptions A block which receives a subscription set instance, that can be - used to add an initial set of subscriptions which will be executed - when the Realm is first opened. - @param rerunOnOpen If true, allows to run the initial set of subscriptions specified, on every app startup. - This can be used to re-run dynamic time ranges and other queries that require a - re-computation of a static variable. - @param clientResetMode Determines file recovery behavior in the event of a client reset. - See: https://docs.mongodb.com/realm/sync/error-handling/client-resets/ - @param beforeResetBlock A callback which notifies prior to a client reset occurring. See: `RLMClientResetBeforeBlock` - @param afterResetBlock A callback which notifies after a client reset has occurred. See: `RLMClientResetAfterBlock` - - @return A `RLMRealmConfiguration` instance with a flexible sync configuration. - */ -- (RLMRealmConfiguration *)flexibleSyncConfigurationWithInitialSubscriptions:(RLMFlexibleSyncInitialSubscriptionsBlock)initialSubscriptions - rerunOnOpen:(BOOL)rerunOnOpen - clientResetMode:(RLMClientResetMode)clientResetMode - notifyBeforeReset:(nullable RLMClientResetBeforeBlock)beforeResetBlock - notifyAfterReset:(nullable RLMClientResetAfterBlock)afterResetBlock NS_REFINED_FOR_SWIFT; - -/** - Create a flexible sync configuration instance, which can be used to open a Realm that - supports flexible sync. - - @note A single server-side Device Sync App can sync data with either partition-based realms or flexible sync based realms. - In order for an application to contain both partition-based and flexible sync realms, more than one - server-side Device Sync App must be used. - - @param initialSubscriptions A block which receives a subscription set instance, that can be - used to add an initial set of subscriptions which will be executed - when the Realm is first opened. - @param rerunOnOpen If true, allows to run the initial set of subscriptions specified, on every app startup. - This can be used to re-run dynamic time ranges and other queries that require a - re-computation of a static variable. - @param clientResetMode Determines file recovery behavior in the event of a client reset. - See: https://docs.mongodb.com/realm/sync/error-handling/client-resets/ - @param manualClientResetHandler An error reporting block that is invoked during a client reset. - @See `RLMSyncErrorReportingBlock` and `RLMClientResetInfo` - - @return A `RLMRealmConfiguration` instance with a flexible sync configuration. - */ -- (RLMRealmConfiguration *)flexibleSyncConfigurationWithInitialSubscriptions:(RLMFlexibleSyncInitialSubscriptionsBlock)initialSubscriptions - rerunOnOpen:(BOOL)rerunOnOpen - clientResetMode:(RLMClientResetMode)clientResetMode - manualClientResetHandler:(nullable RLMSyncErrorReportingBlock)manualClientResetHandler NS_REFINED_FOR_SWIFT; - -#pragma mark - Sessions - -/** - Retrieve a valid session object belonging to this user for a given URL, or `nil` - if no such object exists. - */ -- (nullable RLMSyncSession *)sessionForPartitionValue:(id)partitionValue; - -/// Retrieve all the valid sessions belonging to this user. -@property (nonatomic, readonly) NSArray *allSessions; - -#pragma mark - Custom Data - -/** - The custom data of the user. - This is configured in your Atlas App Services app. - */ -@property (nonatomic, readonly) NSDictionary *customData NS_REFINED_FOR_SWIFT; - -/** - The profile of the user. - */ -@property (nonatomic, readonly) RLMUserProfile *profile; - -/** - Refresh a user's custom data. This will, in effect, refresh the user's auth session. - */ -- (void)refreshCustomDataWithCompletion:(RLMUserCustomDataBlock)completion NS_REFINED_FOR_SWIFT; - -/** - Links the currently authenticated user with a new identity, where the identity is defined by the credential - specified as a parameter. This will only be successful if this `RLMUser` is the currently authenticated - with the client from which it was created. On success a new user will be returned with the new linked credentials. - - @param credentials The `RLMCredentials` used to link the user to a new identity. - @param completion The completion handler to call when the linking is complete. - If the operation is successful, the result will contain a new - `RLMUser` object representing the currently logged in user. -*/ -- (void)linkUserWithCredentials:(RLMCredentials *)credentials - completion:(RLMOptionalUserBlock)completion NS_REFINED_FOR_SWIFT; - -/** - Removes the user - - This logs out and destroys the session related to this user. The completion block will return an error - if the user is not found or is already removed. - - @param completion A callback invoked on completion -*/ -- (void)removeWithCompletion:(RLMUserOptionalErrorBlock)completion; - -/** - Permanently deletes this user from your Atlas App Services app. - - The users state will be set to `Removed` and the session will be destroyed. - If the delete request fails, the local authentication state will be untouched. - - @param completion A callback invoked on completion -*/ -- (void)deleteWithCompletion:(RLMUserOptionalErrorBlock)completion; - -/** - Logs out the current user - - The users state will be set to `Removed` is they are an anonymous user or `LoggedOut` if they are authenticated by an email / password or third party auth clients - If the logout request fails, this method will still clear local authentication state. - - @param completion A callback invoked on completion -*/ -- (void)logOutWithCompletion:(RLMUserOptionalErrorBlock)completion; - -/** - A client for the user API key authentication provider which - can be used to create and modify user API keys. - - This client should only be used by an authenticated user. -*/ -@property (nonatomic, readonly) RLMAPIKeyAuth *apiKeysAuth; - -/// A client for interacting with a remote MongoDB instance -/// @param serviceName The name of the MongoDB service -- (RLMMongoClient *)mongoClientWithServiceName:(NSString *)serviceName NS_REFINED_FOR_SWIFT; - -/** - Calls the Atlas App Services function with the provided name and arguments. - - @param name The name of the Atlas App Services function to be called. - @param arguments The `BSONArray` of arguments to be provided to the function. - @param completion The completion handler to call when the function call is complete. - This handler is executed on a non-main global `DispatchQueue`. -*/ -- (void)callFunctionNamed:(NSString *)name - arguments:(NSArray> *)arguments - completionBlock:(RLMCallFunctionCompletionBlock)completion NS_REFINED_FOR_SWIFT; - -/// :nodoc: -- (instancetype)init __attribute__((unavailable("RLMUser cannot be created directly"))); -/// :nodoc: -+ (instancetype)new __attribute__((unavailable("RLMUser cannot be created directly"))); - -@end - -#pragma mark - User info classes - -/** - An identity of a user. A user can have multiple identities, usually associated with multiple providers. - Note this is different from a user's unique identifier string. - @seeAlso `RLMUser.identifier` - */ -RLM_SWIFT_SENDABLE RLM_FINAL // immutable final class -@interface RLMUserIdentity : NSObject - -/** - The associated provider type - */ -@property (nonatomic, readonly) NSString *providerType; - -/** - The string which identifies the RLMUserIdentity - */ -@property (nonatomic, readonly) NSString *identifier; - -/** - Initialize an RLMUserIdentity for the given identifier and provider type. - @param providerType the associated provider type - @param identifier the identifier of the identity - */ -- (instancetype)initUserIdentityWithProviderType:(NSString *)providerType - identifier:(NSString *)identifier; - -@end - -/** - A profile for a given User. - */ -RLM_SWIFT_SENDABLE RLM_FINAL // immutable final class -@interface RLMUserProfile : NSObject - -/// The full name of the user. -@property (nonatomic, readonly, nullable) NSString *name; -/// The email address of the user. -@property (nonatomic, readonly, nullable) NSString *email; -/// A URL to the user's profile picture. -@property (nonatomic, readonly, nullable) NSString *pictureURL; -/// The first name of the user. -@property (nonatomic, readonly, nullable) NSString *firstName; -/// The last name of the user. -@property (nonatomic, readonly, nullable) NSString *lastName; -/// The gender of the user. -@property (nonatomic, readonly, nullable) NSString *gender; -/// The birthdate of the user. -@property (nonatomic, readonly, nullable) NSString *birthday; -/// The minimum age of the user. -@property (nonatomic, readonly, nullable) NSString *minAge; -/// The maximum age of the user. -@property (nonatomic, readonly, nullable) NSString *maxAge; -/// The BSON dictionary of metadata associated with this user. -@property (nonatomic, readonly) NSDictionary *metadata NS_REFINED_FOR_SWIFT; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMUser.mm b/Realm/RLMUser.mm deleted file mode 100644 index 1e856e2bcf..0000000000 --- a/Realm/RLMUser.mm +++ /dev/null @@ -1,448 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMUser_Private.hpp" - -#import "RLMAPIKeyAuth.h" -#import "RLMApp_Private.hpp" -#import "RLMBSON_Private.hpp" -#import "RLMCredentials_Private.hpp" -#import "RLMMongoClient_Private.hpp" -#import "RLMProviderClient_Private.hpp" -#import "RLMRealmConfiguration_Private.h" -#import "RLMSyncConfiguration_Private.hpp" -#import "RLMSyncSession_Private.hpp" -#import "RLMUtil.hpp" - -#import -#import -#import -#import - -using namespace realm; - -@implementation RLMUserSubscriptionToken { - std::shared_ptr _user; - std::optional::Token> _token; -} - -- (instancetype)initWithUser:(std::shared_ptr)user token:(realm::Subscribable::Token&&)token { - if (self = [super init]) { - _user = std::move(user); - _token = std::move(token); - } - return self; -} - -- (void)unsubscribe { - _token.reset(); - _user.reset(); -} -@end - -@implementation RLMUser - -#pragma mark - API - -- (instancetype)initWithUser:(std::shared_ptr)user { - if (self = [super init]) { - _user = std::static_pointer_cast(user); - _app = [RLMApp cachedAppWithId:@(user->app_id().c_str())]; - return self; - } - return nil; -} - -- (BOOL)isEqual:(id)object { - if (auto user = RLMDynamicCast(object)) { - return _user == user->_user; - } - return NO; -} - -- (NSUInteger)hash { - return NSUInteger(_user.get()); -} - -- (RLMRealmConfiguration *)configurationWithPartitionValue:(nullable id)partitionValue { - return [self configurationWithPartitionValue:partitionValue clientResetMode:RLMClientResetModeRecoverUnsyncedChanges]; -} - -- (RLMRealmConfiguration *)configurationWithPartitionValue:(nullable id)partitionValue - clientResetMode:(RLMClientResetMode)clientResetMode { - auto syncConfig = [[RLMSyncConfiguration alloc] initWithUser:self - partitionValue:partitionValue]; - syncConfig.clientResetMode = clientResetMode; - RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; - config.syncConfiguration = syncConfig; - return config; -} - -- (RLMRealmConfiguration *)configurationWithPartitionValue:(nullable id)partitionValue - clientResetMode:(RLMClientResetMode)clientResetMode - notifyBeforeReset:(nullable RLMClientResetBeforeBlock)beforeResetBlock - notifyAfterReset:(nullable RLMClientResetAfterBlock)afterResetBlock { - auto syncConfig = [[RLMSyncConfiguration alloc] initWithUser:self - partitionValue:partitionValue]; - syncConfig.clientResetMode = clientResetMode; - syncConfig.beforeClientReset = beforeResetBlock; - syncConfig.afterClientReset = afterResetBlock; - RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; - config.syncConfiguration = syncConfig; - return config; -} - -- (RLMRealmConfiguration *)configurationWithPartitionValue:(nullable id)partitionValue - clientResetMode:(RLMClientResetMode)clientResetMode - manualClientResetHandler:(nullable RLMSyncErrorReportingBlock)manualClientResetHandler { - auto syncConfig = [[RLMSyncConfiguration alloc] initWithUser:self - partitionValue:partitionValue]; - syncConfig.clientResetMode = clientResetMode; - syncConfig.manualClientResetHandler = manualClientResetHandler; - RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; - config.syncConfiguration = syncConfig; - return config; -} - -- (RLMRealmConfiguration *)flexibleSyncConfiguration { - RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; - config.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:self]; - return config; -} - -- (RLMRealmConfiguration *)flexibleSyncConfigurationWithClientResetMode:(RLMClientResetMode)clientResetMode - notifyBeforeReset:(nullable RLMClientResetBeforeBlock)beforeResetBlock - notifyAfterReset:(nullable RLMClientResetAfterBlock)afterResetBlock { - auto syncConfig = [[RLMSyncConfiguration alloc] initWithUser:self]; - RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; - syncConfig.clientResetMode = clientResetMode; - syncConfig.beforeClientReset = beforeResetBlock; - syncConfig.afterClientReset = afterResetBlock; - config.syncConfiguration = syncConfig; - return config; -} - -- (RLMRealmConfiguration *)flexibleSyncConfigurationWithClientResetMode:(RLMClientResetMode)clientResetMode - manualClientResetHandler:(nullable RLMSyncErrorReportingBlock)manualClientResetHandler { - auto syncConfig = [[RLMSyncConfiguration alloc] initWithUser:self]; - RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; - syncConfig.clientResetMode = clientResetMode; - syncConfig.manualClientResetHandler = manualClientResetHandler; - config.syncConfiguration = syncConfig; - return config; -} - -- (RLMRealmConfiguration *)flexibleSyncConfigurationWithInitialSubscriptions:(RLMFlexibleSyncInitialSubscriptionsBlock)initialSubscriptions - rerunOnOpen:(BOOL)rerunOnOpen { - auto syncConfig = [[RLMSyncConfiguration alloc] initWithUser:self]; - syncConfig.initialSubscriptions = [[RLMInitialSubscriptionsConfiguration alloc] initWithCallback:initialSubscriptions rerunOnOpen:rerunOnOpen]; - RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; - config.syncConfiguration = syncConfig; - return config; -} - -- (RLMRealmConfiguration *)flexibleSyncConfigurationWithInitialSubscriptions:(RLMFlexibleSyncInitialSubscriptionsBlock)initialSubscriptions - rerunOnOpen:(BOOL)rerunOnOpen - clientResetMode:(RLMClientResetMode)clientResetMode - notifyBeforeReset:(nullable RLMClientResetBeforeBlock)beforeResetBlock - notifyAfterReset:(nullable RLMClientResetAfterBlock)afterResetBlock { - auto syncConfig = [[RLMSyncConfiguration alloc] initWithUser:self]; - RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; - syncConfig.clientResetMode = clientResetMode; - syncConfig.beforeClientReset = beforeResetBlock; - syncConfig.afterClientReset = afterResetBlock; - syncConfig.initialSubscriptions = [[RLMInitialSubscriptionsConfiguration alloc] initWithCallback:initialSubscriptions rerunOnOpen:rerunOnOpen]; - - config.syncConfiguration = syncConfig; - return config; -} - -- (RLMRealmConfiguration *)flexibleSyncConfigurationWithInitialSubscriptions:(RLMFlexibleSyncInitialSubscriptionsBlock)initialSubscriptions - rerunOnOpen:(BOOL)rerunOnOpen - clientResetMode:(RLMClientResetMode)clientResetMode - manualClientResetHandler:(nullable RLMSyncErrorReportingBlock)manualClientResetHandler { - auto syncConfig = [[RLMSyncConfiguration alloc] initWithUser:self]; - RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; - syncConfig.clientResetMode = clientResetMode; - syncConfig.manualClientResetHandler = manualClientResetHandler; - syncConfig.initialSubscriptions = [[RLMInitialSubscriptionsConfiguration alloc] initWithCallback:initialSubscriptions rerunOnOpen:rerunOnOpen]; - config.syncConfiguration = syncConfig; - return config; -} - -- (void)logOut { - _user->log_out(); -} - -- (BOOL)isLoggedIn { - return _user->is_logged_in(); -} - -- (std::string)pathForPartitionValue:(std::string const&)value { - SyncConfig config(_user, value); - auto path = _user->path_for_realm(config, value); - if ([NSFileManager.defaultManager fileExistsAtPath:@(path.c_str())]) { - return path; - } - - // Previous versions converted the partition value to a path *twice*, - // so if the file resulting from that exists open it instead - NSString *encodedPartitionValue = [@(value.data()) - stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]]; - NSString *overEncodedRealmName = [[NSString alloc] initWithFormat:@"%@/%@", self.identifier, encodedPartitionValue]; - auto legacyPath = _user->path_for_realm(config, std::string(overEncodedRealmName.UTF8String)); - if ([NSFileManager.defaultManager fileExistsAtPath:@(legacyPath.c_str())]) { - return legacyPath; - } - - return path; -} - -- (std::string)pathForFlexibleSync { - SyncConfig config(_user, SyncConfig::FLXSyncEnabled{}); - return _user->path_for_realm(config, realm::none); -} - -- (nullable RLMSyncSession *)sessionForPartitionValue:(id)partitionValue { - std::stringstream s; - s << RLMConvertRLMBSONToBson(partitionValue); - auto path = [self pathForPartitionValue:s.str()]; - if (auto session = _user->sync_manager()->get_existing_session(path)) { - return [[RLMSyncSession alloc] initWithSyncSession:session]; - } - return nil; -} - -- (NSArray *)allSessions { - auto sessions = _user->sync_manager()->get_all_sessions_for(*_user); - NSMutableArray *buffer = [NSMutableArray arrayWithCapacity:sessions.size()]; - for (auto& session : sessions) { - [buffer addObject:[[RLMSyncSession alloc] initWithSyncSession:std::move(session)]]; - } - return [buffer copy]; -} - -- (NSString *)identifier { - return @(_user->user_id().c_str()); -} - -- (NSArray *)identities { - NSMutableArray *buffer = [NSMutableArray array]; - auto identities = _user->identities(); - for (auto& identity : identities) { - [buffer addObject:[[RLMUserIdentity alloc] initUserIdentityWithProviderType:@(identity.provider_type.c_str()) - identifier:@(identity.id.c_str())]]; - } - - return [buffer copy]; -} - -- (RLMUserState)state { - switch (_user->state()) { - case SyncUser::State::LoggedIn: - return RLMUserStateLoggedIn; - case SyncUser::State::LoggedOut: - return RLMUserStateLoggedOut; - case SyncUser::State::Removed: - return RLMUserStateRemoved; - } -} - -- (void)refreshCustomDataWithCompletion:(RLMUserCustomDataBlock)completion { - _user->refresh_custom_data([completion, self](std::optional error) { - if (!error) { - return completion([self customData], nil); - } - - completion(nil, makeError(*error)); - }); -} - -- (void)linkUserWithCredentials:(RLMCredentials *)credentials - completion:(RLMOptionalUserBlock)completion { - _user->app()->link_user(_user, credentials.appCredentials, - ^(std::shared_ptr user, std::optional error) { - if (error) { - return completion(nil, makeError(*error)); - } - - completion([[RLMUser alloc] initWithUser:user], nil); - }); -} - -- (void)removeWithCompletion:(RLMOptionalErrorBlock)completion { - _user->app()->remove_user(_user, ^(std::optional error) { - [self handleResponse:error completion:completion]; - }); -} - -- (void)deleteWithCompletion:(RLMUserOptionalErrorBlock)completion { - _user->app()->delete_user(_user, ^(std::optional error) { - [self handleResponse:error completion:completion]; - }); -} - -- (void)logOutWithCompletion:(RLMOptionalErrorBlock)completion { - _user->app()->log_out(_user, ^(std::optional error) { - [self handleResponse:error completion:completion]; - }); -} - -- (RLMAPIKeyAuth *)apiKeysAuth { - return [[RLMAPIKeyAuth alloc] initWithApp:_user->app()]; -} - -- (RLMMongoClient *)mongoClientWithServiceName:(NSString *)serviceName { - return [[RLMMongoClient alloc] initWithUser:self serviceName:serviceName]; -} - -- (void)callFunctionNamed:(NSString *)name - arguments:(NSArray> *)arguments - completionBlock:(RLMCallFunctionCompletionBlock)completionBlock { - bson::BsonArray args; - - for (id argument in arguments) { - args.push_back(RLMConvertRLMBSONToBson(argument)); - } - - _user->app()->call_function(_user, name.UTF8String, args, - [completionBlock](std::optional&& response, - std::optional error) { - if (error) { - return completionBlock(nil, makeError(*error)); - } - - completionBlock(RLMConvertBsonToRLMBSON(*response), nil); - }); -} - -- (void)handleResponse:(std::optional)error - completion:(RLMOptionalErrorBlock)completion { - if (error) { - return completion(makeError(*error)); - } - completion(nil); -} - -#pragma mark - Private API - -- (NSString *)refreshToken { - if (_user->refresh_token().empty()) { - return nil; - } - return @(_user->refresh_token().c_str()); -} - -- (NSString *)accessToken { - if (_user->access_token().empty()) { - return nil; - } - return @(_user->access_token().c_str()); -} - -- (NSDictionary *)customData { - if (!_user->custom_data()) { - return @{}; - } - - return (NSDictionary *)RLMConvertBsonToRLMBSON(*_user->custom_data()); -} - -- (RLMUserProfile *)profile { - return [[RLMUserProfile alloc] initWithUserProfile:_user->user_profile()]; -} - -- (RLMUserSubscriptionToken *)subscribe:(RLMUserNotificationBlock)block { - return [[RLMUserSubscriptionToken alloc] initWithUser:_user token:_user->subscribe([block, self] (auto&) { - block(self); - })]; -} -@end - -#pragma mark - RLMUserIdentity - -@implementation RLMUserIdentity - -- (instancetype)initUserIdentityWithProviderType:(NSString *)providerType - identifier:(NSString *)identifier { - if (self = [super init]) { - _providerType = providerType; - _identifier = identifier; - } - return self; -} - -@end - -#pragma mark - RLMUserProfile - -@interface RLMUserProfile () { - app::UserProfile _userProfile; -} -@end - -static NSString* userProfileMemberToNSString(const std::optional& member) { - if (member == util::none) { - return nil; - } - return @(member->c_str()); -} - -@implementation RLMUserProfile - -using UserProfileMember = std::optional (app::UserProfile::*)() const; - -- (instancetype)initWithUserProfile:(app::UserProfile)userProfile { - if (self = [super init]) { - _userProfile = std::move(userProfile); - } - return self; -} - -- (NSString *)name { - return userProfileMemberToNSString(_userProfile.name()); -} -- (NSString *)email { - return userProfileMemberToNSString(_userProfile.email()); -} -- (NSString *)pictureURL { - return userProfileMemberToNSString(_userProfile.picture_url()); -} -- (NSString *)firstName { - return userProfileMemberToNSString(_userProfile.first_name()); -} -- (NSString *)lastName { - return userProfileMemberToNSString(_userProfile.last_name());; -} -- (NSString *)gender { - return userProfileMemberToNSString(_userProfile.gender()); -} -- (NSString *)birthday { - return userProfileMemberToNSString(_userProfile.birthday()); -} -- (NSString *)minAge { - return userProfileMemberToNSString(_userProfile.min_age()); -} -- (NSString *)maxAge { - return userProfileMemberToNSString(_userProfile.max_age()); -} -- (NSDictionary *)metadata { - return (NSDictionary *)RLMConvertBsonToRLMBSON(_userProfile.data()); -} - -@end diff --git a/Realm/RLMUserAPIKey.h b/Realm/RLMUserAPIKey.h deleted file mode 100644 index 2cdd82b2a0..0000000000 --- a/Realm/RLMUserAPIKey.h +++ /dev/null @@ -1,43 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -/// UserAPIKey model for APIKeys recevied from the server. -RLM_SWIFT_SENDABLE RLM_FINAL // immutable final class -@interface RLMUserAPIKey : NSObject - -/// Indicates if the API key is disabled or not -@property (nonatomic, readonly) BOOL disabled; - -/// The name of the key. -@property (nonatomic, readonly) NSString *name; - -/// The actual key. Will only be included in -/// the response when an API key is first created. -@property (nonatomic, readonly, nullable) NSString *key; - -/// The ObjectId of the API key -@property (nonatomic, readonly) RLMObjectId *objectId NS_REFINED_FOR_SWIFT; - -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMUserAPIKey.mm b/Realm/RLMUserAPIKey.mm deleted file mode 100644 index cce6e29d38..0000000000 --- a/Realm/RLMUserAPIKey.mm +++ /dev/null @@ -1,68 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMUserAPIKey.h" -#import "RLMUserAPIKey_Private.hpp" -#import "RLMUtil.hpp" -#import "RLMObjectId_Private.hpp" - -@interface RLMUserAPIKey() { - realm::app::App::UserAPIKey _userAPIKey; -} -@end - -@implementation RLMUserAPIKey - -- (instancetype)initWithUserAPIKey:(realm::app::App::UserAPIKey)userAPIKey { - if (self = [super init]) { - _userAPIKey = userAPIKey; - return self; - } - - return nil; -} - -// Indicates if the API key is disabled or not -- (BOOL)disabled { - return _userAPIKey.disabled; -} - -// The name of the key. -- (NSString *)name { - return @(_userAPIKey.name.c_str()); -} - -// The actual key. Will only be included in -// the response when an API key is first created. -- (NSString *)key { - if (_userAPIKey.key) { - return @(_userAPIKey.key->c_str()); - } - - return nil; -} - -- (RLMObjectId *)objectId { - return [[RLMObjectId alloc] initWithValue:_userAPIKey.id]; -} - -- (realm::app::App::UserAPIKey)_apiKey { - return _userAPIKey; -} - -@end diff --git a/Realm/RLMUserAPIKey_Private.hpp b/Realm/RLMUserAPIKey_Private.hpp deleted file mode 100644 index 903cfcddb1..0000000000 --- a/Realm/RLMUserAPIKey_Private.hpp +++ /dev/null @@ -1,26 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#import - -@interface RLMUserAPIKey () -- (realm::app::App::UserAPIKey)_apiKey; -- (instancetype)initWithUserAPIKey:(realm::app::App::UserAPIKey)userAPIKey; -@end diff --git a/Realm/RLMUser_Private.h b/Realm/RLMUser_Private.h deleted file mode 100644 index 7be97457ab..0000000000 --- a/Realm/RLMUser_Private.h +++ /dev/null @@ -1,42 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -/// Observer block for user notifications. -typedef void(^RLMUserNotificationBlock)(RLMUser *); - -/// Token that identifies an observer. Unsubscribes when deconstructed to -/// avoid dangling observers, therefore this must be retained to hold -/// onto a subscription. -@interface RLMUserSubscriptionToken : NSObject -- (void)unsubscribe; -@end - -@interface RLMUser () -@property (nonatomic, strong, nullable) RLMApp *app; - -/// Subscribe to notifications for this RLMUser. -- (RLMUserSubscriptionToken *)subscribe:(RLMUserNotificationBlock)block; - -- (void)logOut; -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/RLMUser_Private.hpp b/Realm/RLMUser_Private.hpp deleted file mode 100644 index d63a6e6eb9..0000000000 --- a/Realm/RLMUser_Private.hpp +++ /dev/null @@ -1,45 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMUser_Private.h" - -#import "RLMSyncConfiguration.h" - -#import - -@class RLMSyncConfiguration, RLMApp; - -namespace realm::app { -class User; -struct UserProfile; -} - -RLM_HEADER_AUDIT_BEGIN(nullability, sendability) - -@interface RLMUser () -@property (nonatomic, readonly) std::shared_ptr user; -- (instancetype)initWithUser:(std::shared_ptr)user; -- (std::string)pathForPartitionValue:(std::string const&)partitionValue; -- (std::string)pathForFlexibleSync; -@end - -@interface RLMUserProfile () -- (instancetype)initWithUserProfile:(realm::app::UserProfile)userProfile; -@end - -RLM_HEADER_AUDIT_END(nullability, sendability) diff --git a/Realm/Realm-Info.plist b/Realm/Realm-Info.plist index cfe98a9605..c21b79da64 100644 --- a/Realm/Realm-Info.plist +++ b/Realm/Realm-Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 10.53.1 + 20.0.0 CFBundleSignature ???? CFBundleVersion - 10.53.1 + 20.0.0 NSHumanReadableCopyright Copyright © 2014-2021 Realm. All rights reserved. NSPrincipalClass diff --git a/Realm/Realm.h b/Realm/Realm.h index 34f1679bc8..c0359c96af 100644 --- a/Realm/Realm.h +++ b/Realm/Realm.h @@ -38,28 +38,3 @@ #import #import #import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import diff --git a/Realm/Realm.modulemap b/Realm/Realm.modulemap index e7cbca362f..8a2f8a0848 100644 --- a/Realm/Realm.modulemap +++ b/Realm/Realm.modulemap @@ -4,7 +4,6 @@ framework module Realm { umbrella header "Realm.h" header "RLMArray.h" - header "RLMAsymmetricObject.h" header "RLMDecimal128.h" header "RLMDictionary.h" header "RLMEmbeddedObject.h" @@ -15,8 +14,6 @@ framework module Realm { header "RLMObjectId.h" header "RLMObjectSchema.h" header "RLMProperty.h" - header "RLMProviderClient.h" - header "RLMRealm+Sync.h" header "RLMRealm.h" header "RLMRealmConfiguration.h" header "RLMResults.h" @@ -25,39 +22,13 @@ framework module Realm { header "RLMSet.h" header "RLMValue.h" - header "RLMApp.h" - header "RLMCredentials.h" - header "RLMInitialSubscriptionsConfiguration.h" - header "RLMNetworkTransport.h" - header "RLMPushClient.h" - header "RLMRealm+Sync.h" - header "RLMSyncConfiguration.h" - header "RLMSyncManager.h" - header "RLMSyncSession.h" - header "RLMUser.h" - header "RLMUserAPIKey.h" - header "RLMAPIKeyAuth.h" - header "RLMEmailPasswordAuth.h" - header "NSError+RLMSync.h" - header "RLMBSON.h" - header "RLMMongoClient.h" - header "RLMMongoDatabase.h" - header "RLMMongoCollection.h" - header "RLMUpdateResult.h" - header "RLMFindOptions.h" - header "RLMFindOneAndModifyOptions.h" - header "RLMSyncSubscription.h" - explicit module Private { header "RLMAccessor.h" - header "RLMApp_Private.h" header "RLMArray_Private.h" header "RLMAsyncTask_Private.h" header "RLMCollection_Private.h" header "RLMDictionary_Private.h" header "RLMLogger_Private.h" - header "RLMEvent.h" - header "RLMMongoCollection_Private.h" header "RLMObjectBase_Dynamic.h" header "RLMObjectBase_Private.h" header "RLMObjectSchema_Private.h" @@ -74,9 +45,6 @@ framework module Realm { header "RLMSwiftCollectionBase.h" header "RLMSwiftProperty.h" header "RLMSwiftValueStorage.h" - header "RLMSyncConfiguration_Private.h" - header "RLMSyncSubscription_Private.h" - header "RLMUser_Private.h" } explicit module Dynamic { diff --git a/Realm/TestUtils/RLMTestCase.m b/Realm/TestUtils/RLMTestCase.m index b9ffe5ad73..ddc15007a5 100644 --- a/Realm/TestUtils/RLMTestCase.m +++ b/Realm/TestUtils/RLMTestCase.m @@ -219,7 +219,7 @@ - (dispatch_queue_t)bgQueue { return _bgQueue; } -- (void)dispatchAsync:(RLM_SWIFT_SENDABLE dispatch_block_t)block { +- (void)dispatchAsync:(NS_SWIFT_SENDABLE dispatch_block_t)block { dispatch_async(self.bgQueue, ^{ @autoreleasepool { block(); @@ -227,7 +227,7 @@ - (void)dispatchAsync:(RLM_SWIFT_SENDABLE dispatch_block_t)block { }); } -- (void)dispatchAsyncAndWait:(RLM_SWIFT_SENDABLE dispatch_block_t)block { +- (void)dispatchAsyncAndWait:(NS_SWIFT_SENDABLE dispatch_block_t)block { [self dispatchAsync:block]; dispatch_sync(_bgQueue, ^{}); } diff --git a/Realm/TestUtils/TestUtils.mm b/Realm/TestUtils/TestUtils.mm index d4a5b92b97..10385a0e8e 100644 --- a/Realm/TestUtils/TestUtils.mm +++ b/Realm/TestUtils/TestUtils.mm @@ -22,16 +22,9 @@ #import #import -#import "RLMApp_Private.hpp" #import "RLMRealmConfiguration_Private.hpp" #import "RLMRealmUtil.hpp" -#import "RLMSyncConfiguration_Private.hpp" -#import "RLMSyncManager_Private.hpp" -#import "RLMUser_Private.hpp" -#import -#import -#import #import #import @@ -166,77 +159,6 @@ bool RLMHasCachedRealmForPath(NSString *path) { return RLMGetAnyCachedRealmForPath(path.UTF8String); } -// A network transport which doesn't actually do anything -@interface NoOpTransport : NSObject -@end -@implementation NoOpTransport -- (void)sendRequestToServer:(RLMRequest *)request - completion:(RLMNetworkTransportCompletionBlock)completionBlock { -} -- (NSURLSession *)doStreamRequest:(RLMRequest *)request - eventSubscriber:(id)subscriber { - return nil; -} -@end - -class FakeSyncUser : public realm::SyncUser { - std::string user_id() const noexcept override - { - return "user id"; - } - std::string app_id() const noexcept override - { - return "app id"; - } - - std::string access_token() const override - { - return ""; - } - std::string refresh_token() const override - { - return ""; - } - realm::SyncUser::State state() const override - { - return realm::SyncUser::State::LoggedOut; - } - bool access_token_refresh_required() const override - { - return false; - } - realm::SyncManager* sync_manager() override - { - return nullptr; - } - - void request_log_out() override {} - void request_refresh_location(CompletionHandler&&) override {} - void request_access_token(CompletionHandler&&) override {} - - void track_realm(std::string_view) override {} - std::string create_file_action(realm::SyncFileAction, std::string_view, std::optional) override - { - return ""; - } -}; - -@implementation RLMRealmConfiguration (TestUser) -+ (RLMRealmConfiguration *)fakeSyncConfiguration { - RLMRealmConfiguration *config = [RLMRealmConfiguration new]; - config.configRef.sync_config = std::make_shared(); - config.configRef.sync_config->user = std::make_shared(); - [config updateSchemaMode]; - return config; -} - -+ (RLMRealmConfiguration *)fakeFlexibleSyncConfiguration { - RLMRealmConfiguration *config = [RLMRealmConfiguration fakeSyncConfiguration]; - config.configRef.sync_config->flx_sync_requested = true; - return config; -} -@end - bool RLMThreadSanitizerEnabled() { #if __has_feature(thread_sanitizer) return true; diff --git a/Realm/TestUtils/include/RLMTestCase.h b/Realm/TestUtils/include/RLMTestCase.h index 809ffcf545..b1bc13fe2e 100644 --- a/Realm/TestUtils/include/RLMTestCase.h +++ b/Realm/TestUtils/include/RLMTestCase.h @@ -53,8 +53,8 @@ NSData *RLMGenerateKey(void); - (nullable id)nonLiteralNil; - (BOOL)encryptTests; -- (void)dispatchAsync:(RLM_SWIFT_SENDABLE dispatch_block_t)block; -- (void)dispatchAsyncAndWait:(RLM_SWIFT_SENDABLE dispatch_block_t)block; +- (void)dispatchAsync:(NS_SWIFT_SENDABLE dispatch_block_t)block; +- (void)dispatchAsyncAndWait:(NS_SWIFT_SENDABLE dispatch_block_t)block; @property (nonatomic, readonly) dispatch_queue_t bgQueue; diff --git a/Realm/TestUtils/include/TestUtils.h b/Realm/TestUtils/include/TestUtils.h index 5fddace785..3a59ae8bec 100644 --- a/Realm/TestUtils/include/TestUtils.h +++ b/Realm/TestUtils/include/TestUtils.h @@ -18,8 +18,7 @@ #import #import - -#import +#import RLM_HEADER_AUDIT_BEGIN(nullability) @@ -31,11 +30,6 @@ FOUNDATION_EXTERN void RLMAssertThrowsWithReasonMatchingSwift(XCTestCase *self, NSUInteger lineNumber); -@interface RLMRealmConfiguration (TestUser) -+ (RLMRealmConfiguration *)fakeSyncConfiguration; -+ (RLMRealmConfiguration *)fakeFlexibleSyncConfiguration; -@end - // It appears to be impossible to check this from Swift so we need a helper function FOUNDATION_EXTERN bool RLMThreadSanitizerEnabled(void); diff --git a/Realm/Tests/ArrayPropertyTests.m b/Realm/Tests/ArrayPropertyTests.m index f371e84502..ba18041a05 100644 --- a/Realm/Tests/ArrayPropertyTests.m +++ b/Realm/Tests/ArrayPropertyTests.m @@ -1015,9 +1015,10 @@ - (void)testCrossThreadAccess XCTAssertNoThrow([employees lastObject]); }]; - [RLMRealm.defaultRealm beginWriteTransaction]; - [RLMRealm.defaultRealm addObject:company]; - [RLMRealm.defaultRealm commitWriteTransaction]; + RLMRealm *realm = RLMRealm.defaultRealm; + [realm beginWriteTransaction]; + [realm addObject:company]; + [realm commitWriteTransaction]; employees = company.employees; XCTAssertNoThrow(company.employees); diff --git a/Realm/Tests/Decimal128Tests.m b/Realm/Tests/Decimal128Tests.m index 6164eb5df7..d11b03170d 100644 --- a/Realm/Tests/Decimal128Tests.m +++ b/Realm/Tests/Decimal128Tests.m @@ -28,10 +28,8 @@ @implementation Decimal128Tests - (void)testDecimal128Initialization { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@3.14159]; - RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14159" error:nil]; - NSError *error; - RLMDecimal128 *d3 = [[RLMDecimal128 alloc] initWithString:@"1.2.3" error:&error]; - XCTAssertNil(error); + RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14159"]; + RLMDecimal128 *d3 = [[RLMDecimal128 alloc] initWithString:@"1.2.3"]; RLMDecimal128 *d4 = [[RLMDecimal128 alloc] initWithValue:@3.14159]; RLMDecimal128 *d5 = [[RLMDecimal128 alloc] initWithValue:@"123.456"]; RLMDecimal128 *d6 = [[RLMDecimal128 alloc] initWithNumber:@123456789]; @@ -49,7 +47,7 @@ - (void)testDecimal128Initialization { - (void)testDecimal128Decimal { NSNumber *n1 = @3.14159; - RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithString:@"3.14159" error:nil]; + RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithString:@"3.14159"]; XCTAssertEqual(n1.decimalValue._exponent, d1.decimalValue._exponent); XCTAssertEqual(n1.decimalValue._isCompact, d1.decimalValue._isCompact); XCTAssertEqual(n1.decimalValue._isNegative, d1.decimalValue._isNegative); @@ -62,7 +60,7 @@ - (void)testDecimal128Decimal { - (void)testDecimal128Addition { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@3.14159]; - RLMDecimal128 *d2 = [d1 decimalNumberByAdding:[[RLMDecimal128 alloc] initWithString:@"3.14159" error:nil]]; + RLMDecimal128 *d2 = [d1 decimalNumberByAdding:[[RLMDecimal128 alloc] initWithString:@"3.14159"]]; XCTAssertEqual(d2.doubleValue, 6.28318); XCTAssertTrue([d2.stringValue isEqualToString:@"6.28318"]); XCTAssertEqual(d2.doubleValue, 6.28318); @@ -70,14 +68,14 @@ - (void)testDecimal128Addition { - (void)testDecimal128Subtraction { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@2.5]; - RLMDecimal128 *d2 = [d1 decimalNumberBySubtracting:[[RLMDecimal128 alloc] initWithString:@"5.5" error:nil]]; + RLMDecimal128 *d2 = [d1 decimalNumberBySubtracting:[[RLMDecimal128 alloc] initWithString:@"5.5"]]; XCTAssertEqual(d2.doubleValue, -3.0); XCTAssertTrue([d2.stringValue isEqualToString:@"-3"]); } - (void)testDecimal128Division { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@0.21]; - RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"0.7" error:nil]; + RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"0.7"]; RLMDecimal128 *result = [d1 decimalNumberByDividingBy:d2]; XCTAssertEqual(result.doubleValue, 0.3); XCTAssertTrue([result.stringValue isEqualToString:@"3E-1"]); @@ -85,7 +83,7 @@ - (void)testDecimal128Division { - (void)testDecimal128Multiplication { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@1.5]; - RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"2.5" error:nil]; + RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"2.5"]; RLMDecimal128 *result = [d1 decimalNumberByMultiplyingBy:d2]; XCTAssertEqual(result.doubleValue, 3.75); XCTAssertTrue([result.stringValue isEqualToString:@"3.75"]); @@ -95,34 +93,34 @@ - (void)testDecimal128Multiplication { - (void)testDecimal128InitializationEquals { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@3.14159]; - RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14159" error:nil]; + RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14159"]; XCTAssertTrue([d1 isEqual:d2]); } - (void)testDecimal128InitializationGreaterThan { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@3.14160]; - RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14159" error:nil]; + RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14159"]; XCTAssertTrue([d1 isGreaterThan:d2]); } - (void)testDecimal128InitializationGreaterThanEquals { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@3.14158]; - RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14159" error:nil]; - RLMDecimal128 *d3 = [[RLMDecimal128 alloc] initWithString:@"3.14159" error:nil]; + RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14159"]; + RLMDecimal128 *d3 = [[RLMDecimal128 alloc] initWithString:@"3.14159"]; XCTAssertFalse([d1 isGreaterThanOrEqualTo:d2]); XCTAssertTrue([d2 isLessThanOrEqualTo:d3]); } - (void)testDecimal128InitializationLessThan { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@3.14159]; - RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14160" error:nil]; + RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14160"]; XCTAssertTrue([d1 isLessThan:d2]); } - (void)testDecimal128InitializationLessThanEquals { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@3.14159]; - RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14160" error:nil]; - RLMDecimal128 *d3 = [[RLMDecimal128 alloc] initWithString:@"3.14160" error:nil]; + RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14160"]; + RLMDecimal128 *d3 = [[RLMDecimal128 alloc] initWithString:@"3.14160"]; XCTAssertTrue([d1 isLessThanOrEqualTo:d2]); XCTAssertTrue([d2 isLessThanOrEqualTo:d3]); } diff --git a/Realm/Tests/DictionaryPropertyTests.m b/Realm/Tests/DictionaryPropertyTests.m index 23a7b3144f..d058433b5f 100644 --- a/Realm/Tests/DictionaryPropertyTests.m +++ b/Realm/Tests/DictionaryPropertyTests.m @@ -931,10 +931,11 @@ - (void)testCrossThreadAccess { XCTAssertNoThrow(company.employeeDict); XCTAssertNoThrow(employees[@"eo"]); }]; - - [RLMRealm.defaultRealm beginWriteTransaction]; - [RLMRealm.defaultRealm addObject:company]; - [RLMRealm.defaultRealm commitWriteTransaction]; + + RLMRealm *realm = RLMRealm.defaultRealm; + [realm beginWriteTransaction]; + [realm addObject:company]; + [realm commitWriteTransaction]; employees = company.employeeDict; XCTAssertNoThrow(company.employeeDict); diff --git a/Realm/Tests/ObjectTests.m b/Realm/Tests/ObjectTests.m index 521b7d7c63..2857953a48 100644 --- a/Realm/Tests/ObjectTests.m +++ b/Realm/Tests/ObjectTests.m @@ -1077,9 +1077,10 @@ - (void)testCrossThreadAccess { // Unmanaged object can be accessed from other threads [self dispatchAsyncAndWait:^{ XCTAssertNoThrow(obj.intCol = 5); }]; - [RLMRealm.defaultRealm beginWriteTransaction]; - [RLMRealm.defaultRealm addObject:obj]; - [RLMRealm.defaultRealm commitWriteTransaction]; + RLMRealm *realm = RLMRealm.defaultRealm; + [realm beginWriteTransaction]; + [realm addObject:obj]; + [realm commitWriteTransaction]; [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason(obj.intCol, @"incorrect thread"); }]; } @@ -1157,7 +1158,8 @@ - (void)testInvalidatedWithCustomObjectClasses { #pragma mark - Primary Keys - (void)testPrimaryKey { - [[RLMRealm defaultRealm] beginWriteTransaction]; + RLMRealm *realm = RLMRealm.defaultRealm; + [realm beginWriteTransaction]; [PrimaryStringObject createInDefaultRealmWithValue:(@[@"string", @1])]; [PrimaryStringObject createInDefaultRealmWithValue:(@[@"string2", @1])]; @@ -1182,7 +1184,7 @@ - (void)testPrimaryKey { RLMAssertThrowsWithReason([PrimaryNullableIntObject createInDefaultRealmWithValue:(@[NSNull.null, @0])], @"existing primary key value"); - [[RLMRealm defaultRealm] commitWriteTransaction]; + [realm commitWriteTransaction]; } - (void)testCreateOrUpdate { @@ -1294,7 +1296,8 @@ - (void)testCreateOrUpdateWithReorderedColumns { } - (void)testObjectInSet { - [[RLMRealm defaultRealm] beginWriteTransaction]; + RLMRealm *realm = RLMRealm.defaultRealm; + [realm beginWriteTransaction]; // set object with primary and non primary keys as they both override isEqual and hash PrimaryStringObject *obj = [PrimaryStringObject createInDefaultRealmWithValue:(@[@"string2", @1])]; @@ -1311,17 +1314,18 @@ - (void)testObjectInSet { XCTAssertTrue([dict containsObject:strObj]); XCTAssertFalse([dict containsObject:[[StringObject allObjects] firstObject]]); - [[RLMRealm defaultRealm] commitWriteTransaction]; + [realm commitWriteTransaction]; } - (void)testObjectForKey { - [RLMRealm.defaultRealm beginWriteTransaction]; + RLMRealm *realm = RLMRealm.defaultRealm; + [realm beginWriteTransaction]; PrimaryStringObject *strObj = [PrimaryStringObject createInDefaultRealmWithValue:@[@"key", @0]]; PrimaryNullableStringObject *nullStrObj = [PrimaryNullableStringObject createInDefaultRealmWithValue:@[NSNull.null, @0]]; PrimaryIntObject *intObj = [PrimaryIntObject createInDefaultRealmWithValue:@[@0]]; PrimaryNullableIntObject *nonNullIntObj = [PrimaryNullableIntObject createInDefaultRealmWithValue:@[@0]]; PrimaryNullableIntObject *nullIntObj = [PrimaryNullableIntObject createInDefaultRealmWithValue:@[NSNull.null]]; - [RLMRealm.defaultRealm commitWriteTransaction]; + [realm commitWriteTransaction]; // no PK RLMAssertThrowsWithReason([StringObject objectForPrimaryKey:@""], @@ -1584,6 +1588,7 @@ - (void)testThawUpdatedOnDifferentThread { } - (void)testThawCreatedOnDifferentThread { + __attribute((objc_precise_lifetime)) RLMRealm *realm = [RLMRealm defaultRealm]; XCTAssertEqual([[IntObject allObjects] count], 0); __block IntObject *frozen; diff --git a/Realm/Tests/QueryTests.m b/Realm/Tests/QueryTests.m index 92233e772b..6e0b3fd82d 100644 --- a/Realm/Tests/QueryTests.m +++ b/Realm/Tests/QueryTests.m @@ -704,15 +704,24 @@ - (void)testRLMValueQuery { @"object must be of type NSArray for BETWEEN operations"); // Binary based comparability - RLMAssertCount(AllTypesObject, 2U, @"anyCol == '0'"); - RLMAssertCount(AllTypesObject, allValues.count-2, @"anyCol != '0'"); - RLMAssertCount(AllTypesObject, 2U, @"anyCol BEGINSWITH '1'"); + RLMAssertCount(AllTypesObject, 1U, @"anyCol == '0'"); + RLMAssertCount(AllTypesObject, allValues.count-1, @"anyCol != '0'"); + RLMAssertCount(AllTypesObject, 1U, @"anyCol BEGINSWITH '1'"); RLMAssertCount(AllTypesObject, 0U, @"anyCol BEGINSWITH 'a'"); - RLMAssertCount(AllTypesObject, 2U, @"anyCol ENDSWITH '1'"); + RLMAssertCount(AllTypesObject, 1U, @"anyCol ENDSWITH '1'"); RLMAssertCount(AllTypesObject, 0U, @"anyCol ENDSWITH 'a'"); - RLMAssertCount(AllTypesObject, 2U, @"anyCol CONTAINS '1'"); + RLMAssertCount(AllTypesObject, 1U, @"anyCol CONTAINS '1'"); RLMAssertCount(AllTypesObject, 0U, @"anyCol CONTAINS 'a'"); + RLMAssertCount(AllTypesObject, 1U, @"anyCol == %@", [@"0" dataUsingEncoding:NSUTF8StringEncoding]); + RLMAssertCount(AllTypesObject, allValues.count-1, @"anyCol != %@", [@"0" dataUsingEncoding:NSUTF8StringEncoding]); + RLMAssertCount(AllTypesObject, 1U, @"anyCol BEGINSWITH %@", [@"1" dataUsingEncoding:NSUTF8StringEncoding]); + RLMAssertCount(AllTypesObject, 0U, @"anyCol BEGINSWITH %@", [@"a" dataUsingEncoding:NSUTF8StringEncoding]); + RLMAssertCount(AllTypesObject, 1U, @"anyCol ENDSWITH %@", [@"1" dataUsingEncoding:NSUTF8StringEncoding]); + RLMAssertCount(AllTypesObject, 0U, @"anyCol ENDSWITH %@", [@"a" dataUsingEncoding:NSUTF8StringEncoding]); + RLMAssertCount(AllTypesObject, 1U, @"anyCol CONTAINS %@", [@"1" dataUsingEncoding:NSUTF8StringEncoding]); + RLMAssertCount(AllTypesObject, 0U, @"anyCol CONTAINS %@", [@"a" dataUsingEncoding:NSUTF8StringEncoding]); + XCTAssertThrowsSpecificNamed([AllTypesObject objectsWhere:@"anyCol CONATINS 0"], NSException, NSInvalidArgumentException, @@ -3712,7 +3721,7 @@ - (void)testDictionaryQueryAllValues { } - (void)testDictionaryQueryKeySubscript { - void (^test)(NSString *, NSArray *, BOOL) = ^(NSString *property, NSArray *values, BOOL isNumeric) { + void (^test)(NSString *, NSArray *, id (^)(NSString *)) = ^(NSString *property, NSArray *values, id (^string)(NSString *)) { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey": values[0]}}]; @@ -3726,7 +3735,7 @@ - (void)testDictionaryQueryKeySubscript { RLMAssertCount(AllDictionariesObject, 2U, @"%K['aKey'] !=[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] =[cd] %@", property, values[0]); - if (isNumeric) { + if (!string) { RLMAssertCount(AllDictionariesObject, 0U, @"%K['aKey'] > %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 3U, @"NOT %K['aKey'] > %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 0U, @"%K['aKey'] >[c] %@", property, values[0]); @@ -3744,51 +3753,53 @@ - (void)testDictionaryQueryKeySubscript { RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] =[cd] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"%K['aKey'] !=[cd] %@", property, values[0]); // BEGINSWITH - RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] BEGINSWITH 'he'", property); - RLMAssertCount(AllDictionariesObject, 2U, @"NOT %K['aKey'] BEGINSWITH 'he'", property); - RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] BEGINSWITH[c] 'he'", property); - RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] BEGINSWITH[cd] 'he'", property); + RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] BEGINSWITH %@", property, string(@"he")); + RLMAssertCount(AllDictionariesObject, 2U, @"NOT %K['aKey'] BEGINSWITH %@", property, string(@"he")); + RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] BEGINSWITH[c] %@", property, string(@"he")); + RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] BEGINSWITH[cd] %@", property, string(@"he")); RLMAssertCount(AllDictionariesObject, 0U, @"%K['aKey'] BEGINSWITH NULL", property); // CONTAINS - RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] CONTAINS 'el'", property); - RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] CONTAINS[c] 'el'", property); - RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] CONTAINS[cd] 'el'", property); + RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] CONTAINS %@", property, string(@"el")); + RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] CONTAINS[c] %@", property, string(@"el")); + RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] CONTAINS[cd] %@", property, string(@"el")); RLMAssertCount(AllDictionariesObject, 0U, @"%K['aKey'] CONTAINS NULL", property); // ENDSWITH - RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] ENDSWITH 'lo'", property); - RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] ENDSWITH[c] 'lo'", property); - RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] ENDSWITH[cd] 'lo'", property); + RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] ENDSWITH %@", property, string(@"lo")); + RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] ENDSWITH[c] %@", property, string(@"lo")); + RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] ENDSWITH[cd] %@", property, string(@"lo")); RLMAssertCount(AllDictionariesObject, 0U, @"%K['aKey'] ENDSWITH NULL", property); // LIKE - RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] LIKE 'hel*'", property); - RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] LIKE[c] 'hel*'", property); + RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] LIKE %@", property, string(@"hel*")); + RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] LIKE[c] %@", property, string(@"hel*")); RLMAssertCount(AllDictionariesObject, 2U, @"%K['aKey'] LIKE NULL", property); - RLMAssertCount(AllDictionariesObject, 2U, @"NOT %K['aKey'] LIKE 'hel*'", property); - RLMAssertCount(AllDictionariesObject, 2U, @"NOT %K['aKey'] LIKE[c] 'hel*'", property); + RLMAssertCount(AllDictionariesObject, 2U, @"NOT %K['aKey'] LIKE %@", property, string(@"hel*")); + RLMAssertCount(AllDictionariesObject, 2U, @"NOT %K['aKey'] LIKE[c] %@", property, string(@"hel*")); RLMAssertCount(AllDictionariesObject, 1U, @"NOT %K['aKey'] LIKE NULL", property); - RLMAssertThrowsWithReasonMatching(([AllDictionariesObject objectsInRealm:realm where:@"%K['aKey'] LIKE[cd] 'hel*'", property]), @"not supported"); + RLMAssertThrowsWithReasonMatching(([AllDictionariesObject objectsInRealm:realm where:@"%K['aKey'] LIKE[cd] %@", property, string(@"hel*")]), @"not supported"); } [realm beginWriteTransaction]; [realm deleteAllObjects]; [realm commitWriteTransaction]; }; - test(@"intDict", @[@456, @123, @789], YES); - test(@"doubleDict", @[@456.123, @123.123, @789.123], YES); - test(@"boolDict", @[@NO, @NO, @YES], YES); + test(@"intDict", @[@456, @123, @789], nil); + test(@"doubleDict", @[@456.123, @123.123, @789.123], nil); + test(@"boolDict", @[@NO, @NO, @YES], nil); test(@"decimalDict", @[[RLMDecimal128 decimalWithNumber:@456.123], [RLMDecimal128 decimalWithNumber:@123.123], - [RLMDecimal128 decimalWithNumber:@789.123]], YES); + [RLMDecimal128 decimalWithNumber:@789.123]], nil); test(@"dateDict", @[[NSDate dateWithTimeIntervalSince1970:4000], [NSDate dateWithTimeIntervalSince1970:2000], - [NSDate dateWithTimeIntervalSince1970:8000]], YES); + [NSDate dateWithTimeIntervalSince1970:8000]], nil); test(@"dataDict", @[[NSData dataWithBytes:"hello" length:5], [NSData dataWithBytes:"Héllo" length:5], - [NSData dataWithBytes:"HELLO" length:5]], NO); + [NSData dataWithBytes:"HELLO" length:5]], ^(NSString *str) { + return [str dataUsingEncoding:NSUTF8StringEncoding]; + }); test(@"objectIdDict", @[[[RLMObjectId alloc] initWithString:@"60425fff91d7a195d5ddac1b" error:nil], [[RLMObjectId alloc] initWithString:@"60425fff91d7a195d5ddac1a" error:nil], - [[RLMObjectId alloc] initWithString:@"60425fff91d7a195d5ddac1c" error:nil]], YES); - test(@"stringDict", @[@"hello", @"Héllo", @"HELLO"], NO); + [[RLMObjectId alloc] initWithString:@"60425fff91d7a195d5ddac1c" error:nil]], nil); + test(@"stringDict", @[@"hello", @"Héllo", @"HELLO"], ^(NSString *str) { return str; }); } - (void)testDictionaryQueryKeySubscriptWithObjectCol { diff --git a/Realm/Tests/RealmConfigurationTests.mm b/Realm/Tests/RealmConfigurationTests.mm index d2c65368b0..8eed66df96 100644 --- a/Realm/Tests/RealmConfigurationTests.mm +++ b/Realm/Tests/RealmConfigurationTests.mm @@ -20,10 +20,8 @@ #import "TestUtils.h" -#import "RLMApp_Private.h" #import "RLMRealmConfiguration_Private.hpp" #import "RLMTestObjects.h" -#import "RLMUser_Private.h" #import "RLMUtil.hpp" @interface RealmConfigurationTests : RLMTestCase @@ -108,23 +106,6 @@ - (void)testSchemaModeTransitions { configuration.deleteRealmIfMigrationNeeded = false; XCTAssertEqual(configuration.schemaMode, realm::SchemaMode::Automatic); - - configuration = [RLMRealmConfiguration fakeSyncConfiguration]; - XCTAssertEqual(configuration.schemaMode, realm::SchemaMode::AdditiveDiscovered); - configuration.objectClasses = @[]; - XCTAssertEqual(configuration.schemaMode, realm::SchemaMode::AdditiveExplicit); - configuration.readOnly = true; - XCTAssertEqual(configuration.schemaMode, realm::SchemaMode::ReadOnly); - configuration.objectClasses = nil; - XCTAssertEqual(configuration.schemaMode, realm::SchemaMode::ReadOnly); - configuration.readOnly = false; - XCTAssertEqual(configuration.schemaMode, realm::SchemaMode::AdditiveDiscovered); - configuration.readOnly = true; - XCTAssertEqual(configuration.schemaMode, realm::SchemaMode::ReadOnly); - configuration.objectClasses = @[]; - XCTAssertEqual(configuration.schemaMode, realm::SchemaMode::ReadOnly); - configuration.readOnly = false; - XCTAssertEqual(configuration.schemaMode, realm::SchemaMode::AdditiveExplicit); } #pragma mark - Default Configuration diff --git a/Realm/Tests/RealmTests.mm b/Realm/Tests/RealmTests.mm index 2bcf08e5c9..eb77299028 100644 --- a/Realm/Tests/RealmTests.mm +++ b/Realm/Tests/RealmTests.mm @@ -157,17 +157,17 @@ - (void)testReadOnlyRealmMustExist { - (void)testCannotHaveReadOnlyAndReadWriteRealmsAtSamePathAtSameTime { NSString *exceptionReason = @"Realm at path '.*' already opened with different read permissions"; @autoreleasepool { - XCTAssertNoThrow([self realmWithTestPath]); + RLMRealm *realm = self.realmWithTestPath; RLMAssertThrowsWithReasonMatching([self readOnlyRealmWithURL:RLMTestRealmURL() error:nil], exceptionReason); } @autoreleasepool { - XCTAssertNoThrow([self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]); + RLMRealm *realm = [self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]; RLMAssertThrowsWithReasonMatching([self realmWithTestPath], exceptionReason); } [self dispatchAsyncAndWait:^{ - XCTAssertNoThrow([self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]); + RLMRealm *realm = [self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]; RLMAssertThrowsWithReasonMatching([self realmWithTestPath], exceptionReason); }]; } @@ -2606,19 +2606,20 @@ - (void)testThawDifferentThread { } - (void)testThawPreviousVersion { - RLMRealm *frozenRealm = [[RLMRealm defaultRealm] freeze]; + RLMRealm *realm = RLMRealm.defaultRealm; + RLMRealm *frozenRealm = [realm freeze]; XCTAssertTrue(frozenRealm.frozen); XCTAssertEqual(frozenRealm.isEmpty, [[RLMRealm defaultRealm] isEmpty]); - [RLMRealm.defaultRealm beginWriteTransaction]; + [realm beginWriteTransaction]; [IntObject createInDefaultRealmWithValue:@[@1]]; - [RLMRealm.defaultRealm commitWriteTransaction]; - XCTAssertNotEqual(frozenRealm.isEmpty, [[RLMRealm defaultRealm] isEmpty], "Contents of frozen Realm should not mutate"); + [realm commitWriteTransaction]; + XCTAssertNotEqual(frozenRealm.isEmpty, realm.isEmpty, "Contents of frozen Realm should not mutate"); RLMRealm *thawed = [frozenRealm thaw]; XCTAssertFalse(thawed.isFrozen); - XCTAssertEqual(thawed.isEmpty, [[RLMRealm defaultRealm] isEmpty], "Thawed realm should reflect transactions since the original reference was frozen"); + XCTAssertEqual(thawed.isEmpty, realm.isEmpty, "Thawed realm should reflect transactions since the original reference was frozen"); XCTAssertNotEqual(thawed.isEmpty, frozenRealm.isEmpty); } @@ -3031,37 +3032,3 @@ - (void)testCustomLoggerLogMessage { XCTAssertFalse([logs containsString:@"IMPORTANT TRACE"]); // Trace } @end - -@interface RLMMetricsTests : RLMTestCase -@property (nonatomic, strong) RLMLogger *logger; -@end - -@implementation RLMMetricsTests -- (void)setUp { - _logger = RLMLogger.defaultLogger; -} -- (void)tearDown { - RLMLogger.defaultLogger = _logger; -} - -- (void)testSyncConnectionMetrics { - __block NSMutableString *logs = [[NSMutableString alloc] init]; - RLMLogger *logger = [[RLMLogger alloc] initWithLevel:RLMLogLevelDebug - logFunction:^(RLMLogLevel level, NSString * message) { - [logs appendFormat:@" %@ %lu %@\n", [NSDate date], level, message]; - }]; - RLMLogger.defaultLogger = logger; - RLMApp *app = [RLMApp appWithId:@"test-id"]; - // We don't even need the login to succeed, we only want for the logger - // to log the values on device info after trying to login. - [app loginWithCredential:RLMCredentials.anonymousCredentials completion:^(RLMUser *, NSError *) {}]; - // Verifying that this values are set on device_info. - // Only the following values are logged by core (sdk, sdk version, platform version). - NSString *realmVersion = [NSString stringWithFormat:@"sdk version: %@", REALM_COCOA_VERSION]; - XCTAssertTrue([logs containsString:realmVersion]); - XCTAssertTrue([logs containsString:@"sdk: Realm Swift"]); - auto processInfo = [NSProcessInfo processInfo]; - NSString *version = [NSString stringWithFormat:@"version: %@", processInfo.operatingSystemVersionString]; - XCTAssertTrue([logs containsString:version]); -} -@end diff --git a/Realm/Tests/SchemaTests.mm b/Realm/Tests/SchemaTests.mm index 5ae3751d5d..3661aa6590 100644 --- a/Realm/Tests/SchemaTests.mm +++ b/Realm/Tests/SchemaTests.mm @@ -22,7 +22,6 @@ #import "TestUtils.h" #import "RLMAccessor.h" -#import "RLMApp_Private.h" #import "RLMObjectSchema_Private.hpp" #import "RLMObject_Private.h" #import "RLMProperty_Private.h" @@ -30,7 +29,6 @@ #import "RLMRealm_Dynamic.h" #import "RLMRealm_Private.hpp" #import "RLMSchema_Private.hpp" -#import "RLMUser_Private.h" #import "RLMUtil.hpp" #import @@ -1223,26 +1221,6 @@ - (void)testInsertingColumnsInBackgroundProcess { XCTAssertEqual(query.count, 3U); } -- (void)testExplicitlyIncludedEmbeddedOrphanIsRejectedForSyncRealm { - // Test each different order of setting properties because there's a bunch of awkward state involved - RLMRealmConfiguration *config = [RLMRealmConfiguration fakeSyncConfiguration]; - config.objectClasses = @[OrphanObject.class]; - RLMAssertThrowsWithReason([RLMRealm realmWithConfiguration:config error:nil], - @"Embedded object 'OrphanObject' is unreachable by any link path from top level objects."); - - config = [RLMRealmConfiguration defaultConfiguration]; - config.syncConfiguration = [RLMRealmConfiguration fakeSyncConfiguration].syncConfiguration; - config.objectClasses = @[OrphanObject.class]; - RLMAssertThrowsWithReason([RLMRealm realmWithConfiguration:config error:nil], - @"Embedded object 'OrphanObject' is unreachable by any link path from top level objects."); - - config = [RLMRealmConfiguration defaultConfiguration]; - config.objectClasses = @[OrphanObject.class]; - config.syncConfiguration = [RLMRealmConfiguration fakeSyncConfiguration].syncConfiguration; - RLMAssertThrowsWithReason([RLMRealm realmWithConfiguration:config error:nil], - @"Embedded object 'OrphanObject' is unreachable by any link path from top level objects."); -} - - (void)testExplicitlyIncludedEmbeddedOrphanIsAllowedForLocalRealm { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[OrphanObject.class]; diff --git a/Realm/Tests/SetPropertyTests.m b/Realm/Tests/SetPropertyTests.m index a4f19b8bab..2de988d19a 100644 --- a/Realm/Tests/SetPropertyTests.m +++ b/Realm/Tests/SetPropertyTests.m @@ -924,9 +924,10 @@ - (void)testCrossThreadAccess XCTAssertNoThrow([employees allObjects]); }]; - [RLMRealm.defaultRealm beginWriteTransaction]; - [RLMRealm.defaultRealm addObject:company]; - [RLMRealm.defaultRealm commitWriteTransaction]; + RLMRealm *realm = RLMRealm.defaultRealm; + [realm beginWriteTransaction]; + [realm addObject:company]; + [realm commitWriteTransaction]; employees = company.employeeSet; XCTAssertNoThrow(company.employeeSet); diff --git a/Realm/Tests/SwiftUISyncTestHost/ContentView.swift b/Realm/Tests/SwiftUISyncTestHost/ContentView.swift deleted file mode 100644 index 66a22fae49..0000000000 --- a/Realm/Tests/SwiftUISyncTestHost/ContentView.swift +++ /dev/null @@ -1,558 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import SwiftUI -import RealmSwift -import Combine - -class SwiftPerson: Object, ObjectKeyIdentifiable { - @Persisted(primaryKey: true) public var _id: ObjectId - @Persisted public var firstName: String = "" - @Persisted public var lastName: String = "" - @Persisted public var age: Int = 30 -} - -enum LoggingViewState { - case initial - case loggingIn - case loggedIn - case syncing -} - -struct MainView: View { - let testType = TestType(rawValue: ProcessInfo.processInfo.environment["async_view_type"]!)! - let partitionValue: String? = ProcessInfo.processInfo.environment["partition_value"] - - @State var viewState: LoggingViewState = .initial - @State var user: User? - - var body: some View { - VStack { - LoginView(didLogin: { user in - viewState = .loggedIn - self.user = user - }, loggingIn: { - viewState = .loggingIn - }) - switch viewState { - case .initial: - VStack { - Text("Waiting For Login") - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.purple) - .transition(AnyTransition.move(edge: .trailing)) - case .loggingIn: - VStack { - ProgressView("Logging in...") - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.blue) - .transition(AnyTransition.move(edge: .leading)) - case .loggedIn: - VStack { - Text("Logged in") - .accessibilityIdentifier("logged_view") - Button("Sync") { - viewState = .syncing - } - .accessibilityIdentifier("sync_button") - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.yellow) - .transition(AnyTransition.move(edge: .trailing)) - case .syncing: - switch testType { - case .asyncOpen: - AsyncOpenView() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.green) - .transition(AnyTransition.move(edge: .leading)) - case .asyncOpenEnvironmentPartition: - AsyncOpenPartitionView() - .environment(\.partitionValue, partitionValue) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.green) - .transition(AnyTransition.move(edge: .leading)) - case .asyncOpenEnvironmentConfiguration: - AsyncOpenPartitionView() - .environment(\.realmConfiguration, user!.configuration(partitionValue: partitionValue!)) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.green) - .transition(AnyTransition.move(edge: .leading)) - case .asyncOpenFlexibleSync: - AsyncOpenFlexibleSyncView(firstName: partitionValue!) - .environment(\.realmConfiguration, user!.flexibleSyncConfiguration()) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.green) - .transition(AnyTransition.move(edge: .leading)) - case .asyncOpenCustomConfiguration: - AsyncOpenPartitionView() - .environment(\.realmConfiguration, getConfigurationForUser(user!)) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.green) - .transition(AnyTransition.move(edge: .leading)) - case .autoOpen: - AutoOpenView() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.green) - .transition(AnyTransition.move(edge: .leading)) - case .autoOpenEnvironmentPartition: - AutoOpenPartitionView() - .environment(\.partitionValue, partitionValue) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.green) - .transition(AnyTransition.move(edge: .leading)) - case .autoOpenEnvironmentConfiguration: - AutoOpenPartitionView() - .environment(\.realmConfiguration, user!.configuration(partitionValue: partitionValue!)) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.green) - .transition(AnyTransition.move(edge: .leading)) - case .autoOpenFlexibleSync: - AutoOpenFlexibleSyncView(firstName: partitionValue!) - .environment(\.realmConfiguration, user!.flexibleSyncConfiguration()) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.green) - .transition(AnyTransition.move(edge: .leading)) - case .autoOpenCustomConfiguration: - AutoOpenPartitionView() - .environment(\.realmConfiguration, getConfigurationForUser(user!)) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.green) - .transition(AnyTransition.move(edge: .leading)) - } - } - } - } - - func getConfigurationForUser(_ user: User) -> Realm.Configuration { - var configuration = user.configuration(partitionValue: partitionValue!) - configuration.encryptionKey = getKey() - return configuration - } - - func getKey() -> Data { - var key = Data(count: 64) - _ = key.withUnsafeMutableBytes { (pointer: UnsafeMutableRawBufferPointer) in - SecRandomCopyBytes(kSecRandomDefault, 64, pointer.baseAddress!) } - return key - } -} - -struct LoginView: View { - @ObservedObject var loginHelper = LoginHelper() - var didLogin: (User) -> Void - var loggingIn: () -> Void - - var body: some View { - VStack { - Button("Log In User 1") { - loggingIn() - loginHelper.login(email: ProcessInfo.processInfo.environment["email1"]!, - password: "password", - completion: { user in - didLogin(user) - }) - } - .accessibilityIdentifier("login_button_1") - Button("Log In User 2") { - loggingIn() - loginHelper.login(email: ProcessInfo.processInfo.environment["email2"]!, - password: "password", - completion: { user in - didLogin(user) - }) - } - .accessibilityIdentifier("login_button_2") - Button("Logout") { - loginHelper.logout() - } - .accessibilityIdentifier("logout_button") - Button("Logout All Users") { - loginHelper.logoutAllUsers() - } - .accessibilityIdentifier("logout_users_button") - } - } -} - -class LoginHelper: ObservableObject { - var cancellables = Set() - - private let appConfig = AppConfiguration(baseURL: "http://localhost:9090") - - func login(email: String, password: String, completion: @escaping (User) -> Void) { - Logger.shared.level = .all - let app = RealmSwift.App(id: ProcessInfo.processInfo.environment["app_id"]!, configuration: appConfig) - app.login(credentials: .emailPassword(email: email, password: password)) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { result in - if case let .failure(error) = result { - print("Login user error \(error)") - } - }, receiveValue: { user in - completion(user) - }) - .store(in: &cancellables) - } - - func logout() { - let app = RealmSwift.App(id: ProcessInfo.processInfo.environment["app_id"]!, configuration: appConfig) - app.currentUser?.logOut { _ in } - } - - func logoutAllUsers() { - let app = RealmSwift.App(id: ProcessInfo.processInfo.environment["app_id"]!, configuration: appConfig) - for (_, user) in app.allUsers { - user.logOut { _ in } - } - } -} - -struct AsyncOpenView: View { - @State var canNavigate: Bool = false - @State var progress: Progress? - @AsyncOpen(appId: ProcessInfo.processInfo.environment["app_id"]!, - partitionValue: ProcessInfo.processInfo.environment["partition_value"]!, - timeout: 2000) - var asyncOpen - - var body: some View { - VStack { - switch asyncOpen { - case .connecting: - ProgressView() - case .waitingForUser: - ProgressView("Waiting for user to logged in...") - case .open(let realm): - if canNavigate { - ListView() - .environment(\.realm, realm) - .transition(AnyTransition.move(edge: .leading)) - } else { - VStack { - Text(String(progress!.completedUnitCount)) - .accessibilityIdentifier("progress_text_view") - Button("Navigate Next View") { - canNavigate = true - } - .accessibilityIdentifier("show_list_button_view") - } - } - case .error(let error): - ErrorView(error: error) - .background(Color.red) - .transition(AnyTransition.move(edge: .trailing)) - case .progress(let progress): - ProgressView(progress) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.yellow) - .transition(AnyTransition.move(edge: .trailing)) - .onAppear { - self.progress = progress - } - } - } - } -} - -struct AutoOpenView: View { - @State var canNavigate: Bool = false - @State var progress: Progress? - @AutoOpen(appId: ProcessInfo.processInfo.environment["app_id"]!, - partitionValue: ProcessInfo.processInfo.environment["partition_value"]!, - timeout: 2000) - var autoOpen - - var body: some View { - VStack { - switch autoOpen { - case .connecting: - ProgressView() - case .waitingForUser: - ProgressView("Waiting for user to log in...") - case .open(let realm): - if canNavigate { - ListView() - .environment(\.realm, realm) - .transition(AnyTransition.move(edge: .leading)) - } else { - VStack { - Text(String(progress!.completedUnitCount)) - .accessibilityIdentifier("progress_text_view") - Button("Navigate Next View") { - canNavigate = true - } - .accessibilityIdentifier("show_list_button_view") - } - } - case .error(let error): - ErrorView(error: error) - .background(Color.red) - .transition(AnyTransition.move(edge: .trailing)) - case .progress(let progress): - ProgressView(progress) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.yellow) - .transition(AnyTransition.move(edge: .trailing)) - .onAppear { - self.progress = progress - } - } - } - } -} - -struct AsyncOpenPartitionView: View { - @AsyncOpen(appId: ProcessInfo.processInfo.environment["app_id"]!, - partitionValue: "wrong_partition_value", - timeout: 2000) - var asyncOpen - - var body: some View { - VStack { - switch asyncOpen { - case .connecting: - ProgressView() - case .waitingForUser: - VStack { - Button("User") {} - .accessibilityIdentifier("waiting_user_view") - ProgressView("Waiting for user to logged in...") - } - case .open(let realm): - if ProcessInfo.processInfo.environment["is_sectioned_results"] == "true" { - SectionedResultsView() - .environment(\.realm, realm) - .transition(AnyTransition.move(edge: .leading)) - } else { - ListView() - .environment(\.realm, realm) - .transition(AnyTransition.move(edge: .leading)) - } - case .error(let error): - ErrorView(error: error) - .background(Color.red) - .transition(AnyTransition.move(edge: .trailing)) - case .progress(let progress): - ProgressView(progress) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.yellow) - .transition(AnyTransition.move(edge: .trailing)) - } - } - } -} - -struct AutoOpenPartitionView: View { - @AutoOpen(appId: ProcessInfo.processInfo.environment["app_id"]!, - partitionValue: "wrong_partition_value", - timeout: 2000) - var autoOpen - - var body: some View { - VStack { - switch autoOpen { - case .connecting: - ProgressView() - case .waitingForUser: - VStack { - Button("User") {} - .accessibilityIdentifier("waiting_user_view") - ProgressView("Waiting for user to logged in...") - } - case .open(let realm): - ListView() - .environment(\.realm, realm) - .transition(AnyTransition.move(edge: .leading)) - case .error(let error): - ErrorView(error: error) - .background(Color.red) - .transition(AnyTransition.move(edge: .trailing)) - case .progress(let progress): - ProgressView(progress) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.yellow) - .transition(AnyTransition.move(edge: .trailing)) - } - } - } -} - -enum SubscriptionState { - case initial - case completed - case navigate -} - -struct AsyncOpenFlexibleSyncView: View { - @State var subscriptionState: SubscriptionState = .initial - @AsyncOpen(appId: ProcessInfo.processInfo.environment["app_id"]!, - timeout: 2000) - var asyncOpen - let firstName: String - - var body: some View { - VStack { - switch asyncOpen { - case .connecting: - ProgressView() - case .waitingForUser: - ProgressView("Waiting for user to logged in...") - case .open(let realm): - switch subscriptionState { - case .initial: - ProgressView("Subscribing to Query") - .onAppear { - Task { - do { - let subs = realm.subscriptions - try await subs.update { - subs.append(QuerySubscription(name: "person_age") { - $0.age > 5 && $0.firstName == firstName - }) - } - subscriptionState = .completed - } - } - } - case .completed: - VStack { - Button("Navigate Next View") { - subscriptionState = .navigate - } - .accessibilityIdentifier("show_list_button_view") - } - case .navigate: - ListView() - .environment(\.realm, realm) - .transition(AnyTransition.move(edge: .leading)) - } - case .error(let error): - ErrorView(error: error) - .background(Color.red) - .transition(AnyTransition.move(edge: .trailing)) - case .progress(let progress): - ProgressView(progress) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.yellow) - .transition(AnyTransition.move(edge: .trailing)) - } - } - } -} - -struct AutoOpenFlexibleSyncView: View { - @State var subscriptionState: SubscriptionState = .initial - @AutoOpen(appId: ProcessInfo.processInfo.environment["app_id"]!, - timeout: 2000) - var asyncOpen - let firstName: String - - var body: some View { - VStack { - switch asyncOpen { - case .connecting: - ProgressView() - case .waitingForUser: - ProgressView("Waiting for user to logged in...") - case .open(let realm): - switch subscriptionState { - case .initial: - ProgressView("Subscribing to Query") - .onAppear { - Task { - do { - let subs = realm.subscriptions - try await subs.update { - subs.append(QuerySubscription(name: "person_age") { - $0.age > 2 && $0.firstName == firstName - }) - } - subscriptionState = .completed - } - } - } - case .completed: - VStack { - Button("Navigate Next View") { - subscriptionState = .navigate - } - .accessibilityIdentifier("show_list_button_view") - } - case .navigate: - ListView() - .environment(\.realm, realm) - .transition(AnyTransition.move(edge: .leading)) - } - case .error(let error): - ErrorView(error: error) - .background(Color.red) - .transition(AnyTransition.move(edge: .trailing)) - case .progress(let progress): - ProgressView(progress) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.yellow) - .transition(AnyTransition.move(edge: .trailing)) - } - } - } -} - -struct ErrorView: View { - var error: Error - var body: some View { - VStack(spacing: 20) { - Text("Error") - Text(error.localizedDescription) - } - .padding() - } -} - -struct ListView: View { - @ObservedResults(SwiftPerson.self) var objects - - var body: some View { - List { - ForEach(objects) { object in - Text("\(object.firstName)") - } - } - .navigationTitle("SwiftPerson's List") - } -} - -@available(macOS 13, *) -struct SectionedResultsView: View { - @ObservedSectionedResults(SwiftPerson.self, sectionKeyPath: \.firstName) var objects - - var body: some View { - List { - ForEach(objects) { section in - Section(section.key) { - ForEach(section) { object in - Text("\(object.firstName) \(object.lastName)") - } - } - } - } - .navigationTitle("SwiftPerson's List") - } -} diff --git a/Realm/Tests/SwiftUISyncTestHost/Info.plist b/Realm/Tests/SwiftUISyncTestHost/Info.plist deleted file mode 100644 index 107806053e..0000000000 --- a/Realm/Tests/SwiftUISyncTestHost/Info.plist +++ /dev/null @@ -1,37 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - NSHumanReadableCopyright - Copyright © 2021 Realm. All rights reserved. - UIAppFonts - - Effra_Rg.ttf - Effra_Bd.ttf - Effra_Lt.ttf - - - diff --git a/Realm/Tests/SwiftUISyncTestHost/SwiftUISyncTestHostApp.swift b/Realm/Tests/SwiftUISyncTestHost/SwiftUISyncTestHostApp.swift deleted file mode 100644 index c15a579559..0000000000 --- a/Realm/Tests/SwiftUISyncTestHost/SwiftUISyncTestHostApp.swift +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -import SwiftUI -import RealmSwift - -@main -struct App: SwiftUI.App { - var body: some Scene { - return WindowGroup { - MainView() - } - } -} diff --git a/Realm/Tests/SwiftUISyncTestHost/TestType.swift b/Realm/Tests/SwiftUISyncTestHost/TestType.swift deleted file mode 100644 index b7a1d104af..0000000000 --- a/Realm/Tests/SwiftUISyncTestHost/TestType.swift +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -enum TestType: String { - case asyncOpen - case asyncOpenEnvironmentPartition - case asyncOpenEnvironmentConfiguration - case asyncOpenFlexibleSync - case asyncOpenCustomConfiguration - case autoOpen - case autoOpenEnvironmentPartition - case autoOpenEnvironmentConfiguration - case autoOpenFlexibleSync - case autoOpenCustomConfiguration -} diff --git a/Realm/Tests/SwiftUISyncTestHostUITests/Info.plist b/Realm/Tests/SwiftUISyncTestHostUITests/Info.plist deleted file mode 100644 index 64d65ca495..0000000000 --- a/Realm/Tests/SwiftUISyncTestHostUITests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/Realm/Tests/SwiftUISyncTestHostUITests/SwiftUISyncTestHostUITests-Bridging-Header.h b/Realm/Tests/SwiftUISyncTestHostUITests/SwiftUISyncTestHostUITests-Bridging-Header.h deleted file mode 100644 index 0919813465..0000000000 --- a/Realm/Tests/SwiftUISyncTestHostUITests/SwiftUISyncTestHostUITests-Bridging-Header.h +++ /dev/null @@ -1,23 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "RLMSyncTestCase.h" -#import "RLMChildProcessEnvironment.h" -#import "RLMUser+ObjectServerTests.h" -#import "RLMApp_Private.h" -#import "TestUtils.h" diff --git a/Realm/Tests/SwiftUISyncTestHostUITests/SwiftUISyncTestHostUITests.entitlements b/Realm/Tests/SwiftUISyncTestHostUITests/SwiftUISyncTestHostUITests.entitlements deleted file mode 100644 index e89b7f323c..0000000000 --- a/Realm/Tests/SwiftUISyncTestHostUITests/SwiftUISyncTestHostUITests.entitlements +++ /dev/null @@ -1,8 +0,0 @@ - - - - - com.apple.security.app-sandbox - - - diff --git a/Realm/Tests/SwiftUISyncTestHostUITests/SwiftUISyncTestHostUITests.swift b/Realm/Tests/SwiftUISyncTestHostUITests/SwiftUISyncTestHostUITests.swift deleted file mode 100644 index bf8687d6f3..0000000000 --- a/Realm/Tests/SwiftUISyncTestHostUITests/SwiftUISyncTestHostUITests.swift +++ /dev/null @@ -1,396 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import XCTest -import RealmSwift - -class SwiftUISyncUITests: SwiftSyncTestCase { - override func tearDown() { - logoutAllUsers() - application.terminate() - super.tearDown() - } - - override var objectTypes: [ObjectBase.Type] { - [SwiftPerson.self] - } - - let application = XCUIApplication() - - func populateData(count: Int) throws { - try write { realm in - for i in 1...count { - realm.add(SwiftPerson(firstName: name, lastName: randomString(7), age: i)) - } - } - } - - func registerEmail() -> String { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - app.emailPasswordAuth.registerUser(email: email, password: "password").await(self) - return email - } - - // Login for given user - enum UserType: Int { - case first = 1 - case second = 2 - } - - func loginUser(_ type: UserType) { - let loginButton = application.buttons["login_button_\(type.rawValue)"] - XCTAssertTrue(loginButton.waitForExistence(timeout: 2)) - loginButton.tap() - - let loggingView = application.staticTexts["logged_view"] - XCTAssertTrue(loggingView.waitForExistence(timeout: 6)) - } - - func asyncOpen() { - loginUser(.first) - - // Query for button to start syncing - let syncButtonView = application.buttons["sync_button"] - XCTAssertTrue(syncButtonView.waitForExistence(timeout: 2)) - syncButtonView.tap() - } - - func logoutAllUsers() { - let loginButton = application.buttons["logout_users_button"] - XCTAssertTrue(loginButton.waitForExistence(timeout: 2)) - loginButton.tap() - } - - func launch(_ test: TestType, _ env: [String: String]? = nil) { - application.launchEnvironment["app_id"] = appId - application.launchEnvironment["partition_value"] = name - application.launchEnvironment["email1"] = registerEmail() - application.launchEnvironment["email2"] = registerEmail() - application.launchEnvironment["async_view_type"] = test.rawValue - if let env { - application.launchEnvironment.merge(env, uniquingKeysWith: { $1 }) - } - application.launch() - asyncOpen() - } -} - -class PBSSwiftUISyncUITests: SwiftUISyncUITests { - // MARK: - AsyncOpen - func testDownloadRealmAsyncOpenApp() throws { - try populateData(count: 1) - launch(.asyncOpen) - - // Test progress is greater than 0 - let progressView = application.staticTexts["progress_text_view"] - XCTAssertTrue(progressView.waitForExistence(timeout: 2)) - let progressValue = progressView.value as! String - XCTAssertTrue(Int64(progressValue)! > 0) - - // Query for button to navigate to next view - let nextViewView = application.buttons["show_list_button_view"] - nextViewView.tap() - - // Test show ListView after syncing realm environment - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 1) - } - - func testDownloadRealmAsyncOpenAppWithEnvironmentPartitionValue() throws { - try populateData(count: 2) - launch(.asyncOpenEnvironmentPartition) - - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 2) - } - - func testDownloadRealmAsyncOpenAppWithEnvironmentConfiguration() throws { - try populateData(count: 3) - launch(.asyncOpenEnvironmentConfiguration) - - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 3) - } - - func testObservedResults() throws { - try populateData(count: 2) - launch(.asyncOpenEnvironmentPartition) - - // Test show ListView after syncing realm - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 2) - - loginUser(.second) - - // Query for button to start syncing - let syncButtonView = application.buttons["sync_button"] - XCTAssertTrue(syncButtonView.waitForExistence(timeout: 2)) - syncButtonView.tap() - - // Test show ListView after logging new user - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 2) - - try populateData(count: 2) - XCTAssertTrue(table.cells.element(boundBy: 3).waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 4) - - loginUser(.first) - // Make sure the first user also has 4 SwiftPerson's - XCTAssertTrue(syncButtonView.waitForExistence(timeout: 2)) - syncButtonView.tap() - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 4) - } - - func testObservedSectionedResults() throws { - try write { realm in - realm.add(SwiftPerson(firstName: "Joe", lastName: "Blogs")) - realm.add(SwiftPerson(firstName: "Jane", lastName: "Doe")) - } - - launch(.asyncOpenEnvironmentPartition, ["is_sectioned_results": "true"]) - - // Test show ListView after syncing realm - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 4) // Includes section headers and cells - XCTAssertEqual(table.staticTexts.count, 4) - - loginUser(.second) - - // Query for button to start syncing - let syncButtonView = application.buttons["sync_button"] - XCTAssertTrue(syncButtonView.waitForExistence(timeout: 2)) - syncButtonView.tap() - - // Test show ListView after logging new user - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 4) - - try write { realm in - realm.add(SwiftPerson(firstName: "Joe", lastName: "Blogs2")) - realm.add(SwiftPerson(firstName: "Jane", lastName: "Doe2")) - } - XCTAssertEqual(table.cells.count, 6) - - loginUser(.first) - // Make sure the first user also has 4 SwiftPerson's - XCTAssertTrue(syncButtonView.waitForExistence(timeout: 2)) - syncButtonView.tap() - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 6) - XCTAssertEqual(table.staticTexts.count, 6) - } - - func testAsyncOpenMultiUser() throws { - try populateData(count: 3) - launch(.asyncOpenEnvironmentPartition) - - // Test show ListView after syncing realm - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 3) - - loginUser(.second) - - // Query for button to start syncing - let syncButtonView = application.buttons["sync_button"] - XCTAssertTrue(syncButtonView.waitForExistence(timeout: 2)) - syncButtonView.tap() - - // Test show ListView after logging new user - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 3) - } - - func testAsyncOpenAndLogout() throws { - try populateData(count: 2) - launch(.asyncOpenEnvironmentPartition) - - // Test show ListView after syncing realm - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 2) - - let logoutButtonView = application.buttons["logout_button"] - XCTAssertTrue(logoutButtonView.waitForExistence(timeout: 2)) - logoutButtonView.tap() - - let waitingUserView = application.buttons["waiting_user_view"] - XCTAssertTrue(waitingUserView.waitForExistence(timeout: 2)) - } - - func testAsyncOpenWithDeferRealmConfiguration() throws { - try populateData(count: 20) - launch(.asyncOpenCustomConfiguration) - - // Test show ListView after syncing realm - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 20) - - // Check if there is more than one realm when using environment values. - let enumerator = FileManager.default.enumerator(at: clientDataRoot(), includingPropertiesForKeys: [.nameKey, .isDirectoryKey]) - var counter = 0 - while let element = enumerator?.nextObject() as? URL { - if element.path.hasSuffix(".realm") { counter += 1 } - } - // Synced Realm and Sync Metadata - XCTAssertEqual(counter, 2) - } - - // MARK: - AutoOpen - func testDownloadRealmAutoOpenApp() throws { - try populateData(count: 1) - launch(.autoOpen) - - // Test progress is greater than 0 - let progressView = application.staticTexts["progress_text_view"] - XCTAssertTrue(progressView.waitForExistence(timeout: 2)) - let progressValue = progressView.value as! String - XCTAssertTrue(Int64(progressValue)! > 0) - - // Query for button to navigate to next view - let nextViewView = application.buttons["show_list_button_view"] - nextViewView.tap() - - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 1) - } - - func testDownloadRealmAutoOpenAppWithEnvironmentPartitionValue() throws { - try populateData(count: 2) - launch(.autoOpenEnvironmentPartition) - - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 2) - } - - func testDownloadRealmAutoOpenAppWithEnvironmentConfiguration() throws { - try populateData(count: 3) - launch(.autoOpenEnvironmentConfiguration) - - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 3) - } - - func testAutoOpenMultiUser() throws { - try populateData(count: 3) - launch(.autoOpenEnvironmentPartition) - - // Test show ListView after syncing realm - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 3) - - loginUser(.second) - - // Query for button to start syncing - let syncButtonView = application.buttons["sync_button"] - XCTAssertTrue(syncButtonView.waitForExistence(timeout: 2)) - syncButtonView.tap() - - // Test show ListView after logging new user - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 3) - } - - func testAutoOpenAndLogout() throws { - try populateData(count: 2) - launch(.autoOpenEnvironmentPartition) - - // Test show ListView after syncing realm - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 2) - - let logoutButtonView = application.buttons["logout_button"] - XCTAssertTrue(logoutButtonView.waitForExistence(timeout: 2)) - logoutButtonView.tap() - - let waitingUserView = application.buttons["waiting_user_view"] - XCTAssertTrue(waitingUserView.waitForExistence(timeout: 2)) - } - - func testAutoOpenWithDeferRealmConfiguration() throws { - try populateData(count: 20) - launch(.autoOpenCustomConfiguration) - - // Test show ListView after syncing realm - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 20) - - // Check if there is more than one realm when using environment values. - let enumerator = FileManager.default.enumerator(at: clientDataRoot(), includingPropertiesForKeys: [.nameKey, .isDirectoryKey]) - var counter = 0 - while let element = enumerator?.nextObject() as? URL { - if element.path.hasSuffix(".realm") { counter += 1 } - } - // Synced Realm and Sync Metadata - XCTAssertEqual(counter, 2) - } -} - -class FLXSwiftUISyncUITests: SwiftUISyncUITests { - override func createApp() throws -> String { - try createFlexibleSyncApp() - } - - override func configuration(user: User) -> Realm.Configuration { - user.flexibleSyncConfiguration() - } - - func testFlexibleSyncAsyncOpenRoundTrip() throws { - try populateData(count: 10) - launch(.asyncOpenFlexibleSync) - - // Query for button to navigate to next view - let nextViewView = application.buttons["show_list_button_view"] - XCTAssertTrue(nextViewView.waitForExistence(timeout: 10)) - nextViewView.tap() - - // Test show ListView after syncing realm - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 5) - } - - func testFlexibleSyncAutoOpenRoundTrip() throws { - try populateData(count: 20) - launch(.autoOpenFlexibleSync) - - // Query for button to navigate to next view - let nextViewView = application.buttons["show_list_button_view"] - XCTAssertTrue(nextViewView.waitForExistence(timeout: 10)) - nextViewView.tap() - - // Test show ListView after syncing realm - let table = application.tables.firstMatch - XCTAssertTrue(table.waitForExistence(timeout: 6)) - XCTAssertEqual(table.cells.count, 18) - } -} diff --git a/RealmSwift.podspec b/RealmSwift.podspec index 6fb153afac..79cd7c6b75 100644 --- a/RealmSwift.podspec +++ b/RealmSwift.podspec @@ -7,7 +7,7 @@ Pod::Spec.new do |s| s.description = <<-DESC The Realm Database, for Swift. (If you want to use Realm from Objective-C, see the “Realm” pod.) - Realm is a fast, easy-to-use replacement for Core Data & SQLite. Use it with Atlas Device Sync for realtime, automatic data sync. Works on iOS, macOS, tvOS & watchOS. Learn more and get help at https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/. + Realm is a fast, easy-to-use replacement for Core Data & SQLite. Works on iOS, macOS, tvOS & watchOS. Learn more and get help at https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/. DESC s.homepage = "https://realm.io" s.source = { :git => 'https://github.com/realm/realm-swift.git', :tag => "v#{s.version}" } @@ -27,7 +27,6 @@ Pod::Spec.new do |s| s.dependency 'Realm', "= #{s.version}" s.source_files = 'RealmSwift/*.swift', 'RealmSwift/Impl/*.swift', 'Realm/Swift/*.swift' - s.exclude_files = 'RealmSwift/Nonsync.swift' s.resource_bundles = {'realm_swift_privacy' => ['RealmSwift/PrivacyInfo.xcprivacy']} s.pod_target_xcconfig = { diff --git a/RealmSwift/App.swift b/RealmSwift/App.swift deleted file mode 100644 index b10f97d2bb..0000000000 --- a/RealmSwift/App.swift +++ /dev/null @@ -1,638 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import AuthenticationServices -import Combine -import Foundation -import Realm -import Realm.Private - -/** -An object representing the Realm App configuration - -- see: `RLMAppConfiguration` - -- note: `AppConfiguration` options cannot be modified once the `App` using it - is created. App's configuration values are cached when the App is created so any modifications after it - will not have any effect. -*/ -public typealias AppConfiguration = RLMAppConfiguration -public extension AppConfiguration { - /// :nodoc: - @available(*, deprecated, message: "localAppName and localAppVersion are not used for anything and should not be supplied") - convenience init(baseURL: String? = nil, transport: RLMNetworkTransport? = nil, - localAppName: String?, localAppVersion: String?, - defaultRequestTimeoutMS: UInt? = nil, enableSessionMultiplexing: Bool? = nil, - syncTimeouts: SyncTimeoutOptions? = nil) { - self.init(baseURL: baseURL, transport: transport, localAppName: localAppName, localAppVersion: localAppVersion) - if let defaultRequestTimeoutMS { - self.defaultRequestTimeoutMS = defaultRequestTimeoutMS - } - if let enableSessionMultiplexing { - self.enableSessionMultiplexing = enableSessionMultiplexing - } - if let syncTimeouts { - self.syncTimeouts = syncTimeouts - } - } - - /** - Memberwise convenience initializer - - All fields have sensible defaults if not set and typically do not need to be customized. - - - Parameters: - - baseURL: A custom Atlas App Services URL for when using a non-standard deployment - - transport: A network transport used for calls to the server. - - defaultRequestTimeoutMS: The default timeout for non-sync HTTP requests made to the server. - - enableSessionMultiplexing: Use a single network connection per sync user rather than one per sync Realm. - - syncTimeouts: Timeout options for sync connections. - */ - @_disfavoredOverload // this is ambiguous with the base init if nil is explicitly passed - convenience init(baseURL: String? = nil, transport: RLMNetworkTransport? = nil, - defaultRequestTimeoutMS: UInt? = nil, enableSessionMultiplexing: Bool? = nil, - syncTimeouts: SyncTimeoutOptions? = nil) { - self.init(baseURL: baseURL, transport: transport) - if let defaultRequestTimeoutMS { - self.defaultRequestTimeoutMS = defaultRequestTimeoutMS - } - if let enableSessionMultiplexing { - self.enableSessionMultiplexing = enableSessionMultiplexing - } - if let syncTimeouts { - self.syncTimeouts = syncTimeouts - } - } -} - -/** -An object representing a client which performs network calls on -Realm Cloud user api keys - -- see: `RLMAPIKeyAuth` -*/ -public typealias APIKeyAuth = RLMAPIKeyAuth - -/** -An object representing a client which performs network calls on -Realm Cloud user registration & password functions - -- see: `RLMEmailPasswordAuth` -*/ -public typealias EmailPasswordAuth = RLMEmailPasswordAuth - -/** - An object representing the social profile of a User. - */ -public typealias UserProfile = RLMUserProfile - -extension UserProfile { - /** - The metadata of the user. - The auth provider of the user is responsible for populating this `Document`. - */ - public var metadata: Document { - guard let rlmMetadata = self.__metadata as RLMBSON?, - let anyBSON = ObjectiveCSupport.convert(object: rlmMetadata), - case let .document(metadata) = anyBSON else { - return [:] - } - - return metadata - } -} - -/// A block type used to report an error -public typealias EmailPasswordAuthOptionalErrorBlock = RLMEmailPasswordAuthOptionalErrorBlock -extension EmailPasswordAuth { - /// Resets the password of an email identity using the - /// password reset function set up in the application. - /// - Parameters: - /// - email: The email address of the user. - /// - password: The desired new password. - /// - args: A list of arguments passed in as a BSON array. - /// - completion: A callback to be invoked once the call is complete. - public func callResetPasswordFunction(email: String, - password: String, - args: [AnyBSON], - _ completion: @escaping EmailPasswordAuthOptionalErrorBlock) { - let bson = ObjectiveCSupport.convert(object: .array(args)) - __callResetPasswordFunction(email, password: password, args: bson as! [RLMBSON], completion: completion) - } - - /** - Resets the password of an email identity using the - password reset function set up in the application. - - @param email The email address of the user. - @param password The desired new password. - @param args A list of arguments passed in as a BSON array. - @returns A publisher that eventually return `Result.success` or `Error`. - */ - @available(macOS 10.15, watchOS 6.0, iOS 13.0, tvOS 13.0, *) - public func callResetPasswordFunction(email: String, password: String, args: [AnyBSON]) -> Future { - promisify { - self.callResetPasswordFunction(email: email, password: password, args: args, $0) - } - } - - /// Resets the password of an email identity using the - /// password reset function set up in the application. - /// - Parameters: - /// - email: The email address of the user. - /// - password: The desired new password. - /// - args: A list of arguments passed in as a BSON array. - @available(macOS 10.15, watchOS 6.0, iOS 13.0, tvOS 13.0, *) - public func callResetPasswordFunction(email: String, - password: String, - args: [AnyBSON]) async throws { - let bson = ObjectiveCSupport.convert(object: .array(args)) - return try await __callResetPasswordFunction(email, password: password, args: bson as! [RLMBSON]) - } -} - -/** -An object representing a client which performs network calls on -Realm Cloud for registering devices to push notifications - -- see `RLMPushClient` - */ -public typealias PushClient = RLMPushClient - -/// An object which is used within UserAPIKeyProviderClient -public typealias UserAPIKey = RLMUserAPIKey -extension UserAPIKey { - /// The ObjectId of the API key. - public var objectId: ObjectId { - __objectId as! ObjectId - } -} - -/** -`Credentials`is an enum representing supported authentication types for Atlas App Services. -Example Usage: -``` -let credentials = Credentials.JWT(token: myToken) -``` -*/ -@frozen public enum Credentials: Sendable { - /// Credentials from a Facebook access token. - case facebook(accessToken: String) - /// Credentials from a Google serverAuthCode. - case google(serverAuthCode: String) - /// Credentials from a Google idToken. - case googleId(token: String) - /// Credentials from an Apple id token. - case apple(idToken: String) - /// Credentials from an email and password. - case emailPassword(email: String, password: String) - /// Credentials from a JSON Web Token - case jwt(token: String) - /// Credentials for an Atlas App Services function using a mongodb document as a json payload. - /// If the json can not be successfully serialised and error will be produced and the object will be nil. - case function(payload: Document) - /// Credentials from a user api key. - case userAPIKey(String) - /// Credentials from a sever api key. - case serverAPIKey(String) - /// Represents anonymous credentials - case anonymous -} - -/// The `App` has the fundamental set of methods for communicating with a Realm -/// application backend. -/// This interface provides access to login and authentication. -public typealias App = RLMApp - -public extension App { - /** - Updates the base url used by Atlas device sync, in case the need to roam between servers (cloud and/or edge server). - - parameter url: The new base url to connect to. Setting `nil` will reset the base url to the default url. - - parameter completion: A callback invoked after completion. - - note: Updating the base URL will trigger a client reset. - */ - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - @_spi(RealmSwiftExperimental) func updateBaseUrl(to url: String?, _ completion: @Sendable @escaping (Error?) -> Void) { - self.__updateBaseURL(url, completion: completion) - } - - /** - Updates the base url used by Atlas device sync, in case the need to roam between servers (cloud and/or edge server). - - parameter url: The new base url to connect to. Setting `nil` will reset the base url to the default url. - - parameter completion: A callback invoked after completion. - - note: Updating the base URL will trigger a client reset. - - returns A publisher that eventually return `Result.success` or `Error`. - */ - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - @_spi(RealmSwiftExperimental) func updateBaseUrl(to url: String?) -> Future { - promisify { - self.__updateBaseURL(url, completion: $0) - } - } - - /** - Login to a user for the Realm app. - - - parameter credentials: The credentials identifying the user. - - parameter completion: A callback invoked after completion. Will return `Result.success(User)` or `Result.failure(Error)`. - */ - @preconcurrency - func login(credentials: Credentials, _ completion: @Sendable @escaping (Result) -> Void) { - self.__login(withCredential: ObjectiveCSupport.convert(object: credentials)) { user, error in - if let user = user { - completion(.success(user)) - } else { - completion(.failure(error ?? Realm.Error.callFailed)) - } - } - } - - /** - Updates the base url used by Atlas device sync, in case the need to roam between servers (cloud and/or edge server). - - parameter url: The new base url to connect to. Setting `nil` will reset the base url to the default url. - - parameter completion: A callback invoked after completion. Will return `Result.success` or `Result.failure(Error)`. - - note: Updating the base URL will trigger a client reset. - */ - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - @_spi(RealmSwiftExperimental) func updateBaseUrl(to url: String?, _ completion: @Sendable @escaping (Result) -> Void) { - self.__updateBaseURL(url, completion: { error in - if let error = error { - completion(.failure(error)) - } else { - completion(.success(())) - } - }) - } - - /** - Login to a user for the Realm app. - - parameter credentials: The credentials identifying the user. - - returns: A publisher that eventually return `User` or `Error`. - */ - @available(macOS 10.15, watchOS 6.0, iOS 13.0, tvOS 13.0, *) - func login(credentials: Credentials) -> Future { - return future { self.login(credentials: credentials, $0) } - } - - /** - Login to a user for the Realm app. - - parameter credentials: The credentials identifying the user. - */ - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - func login(credentials: Credentials) async throws -> User { - try await __login(withCredential: ObjectiveCSupport.convert(object: credentials)) - } - - /** - Updates the base url used by Atlas device sync, in case the need to roam between servers (cloud and/or edge server). - - parameter url: The new base url to connect to. Setting `nil` will reset the base url to the default url. - - note: Updating the base URL will trigger a client reset. - */ - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - @_spi(RealmSwiftExperimental) func updateBaseUrl(to url: String?) async throws { - try await __updateBaseURL(url) - } -} - -/// Use this delegate to be provided a callback once authentication has succeed or failed -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -public typealias ASLoginDelegate = RLMASLoginDelegate - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -extension App { - /** - Sets the ASAuthorizationControllerDelegate to be handled by `App` - - Parameter controller: The ASAuthorizationController in which you want `App` to consume its delegate. - - Usage: - ``` - let app = App(id: "my-app-id") - let appleIDProvider = ASAuthorizationAppleIDProvider() - let request = appleIDProvider.createRequest() - request.requestedScopes = [.fullName, .email] - - let authorizationController = ASAuthorizationController(authorizationRequests: [request]) - app.setASAuthorizationControllerDelegate(controller: authorizationController) - authorizationController.presentationContextProvider = self - authorizationController.performRequests() - ``` - */ - public func setASAuthorizationControllerDelegate(for controller: ASAuthorizationController) { - self.__setASAuthorizationControllerDelegateFor(controller) - } -} - -/// :nodoc: -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -@frozen public struct AppSubscription: Subscription { - private let token: RLMAppSubscriptionToken - internal init(token: RLMAppSubscriptionToken) { - self.token = token - } - - /// A unique identifier for identifying publisher streams. - public var combineIdentifier: CombineIdentifier { - return CombineIdentifier(token) - } - - /// This function is not implemented. - /// - /// Realm publishers do not support backpressure and so this function does nothing. - public func request(_ demand: Subscribers.Demand) { - } - - /// Stop emitting values on this subscription. - public func cancel() { - token.unsubscribe() - } -} - -/// :nodoc: -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -public struct AppPublisher: Publisher, @unchecked Sendable { // DispatchQueue - /// This publisher cannot fail. - public typealias Failure = Never - /// This publisher emits App. - public typealias Output = App - - private let app: App - - private let scheduler: any Scheduler - - internal init(_ app: App, scheduler: S) { - self.app = app - self.scheduler = scheduler - } - - /// :nodoc: - public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { - let token = app.subscribe { app in - self.scheduler.schedule { - _ = subscriber.receive(app) - } - } - - subscriber.receive(subscription: AppSubscription(token: token)) - } - - /// :nodoc: - public func receive(on scheduler: S) -> Self { - return Self(app, scheduler: scheduler) - } -} - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -extension App { - /// A publisher that emits Void each time the app changes. - /// - /// Despite the name, this actually emits *after* the app has changed. - public var objectWillChange: AppPublisher { - return AppPublisher(self, scheduler: DispatchQueue.main) - } -} -#if compiler(>=6) -extension App: @retroactive ObservableObject {} -#else -extension App: ObservableObject {} -#endif - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -internal func promisify(_ fn: @escaping (@escaping @Sendable (Error?) -> Void) -> Void) -> Future { - return future { promise in - fn { error in - if let error = error { - promise(.failure(error)) - } else { - promise(.success(())) - } - } - } -} - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -public extension EmailPasswordAuth { - /** - Registers a new email identity with the username/password provider, - and sends a confirmation email to the provided address. - - @param email The email address of the user to register. - @param password The password that the user created for the new username/password identity. - @returns A publisher that eventually return `Result.success` or `Error`. - */ - func registerUser(email: String, password: String) -> Future { - promisify { - self.registerUser(email: email, password: password, completion: $0) - } - } - - /** - Confirms an email identity with the username/password provider. - - @param token The confirmation token that was emailed to the user. - @param tokenId The confirmation token id that was emailed to the user. - @returns A publisher that eventually return `Result.success` or `Error`. - */ - func confirmUser(_ token: String, tokenId: String) -> Future { - promisify { - self.confirmUser(token, tokenId: tokenId, completion: $0) - } - } - - /** - Re-sends a confirmation email to a user that has registered but - not yet confirmed their email address. - @param email The email address of the user to re-send a confirmation for. - @returns A publisher that eventually return `Result.success` or `Error`. - */ - func resendConfirmationEmail(email: String) -> Future { - promisify { - self.resendConfirmationEmail(email, completion: $0) - } - } - - /** - Retries custom confirmation function for a given email address. - - @param email The email address of the user to retry custom confirmation logic. - @returns A publisher that eventually return `Result.success` or `Error`. - */ - func retryCustomConfirmation(email: String) -> Future { - promisify { - self.retryCustomConfirmation(email, completion: $0) - } - } - - /** - Sends a password reset email to the given email address. - @param email The email address of the user to send a password reset email for. - @returns A publisher that eventually return `Result.success` or `Error`. - */ - func sendResetPasswordEmail(email: String) -> Future { - promisify { - self.sendResetPasswordEmail(email, completion: $0) - } - } - - /** - Resets the password of an email identity using the - password reset token emailed to a user. - - @param password The new password. - @param token The password reset token that was emailed to the user. - @param tokenId The password reset token id that was emailed to the user. - @returns A publisher that eventually return `Result.success` or `Error`. - */ - func resetPassword(to: String, token: String, tokenId: String) -> Future { - promisify { - self.resetPassword(to: to, token: token, tokenId: tokenId, completion: $0) - } - } -} - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -public extension APIKeyAuth { - /** - Creates a user API key that can be used to authenticate as the current user. - @param name The name of the API key to be created. - @returns A publisher that eventually return `UserAPIKey` or `Error`. - */ - func createAPIKey(named: String) -> Future { - return future { self.createAPIKey(named: named, completion: $0) } - } - - /** - Fetches a user API key associated with the current user. - @param objectId The ObjectId of the API key to fetch. - @returns A publisher that eventually return `UserAPIKey` or `Error`. - */ - func fetchAPIKey(_ objectId: ObjectId) -> Future { - return future { self.fetchAPIKey(objectId, $0) } - } - - /** - Fetches the user API keys associated with the current user. - @returns A publisher that eventually return `[UserAPIKey]` or `Error`. - */ - func fetchAPIKeys() -> Future<[UserAPIKey], Error> { - return future { self.fetchAPIKeys($0) } - } - - /** - Deletes a user API key associated with the current user. - @param objectId The ObjectId of the API key to delete. - @returns A publisher that eventually return `Result.success` or `Error`. - */ - func deleteAPIKey(_ objectId: ObjectId) -> Future { - promisify { - self.deleteAPIKey(objectId, completion: $0) - } - } - - /** - Enables a user API key associated with the current user. - @param objectId The ObjectId of the API key to enable. - @returns A publisher that eventually return `Result.success` or `Error`. - */ - func enableAPIKey(_ objectId: ObjectId) -> Future { - promisify { - self.enableAPIKey(objectId, completion: $0) - } - } - - /** - Disables a user API key associated with the current user. - @param objectId The ObjectId of the API key to disable. - @returns A publisher that eventually return `Result.success` or `Error`. - */ - func disableAPIKey(_ objectId: ObjectId) -> Future { - promisify { - self.disableAPIKey(objectId, completion: $0) - } - } -} - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -public extension PushClient { - /// Request to register device token to the server - /// @param token device token - /// @param user - device's user - /// @returns A publisher that eventually return `Result.success` or `Error`. - func registerDevice(token: String, user: User) -> Future { - promisify { - self.registerDevice(token: token, user: user, completion: $0) - } - } - - /// Request to deregister a device for a user - /// @param user - devoce's user - /// @returns A publisher that eventually return `Result.success` or `Error`. - func deregisterDevice(user: User) -> Future { - promisify { - self.deregisterDevice(user: user, completion: $0) - } - } -} - -public extension APIKeyAuth { - /** - Creates a user API key that can be used to authenticate as the current user. - @param name The name of the API key to be created. - @completion A completion that eventually return `Result.success(UserAPIKey)` or `Result.failure(Error)`. - */ - @preconcurrency - func createAPIKey(named: String, completion: @escaping @Sendable (Result) -> Void) { - createAPIKey(named: named) { (userApiKey, error) in - if let userApiKey = userApiKey { - completion(.success(userApiKey)) - } else { - completion(.failure(error ?? Realm.Error.callFailed)) - } - } - } - - /** - Fetches a user API key associated with the current user. - @param objectId The ObjectId of the API key to fetch. - @completion A completion that eventually return `Result.success(UserAPIKey)` or `Result.failure(Error)`. - */ - @preconcurrency - func fetchAPIKey(_ objectId: ObjectId, _ completion: @escaping @Sendable (Result) -> Void) { - fetchAPIKey(objectId) { (userApiKey, error) in - if let userApiKey = userApiKey { - completion(.success(userApiKey)) - } else { - completion(.failure(error ?? Realm.Error.callFailed)) - } - } - } - - /** - Fetches the user API keys associated with the current user. - @completion A completion that eventually return `Result.success([UserAPIKey])` or `Result.failure(Error)`. - */ - @preconcurrency - func fetchAPIKeys(_ completion: @escaping @Sendable (Result<[UserAPIKey], Error>) -> Void) { - fetchAPIKeys { (userApiKeys, error) in - if let userApiKeys = userApiKeys { - completion(.success(userApiKeys)) - } else { - completion(.failure(error ?? Realm.Error.callFailed)) - } - } - } -} diff --git a/RealmSwift/AsymmetricObject.swift b/RealmSwift/AsymmetricObject.swift deleted file mode 100644 index c7c0e59561..0000000000 --- a/RealmSwift/AsymmetricObject.swift +++ /dev/null @@ -1,132 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import Foundation -import Realm -import Realm.Private - -/** - `AsymmetricObject` is a base class used to define asymmetric Realm objects. - - Asymmetric objects can only be created using the `create(_ type:, value:)` - function, and cannot be added, removed or queried. - When created, asymmetric objects will be synced unidirectionally to the MongoDB - database and cannot be accessed locally. - - Linking an asymmetric object within an `Object` is not allowed and will throw an error. - - The property types supported on `AsymmetricObject` are the same as for `Object`, - except for that asymmetric objects can only link to embedded objects, so `Object` - and `List` properties are not supported (`EmbeddedObject` and - `List` *are*). - - ```swift - class Person: AsymmetricObject { - @Persisted(primaryKey: true) var _id: ObjectId - @Persisted var name: String - @Persisted var age: Int - } - ``` - */ -public typealias AsymmetricObject = RealmSwiftAsymmetricObject -extension AsymmetricObject { - // MARK: Initializers - - /** - Creates an unmanaged instance of a Realm object. - - The `value` argument is used to populate the object. It can be a key-value coding compliant object, an array or - dictionary returned from the methods in `NSJSONSerialization`, or an `Array` containing one element for each - managed property. An exception will be thrown if any required properties are not present and those properties were - not defined with default values. - - When passing in an `Array` as the `value` argument, all properties must be present, valid and in the same order as - the properties defined in the model. - - - parameter value: The value used to populate the object. - */ - public convenience init(value: Any) { - self.init() - RLMInitializeWithValue(self, value, .partialPrivateShared()) - } - - - // MARK: Properties - - /// The object schema which lists the managed properties for the object. - public var objectSchema: ObjectSchema { - return ObjectSchema(RLMObjectBaseObjectSchema(self)!) - } - - /// A human-readable description of the object. - open override var description: String { return super.description } - - /** - WARNING: This is an internal helper method not intended for public use. - It is not considered part of the public API. - :nodoc: - */ - public override static func _getProperties() -> [RLMProperty] { - ObjectUtil.getSwiftProperties(self) - } - - // MARK: Object Customization - - /** - Override this method to specify a map of public-private property names. - This will set a different persisted property name on the Realm, and allows using the public name - for any operation with the property. (Ex: Queries, Sorting, ...). - This very helpful if you need to map property names from your `Device Sync` JSON schema - to local property names. - - ```swift - class Person: AsymmetricObject { - @Persisted var firstName: String - @Persisted var birthDate: Date - @Persisted var age: Int - - override class public func propertiesMapping() -> [String : String] { - ["firstName": "first_name", - "birthDate": "birth_date"] - } - } - ``` - - - note: Only property that have a different column name have to be added to the properties mapping - dictionary. - - - returns: A dictionary of public-private property names. - */ - @objc open override class func propertiesMapping() -> [String: String] { return [:] } - - /// :nodoc: - @available(*, unavailable, renamed: "propertiesMapping", message: "`_realmColumnNames` private API is unavailable in our Swift SDK, please use the override `.propertiesMapping()` instead.") - @objc open override class func _realmColumnNames() -> [String: String] { return [:] } - - // MARK: Key-Value Coding & Subscripting - - /// Returns or sets the value of the property with the given name. - @objc open subscript(key: String) -> Any? { - get { - return RLMDynamicGetByName(self, key) - } - set { - dynamicSet(object: self, key: key, value: newValue) - } - } -} diff --git a/RealmSwift/BSON.swift b/RealmSwift/BSON.swift deleted file mode 100644 index 978b9d9992..0000000000 --- a/RealmSwift/BSON.swift +++ /dev/null @@ -1,492 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import Realm - -/// A tag protocol which marks types that can be used as the partition value -/// for synchronized Realms. -public protocol PartitionValue: Sendable { -} - -/// Protocol representing a BSON value. -/// - SeeAlso: bsonspec.org -public protocol BSON: PartitionValue, Equatable { -} - -extension NSNull: BSON { -} - -extension Int: BSON { -} - -extension Int32: BSON { -} - -extension Int64: BSON { -} - -extension Bool: BSON { -} - -extension Double: BSON { -} - -extension String: BSON { -} - -extension Data: BSON { -} - -extension Date: BSON { -} - -extension Decimal128: BSON { -} - -extension ObjectId: BSON { -} - -extension UUID: BSON { -} - -/// A Dictionary object representing a `BSON` document. -public typealias Document = Dictionary - -extension Dictionary: BSON, PartitionValue where Key == String, Value == AnyBSON? { -} - -extension Array: BSON, PartitionValue where Element == AnyBSON? { -} - -extension NSRegularExpression: BSON { -} - -/// MaxKey will always be the greatest value when comparing to other BSON types -public typealias MaxKey = RLMMaxKey - -extension MaxKey: BSON { -} - -/// MinKey will always be the smallest value when comparing to other BSON types -public typealias MinKey = RLMMinKey - -extension MinKey: BSON { -} - -/// Enum representing a BSON value. -/// - SeeAlso: bsonspec.org -@frozen public enum AnyBSON: BSON, Sendable { - /// A BSON double. - case double(Double) - - /// A BSON string. - /// - SeeAlso: https://docs.mongodb.com/manual/reference/bson-types/#string - case string(String) - - /// A BSON document. - indirect case document(Document) - - /// A BSON array. - indirect case array([AnyBSON?]) - - /// A BSON binary. - case binary(Data) - - /// A BSON ObjectId. - /// - SeeAlso: https://docs.mongodb.com/manual/reference/bson-types/#objectid - case objectId(ObjectId) - - /// A BSON boolean. - case bool(Bool) - - /// A BSON UTC datetime. - /// - SeeAlso: https://docs.mongodb.com/manual/reference/bson-types/#date - case datetime(Date) - - /// A BSON regular expression. - case regex(NSRegularExpression) - - /// A BSON int32. - case int32(Int32) - - /// A BSON timestamp. - /// - SeeAlso: https://docs.mongodb.com/manual/reference/bson-types/#timestamps - case timestamp(Date) - - /// A BSON int64. - case int64(Int64) - - /// A BSON Decimal128. - /// - SeeAlso: https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst - case decimal128(Decimal128) - - /// A UUID. - case uuid(UUID) - - /// A BSON minKey. - case minKey - - /// A BSON maxKey. - case maxKey - - /// A BSON null type. - case null - - /// Initialize a `BSON` from an integer. On 64-bit systems, this will result in an `.int64`. On 32-bit systems, - /// this will result in an `.int32`. - public init(_ int: Int) { - if MemoryLayout.size == 4 { - self = .int32(Int32(int)) - } else { - self = .int64(Int64(int)) - } - } - - /// :nodoc: - static func convert(_ bson: T) -> AnyBSON { - switch bson { - case let val as Int: - return .int64(Int64(val)) - case let val as Int32: - return .int32(val) - case let val as Int64: - return .int64(val) - case let val as Double: - return .double(val) - case let val as String: - return .string(val) - case let val as Data: - return .binary(val) - case let val as Date: - return .datetime(val) - case let val as Decimal128: - return .decimal128(val) - case let val as UUID: - return .uuid(val) - case let val as ObjectId: - return .objectId(val) - case let val as Document: - return .document(val) - case let val as Array: - return .array(val) - case let val as Bool: - return .bool(val) - case is MaxKey: - return .maxKey - case is MinKey: - return .minKey - case let val as NSRegularExpression: - return .regex(val) - case let val as AnyBSON: - return val - default: - return .null - } - } - - /// Initialize a `BSON` from a type `T`. If this is not a valid `BSON` type, - /// it will be considered `BSON` null type and will return `nil`. - public init(_ bson: T) { - self = Self.convert(bson) - } - - /// Initialize a `BSON` from type `PartitionValue`. If this is not a valid `BSON` type, - /// it will be considered `BSON` null type and will return `nil`. - init(partitionValue: PartitionValue) { - self = Self.convert(partitionValue) - } - - /// If this `BSON` is an `.int32`, return it as an `Int32`. Otherwise, return nil. - public var int32Value: Int32? { - guard case let .int32(i) = self else { - return nil - } - return i - } - - /// If this `BSON` is a `.regex`, return it as a `RegularExpression`. Otherwise, return nil. - public var regexValue: NSRegularExpression? { - guard case let .regex(r) = self else { - return nil - } - return r - } - - /// If this `BSON` is an `.int64`, return it as an `Int64`. Otherwise, return nil. - public var int64Value: Int64? { - guard case let .int64(i) = self else { - return nil - } - return i - } - - /// If this `BSON` is an `.objectId`, return it as an `ObjectId`. Otherwise, return nil. - public var objectIdValue: ObjectId? { - guard case let .objectId(o) = self else { - return nil - } - return o - } - - /// If this `BSON` is a `.date`, return it as a `Date`. Otherwise, return nil. - public var dateValue: Date? { - guard case let .datetime(d) = self else { - return nil - } - return d - } - - /// If this `BSON` is an `.array`, return it as an `[BSON]`. Otherwise, return nil. - public var arrayValue: [AnyBSON?]? { - guard case let .array(a) = self else { - return nil - } - return a - } - - /// If this `BSON` is a `.string`, return it as a `String`. Otherwise, return nil. - public var stringValue: String? { - guard case let .string(s) = self else { - return nil - } - return s - } - - /// If this `BSON` is a `.document`, return it as a `Document`. Otherwise, return nil. - public var documentValue: Document? { - guard case let .document(d) = self else { - return nil - } - return d - } - - /// If this `BSON` is a `.bool`, return it as an `Bool`. Otherwise, return nil. - public var boolValue: Bool? { - guard case let .bool(b) = self else { - return nil - } - return b - } - - /// If this `BSON` is a `.binary`, return it as a `Binary`. Otherwise, return nil. - public var binaryValue: Data? { - guard case let .binary(b) = self else { - return nil - } - return b - } - - /// If this `BSON` is a `.double`, return it as a `Double`. Otherwise, return nil. - public var doubleValue: Double? { - guard case let .double(d) = self else { - return nil - } - return d - } - - /// If this `BSON` is a `.decimal128`, return it as a `Decimal128`. Otherwise, return nil. - public var decimal128Value: Decimal128? { - guard case let .decimal128(d) = self else { - return nil - } - return d - } - - /// If this `BSON` is a `.timestamp`, return it as a `Timestamp`. Otherwise, return nil. - public var timestampValue: Date? { - guard case let .timestamp(t) = self else { - return nil - } - return t - } - - /// If this `BSON` is a `.uuid`, return it as a `UUID`. Otherwise, return nil. - public var uuidValue: UUID? { - guard case let .uuid(s) = self else { - return nil - } - return s - } - - /// If this `BSON` is a `.null` return true. Otherwise, false. - public var isNull: Bool { - return self == .null - } - - /// Return this BSON as an `Int` if possible. - /// This will coerce non-integer numeric cases (e.g. `.double`) into an `Int` if such coercion would be lossless. - public func asInt() -> Int? { - switch self { - case let .int32(value): - return Int(value) - case let .int64(value): - return Int(exactly: value) - case let .double(value): - return Int(exactly: value) - default: - return nil - } - } - - /// Return this BSON as an `Int32` if possible. - /// This will coerce numeric cases (e.g. `.double`) into an `Int32` if such coercion would be lossless. - public func asInt32() -> Int32? { - switch self { - case let .int32(value): - return value - case let .int64(value): - return Int32(exactly: value) - case let .double(value): - return Int32(exactly: value) - default: - return nil - } - } - - /// Return this BSON as an `Int64` if possible. - /// This will coerce numeric cases (e.g. `.double`) into an `Int64` if such coercion would be lossless. - public func asInt64() -> Int64? { - switch self { - case let .int32(value): - return Int64(value) - case let .int64(value): - return value - case let .double(value): - return Int64(exactly: value) - default: - return nil - } - } - - /// Return this BSON as a `Double` if possible. - /// This will coerce numeric cases (e.g. `.decimal128`) into a `Double` if such coercion would be lossless. - public func asDouble() -> Double? { - switch self { - case let .double(d): - return d - default: - guard let intValue = self.asInt() else { - return nil - } - return Double(intValue) - } - } - - /// Return this BSON as a `Decimal128` if possible. - /// This will coerce numeric cases (e.g. `.double`) into a `Decimal128` if such coercion would be lossless. - public func asDecimal128() -> Decimal128? { - let str: String - switch self { - case let .decimal128(d): - return d - case let .int64(i): - str = String(i) - case let .int32(i): - str = String(i) - case let .double(d): - str = String(d) - default: - return nil - } - return try? Decimal128(string: str) - } - - /// Return this BSON as a `T` if possible, otherwise nil. - public func value() -> T? { - switch self { - case .int32(let val): - if T.self == Int.self && MemoryLayout.size == 4 { - return Int(val) as? T - } - return val as? T - case .int64(let val): - if T.self == Int.self && MemoryLayout.size != 4 { - return Int(val) as? T - } - return val as? T - case .bool(let val): - return val as? T - case .double(let val): - return val as? T - case .string(let val): - return val as? T - case .binary(let val): - return val as? T - case .datetime(let val): - return val as? T - case .decimal128(let val): - return val as? T - case .objectId(let val): - return val as? T - case .document(let val): - return val as? T - case .array(let val): - return val as? T - case .maxKey: - return MaxKey() as? T - case .minKey: - return MinKey() as? T - case .regex(let val): - return val as? T - default: - return nil - } - } -} - -extension AnyBSON: ExpressibleByStringLiteral { - public init(stringLiteral value: String) { - self = .string(value) - } -} - -extension AnyBSON: ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = .bool(value) - } -} - -extension AnyBSON: ExpressibleByFloatLiteral { - public init(floatLiteral value: Double) { - self = .double(value) - } -} - -extension AnyBSON: ExpressibleByIntegerLiteral { - /// Initialize a `BSON` from an integer. On 64-bit systems, this will result in an `.int64`. On 32-bit systems, - /// this will result in an `.int32`. - public init(integerLiteral value: Int) { - self.init(value) - } -} - -extension AnyBSON: ExpressibleByDictionaryLiteral { - public init(dictionaryLiteral elements: (String, AnyBSON?)...) { - self = .document(Document(uniqueKeysWithValues: elements)) - } -} - -extension AnyBSON: ExpressibleByArrayLiteral { - public init(arrayLiteral elements: AnyBSON?...) { - self = .array(elements) - } -} - -extension AnyBSON: Equatable {} - -extension AnyBSON: Hashable {} diff --git a/RealmSwift/Combine.swift b/RealmSwift/Combine.swift index 4049597d45..015987031a 100644 --- a/RealmSwift/Combine.swift +++ b/RealmSwift/Combine.swift @@ -942,70 +942,6 @@ public enum RealmPublishers { return realm(sourceRealm.rlmRealm.configurationSharingSchema(), scheduler) } - /// A publisher which emits an asynchronously opened Realm. - @frozen public struct AsyncOpenPublisher: Publisher { - /// This publisher can fail if there is an error opening the Realm. - public typealias Failure = Error - /// This publisher emits an opened Realm. - public typealias Output = Realm - - private let configuration: Realm.Configuration - private let callbackQueue: DispatchQueue - private let onProgressNotificationCallback: ((SyncSession.Progress) -> Void)? - - internal init(configuration: Realm.Configuration, - callbackQueue: DispatchQueue = .main, - onProgressNotificationCallback: ((SyncSession.Progress) -> Void)? = nil) { - self.configuration = configuration - self.callbackQueue = callbackQueue - self.onProgressNotificationCallback = onProgressNotificationCallback - } - - /// Triggers an event when there is a notification on the async open progress. - /// - /// This should be called directly after invoking the publisher. - /// - /// - Parameter onProgressNotificationCallback: Callback which will be invoked when there is an update on progress. - /// - Returns: A publisher that emits an asynchronously opened Realm. - public func onProgressNotification(_ onProgressNotificationCallback: @escaping ((SyncSession.Progress) -> Void)) -> Self { - Self(configuration: configuration, - callbackQueue: callbackQueue, - onProgressNotificationCallback: onProgressNotificationCallback) - } - - /// :nodoc: - public func receive(subscriber: S) where S: Subscriber, S.Failure == Failure, Output == S.Input { - let rlmTask = RLMRealm.asyncOpen(with: configuration.rlmConfiguration, - callbackQueue: callbackQueue) { rlmRealm, error in - if let realm = rlmRealm.flatMap(Realm.init) { - _ = subscriber.receive(realm) - subscriber.receive(completion: .finished) - } else { - subscriber.receive(completion: .failure(error ?? Realm.Error.callFailed)) - } - } - let task = Realm.AsyncOpenTask(rlmTask: rlmTask) - if let onProgressNotificationCallback { - task.addProgressNotification(queue: callbackQueue, block: onProgressNotificationCallback) - } - subscriber.receive(subscription: AsyncOpenSubscription(task: task)) - } - - /// Specifies the scheduler on which to perform the async open task. - /// - /// - parameter scheduler: The serial dispatch queue to receive values on. - /// - returns: A publisher which delivers values to the given scheduler. - public func receive(on scheduler: S) -> Self { - guard let queue = scheduler as? DispatchQueue else { - fatalError("Cannot subscribe on scheduler \(scheduler): only serial dispatch queues are currently implemented.") - } - - return Self(configuration: configuration, - callbackQueue: queue, - onProgressNotificationCallback: onProgressNotificationCallback) - } - } - /// A publisher which emits Void each time the Realm is refreshed. /// /// Despite the name, this actually emits *after* the Realm is refreshed. diff --git a/RealmSwift/Decimal128.swift b/RealmSwift/Decimal128.swift index 6ff32bd152..3c0f779ce8 100644 --- a/RealmSwift/Decimal128.swift +++ b/RealmSwift/Decimal128.swift @@ -59,12 +59,11 @@ public final class Decimal128: RLMDecimal128, Decodable, @unchecked Sendable { /// Parse the given string as a Decimal128. /// - /// This initializer never throws and is marked as `throws` only because removing it is a breaking - /// change. Strings which cannot be parsed as a Decimal128 return a value where `isNaN` is `true`. + /// Strings which cannot be parsed as a Decimal128 return a value where `isNaN` is `true`. /// /// - parameter string: The string to parse. - public override required init(string: String) throws { - try super.init(string: string) + public override required init(string: String) { + super.init(string: string) } /// Creates a new Decimal128 by decoding from the given decoder. @@ -75,7 +74,7 @@ public final class Decimal128: RLMDecimal128, Decodable, @unchecked Sendable { public required init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let strValue = try? container.decode(String.self) { - try super.init(string: strValue) + super.init(string: strValue) } else if let intValue = try? container.decode(Int64.self) { super.init(number: intValue as NSNumber) } else if let doubleValue = try? container.decode(Double.self) { @@ -124,10 +123,8 @@ extension Decimal128: ExpressibleByFloatLiteral { extension Decimal128: ExpressibleByStringLiteral { /// Creates a new Decimal128 from the given string literal. - /// - /// Aborts if the string cannot be parsed as a Decimal128. public convenience init(stringLiteral value: String) { - try! self.init(string: value) + self.init(string: value) } } diff --git a/RealmSwift/Error.swift b/RealmSwift/Error.swift index 5028edb100..c3825227cb 100644 --- a/RealmSwift/Error.swift +++ b/RealmSwift/Error.swift @@ -53,10 +53,3 @@ extension Realm.Error: @retroactive Equatable {} #else extension Realm.Error: Equatable {} #endif - -// FIXME: we should not be defining this but it's a breaking change to remove -/// Returns a Boolean indicating whether the errors are identical. -public func == (lhs: Error, rhs: Error) -> Bool { - return lhs._code == rhs._code - && lhs._domain == rhs._domain -} diff --git a/RealmSwift/Events.swift b/RealmSwift/Events.swift deleted file mode 100644 index 1047f392f9..0000000000 --- a/RealmSwift/Events.swift +++ /dev/null @@ -1,308 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import Realm.Private -import Combine - -/** - Realm event recording can be used to record all reads and writes performed on - a Realm and report them to the server. Enable event recording by setting the - `eventConfiguration` property of the `Realm.Configuration` used to open a - Realm, and then obtain an `Events` instance with the `events` property on the - `Realm`. -*/ -public struct Events { - let context: OpaquePointer - let realm: RLMRealm - - /** - Begin recording events with the given activity name. - - All queries run and all objects instantiated within an event scope will be - automatically reported as 'read' events when the scope is ended. All - objects modified within an event scope will produce 'write' events which - report the initial state of the object and the new values of all properties - which changed. - - - returns: A scope object used to commit or cancel the scope. - */ - public func beginScope(activity: String) -> Scope { - Scope(realm: realm, context: context, id: RLMEventBeginScope(context, activity)) - } - - /// :nodoc: - @available(*, unavailable, message: "Use EventScope.commit()") - public func endScope(completion: ((Swift.Error?) -> Void)? = nil) { - fatalError() - } - - /** - Record a custom event. - - This function saves the event to disk locally and then asynchronously sends - them to the server. The optional completion function is called when the - event data has been successfully persisted, and *not* when the actual - upload has completed. - - This function does not interact with event scopes, and can be called with no active scope. - - - Parameters: - - activity: The activity name. This is an arbitrary string stored as-in - in the `activity` event property. - - eventType: The type of event. This is an arbitrary string stored - as-in in the `eventType` event property. - - data: The data payload for this event. If supplied, the string stored - in the `data` event property. Note that while automatically - generated events all store JSON in this field, custom events - are not required to do so. - - completion: An optional completion handler which will be called once - the event has either been saved to the event Realm (but - not necessarily uploaded to the server) or an error has - occurred. A nil error indicates success. - */ - public func recordEvent(activity: String, eventType: String? = nil, data: String? = nil, - completion: ((Swift.Error?) -> Void)? = nil) { - RLMEventRecordEvent(context, activity, eventType, data, completion) - } - - /** - Replace the metadata supplied in the event configuration with new values. - - If called while an event scope is active, the new metadata will not be used - until the next event scope is begun. - - See ``EventConfiguration.metadata`` for more details on event metdata. - */ - public func updateMetadata(_ newMetadata: [String: String]) { - RLMEventUpdateMetadata(context, newMetadata) - } - - init?(_ realm: Realm) { - if let context = RLMEventGetContext(realm.rlmRealm) { - self.context = context - self.realm = realm.rlmRealm - } else { - return nil - } - } - - /** - An object which represents an active event scope which can be used to - either commit or cancel the scope. - */ - public class Scope { - /** - End recording the event scope and report all generated events. - - This function saves the events to disk locally and then - asynchronously sends them to the server. The optional completion function - is called when the event data has been successfully persisted, and *not* - when the actual upload has completed. - - An exception will be thrown if this scope has already been committed or - cancelled (i.e. if ``isActive`` is `false`). - */ - public func commit(completion: ((Swift.Error?) -> Void)? = nil) { - RLMEventCommitScope(context, id, completion) - } - - /** - Cancel this event scope and discard all generated events. - - An exception will be thrown if this scope has already been committed or - cancelled (i.e. if ``isActive`` is `false`). - */ - public func cancel() { - RLMEventCancelScope(context, id) - } - - /** - True if this scope has not been committed or cancelled, and false otherwise. - */ - public var isActive: Bool { - RLMEventIsActive(context, id) - } - - let realm: RLMRealm - let context: OpaquePointer - let id: UInt64 - fileprivate init(realm: RLMRealm, context: OpaquePointer, id: UInt64) { - self.realm = realm - self.context = context - self.id = id - } - deinit { - guard isActive else { return } - logRuntimeIssue("Deallocating an active event scope. The scope's events will be discarded.") - cancel() - } - } -} - -@available(macOS 10.15, watchOS 6.0, iOS 13.0, tvOS 13.0, macCatalyst 13.0, *) -extension Events.Scope { - /** - End recording the event scope and report all generated events. - - This function saves the events to disk locally and then asynchronously - sends them to the server. The returned future is fulfilled when the event - data has been successfully persisted, and *not* when the actual upload has - completed. - - An exception will be thrown if this scope has already been committed or - cancelled (i.e. if ``isActive`` is `false`). - */ - @_disfavoredOverload - public func commit() -> Future { - promisify { - RLMEventCommitScope(self.context, self.id, $0) - } - } -} - -@available(macOS 10.15, watchOS 6.0, iOS 13.0, tvOS 13.0, macCatalyst 13.0, *) -public extension Events { - @available(*, unavailable, message: "Use EventScope.commit()") - func endScope() -> Future { - fatalError() - } - - /** - Record a custom event. - - This function saves the events to disk locally and then asynchronously - sends them to the server. The returned future is fulfilled when the event - data has been successfully persisted, and *not* when the actual upload has - completed. - - This function does not interact with event scopes, and can be called with no active scope. - - - Parameters: - - activity: The activity name. This is an arbitrary string stored as-in - in the `activity` event property. - - eventType: The type of event. This is an arbitrary string stored - as-in in the `eventType` event property. - - data: The data payload for this event. If supplied, the string stored - in the `data` event property. Note that while automatically - generated events all store JSON in this field, custom events - are not required to do so. - */ - @_disfavoredOverload - func recordEvent(activity: String, eventType: String? = nil, data: String? = nil) - -> Future { - promisify { - recordEvent(activity: activity, eventType: eventType, data: data, completion: $0) - } - } -} - -extension Realm { - /// Get the event context for the Realm. Will be `nil` unless an - /// ``EventConfiguration`` was set while opening the Realm. - public var events: Events? { - Events(self) - } -} - -/// Configuration parameters for Realm event recording. -/// -/// Enabling Realm event recording is done by setting -/// ``Realm.Configuration.eventConfiguration`` to any non-nil -/// `EventConfiguration`. A default-initialized configuration is valid, but -/// some properties may need to be customized. -/// -/// Using Realm event recording requires including the collection `AuditEvent` -/// in your schema defined on the server for the App which you will be writing -/// events to. The schema must contain the following fields: -/// - `_id`: `ObjectId` -/// - `activity`: `String` -/// - `event`: `String?` -/// - `data`: `String?` -/// - `timestamp`: `Date` -/// In addition, there must be a `String?` field for each metadata key used. -@frozen public struct EventConfiguration: Sendable { - /// Metadata which is attached to each event generated. Each key in the - /// metadata dictionary is stored in a column with that name in the event - /// Realm, and so the schema configured on the server for the AuditEvent - /// collection must include all metadata fields which will be used. The - /// metadata fields must be of type `String?` in the server-side schema. - public var metadata: [String: String]? - - /// The sync user to write event data with. If not supplied, the user from - /// the Realm being traced will be used. This can be a ``User`` associated - /// with a different ``App`` from the Realm being traced if desired. - /// - /// The user must be associated with a partition-based sync app. If the - /// traced Realm is using flexible sync, setting this field to a PBS user - /// is mandatory. - public var syncUser: User? - - /// A string prepended to the randomly-generated partition values used for - /// uploading event data to the server. This can be customized to ensure - /// that you can distinguish event partitions from partitions used by your - /// application. - public var partitionPrefix: String = "events" - - /// A logger callback function. This function should be thread-safe as it - /// may be called from multiple threads simultaneously. - public typealias LoggerFunction = @Sendable (SyncLogLevel, String) -> Void - /// A logger which will be called to report information about the work done - /// on the background event thread. If `nil`, this is instead reported to - /// the ``SyncManager``'s logger. - @preconcurrency - public var logger: LoggerFunction? - - /// The error handler which will be called if a sync error occurs when - /// uploading event data. If `nil`, the error will be logged and then - /// `abort()` will be called. Production usage should always define a - /// custom error handler unless aborting on error is desired. - @preconcurrency - public var errorHandler: (@Sendable (Swift.Error) -> Void)? - - /// Creates an `EventConfiguration` which enables Realm event recording. - @preconcurrency - public init(metadata: [String: String]? = nil, syncUser: User? = nil, - partitionPrefix: String = "events", logger: LoggerFunction? = nil, - errorHandler: (@Sendable (Swift.Error) -> Void)? = nil) { - self.metadata = metadata - self.syncUser = syncUser - self.partitionPrefix = partitionPrefix - self.logger = logger - self.errorHandler = errorHandler - } -} - -/// A type which has a custom representation in Realm events. -/// -/// By default, objects are serialized to JSON using built-in rules which -/// include every property. If you wish to customize how a class is serialized -/// in events, you can declare it as conforming to this protocol and -/// define `customEventRepresentation()`. -@objc(RLMCustomEventRepresentable) -public protocol CustomEventRepresentable { - /// Get the custom event serialization for this object. - /// - /// This function must return a valid JSON String, as this is included in a - /// larger JSON document. Implementations of this function should be "pure" - /// and access no data other than that which is obtainable from the Object - /// it is called on, and it should not mutate the object which it is called - /// on. This function is called on a background thread in a somewhat - /// unusual context, and attempting to access other data is likely to cause - /// problems. - @objc func customEventRepresentation() -> String -} diff --git a/RealmSwift/Impl/ObjcBridgeable.swift b/RealmSwift/Impl/ObjcBridgeable.swift index 5f1cfb6d5f..5a3cf70d18 100644 --- a/RealmSwift/Impl/ObjcBridgeable.swift +++ b/RealmSwift/Impl/ObjcBridgeable.swift @@ -140,7 +140,7 @@ extension Decimal128: BuiltInObjcBridgeable { return Decimal128(number: number) } if let str = value as? String { - return .some((try? Decimal128(string: str)) ?? Decimal128("nan")) + return Decimal128(string: str) } return .none } diff --git a/RealmSwift/Map.swift b/RealmSwift/Map.swift index d65e6b3780..4e25f52be1 100644 --- a/RealmSwift/Map.swift +++ b/RealmSwift/Map.swift @@ -33,8 +33,6 @@ extension String: _MapKey { } subclass or one of the following types: Bool, Int, Int8, Int16, Int32, Int64, Float, Double, String, Data, Date, Decimal128, and ObjectId (and their optional versions) - - Note: Optional versions of the above types *except* `Object` are only supported in non-synchronized Realms. - Map only supports `String` as a key. Realm disallows the use of `.` or `$` characters within a dictionary key. Unlike Swift's native collections, `Map`s is a reference types, and are only immutable if the Realm that manages them @@ -136,29 +134,6 @@ public final class Map: RLMSwiftColle } } - /** - Merges the given map into this map, using a combining closure to determine - the value for any duplicate keys. - - If `other` contains a key which is already present in this map, `combine` - will be called with the value currently in the map and the value in the - other map. The value returned by the closure will be stored in the map for - that key. - - - Note: If a value being added to the map is an unmanaged object and the - map is managed then that unmanaged object will be added to the Realm. - - - warning: This method may only be called on managed Maps during a write transaction. - - - parameter other: The map to merge into this map. - - parameter combine: A closure that takes the current and new values for - any duplicate keys. The closure returns the desired value for - the final map. - */ - public func merge(_ other: Map, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows { - try merge(other.asKeyValueSequence(), uniquingKeysWith: combine) - } - /** Removes the given key and its associated object, only if the key exists in the map. If the key does not exist, the map will not be modified. @@ -979,29 +954,9 @@ extension Map: Encodable where Key: Encodable, Value: Encodable { // MARK: Sequence Support extension Map: Sequence { - // NEXT-MAJOR: change this to KeyValueSequence /// Returns a `RLMMapIterator` that yields successive elements in the `Map`. - public func makeIterator() -> RLMMapIterator> { - return RLMMapIterator(collection: rlmDictionary) - } -} - -extension Map { - /// An adaptor for Map which makes it a sequence of `(key: Key, value: Value)` instead of a sequence of `SingleMapEntry`. - public struct KeyValueSequence: Sequence { - private let map: Map - fileprivate init(_ map: Map) { - self.map = map - } - - public func makeIterator() -> RLMKeyValueIterator { - return RLMKeyValueIterator(collection: map.rlmDictionary) - } - } - - /// Returns this Map as a sequence of `(key: Key, value: Value)` - public func asKeyValueSequence() -> KeyValueSequence { - return KeyValueSequence(self) + public func makeIterator() -> RLMKeyValueIterator { + return RLMKeyValueIterator(collection: rlmDictionary) } } diff --git a/RealmSwift/MongoClient.swift b/RealmSwift/MongoClient.swift deleted file mode 100644 index 24fda36c4f..0000000000 --- a/RealmSwift/MongoClient.swift +++ /dev/null @@ -1,1297 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import Combine -import Foundation -import Realm -import Realm.Private - -/** - * The `MongoClient` enables reading and writing on a MongoDB database via the Realm Cloud service. - * - * It provides access to instances of `MongoDatabase`, which in turn provide access to specific - * `MongoCollection`s that hold your data. - * - * - Note: - * Before you can read or write data, a user must log in. - * - * - SeeAlso: - * `App`, `MongoDatabase`, `MongoCollection` - */ -public typealias MongoClient = RLMMongoClient - -/** - * The `MongoDatabase` represents a MongoDB database, which holds a group - * of collections that contain your data. - * - * It can be retrieved from the `MongoClient`. - * - * Use it to get `MongoCollection`s for reading and writing data. - * - * - Note: - * Before you can read or write data, a user must log in`. - * - * - SeeAlso: - * `MongoClient`, `MongoCollection` - */ -public typealias MongoDatabase = RLMMongoDatabase - -/// Options to use when executing a `find` command on a `MongoCollection`. -public typealias FindOptions = RLMFindOptions - -extension FindOptions { - /// Limits the fields to return for all matching documents. - public var projection: Document? { - get { - return __projection.map(ObjectiveCSupport.convertBson)??.documentValue - } - set { - __projection = newValue.map(AnyBSON.init).map(ObjectiveCSupport.convertBson) - } - } - - /// The order in which to return matching documents. - @available(*, deprecated, message: "Use `sorting`") - public var sort: Document? { - get { - return __sort.map(ObjectiveCSupport.convertBson)??.documentValue - } - set { - __sort = newValue.map(AnyBSON.init).map(ObjectiveCSupport.convertBson) - } - } - - /// The order in which to return matching documents. - public var sorting: [Document] { - get { - return __sorting.map(ObjectiveCSupport.convertBson).map({$0!.documentValue!}) - } - set { - __sorting = newValue.map(AnyBSON.init).map(ObjectiveCSupport.convertBson) - } - } - - // NEXT-MAJOR: there's no reason for limit to be optional here - /// Options to use when executing a `find` command on a `MongoCollection`. - /// - Parameters: - /// - limit: The maximum number of documents to return. Specifying 0 will return all documents. - /// - projected: Limits the fields to return for all matching documents. - /// - sort: The order in which to return matching documents. - @available(*, deprecated, message: "Use init(limit:projection:sorting:)") - public convenience init(_ limit: Int?, _ projection: Document?, _ sort: Document?) { - self.init() - self.limit = limit ?? 0 - self.projection = projection - self.sort = sort - } - - /// Options to use when executing a `find` command on a `MongoCollection`. - /// - Parameters: - /// - limit: The maximum number of documents to return. Specifying 0 will return all documents. - /// - projected: Limits the fields to return for all matching documents. - /// - sorting: The order in which to return matching documents. - public convenience init(_ limit: Int = 0, _ projection: Document? = nil, _ sorting: [Document] = []) { - self.init() - self.limit = limit - self.projection = projection - self.sorting = sorting - } - - /// Options to use when executing a `find` command on a `MongoCollection`. - /// - Parameters: - /// - limit: The maximum number of documents to return. Specifying 0 will return all documents. - /// - projected: Limits the fields to return for all matching documents. - /// - sort: The order in which to return matching documents. - @available(*, deprecated, message: "Use init(limit:projection:sorting:)") - public convenience init(limit: Int?, projection: Document?, sort: Document?) { - self.init(limit, projection, sort) - } - -} - -/// Options to use when executing a `findOneAndUpdate`, `findOneAndReplace`, -/// or `findOneAndDelete` command on a `MongoCollection`. -public typealias FindOneAndModifyOptions = RLMFindOneAndModifyOptions - -extension FindOneAndModifyOptions { - /// Limits the fields to return for all matching documents. - public var projection: Document? { - get { - return __projection.map(ObjectiveCSupport.convertBson)??.documentValue - } - set { - __projection = newValue.map(AnyBSON.init).map(ObjectiveCSupport.convertBson) - } - } - - /// The order in which to return matching documents. - @available(*, deprecated, message: "Use `sorting`") - public var sort: Document? { - get { - return __sort.map(ObjectiveCSupport.convertBson)??.documentValue - } - set { - __sort = newValue.map(AnyBSON.init).map(ObjectiveCSupport.convertBson) - } - } - - /// The order in which to return matching documents, defined by `SortDescriptor` - public var sorting: [Document] { - get { - return __sorting.map(ObjectiveCSupport.convertBson).map({$0!.documentValue!}) - } - set { - __sorting = newValue.map(AnyBSON.init).map(ObjectiveCSupport.convertBson) - } - } - - /// Options to use when executing a `findOneAndUpdate`, `findOneAndReplace`, - /// or `findOneAndDelete` command on a `MongoCollection` - /// - Parameters: - /// - projection: Limits the fields to return for all matching documents. - /// - sort: The order in which to return matching documents. - /// - upsert: Whether or not to perform an upsert, default is false - /// (only available for findOneAndReplace and findOneAndUpdate) - /// - shouldReturnNewDocument: When true then the new document is returned, - /// Otherwise the old document is returned (default) - /// (only available for findOneAndReplace and findOneAndUpdate) - @available(*, deprecated, message: "Use init(projection:sorting:upsert:shouldReturnNewDocument:)") - public convenience init(_ projection: Document?, - _ sort: Document?, - _ upsert: Bool=false, - _ shouldReturnNewDocument: Bool=false) { - self.init() - self.projection = projection - self.sort = sort - self.upsert = upsert - self.shouldReturnNewDocument = shouldReturnNewDocument - } - - /// Options to use when executing a `findOneAndUpdate`, `findOneAndReplace`, - /// or `findOneAndDelete` command on a `MongoCollection` - /// - Parameters: - /// - projection: Limits the fields to return for all matching documents. - /// - sorting: The order in which to return matching documents. - /// - upsert: Whether or not to perform an upsert, default is false - /// (only available for findOneAndReplace and findOneAndUpdate) - /// - shouldReturnNewDocument: When true then the new document is returned, - /// Otherwise the old document is returned (default) - /// (only available for findOneAndReplace and findOneAndUpdate) - public convenience init(_ projection: Document?, - _ sorting: [Document] = [], - _ upsert: Bool=false, - _ shouldReturnNewDocument: Bool=false) { - self.init() - self.projection = projection - self.sorting = sorting - self.upsert = upsert - self.shouldReturnNewDocument = shouldReturnNewDocument - } - - /// Options to use when executing a `findOneAndUpdate`, `findOneAndReplace`, - /// or `findOneAndDelete` command on a `MongoCollection` - /// - Parameters: - /// - projection: Limits the fields to return for all matching documents. - /// - sort: The order in which to return matching documents. - /// - upsert: Whether or not to perform an upsert, default is false - /// (only available for findOneAndReplace and findOneAndUpdate) - /// - shouldReturnNewDocument: When true then the new document is returned, - /// Otherwise the old document is returned (default) - /// (only available for findOneAndReplace and findOneAndUpdate) - @available(*, deprecated, message: "Use init(projection:sorting:upsert:shouldReturnNewDocument:)") - public convenience init(projection: Document?, - sort: Document?, - upsert: Bool=false, - shouldReturnNewDocument: Bool=false) { - self.init(projection, sort, upsert, shouldReturnNewDocument) - } -} - -/// The result of an `updateOne` or `updateMany` operation a `MongoCollection`. -public typealias UpdateResult = RLMUpdateResult - -/// Block which returns Result.success(DocumentId) on a successful insert or Result.failure(error) -public typealias MongoInsertBlock = @Sendable (Result) -> Void -/// Block which returns Result.success([ObjectId]) on a successful insertMany or Result.failure(error) -public typealias MongoInsertManyBlock = @Sendable (Result<[AnyBSON], Error>) -> Void -/// Block which returns Result.success([Document]) on a successful find operation or Result.failure(error) -public typealias MongoFindBlock = @Sendable (Result<[Document], Error>) -> Void -/// Block which returns Result.success(Document?) on a successful findOne operation or Result.failure(error) -public typealias MongoFindOneBlock = @Sendable (Result) -> Void -/// Block which returns Result.success(Int) on a successful count operation or Result.failure(error) -public typealias MongoCountBlock = @Sendable (Result) -> Void -/// Block which returns Result.success(UpdateResult) on a successful update operation or Result.failure(error) -public typealias MongoUpdateBlock = @Sendable (Result) -> Void - -/** - * The `MongoCollection` represents a MongoDB collection. - * - * You can get an instance from a `MongoDatabase`. - * - * Create, read, update, and delete methods are available. - * - * Operations against the Realm Cloud server are performed asynchronously. - * - * - Note: - * Before you can read or write data, a user must log in. - * - * - SeeAlso: - * `MongoClient`, `MongoDatabase` - */ -public typealias MongoCollection = RLMMongoCollection - -/// Acts as a middleman and processes events with WatchStream -public typealias ChangeStream = RLMChangeStream - -/// Delegate which is used for subscribing to changes on a `MongoCollection.watch()` stream. -public protocol ChangeEventDelegate: AnyObject { - /// The stream was opened. - /// - Parameter changeStream: The `ChangeStream` subscribing to the stream changes. - func changeStreamDidOpen(_ changeStream: ChangeStream) - /// The stream has been closed. - /// - Parameter error: If an error occurred when closing the stream, an error will be passed. - func changeStreamDidClose(with error: Error?) - /// A error has occurred while streaming. - /// - Parameter error: The streaming error. - func changeStreamDidReceive(error: Error) - /// Invoked when a change event has been received. - /// - Parameter changeEvent:The change event in BSON format. - func changeStreamDidReceive(changeEvent: AnyBSON?) -} - -extension MongoCollection { - /// Opens a MongoDB change stream against the collection to watch for changes. The resulting stream will be notified - /// of all events on this collection that the active user is authorized to see based on the configured MongoDB - /// rules. - /// - Parameters: - /// - delegate: The delegate that will react to events and errors from the resulting change stream. - /// - queue: Dispatches streaming events to an optional queue, if no queue is provided the main queue is used - /// - Returns: A ChangeStream which will manage the streaming events. - public func watch(delegate: ChangeEventDelegate, queue: DispatchQueue = .main) -> ChangeStream { - return self.__watch(with: ChangeEventDelegateProxy(delegate), - delegateQueue: queue) - } - - /// Opens a MongoDB change stream against the collection to watch for changes. The provided BSON document will be - /// used as a match expression filter on the change events coming from the stream. - /// - /// See https://docs.mongodb.com/manual/reference/operator/aggregation/match/ for documentation around how to define - /// a match filter. - /// - /// Defining the match expression to filter ChangeEvents is similar to defining the match expression for triggers: - /// https://docs.mongodb.com/realm/triggers/database-triggers/ - /// - Parameters: - /// - matchFilter: The $match filter to apply to incoming change events - /// - delegate: The delegate that will react to events and errors from the resulting change stream. - /// - queue: Dispatches streaming events to an optional queue, if no queue is provided the main queue is used - /// - Returns: A ChangeStream which will manage the streaming events. - public func watch(matchFilter: Document, delegate: ChangeEventDelegate, queue: DispatchQueue = .main) -> ChangeStream { - __watch(withMatchFilter: ObjectiveCSupport.convert(matchFilter), - delegate: ChangeEventDelegateProxy(delegate), - delegateQueue: queue) - } - - /// Opens a MongoDB change stream against the collection to watch for changes - /// made to specific documents. The documents to watch must be explicitly - /// specified by their _id. - /// - Parameters: - /// - filterIds: The list of _ids in the collection to watch. - /// - delegate: The delegate that will react to events and errors from the resulting change stream. - /// - queue: Dispatches streaming events to an optional queue, if no queue is provided the main queue is used - /// - Returns: A ChangeStream which will manage the streaming events. - public func watch(filterIds: [ObjectId], delegate: ChangeEventDelegate, queue: DispatchQueue = .main) -> ChangeStream { - __watch(withFilterIds: filterIds, - delegate: ChangeEventDelegateProxy(delegate), - delegateQueue: queue) - } -} - -// MongoCollection methods with result type completions -extension MongoCollection { - /// Encodes the provided value to BSON and inserts it. If the value is missing an identifier, one will be - /// generated for it. - /// - Parameters: - /// - document: document A `Document` value to insert. - /// - completion: The result of attempting to perform the insert. An Id will be returned for the inserted object on sucess - @preconcurrency - public func insertOne(_ document: Document, _ completion: @escaping MongoInsertBlock) { - let bson = ObjectiveCSupport.convert(document) - __insertOneDocument(bson) { objectId, error in - if let o = objectId.map(ObjectiveCSupport.convert), let objectId = o { - completion(.success(objectId)) - } else { - completion(.failure(error ?? Realm.Error.callFailed)) - } - } - } - - /// Encodes the provided values to BSON and inserts them. If any values are missing identifiers, - /// they will be generated. - /// - Parameters: - /// - documents: The `Document` values in a bson array to insert. - /// - completion: The result of the insert, returns an array inserted document ids in order. - @preconcurrency - public func insertMany(_ documents: [Document], _ completion: @escaping MongoInsertManyBlock) { - let bson = documents.map(ObjectiveCSupport.convert) - __insertManyDocuments(bson) { objectIds, error in - if let objectIds = objectIds?.compactMap(ObjectiveCSupport.convert) { - completion(.success(objectIds)) - } else { - completion(.failure(error ?? Realm.Error.callFailed)) - } - } - } - - /// Finds the documents in this collection which match the provided filter. - /// - Parameters: - /// - filter: A `Document` as bson that should match the query. - /// - options: `FindOptions` to use when executing the command. - /// - completion: The resulting bson array of documents or error if one occurs - @preconcurrency - public func find(filter: Document, options: FindOptions = FindOptions(), - _ completion: @escaping MongoFindBlock) { - let bson = ObjectiveCSupport.convert(filter) - __findWhere(bson, options: options) { documents, error in - if let bson = documents?.map(ObjectiveCSupport.convert) { - completion(.success(bson)) - } else { - completion(.failure(error ?? Realm.Error.callFailed)) - } - } - } - - /// Returns one document from a collection or view which matches the - /// provided filter. If multiple documents satisfy the query, this method - /// returns the first document according to the query's sort order or natural - /// order. - /// - Parameters: - /// - filter: A `Document` as bson that should match the query. - /// - options: `FindOptions` to use when executing the command. - /// - completion: The resulting bson or error if one occurs - @preconcurrency - public func findOneDocument(filter: Document, options: FindOptions = FindOptions(), - _ completion: @escaping MongoFindOneBlock) { - let bson = ObjectiveCSupport.convert(filter) - __findOneDocumentWhere(bson, options: options) { document, error in - if let error = error { - completion(.failure(error)) - } else { - completion(.success(document.map(ObjectiveCSupport.convert))) - } - } - } - - /// Runs an aggregation framework pipeline against this collection. - /// - Parameters: - /// - pipeline: A bson array made up of `Documents` containing the pipeline of aggregation operations to perform. - /// - completion: The resulting bson array of documents or error if one occurs - @preconcurrency - public func aggregate(pipeline: [Document], _ completion: @escaping MongoFindBlock) { - let bson = pipeline.map(ObjectiveCSupport.convert) - __aggregate(withPipeline: bson) { documents, error in - if let bson = documents?.map(ObjectiveCSupport.convert) { - completion(.success(bson)) - } else { - completion(.failure(error ?? Realm.Error.callFailed)) - } - } - } - - /// Counts the number of documents in this collection matching the provided filter. - /// - Parameters: - /// - filter: A `Document` as bson that should match the query. - /// - limit: The max amount of documents to count - /// - completion: Returns the count of the documents that matched the filter. - @preconcurrency - public func count(filter: Document, limit: Int? = nil, _ completion: @escaping MongoCountBlock) { - let bson = ObjectiveCSupport.convert(filter) - __countWhere(bson, limit: limit ?? 0) { count, error in - if let error = error { - completion(.failure(error)) - } else { - completion(.success(count)) - } - } - } - - /// Deletes a single matching document from the collection. - /// - Parameters: - /// - filter: A `Document` as bson that should match the query. - /// - completion: The result of performing the deletion. Returns the count of deleted objects - @preconcurrency - public func deleteOneDocument(filter: Document, _ completion: @escaping MongoCountBlock) { - let bson = ObjectiveCSupport.convert(filter) - __deleteOneDocumentWhere(bson) { count, error in - if let error = error { - completion(.failure(error)) - } else { - completion(.success(count)) - } - } - } - - /// Deletes multiple documents - /// - Parameters: - /// - filter: Document representing the match criteria - /// - completion: The result of performing the deletion. Returns the count of the deletion - @preconcurrency - public func deleteManyDocuments(filter: Document, _ completion: @escaping MongoCountBlock) { - let bson = ObjectiveCSupport.convert(filter) - __deleteManyDocumentsWhere(bson) { count, error in - if let error = error { - completion(.failure(error)) - } else { - completion(.success(count)) - } - } - } - - /// Updates a single document matching the provided filter in this collection. - /// - Parameters: - /// - filter: A bson `Document` representing the match criteria. - /// - update: A bson `Document` representing the update to be applied to a matching document. - /// - upsert: When true, creates a new document if no document matches the query. - /// - completion: The result of the attempt to update a document. - @preconcurrency - public func updateOneDocument(filter: Document, update: Document, upsert: Bool = false, - _ completion: @escaping MongoUpdateBlock) { - let filterBSON = ObjectiveCSupport.convert(filter) - let updateBSON = ObjectiveCSupport.convert(update) - __updateOneDocumentWhere(filterBSON, updateDocument: updateBSON, - upsert: upsert) { updateResult, error in - if let updateResult = updateResult { - completion(.success(updateResult)) - } else { - completion(.failure(error ?? Realm.Error.callFailed)) - } - } - } - - /// Updates multiple documents matching the provided filter in this collection. - /// - Parameters: - /// - filter: A bson `Document` representing the match criteria. - /// - update: A bson `Document` representing the update to be applied to a matching document. - /// - upsert: When true, creates a new document if no document matches the query. - /// - completion: The result of the attempt to update a document. - @preconcurrency - public func updateManyDocuments(filter: Document, update: Document, upsert: Bool = false, - _ completion: @escaping MongoUpdateBlock) { - let filterBSON = ObjectiveCSupport.convert(filter) - let updateBSON = ObjectiveCSupport.convert(update) - __updateManyDocumentsWhere(filterBSON, updateDocument: updateBSON, - upsert: upsert) { updateResult, error in - if let updateResult = updateResult { - completion(.success(updateResult)) - } else { - completion(.failure(error ?? Realm.Error.callFailed)) - } - } - } - - /// Updates a single document in a collection based on a query filter and - /// returns the document in either its pre-update or post-update form. Unlike - /// `updateOneDocument`, this action allows you to atomically find, update, and - /// return a document with the same command. This avoids the risk of other - /// update operations changing the document between separate find and update - /// operations. - /// - Parameters: - /// - filter: A bson `Document` representing the match criteria. - /// - update: A bson `Document` representing the update to be applied to a matching document. - /// - options: `RemoteFindOneAndModifyOptions` to use when executing the command. - /// - completion: The result of the attempt to update a document. - @preconcurrency - public func findOneAndUpdate(filter: Document, update: Document, - options: FindOneAndModifyOptions = .init(), - _ completion: @escaping MongoFindOneBlock) { - let filterBSON = ObjectiveCSupport.convert(filter) - let updateBSON = ObjectiveCSupport.convert(update) - __findOneAndUpdateWhere(filterBSON, updateDocument: updateBSON, - options: options) { document, error in - if let error = error { - completion(.failure(error)) - } else { - let bson: Document? = document?.mapValues { ObjectiveCSupport.convert(object: $0) } - completion(.success(bson)) - } - } - } - - /// Overwrites a single document in a collection based on a query filter and - /// returns the document in either its pre-replacement or post-replacement - /// form. Unlike `updateOneDocument`, this action allows you to atomically find, - /// replace, and return a document with the same command. This avoids the - /// risk of other update operations changing the document between separate - /// find and update operations. - /// - Parameters: - /// - filter: A `Document` that should match the query. - /// - replacement: A `Document` describing the replacement. - /// - options: `FindOneAndModifyOptions` to use when executing the command. - /// - completion: The result of the attempt to replace a document. - @preconcurrency - public func findOneAndReplace(filter: Document, replacement: Document, - options: FindOneAndModifyOptions = .init(), - _ completion: @escaping MongoFindOneBlock) { - let filterBSON = ObjectiveCSupport.convert(filter) - let replacementBSON = ObjectiveCSupport.convert(replacement) - __findOneAndReplaceWhere(filterBSON, replacementDocument: replacementBSON, - options: options) { document, error in - if let error = error { - completion(.failure(error)) - } else { - completion(.success(document.map(ObjectiveCSupport.convert))) - } - } - } - - /// Removes a single document from a collection based on a query filter and - /// returns a document with the same form as the document immediately before - /// it was deleted. Unlike `deleteOneDocument`, this action allows you to atomically - /// find and delete a document with the same command. This avoids the risk of - /// other update operations changing the document between separate find and - /// delete operations. - /// - Parameters: - /// - filter: A `Document` that should match the query. - /// - options: `FindOneAndModifyOptions` to use when executing the command. - /// - completion: The result of the attempt to delete a document. - @preconcurrency - public func findOneAndDelete(filter: Document, options: FindOneAndModifyOptions = .init(), - _ completion: @escaping MongoFindOneBlock) { - let filterBSON = ObjectiveCSupport.convert(filter) - __findOneAndDeleteWhere(filterBSON, options: options) { document, error in - if let error = error { - completion(.failure(error)) - } else { - completion(.success(document.map(ObjectiveCSupport.convert))) - } - } - } -} - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -extension MongoCollection { - /// Encodes the provided value to BSON and inserts it. If the value is missing an identifier, one will be - /// generated for it. - /// - Parameters: - /// - document: A `Document` value to insert. - /// - Returns: The object id of the inserted document. - public func insertOne(_ document: Document) async throws -> AnyBSON { - try await ObjectiveCSupport.convert(object: __insertOneDocument(ObjectiveCSupport.convert(document)))! - } - - /// Encodes the provided values to BSON and inserts them. If any values are missing identifiers, - /// they will be generated. - /// - Parameters: - /// - documents: The `Document` values in a bson array to insert. - /// - Returns: The object ids of inserted documents. - public func insertMany(_ documents: [Document]) async throws -> [AnyBSON] { - try await __insertManyDocuments(documents.map(ObjectiveCSupport.convert)) - .compactMap(ObjectiveCSupport.convertBson(object:)) - } - -#if compiler(<6) - /// Finds the documents in this collection which match the provided filter. - /// - Parameters: - /// - filter: A `Document` as bson that should match the query. - /// - options: `FindOptions` to use when executing the command. - /// - Returns: Array of `Document` filtered. - @_unsafeInheritExecutor - public func find(filter: Document, options: FindOptions? = nil) async throws -> [Document] { - try await __findWhere(ObjectiveCSupport.convert(filter), - options: options ?? .init()) - .map(ObjectiveCSupport.convert) - } - - /// Returns one document from a collection or view which matches the - /// provided filter. If multiple documents satisfy the query, this method - /// returns the first document according to the query's sort order or natural - /// order. - /// - Parameters: - /// - filter: A `Document` as bson that should match the query. - /// - options: `FindOptions` to use when executing the command. - /// - Returns: `Document` filtered. - @_unsafeInheritExecutor - public func findOneDocument(filter: Document, options: FindOptions? = nil) async throws -> Document? { - try await __findOneDocumentWhere(ObjectiveCSupport.convert(filter), - options: options ?? .init()) - .map(ObjectiveCSupport.convert) - } -#else - /// Finds the documents in this collection which match the provided filter. - /// - Parameters: - /// - filter: A `Document` as bson that should match the query. - /// - options: `FindOptions` to use when executing the command. - /// - Returns: Array of `Document` filtered. - public func find(filter: Document, options: FindOptions = .init(), - _isolation: isolated (any Actor)? = #isolation) async throws -> [Document] { - try await withCheckedThrowingContinuation { continuation in - __findWhere(ObjectiveCSupport.convert(filter), - options: options) { bson, error in - if let error { - continuation.resume(throwing: error) - } else { - continuation.resume(returning: bson!.map(ObjectiveCSupport.convert)) - } - } - } - } - - /// Returns one document from a collection or view which matches the - /// provided filter. If multiple documents satisfy the query, this method - /// returns the first document according to the query's sort order or natural - /// order. - /// - Parameters: - /// - filter: A `Document` as bson that should match the query. - /// - options: `FindOptions` to use when executing the command. - /// - Returns: `Document` filtered. - public func findOneDocument(filter: Document, options: FindOptions = .init(), - _isolation: isolated (any Actor)? = #isolation) async throws -> Document? { - try await withCheckedThrowingContinuation { continuation in - __findOneDocumentWhere(ObjectiveCSupport.convert(filter), - options: options) { bson, error in - if let error { - continuation.resume(throwing: error) - } else { - continuation.resume(returning: bson.map(ObjectiveCSupport.convert)) - } - } - } - } -#endif - - /// Runs an aggregation framework pipeline against this collection. - /// - Parameters: - /// - pipeline: A bson array made up of `Documents` containing the pipeline of aggregation operations to perform. - /// - Returns:An array of `Document` result of the aggregation operation. - public func aggregate(pipeline: [Document]) async throws -> [Document] { - try await __aggregate(withPipeline: pipeline.map(ObjectiveCSupport.convert)) - .map(ObjectiveCSupport.convert) - } - - /// Counts the number of documents in this collection matching the provided filter. - /// - Parameters: - /// - filter: A `Document` as bson that should match the query. - /// - limit: The max amount of documents to count - /// - Returns: Count of the documents that matched the filter. - public func count(filter: Document, limit: Int? = nil) async throws -> Int { - try await __countWhere(ObjectiveCSupport.convert(filter), limit: limit ?? 0) - } - - /// Deletes a single matching document from the collection. - /// - Parameters: - /// - filter: A `Document` as bson that should match the query. - /// - Returns: `Int` count of deleted documents. - public func deleteOneDocument(filter: Document) async throws -> Int { - try await __deleteOneDocumentWhere(ObjectiveCSupport.convert(filter)) - } - - /// Deletes multiple documents - /// - Parameters: - /// - filter: Document representing the match criteria - /// - Returns: `Int` count of deleted documents. - public func deleteManyDocuments(filter: Document) async throws -> Int { - try await __deleteManyDocumentsWhere(ObjectiveCSupport.convert(filter)) - } - - // NEXT-MAJOR: there's no reason for upsert to be optional - /// Updates a single document matching the provided filter in this collection. - /// - Parameters: - /// - filter: A bson `Document` representing the match criteria. - /// - update: A bson `Document` representing the update to be applied to a matching document. - /// - upsert: When true, creates a new document if no document matches the query. - /// - Returns: `UpdateResult`result of the `updateOne` operation. - public func updateOneDocument(filter: Document, - update: Document, - upsert: Bool? = nil) async throws -> UpdateResult { - try await __updateOneDocumentWhere(ObjectiveCSupport.convert(filter), - updateDocument: ObjectiveCSupport.convert(update), - upsert: upsert ?? false) - } - - /// Updates multiple documents matching the provided filter in this collection. - /// - Parameters: - /// - filter: A bson `Document` representing the match criteria. - /// - update: A bson `Document` representing the update to be applied to a matching document. - /// - upsert: When true, creates a new document if no document matches the query. - /// - Returns:`UpdateResult`result of the `updateMany` operation. - public func updateManyDocuments(filter: Document, - update: Document, - upsert: Bool? = nil) async throws -> UpdateResult { - try await __updateManyDocumentsWhere(ObjectiveCSupport.convert(filter), - updateDocument: ObjectiveCSupport.convert(update), - upsert: upsert ?? false) - } - -#if compiler(<6) - /// Updates a single document in a collection based on a query filter and - /// returns the document in either its pre-update or post-update form. Unlike - /// `updateOneDocument`, this action allows you to atomically find, update, and - /// return a document with the same command. This avoids the risk of other - /// update operations changing the document between separate find and update - /// operations. - /// - Parameters: - /// - filter: A bson `Document` representing the match criteria. - /// - update: A bson `Document` representing the update to be applied to a matching document. - /// - options: `RemoteFindOneAndModifyOptions` to use when executing the command. - /// - Returns: `Document` result of the attempt to update a document or `nil` if document wasn't found. - @_unsafeInheritExecutor - public func findOneAndUpdate(filter: Document, update: Document, - options: FindOneAndModifyOptions? = nil) async throws -> Document? { - try await __findOneAndUpdateWhere(ObjectiveCSupport.convert(filter), - updateDocument: ObjectiveCSupport.convert(update), - options: options ?? .init()) - .map(ObjectiveCSupport.convert) - } - - /// Overwrites a single document in a collection based on a query filter and - /// returns the document in either its pre-replacement or post-replacement - /// form. Unlike `updateOneDocument`, this action allows you to atomically find, - /// replace, and return a document with the same command. This avoids the - /// risk of other update operations changing the document between separate - /// find and update operations. - /// - Parameters: - /// - filter: A `Document` that should match the query. - /// - replacement: A `Document` describing the replacement. - /// - options: `FindOneAndModifyOptions` to use when executing the command. - /// - Returns: `Document`result of the attempt to reaplce a document or `nil` if document wasn't found. - @_unsafeInheritExecutor - public func findOneAndReplace(filter: Document, replacement: Document, - options: FindOneAndModifyOptions? = nil) async throws -> Document? { - try await __findOneAndReplaceWhere(ObjectiveCSupport.convert(filter), - replacementDocument: ObjectiveCSupport.convert(replacement), - options: options ?? .init()) - .map(ObjectiveCSupport.convert) - } - - /// Removes a single document from a collection based on a query filter and - /// returns a document with the same form as the document immediately before - /// it was deleted. Unlike `deleteOneDocument`, this action allows you to atomically - /// find and delete a document with the same command. This avoids the risk of - /// other update operations changing the document between separate find and - /// delete operations. - /// - Parameters: - /// - filter: A `Document` that should match the query. - /// - options: `FindOneAndModifyOptions` to use when executing the command. - /// - Returns: `Document` result of the attempt to delete a document or `nil` if document wasn't found. - @_unsafeInheritExecutor - public func findOneAndDelete(filter: Document, - options: FindOneAndModifyOptions? = nil) async throws -> Document? { - try await __findOneAndDeleteWhere(ObjectiveCSupport.convert(filter), - options: options ?? .init()) - .map(ObjectiveCSupport.convert) - } -#else - /// Updates a single document in a collection based on a query filter and - /// returns the document in either its pre-update or post-update form. Unlike - /// `updateOneDocument`, this action allows you to atomically find, update, and - /// return a document with the same command. This avoids the risk of other - /// update operations changing the document between separate find and update - /// operations. - /// - Parameters: - /// - filter: A bson `Document` representing the match criteria. - /// - update: A bson `Document` representing the update to be applied to a matching document. - /// - options: `RemoteFindOneAndModifyOptions` to use when executing the command. - /// - Returns: `Document` result of the attempt to update a document or `nil` if document wasn't found. - public func findOneAndUpdate(filter: Document, update: Document, - options: FindOneAndModifyOptions = .init(), - _isolation: isolated (any Actor)? = #isolation) async throws -> Document? { - try await withCheckedThrowingContinuation { continuation in - __findOneAndUpdateWhere(ObjectiveCSupport.convert(filter), - updateDocument: ObjectiveCSupport.convert(update), - options: options) { bson, error in - if let error { - continuation.resume(throwing: error) - } else { - continuation.resume(returning: bson.map(ObjectiveCSupport.convert)) - } - } - } - } - - /// Overwrites a single document in a collection based on a query filter and - /// returns the document in either its pre-replacement or post-replacement - /// form. Unlike `updateOneDocument`, this action allows you to atomically find, - /// replace, and return a document with the same command. This avoids the - /// risk of other update operations changing the document between separate - /// find and update operations. - /// - Parameters: - /// - filter: A `Document` that should match the query. - /// - replacement: A `Document` describing the replacement. - /// - options: `FindOneAndModifyOptions` to use when executing the command. - /// - Returns: `Document`result of the attempt to reaplce a document or `nil` if document wasn't found. - public func findOneAndReplace(filter: Document, replacement: Document, - options: FindOneAndModifyOptions = .init(), - _isolation: isolated (any Actor)? = #isolation) async throws -> Document? { - try await withCheckedThrowingContinuation { continuation in - __findOneAndReplaceWhere(ObjectiveCSupport.convert(filter), - replacementDocument: ObjectiveCSupport.convert(replacement), - options: options) { bson, error in - if let error { - continuation.resume(throwing: error) - } else { - continuation.resume(returning: bson.map(ObjectiveCSupport.convert)) - } - } - } - } - /// Removes a single document from a collection based on a query filter and - /// returns a document with the same form as the document immediately before - /// it was deleted. Unlike `deleteOneDocument`, this action allows you to atomically - /// find and delete a document with the same command. This avoids the risk of - /// other update operations changing the document between separate find and - /// delete operations. - /// - Parameters: - /// - filter: A `Document` that should match the query. - /// - options: `FindOneAndModifyOptions` to use when executing the command. - /// - Returns: `Document` result of the attempt to delete a document or `nil` if document wasn't found. - public func findOneAndDelete(filter: Document, - options: FindOneAndModifyOptions = .init(), - _isolation: isolated (any Actor)? = #isolation) async throws -> Document? { - try await withCheckedThrowingContinuation { continuation in - __findOneAndDeleteWhere(ObjectiveCSupport.convert(filter), - options: options) { bson, error in - if let error { - continuation.resume(throwing: error) - } else { - continuation.resume(returning: bson.map(ObjectiveCSupport.convert)) - } - } - } - } -#endif -} - -private class ChangeEventDelegateProxy: RLMChangeEventDelegate { - // NEXT-MAJOR: This doesn't need to be weak and making it not weak would - // allow removing the class requirement on ChangeEventDelegate - private weak var proxyDelegate: ChangeEventDelegate? - - init(_ proxyDelegate: ChangeEventDelegate) { - self.proxyDelegate = proxyDelegate - } - - func changeStreamDidOpen(_ changeStream: RLMChangeStream) { - proxyDelegate?.changeStreamDidOpen(changeStream) - } - - func changeStreamDidCloseWithError(_ error: Error?) { - proxyDelegate?.changeStreamDidClose(with: error) - } - - func changeStreamDidReceiveError(_ error: Error) { - proxyDelegate?.changeStreamDidReceive(error: error) - } - - func changeStreamDidReceiveChangeEvent(_ changeEvent: RLMBSON) { - let bson = ObjectiveCSupport.convert(object: changeEvent) - proxyDelegate?.changeStreamDidReceive(changeEvent: bson) - } -} - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -extension Publishers { - private class WatchSubscription: RLMChangeEventDelegate, Subscription where S.Input == AnyBSON, S.Failure == Error { - private var changeStream: RLMChangeStream! - private var subscriber: S - private var onOpen: (@Sendable () -> Void)? - - init(publisher: __shared WatchPublisher, subscriber: S) { - self.subscriber = subscriber - self.onOpen = publisher.openEvent - let scheduler = publisher.scheduler - changeStream = publisher.collection.watch( - withMatchFilter: publisher.matchFilter.map(ObjectiveCSupport.convert) as RLMBSON?, - idFilter: publisher.filterIds as RLMBSON?, - delegate: self as RLMChangeEventDelegate, - scheduler: scheduler ?? DispatchQueue.main.schedule) as RLMChangeStream - } - - func request(_ demand: Subscribers.Demand) { } - - func cancel() { - changeStream.close() - } - - func changeStreamDidOpen(_ changeStream: RLMChangeStream) { - onOpen?() - } - - func changeStreamDidCloseWithError(_ error: Error?) { - if let error = error { - subscriber.receive(completion: .failure(error)) - } else { - subscriber.receive(completion: .finished) - } - } - - func changeStreamDidReceiveError(_ error: Error) { - subscriber.receive(completion: .failure(error)) - } - - func changeStreamDidReceiveChangeEvent(_ changeEvent: RLMBSON) { - if let changeEvent = ObjectiveCSupport.convert(object: changeEvent) { - _ = subscriber.receive(changeEvent) - } - } - } - - /// A publisher that emits a change event each time the remote MongoDB collection changes. - public struct WatchPublisher: Publisher { - public typealias Output = AnyBSON - public typealias Failure = Error - - fileprivate let collection: MongoCollection - fileprivate let scheduler: ((@escaping () -> Void) -> Void)? - fileprivate let filterIds: [ObjectId]? - fileprivate let matchFilter: Document? - fileprivate let openEvent: (@Sendable () -> Void)? - - init(collection: MongoCollection, - scheduler: ((@escaping () -> Void) -> Void)? = nil, - filterIds: [ObjectId]? = nil, - matchFilter: Document? = nil, - onOpen: (@Sendable () -> Void)? = nil) { - self.collection = collection - self.scheduler = scheduler - self.filterIds = filterIds - self.matchFilter = matchFilter - self.openEvent = onOpen - } - - /// Triggers an event when the watch change stream is opened. - /// - /// Use this function when you require a change stream to be open before you perform any work. - /// This should be called directly after invoking the publisher. - /// - /// - Parameter event: Callback which will be invoked once the change stream is open. - /// - Returns: A publisher that emits a change event each time the remote MongoDB collection changes. - public func onOpen(_ event: @escaping @Sendable () -> Void) -> Self { - Self(collection: collection, scheduler: scheduler, - filterIds: filterIds, matchFilter: matchFilter, onOpen: event) - } - - /// :nodoc: - public func receive(subscriber: S) where Failure == S.Failure, Output == S.Input { - let subscription = WatchSubscription(publisher: self, subscriber: subscriber) - subscriber.receive(subscription: subscription) - } - - /// Specifies the scheduler on which to perform subscribe, cancel, and request operations. - /// - /// - parameter scheduler: The scheduler to perform the subscription on. - /// - returns: A publisher which subscribes on the given scheduler. - public func subscribe(on scheduler: S) -> WatchPublisher { - return Self(collection: collection, - scheduler: { scheduler.schedule($0) }, - filterIds: filterIds, - matchFilter: matchFilter, - onOpen: openEvent) - } - } -} - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -extension MongoCollection { - /// Creates a publisher that emits a AnyBSON change event each time the MongoDB collection changes. - /// - /// - returns: A publisher that emits the AnyBSON change event each time the collection changes. - public func watch() -> Publishers.WatchPublisher { - Publishers.WatchPublisher(collection: self) - } - - /// Creates a publisher that emits a AnyBSON change event each time the MongoDB collection changes. - /// - /// - Parameter filterIds: The list of _ids in the collection to watch. - /// - Returns: A publisher that emits the AnyBSON change event each time the collection changes. - public func watch(filterIds: [ObjectId]) -> Publishers.WatchPublisher { - Publishers.WatchPublisher(collection: self, filterIds: filterIds) - } - - /// Creates a publisher that emits a AnyBSON change event each time the MongoDB collection changes. - /// - /// - Parameter matchFilter: The $match filter to apply to incoming change events. - /// - Returns: A publisher that emits the AnyBSON change event each time the collection changes. - public func watch(matchFilter: Document) -> Publishers.WatchPublisher { - Publishers.WatchPublisher(collection: self, matchFilter: matchFilter) - } -} - -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -extension MongoCollection { - /// An async sequence of AnyBSON values containing information about each - /// change to the MongoDB collection - public var changeEvents: AsyncThrowingPublisher { - Publishers.WatchPublisher(collection: self) - .subscribe(on: ImmediateScheduler.shared).values - } - - /// An async sequence of AnyBSON values containing information about each - /// change to the MongoDB collection - /// - parameter onOpen: A callback which is invoked when the watch stream - /// has initialized on the server. Server-side changes triggered before - /// this callback is invoked may not produce change events. - public func changeEvents(onOpen: @Sendable @escaping () -> Void) - -> AsyncThrowingPublisher { - Publishers.WatchPublisher(collection: self, onOpen: onOpen) - .subscribe(on: ImmediateScheduler.shared).values - } - - /// An async sequence of AnyBSON values containing information about each - /// change to objects with ids contained in `filterIds` within the the - /// MongoDB collection. - /// - parameter filterIds: Document ids which should produce change events - /// - parameter onOpen: An optional callback which is invoked when the - /// watch stream has initialized on the server. Server-side changes - /// triggered before this callback is invoked may not produce change - /// events. - public func changeEvents(filterIds: [ObjectId], onOpen: (@Sendable () -> Void)? = nil) - -> AsyncThrowingPublisher { - Publishers.WatchPublisher(collection: self, filterIds: filterIds, onOpen: onOpen) - .subscribe(on: ImmediateScheduler.shared).values - } - - /// An async sequence of AnyBSON values containing information about each - /// change to objects within the MongoDB collection matching the given - /// $match filter. - /// - parameter matchFilter: $match filter to filter the documents which - /// produce change events. - /// - parameter onOpen: An optional callback which is invoked when the - /// watch stream has initialized on the server. Server-side changes - /// triggered before this callback is invoked may not produce change - /// events. - public func changeEvents(matchFilter: Document, onOpen: (@Sendable () -> Void)? = nil) - -> AsyncThrowingPublisher { - Publishers.WatchPublisher(collection: self, matchFilter: matchFilter, onOpen: onOpen) - .subscribe(on: ImmediateScheduler.shared).values - } -} - -@available(macOS 10.15, watchOS 6.0, iOS 13.0, tvOS 13.0, macCatalyst 13.0, *) -@usableFromInline -internal func future(_ fn: @escaping (@escaping @Sendable (Result) -> Void) -> Void) -> Future { - return Future { promise in - // Future.Promise currently isn't marked as Sendable despite that being - // the whole point of Future - typealias SendablePromise = @Sendable (Result) -> Void - fn(unsafeBitCast(promise, to: SendablePromise.self)) - } -} - -@available(macOS 10.15, watchOS 6.0, iOS 13.0, tvOS 13.0, macCatalyst 13.0, *) -public extension MongoCollection { - /// Encodes the provided value to BSON and inserts it. If the value is missing an identifier, one will be - /// generated for it. - /// - parameter document: A `Document` value to insert. - /// - returns: A publisher that eventually return the object id of the inserted document or `Error`. - func insertOne(_ document: Document) -> Future { - return future { self.insertOne(document, $0) } - } - - /// Encodes the provided values to BSON and inserts them. If any values are missing identifiers, - /// they will be generated. - /// - parameter documents: The `Document` values in a bson array to insert. - /// - returns: A publisher that eventually return the object ids of inserted documents or `Error`. - func insertMany(_ documents: [Document]) -> Future<[AnyBSON], Error> { - return future { self.insertMany(documents, $0) } - } - - /// Finds the documents in this collection which match the provided filter. - /// - parameter filter: A `Document` as bson that should match the query. - /// - parameter options: `FindOptions` to use when executing the command. - /// - returns: A publisher that eventually return `[ObjectId]` of documents or `Error`. - func find(filter: Document, options: FindOptions) -> Future<[Document], Error> { - return future { self.find(filter: filter, options: options, $0) } - } - - /// Finds the documents in this collection which match the provided filter. - /// - parameter filter: A `Document` as bson that should match the query. - /// - returns: A publisher that eventually return `[ObjectId]` of documents or `Error`. - func find(filter: Document) -> Future<[Document], Error> { - return future { self.find(filter: filter, $0) } - } - - /// Returns one document from a collection or view which matches the - /// provided filter. If multiple documents satisfy the query, this method - /// returns the first document according to the query's sort order or natural - /// order. - /// - parameter filter: A `Document` as bson that should match the query. - /// - parameter options: `FindOptions` to use when executing the command. - /// - returns: A publisher that eventually return `Document` or `Error`. - func findOneDocument(filter: Document, options: FindOptions) -> Future { - return future { self.findOneDocument(filter: filter, $0) } - } - - /// Returns one document from a collection or view which matches the - /// provided filter. If multiple documents satisfy the query, this method - /// returns the first document according to the query's sort order or natural - /// order. - /// - parameter filter: A `Document` as bson that should match the query. - /// - returns: A publisher that eventually return `Document` or `nil` if document wasn't found or `Error`. - func findOneDocument(filter: Document) -> Future { - return future { self.findOneDocument(filter: filter, $0) } - } - - /// Runs an aggregation framework pipeline against this collection. - /// - parameter pipeline: A bson array made up of `Documents` containing the pipeline of aggregation operations to perform. - /// - returns: A publisher that eventually return `Document` or `Error`. - func aggregate(pipeline: [Document]) -> Future<[Document], Error> { - return future { self.aggregate(pipeline: pipeline, $0) } - } - - /// Counts the number of documents in this collection matching the provided filter. - /// - parameter filter: A `Document` as bson that should match the query. - /// - parameter limit: The max amount of documents to count - /// - returns: A publisher that eventually return `Int` count of documents or `Error`. - func count(filter: Document, limit: Int) -> Future { - return future { self.count(filter: filter, limit: limit, $0) } - } - - /// Counts the number of documents in this collection matching the provided filter. - /// - parameter filter: A `Document` as bson that should match the query. - /// - returns: A publisher that eventually return `Int` count of documents or `Error`. - func count(filter: Document) -> Future { - return future { self.count(filter: filter, $0) } - } - - /// Deletes a single matching document from the collection. - /// - parameter filter: A `Document` as bson that should match the query. - /// - returns: A publisher that eventually return `Int` count of deleted documents or `Error`. - func deleteOneDocument(filter: Document) -> Future { - return future { self.deleteOneDocument(filter: filter, $0) } - } - - /// Deletes multiple documents - /// - parameter filter: Document representing the match criteria - /// - returns: A publisher that eventually return `Int` count of deleted documents or `Error`. - func deleteManyDocuments(filter: Document) -> Future { - return future { self.deleteManyDocuments(filter: filter, $0) } - } - - /// Updates a single document matching the provided filter in this collection. - /// - parameter filter: A bson `Document` representing the match criteria. - /// - parameter update: A bson `Document` representing the update to be applied to a matching document. - /// - parameter upsert: When true, creates a new document if no document matches the query. - /// - returns: A publisher that eventually return `UpdateResult` or `Error`. - func updateOneDocument(filter: Document, update: Document, upsert: Bool) -> Future { - return future { self.updateOneDocument(filter: filter, update: update, upsert: upsert, $0) } - } - - /// Updates a single document matching the provided filter in this collection. - /// - parameter filter: A bson `Document` representing the match criteria. - /// - parameter update: A bson `Document` representing the update to be applied to a matching document. - /// - returns: A publisher that eventually return `UpdateResult` or `Error`. - func updateOneDocument(filter: Document, update: Document) -> Future { - return future { self.updateOneDocument(filter: filter, update: update, $0) } - } - - /// Updates multiple documents matching the provided filter in this collection. - /// - parameter filter: A bson `Document` representing the match criteria. - /// - parameter update: A bson `Document` representing the update to be applied to a matching document. - /// - parameter upsert: When true, creates a new document if no document matches the query. - /// - returns: A publisher that eventually return `UpdateResult` or `Error`. - func updateManyDocuments(filter: Document, update: Document, upsert: Bool) -> Future { - return future { self.updateManyDocuments(filter: filter, update: update, upsert: upsert, $0) } - } - - /// Updates multiple documents matching the provided filter in this collection. - /// - parameter filter: A bson `Document` representing the match criteria. - /// - parameter update: A bson `Document` representing the update to be applied to a matching document. - /// - returns: A publisher that eventually return `UpdateResult` or `Error`. - func updateManyDocuments(filter: Document, update: Document) -> Future { - return future { self.updateManyDocuments(filter: filter, update: update, $0) } - } - - /// Updates a single document in a collection based on a query filter and - /// returns the document in either its pre-update or post-update form. Unlike - /// `updateOneDocument`, this action allows you to atomically find, update, and - /// return a document with the same command. This avoids the risk of other - /// update operations changing the document between separate find and update - /// operations. - /// - parameter filter: A bson `Document` representing the match criteria. - /// - parameter update: A bson `Document` representing the update to be applied to a matching document. - /// - parameter options: `RemoteFindOneAndModifyOptions` to use when executing the command. - /// - returns: A publisher that eventually return `Document` or `nil` if document wasn't found or `Error`. - func findOneAndUpdate(filter: Document, update: Document, options: FindOneAndModifyOptions) -> Future { - return future { self.findOneAndUpdate(filter: filter, update: update, options: options, $0) } - } - - /// Updates a single document in a collection based on a query filter and - /// returns the document in either its pre-update or post-update form. Unlike - /// `updateOneDocument`, this action allows you to atomically find, update, and - /// return a document with the same command. This avoids the risk of other - /// update operations changing the document between separate find and update - /// operations. - /// - parameter filter: A bson `Document` representing the match criteria. - /// - parameter update: A bson `Document` representing the update to be applied to a matching document. - /// - returns: A publisher that eventually return `Document` or `nil` if document wasn't found or `Error`. - func findOneAndUpdate(filter: Document, update: Document) -> Future { - return future { self.findOneAndUpdate(filter: filter, update: update, $0) } - } - - /// Overwrites a single document in a collection based on a query filter and - /// returns the document in either its pre-replacement or post-replacement - /// form. Unlike `updateOneDocument`, this action allows you to atomically find, - /// replace, and return a document with the same command. This avoids the - /// risk of other update operations changing the document between separate - /// find and update operations. - /// - parameter filter: A `Document` that should match the query. - /// - parameter replacement: A `Document` describing the replacement. - /// - parameter options: `FindOneAndModifyOptions` to use when executing the command. - /// - returns: A publisher that eventually return `Document` or `nil` if document wasn't found or `Error`. - func findOneAndReplace(filter: Document, replacement: Document, options: FindOneAndModifyOptions) -> Future { - return future { self.findOneAndReplace(filter: filter, replacement: replacement, options: options, $0) } - } - - /// Overwrites a single document in a collection based on a query filter and - /// returns the document in either its pre-replacement or post-replacement - /// form. Unlike `updateOneDocument`, this action allows you to atomically find, - /// replace, and return a document with the same command. This avoids the - /// risk of other update operations changing the document between separate - /// find and update operations. - /// - parameter filter: A `Document` that should match the query. - /// - parameter replacement: A `Document` describing the replacement. - /// - returns: A publisher that eventually return `Document` or `nil` if document wasn't found or `Error`. - func findOneAndReplace(filter: Document, replacement: Document) -> Future { - return future { self.findOneAndReplace(filter: filter, replacement: replacement, $0) } - } - - /// Removes a single document from a collection based on a query filter and - /// returns a document with the same form as the document immediately before - /// it was deleted. Unlike `deleteOneDocument`, this action allows you to atomically - /// find and delete a document with the same command. This avoids the risk of - /// other update operations changing the document between separate find and - /// delete operations. - /// - parameter filter: A `Document` that should match the query. - /// - parameter options: `FindOneAndModifyOptions` to use when executing the command. - /// - returns: A publisher that eventually return `Document` or `nil` if document wasn't found or `Error`. - func findOneAndDelete(filter: Document, options: FindOneAndModifyOptions) -> Future { - return future { self.findOneAndDelete(filter: filter, options: options, $0) } - } - - /// Removes a single document from a collection based on a query filter and - /// returns a document with the same form as the document immediately before - /// it was deleted. Unlike `deleteOneDocument`, this action allows you to atomically - /// find and delete a document with the same command. This avoids the risk of - /// other update operations changing the document between separate find and - /// delete operations. - /// - /// - parameter filter: A `Document` that should match the query. - /// - returns: A publisher that eventually return `Document` or `nil` if document wasn't found or `Error`. - func findOneAndDelete(filter: Document) -> Future { - return future { self.findOneAndDelete(filter: filter, $0) } - } -} diff --git a/RealmSwift/Nonsync.swift b/RealmSwift/Nonsync.swift deleted file mode 100644 index 9e764663cb..0000000000 --- a/RealmSwift/Nonsync.swift +++ /dev/null @@ -1,46 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import Realm.Private - -public struct SyncConfiguration { - func asConfig() -> RLMSyncConfiguration? { return nil } - init?(config: RLMSyncConfiguration) { return nil } -} - -public struct SyncSession { - public struct Progress { - public let transferredBytes: Int - public let transferrableBytes: Int - public var fractionTransferred: Double { - if transferrableBytes == 0 { - return 1 - } - let percentage = Double(transferredBytes) / Double(transferrableBytes) - return percentage > 1 ? 1 : percentage - } - public var isTransferComplete: Bool { - return transferredBytes >= transferrableBytes - } - - internal init(transferred: UInt, transferrable: UInt) { - transferredBytes = Int(transferred) - transferrableBytes = Int(transferrable) - } - } -} diff --git a/RealmSwift/ObjectiveCSupport+BSON.swift b/RealmSwift/ObjectiveCSupport+BSON.swift deleted file mode 100644 index 1da6275da0..0000000000 --- a/RealmSwift/ObjectiveCSupport+BSON.swift +++ /dev/null @@ -1,184 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import Realm - -/** - :nodoc: - **/ -public extension ObjectiveCSupport { - // FIXME: remove these and rename convertBson to convert on the next major - // version bump - static func convert(object: AnyBSON?) -> RLMBSON? { - if let converted = object.map(self.convertBson), !(converted is NSNull) { - return converted - } - return nil - } - - static func convert(object: RLMBSON?) -> AnyBSON? { - if let object = object { - let converted = convertBson(object: object) - if converted == .null { - return nil - } - return converted - } - return nil - } - - static func convert(_ object: Document) -> [String: RLMBSON] { - object.reduce(into: Dictionary()) { (result: inout [String: RLMBSON], kvp) in - result[kvp.key] = kvp.value.map(convertBson) ?? NSNull() - } - } - - /// Convert an `AnyBSON` to a `RLMBSON`. - static func convertBson(object: AnyBSON) -> RLMBSON { - switch object { - case .int32(let val): - return val as NSNumber - case .int64(let val): - return val as NSNumber - case .double(let val): - return val as NSNumber - case .string(let val): - return val as NSString - case .binary(let val): - return val as NSData - case .datetime(let val): - return val as NSDate - case .timestamp(let val): - return val as NSDate - case .decimal128(let val): - return val as RLMDecimal128 - case .objectId(let val): - return val as RLMObjectId - case .document(let val): - return convert(val) as NSDictionary - case .array(let val): - return val.map { $0.map(convertBson) } as NSArray - case .maxKey: - return MaxKey() - case .minKey: - return MinKey() - case .regex(let val): - return val - case .bool(let val): - return val as NSNumber - case .uuid(let val): - return val as NSUUID - case .null: - return NSNull() - } - } - - static func convert(_ object: [String: RLMBSON]) -> Document { - object.mapValues { convert(object: $0) } - } - - /// Convert a `RLMBSON` to an `AnyBSON`. - static func convertBson(object bson: RLMBSON) -> AnyBSON? { - switch bson.__bsonType { - case .null: - return .null - case .int32: - guard let val = bson as? NSNumber else { - return nil - } - return .int32(Int32(val.intValue)) - case .int64: - guard let val = bson as? NSNumber else { - return nil - } - return .int64(Int64(val.int64Value)) - case .bool: - guard let val = bson as? NSNumber else { - return nil - } - return .bool(val.boolValue) - case .double: - guard let val = bson as? NSNumber else { - return nil - } - return .double(val.doubleValue) - case .string: - guard let val = bson as? NSString else { - return nil - } - return .string(val as String) - case .binary: - guard let val = bson as? NSData else { - return nil - } - return .binary(val as Data) - case .timestamp: - guard let val = bson as? NSDate else { - return nil - } - return .timestamp(val as Date) - case .datetime: - guard let val = bson as? NSDate else { - return nil - } - return .datetime(val as Date) - case .objectId: - guard let val = bson as? RLMObjectId, - let oid = try? ObjectId(string: val.stringValue) else { - return nil - } - return .objectId(oid) - case .decimal128: - guard let val = bson as? RLMDecimal128 else { - return nil - } - return .decimal128(Decimal128(stringLiteral: val.stringValue)) - case .regularExpression: - guard let val = bson as? NSRegularExpression else { - return nil - } - return .regex(val) - case .maxKey: - return .maxKey - case .minKey: - return .minKey - case .document: - guard let val = bson as? Dictionary else { - return nil - } - return .document(convert(val)) - case .array: - guard let val = bson as? Array else { - return nil - } - return .array(val.compactMap { - if let value = $0 { - return convertBson(object: value) - } - return .null - }.map { (v: AnyBSON) -> AnyBSON? in v == .null ? nil : v }) - case .UUID: - guard let val = bson as? NSUUID else { - return nil - } - return .uuid(val as UUID) - default: - return nil - } - } -} diff --git a/RealmSwift/ObjectiveCSupport+Sync.swift b/RealmSwift/ObjectiveCSupport+Sync.swift deleted file mode 100644 index 558294e04f..0000000000 --- a/RealmSwift/ObjectiveCSupport+Sync.swift +++ /dev/null @@ -1,60 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2015 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. -// -//////////////////////////////////////////////////////////////////////////// - -import Realm - -/** - :nodoc: - **/ -public extension ObjectiveCSupport { - /// Convert a `SyncConfiguration` to a `RLMSyncConfiguration`. - static func convert(object: SyncConfiguration) -> RLMSyncConfiguration { - return object.config - } - - /// Convert a `RLMSyncConfiguration` to a `SyncConfiguration`. - static func convert(object: RLMSyncConfiguration) -> SyncConfiguration { - return SyncConfiguration(config: object) - } - - /// Convert a `Credentials` to a `RLMCredentials` - static func convert(object: Credentials) -> RLMCredentials { - switch object { - case .facebook(let accessToken): - return RLMCredentials(facebookToken: accessToken) - case .google(let serverAuthCode): - return RLMCredentials(googleAuthCode: serverAuthCode) - case .googleId(let token): - return RLMCredentials(googleIdToken: token) - case .apple(let idToken): - return RLMCredentials(appleToken: idToken) - case .emailPassword(let email, let password): - return RLMCredentials(email: email, password: password) - case .jwt(let token): - return RLMCredentials(jwt: token) - case .function(let payload): - return RLMCredentials(functionPayload: ObjectiveCSupport.convert(object: AnyBSON(payload)) as! [String: RLMBSON]) - case .userAPIKey(let APIKey): - return RLMCredentials(userAPIKey: APIKey) - case .serverAPIKey(let serverAPIKey): - return RLMCredentials(serverAPIKey: serverAPIKey) - case .anonymous: - return RLMCredentials.anonymous() - } - } -} diff --git a/RealmSwift/ObjectiveCSupport.swift b/RealmSwift/ObjectiveCSupport.swift index 054b171203..21feb1fd15 100644 --- a/RealmSwift/ObjectiveCSupport.swift +++ b/RealmSwift/ObjectiveCSupport.swift @@ -161,75 +161,4 @@ import Realm return object(Int(totalBytes), Int(usedBytes)) } } - - /// Convert a RealmSwift before block to an RLMClientResetBeforeBlock - @preconcurrency - public static func convert(object: (@Sendable (Realm) -> Void)?) -> RLMClientResetBeforeBlock? { - guard let object = object else { - return nil - } - return { localRealm in - return object(Realm(localRealm)) - } - } - - /// Convert an RLMClientResetBeforeBlock to a RealmSwift before block - @preconcurrency - public static func convert(object: RLMClientResetBeforeBlock?) -> (@Sendable (Realm) -> Void)? { - guard let object = object else { - return nil - } - return { localRealm in - return object(localRealm.rlmRealm) - } - } - - /// Convert a RealmSwift after block to an RLMClientResetAfterBlock - @preconcurrency - public static func convert(object: (@Sendable (Realm, Realm) -> Void)?) -> RLMClientResetAfterBlock? { - guard let object = object else { - return nil - } - return { localRealm, remoteRealm in - return object(Realm(localRealm), Realm(remoteRealm)) - } - } - - /// Convert an RLMClientResetAfterBlock to a RealmSwift after block - @preconcurrency - public static func convert(object: RLMClientResetAfterBlock?) -> (@Sendable (Realm, Realm) -> Void)? { - guard let object = object else { - return nil - } - return { localRealm, remoteRealm in - return object(localRealm.rlmRealm, remoteRealm.rlmRealm) - } - } - - /// Converts a swift block receiving a `SyncSubscriptionSet`to a RLMFlexibleSyncInitialSubscriptionsBlock receiving a `RLMSyncSubscriptionSet`. - @preconcurrency - public static func convert(block: @escaping @Sendable (SyncSubscriptionSet) -> Void) -> RLMFlexibleSyncInitialSubscriptionsBlock { - return { subscriptionSet in - return block(SyncSubscriptionSet(subscriptionSet)) - } - } - - /// Converts a block receiving a `RLMSyncSubscriptionSet`to a swift block receiving a `SyncSubscriptionSet`. - @preconcurrency - public static func convert(block: RLMFlexibleSyncInitialSubscriptionsBlock?) -> (@Sendable (SyncSubscriptionSet) -> Void)? { - guard let block = block else { - return nil - } - return { subscriptionSet in - return block(subscriptionSet.rlmSyncSubscriptionSet) - } - } - - /// Converts a block receiving a `RLMSyncSubscriptionSet`to a swift block receiving a `SyncSubscriptionSet`. - @preconcurrency - public static func convert(block: @escaping RLMFlexibleSyncInitialSubscriptionsBlock) -> @Sendable (SyncSubscriptionSet) -> Void { - return { subscriptionSet in - return block(subscriptionSet.rlmSyncSubscriptionSet) - } - } } diff --git a/RealmSwift/PersistedProperty.swift b/RealmSwift/PersistedProperty.swift index de5da25cdc..f353646c35 100644 --- a/RealmSwift/PersistedProperty.swift +++ b/RealmSwift/PersistedProperty.swift @@ -69,8 +69,7 @@ import Realm.Private /// to the initializer. Compound primary keys are not supported, and setting /// more than one property as the primary key will throw an exception at /// runtime. Only Int, String, UUID and ObjectID properties can be made the -/// primary key, and when using Atlas App Services, the primary key must be named -/// `_id`. The primary key property can only be mutated on unmanaged objects, +/// primary key. The primary key property can only be mutated on unmanaged objects, /// and mutating it on an object which has been added to a Realm will throw an /// exception. /// diff --git a/RealmSwift/Projection.swift b/RealmSwift/Projection.swift index f3e514ab78..e00a20ed75 100644 --- a/RealmSwift/Projection.swift +++ b/RealmSwift/Projection.swift @@ -305,12 +305,7 @@ extension ProjectionObservable { public func observe(keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ block: @escaping (ObjectChange) -> Void) -> NotificationToken { - var kps: [String] = schema.map(\.originPropertyKeyPathString) - - // NEXT-MAJOR: stop conflating empty array and nil - if keyPaths?.isEmpty == false { - kps = kps.filter { keyPaths!.contains($0) } - } + let kps: [String] = schema.map(\.originPropertyKeyPathString) // If we're observing on a different queue, we need a projection which // wraps an object confined to that queue. We'll lazily create it the diff --git a/RealmSwift/Realm.swift b/RealmSwift/Realm.swift index c41a038acd..96940827bf 100644 --- a/RealmSwift/Realm.swift +++ b/RealmSwift/Realm.swift @@ -151,28 +151,6 @@ public typealias AsyncTransactionId = RLMAsyncTransactionId })) } - /** - Asynchronously open a Realm and deliver it to a block on the given queue. - - Opening a Realm asynchronously will perform all work needed to get the Realm to - a usable state (such as running potentially time-consuming migrations) on a - background thread before dispatching to the given queue. In addition, - synchronized Realms wait for all remote content available at the time the - operation began to be downloaded and available locally. - - The Realm passed to the publisher is confined to the callback - queue as if `Realm(configuration:queue:)` was used. - - - parameter configuration: A configuration object to use when opening the Realm. - - parameter callbackQueue: The dispatch queue on which the AsyncOpenTask should be run. - - returns: A publisher. If the Realm was successfully opened, it will be received by the subscribers. - Otherwise, a `Swift.Error` describing what went wrong will be passed upstream instead. - */ - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - public static func asyncOpen(configuration: Realm.Configuration = .defaultConfiguration) -> RealmPublishers.AsyncOpenPublisher { - return RealmPublishers.AsyncOpenPublisher(configuration: configuration) - } - /** A task object which can be used to observe or cancel an async open. @@ -195,23 +173,6 @@ public typealias AsyncTransactionId = RLMAsyncTransactionId */ public func cancel() { rlmTask.cancel() } - /** - Register a progress notification block. - - Each registered progress notification block is called whenever the sync - subsystem has new progress data to report until the task is either cancelled - or the completion callback is called. Progress notifications are delivered on - the supplied queue. - - - parameter queue: The queue to deliver progress notifications on. - - parameter block: The block to invoke when notifications are available. - */ - public func addProgressNotification(queue: DispatchQueue = .main, - block: @escaping (SyncSession.Progress) -> Void) { - rlmTask.addSyncProgressNotification(on: queue) { progress in - block(SyncSession.Progress(transferred: progress.transferredBytes, transferrable: progress.transferrableBytes, estimate: progress.progressEstimate)) - } - } } // MARK: Transactions @@ -1090,43 +1051,6 @@ public typealias AsyncTransactionId = RLMAsyncTransactionId } } -// MARK: Sync Subscriptions - -extension Realm { - /** - Returns an instance of `SyncSubscriptionSet`, representing the active subscriptions - for this realm, which can be used to add/remove/update and search flexible sync subscriptions. - Getting the subscriptions from a local or partition-based configured realm will thrown an exception. - - - returns: A `SyncSubscriptionSet`. - - Warning: This feature is currently in beta and its API is subject to change. - */ - public var subscriptions: SyncSubscriptionSet { - return SyncSubscriptionSet(rlmRealm.subscriptions) - } -} - -// MARK: Asymmetric Sync - -extension Realm { - /** - Creates an Asymmetric object, which will be synced unidirectionally and - cannot be queried locally. Only objects which inherit from `AsymmetricObject` - can be created using this method. - - Objects created using this method will not be added to the Realm. - - - warning: This method may only be called during a write transaction. - - - parameter type: The type of the object to create. - - parameter value: The value used to populate the object. - */ - public func create(_ type: T.Type, value: Any = [String: Any]()) { - let typeName = (type as AsymmetricObject.Type).className() - RLMCreateAsymmetricObjectInRealm(rlmRealm, typeName, value) - } -} - // MARK: Equatable extension Realm: Equatable { @@ -1168,46 +1092,8 @@ extension Realm { /// The type of a block to run for notification purposes when the data in a Realm is modified. public typealias NotificationBlock = (_ notification: Realm.Notification, _ realm: Realm) -> Void -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -private func shouldAsyncOpen(_ configuration: Realm.Configuration, - _ downloadBeforeOpen: Realm.OpenBehavior) -> Bool { - switch downloadBeforeOpen { - case .never: - return false - case .once: - return !Realm.fileExists(for: configuration) - case .always: - return true - } -} - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Realm { - /// Options for when to download all data from the server before opening - /// a synchronized Realm. - @frozen public enum OpenBehavior: Sendable { - /// Immediately return the Realm as if the synchronous initializer was - /// used. If this is the first time that the Realm has been opened on - /// this device, the Realm file will initially be empty. Synchronized - /// Realms will contact the server and download new data in the - /// background. - case never - /// Always open the Realm asynchronously and download all data from the - /// server before returning the Realm. This mode will fail to open the - /// Realm if the device is currently offline. - case always - /// Open the Realm asynchronously the first time it is opened on the - /// current device, and then synchronously afterwards. This mode is - /// suitable if you wish to wait to download the server-side data the - /// first time your app is launched on each device, but afterwards - /// support offline launches using the existing local data. - /// - /// Note that if .once is used multiple times simultaneously then calls - /// after the first may see partial local data from the first call and - /// not wait for the download. - case once - } - /** Obtains a `Realm` instance with the given configuration, possibly asynchronously. By default this simply returns the Realm instance exactly as if the @@ -1217,17 +1103,15 @@ extension Realm { will be run in the background, and for synchronized Realms all data will be downloaded from the server before the Realm is returned. - parameter configuration: A configuration object to use when opening the Realm. - - parameter downloadBeforeOpen: When opening the Realm should first download all data from the server. - throws: An `NSError` if the Realm could not be initialized. - returns: An open Realm. */ @MainActor - public init(configuration: Realm.Configuration = .defaultConfiguration, - downloadBeforeOpen: OpenBehavior = .never) async throws { + public init(configuration: Realm.Configuration = .defaultConfiguration) async throws { let scheduler = RLMScheduler.dispatchQueue(.main) let rlmRealm = try await openRealm(configuration: configuration, scheduler: scheduler, - actor: MainActor.shared, downloadBeforeOpen: downloadBeforeOpen) + actor: MainActor.shared) self = Realm(rlmRealm.wrappedValue) } @@ -1255,18 +1139,14 @@ extension Realm { either a local actor or a global actor. The calling function does not need to be isolated to the actor passed in, but if it is not it will not be able to use the returned Realm. - - parameter downloadBeforeOpen: When opening the Realm should first download - all data from the server. - throws: An `NSError` if the Realm could not be initialized. `CancellationError` if the task is cancelled. - returns: An open Realm. */ public init(configuration: Realm.Configuration = .defaultConfiguration, - actor: A, - downloadBeforeOpen: OpenBehavior = .never) async throws { + actor: A) async throws { let scheduler = RLMScheduler.actor(actor, invoke: actor.invoke, verify: await actor.verifier()) - let rlmRealm = try await openRealm(configuration: configuration, scheduler: scheduler, - actor: actor, downloadBeforeOpen: downloadBeforeOpen) + let rlmRealm = try await openRealm(configuration: configuration, scheduler: scheduler, actor: actor) self = Realm(rlmRealm.wrappedValue) } @@ -1293,11 +1173,9 @@ extension Realm { - returns: An open Realm. */ public static func open(configuration: Realm.Configuration = .defaultConfiguration, - _isolation actor: isolated any Actor = #isolation, - downloadBeforeOpen: OpenBehavior = .never) async throws -> Realm { + _isolation actor: isolated any Actor = #isolation) async throws -> Realm { let scheduler = RLMScheduler.actor(actor, invoke: actor.invoke, verify: actor.verifier()) - let rlmRealm = try await openRealm(configuration: configuration, scheduler: scheduler, - actor: actor, downloadBeforeOpen: downloadBeforeOpen) + let rlmRealm = try await openRealm(configuration: configuration, scheduler: scheduler, actor: actor) return Realm(rlmRealm.wrappedValue) } #endif @@ -1534,8 +1412,7 @@ extension Realm { @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) private func openRealm(configuration: Realm.Configuration, scheduler: RLMScheduler, - actor: isolated A, - downloadBeforeOpen: Realm.OpenBehavior + actor: isolated A ) async throws -> Unchecked { let scheduler = RLMScheduler.actor(actor, invoke: actor.invoke, verify: actor.verifier()) let rlmConfiguration = configuration.rlmConfiguration @@ -1551,19 +1428,12 @@ private func openRealm(configuration: Realm.Configuration, } } if let realm = realm { - // This can't be hit on the first open so .once == .never - if downloadBeforeOpen == .always { - let task = RLMAsyncDownloadTask(realm: realm) - try await task.waitWithCancellationHandler() - } return Unchecked(realm) } // We're doing the first open and hitting the expensive path, so do an async // open on a background thread - let task = RLMAsyncOpenTask(configuration: rlmConfiguration, confinedTo: scheduler, - download: shouldAsyncOpen(configuration, downloadBeforeOpen)) - // progress notifications? + let task = RLMAsyncOpenTask(configuration: rlmConfiguration, confinedTo: scheduler) do { try await task.waitWithCancellationHandler() let realm = task.localRealm! @@ -1602,7 +1472,6 @@ extension TaskWithCancellation { } } extension RLMAsyncOpenTask: TaskWithCancellation {} -extension RLMAsyncDownloadTask: TaskWithCancellation {} @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) internal extension Actor { diff --git a/RealmSwift/RealmConfiguration.swift b/RealmSwift/RealmConfiguration.swift index 7c1b5c03f0..bb9413dd81 100644 --- a/RealmSwift/RealmConfiguration.swift +++ b/RealmSwift/RealmConfiguration.swift @@ -53,11 +53,9 @@ extension Realm { - note: The `fileURL`, and `inMemoryIdentifier`, parameters are mutually exclusive. Only set one of them, or none if you wish to use the default file URL. - Synced Realms will set a unique file path unless is an in-memory realm. - parameter fileURL: The local URL to the Realm file. - parameter inMemoryIdentifier: A string used to identify a particular in-memory Realm. - - parameter syncConfiguration: For Realms intended to sync with Atlas App Services, a sync configuration. - parameter encryptionKey: An optional 64-byte key to use to encrypt the data. - parameter readOnly: Whether the Realm is read-only (must be true for read-only files). - parameter schemaVersion: The current schema version. @@ -78,7 +76,6 @@ extension Realm { @preconcurrency public init(fileURL: URL? = URL(fileURLWithPath: RLMRealmPathForFile("default.realm"), isDirectory: false), inMemoryIdentifier: String? = nil, - syncConfiguration: SyncConfiguration? = nil, encryptionKey: Data? = nil, readOnly: Bool = false, schemaVersion: UInt64 = 0, @@ -91,9 +88,6 @@ extension Realm { if let inMemoryIdentifier = inMemoryIdentifier { self.inMemoryIdentifier = inMemoryIdentifier } - if let syncConfiguration = syncConfiguration { - self.syncConfiguration = syncConfiguration - } self.encryptionKey = encryptionKey self.readOnly = readOnly self.schemaVersion = schemaVersion @@ -106,20 +100,6 @@ extension Realm { // MARK: Configuration Properties - /** - A configuration value used to configure a Realm for synchronization with Atlas App Services. - */ - public var syncConfiguration: SyncConfiguration? { - get { - return _syncConfiguration - } - set { - _syncConfiguration = newValue - } - } - - private var _syncConfiguration: SyncConfiguration? - /// The local URL of the Realm file. Mutually exclusive with `inMemoryIdentifier`. public var fileURL: URL? { didSet { @@ -146,21 +126,12 @@ extension Realm { /** Whether to open the Realm in read-only mode. - For non-synchronized Realms, this is required to be able to open Realm files which are not + This is required to be able to open Realm files which are not writeable or are in a directory which is not writeable. This should only be used on files which will not be modified by anyone while they are open, and not just to get a read-only view of a file which may be written to by another thread or process. Opening in read-only mode requires disabling Realm's reader/writer coordination, so committing a write transaction from another process will result in crashes. - - Synchronized Realms must always be writeable (as otherwise no synchronization could happen), - and this instead merely disallows performing write transactions on the Realm. In addition, - it will skip some automatic writes made to the Realm, such as to initialize the Realm's - schema. Setting `readOnly = YES` is not strictly required for Realms which the sync user - does not have write access to, but is highly recommended as it will improve error reporting - and catch some errors earlier. - - Realms using query-based sync cannot be opened in read-only mode. */ public var readOnly: Bool = false @@ -179,19 +150,7 @@ extension Realm { - note: Setting this property to `true` doesn't disable file format migrations. */ - public var deleteRealmIfMigrationNeeded: Bool { - get { - return _deleteRealmIfMigrationNeeded - } - set(newValue) { - if newValue && syncConfiguration != nil { - throwRealmException("Cannot set 'deleteRealmIfMigrationNeeded' when sync is enabled ('syncConfig' is set).") - } - _deleteRealmIfMigrationNeeded = newValue - } - } - - private var _deleteRealmIfMigrationNeeded: Bool = false + public var deleteRealmIfMigrationNeeded: Bool = false /** A block called when opening a Realm for the first time during the @@ -247,21 +206,11 @@ extension Realm { If a realm file already exists at the configurations's destination path, the seed file will not be copied and the already existing realm will be opened instead. - Note that to use this parameter with a synced Realm configuration - the seed Realm must be appropriately copied to a destination with - `Realm.writeCopy(configuration:)` first. - This option is mutually exclusive with `inMemoryIdentifier`. Setting a `seedFilePath` will nil out the `inMemoryIdentifier`. */ public var seedFilePath: URL? - /** - Configuration for Realm event recording. Events are enabled if this is set - to a non-nil value. - */ - public var eventConfiguration: EventConfiguration? - /// A custom schema to use for the Realm. private var customSchema: RLMSchema? @@ -272,14 +221,11 @@ extension Realm { internal var rlmConfiguration: RLMRealmConfiguration { let configuration = RLMRealmConfiguration() - if let syncConfiguration = syncConfiguration { - configuration.syncConfiguration = syncConfiguration.config - } if let fileURL = fileURL { configuration.fileURL = fileURL } else if let inMemoryIdentifier = inMemoryIdentifier { configuration.inMemoryIdentifier = inMemoryIdentifier - } else if syncConfiguration == nil { + } else { fatalError("A Realm Configuration must specify a path or an in-memory identifier.") } configuration.seedFilePath = self.seedFilePath @@ -293,16 +239,6 @@ extension Realm { configuration.setCustomSchemaWithoutCopying(self.customSchema) configuration.disableFormatUpgrade = self.disableFormatUpgrade configuration.maximumNumberOfActiveVersions = self.maximumNumberOfActiveVersions ?? 0 - if let eventConfiguration = eventConfiguration { - let rlmConfig = RLMEventConfiguration() - rlmConfig.partitionPrefix = eventConfiguration.partitionPrefix - rlmConfig.syncUser = eventConfiguration.syncUser - rlmConfig.metadata = eventConfiguration.metadata - rlmConfig.logger = eventConfiguration.logger - rlmConfig.errorHandler = eventConfiguration.errorHandler - configuration.eventConfiguration = rlmConfig - } - return configuration } @@ -310,7 +246,6 @@ extension Realm { var configuration = Configuration() configuration.fileURL = rlmConfiguration.fileURL configuration._inMemoryIdentifier = rlmConfiguration.inMemoryIdentifier - configuration._syncConfiguration = rlmConfiguration.syncConfiguration.map(SyncConfiguration.init(config:)) configuration.encryptionKey = rlmConfiguration.encryptionKey configuration.readOnly = rlmConfiguration.readOnly configuration.schemaVersion = rlmConfiguration.schemaVersion @@ -320,15 +255,7 @@ extension Realm { configuration.customSchema = rlmConfiguration.customSchema configuration.disableFormatUpgrade = rlmConfiguration.disableFormatUpgrade configuration.maximumNumberOfActiveVersions = rlmConfiguration.maximumNumberOfActiveVersions - if let eventConfiguration = rlmConfiguration.eventConfiguration { - configuration.eventConfiguration = EventConfiguration(metadata: eventConfiguration.metadata, - syncUser: eventConfiguration.syncUser, - partitionPrefix: eventConfiguration.partitionPrefix, - errorHandler: eventConfiguration.errorHandler) - } - configuration.seedFilePath = rlmConfiguration.seedFilePath - return configuration } } @@ -351,7 +278,6 @@ extension Realm.Configuration: Equatable { public static func == (lhs: Realm.Configuration, rhs: Realm.Configuration) -> Bool { lhs.encryptionKey == rhs.encryptionKey && lhs.fileURL == rhs.fileURL && - lhs.syncConfiguration?.partitionValue == rhs.syncConfiguration?.partitionValue && lhs.inMemoryIdentifier == rhs.inMemoryIdentifier && lhs.readOnly == rhs.readOnly && lhs.schemaVersion == rhs.schemaVersion diff --git a/RealmSwift/Results.swift b/RealmSwift/Results.swift index 68ad91c000..3c016ae847 100644 --- a/RealmSwift/Results.swift +++ b/RealmSwift/Results.swift @@ -146,145 +146,6 @@ extension Projection: KeypathSortable {} public func makeIterator() -> RLMIterator { return RLMIterator(collection: collection) } - - // MARK: Flexible Sync - -#if compiler(<6) - /** - Creates a SyncSubscription matching the Results' local query. - After committing the subscription to the realm's local subscription set, the method - will wait for downloads according to `WaitForSyncMode`. - - ### Unnamed subscriptions ### - If `.subscribe()` is called without a name whose query matches an unnamed subscription, another subscription is not created. - - If `.subscribe()` is called without a name whose query matches a named subscription, an additional unnamed subscription is created. - ### Named Subscriptions ### - If `.subscribe()` is called with a name whose query matches an unnamed subscription, an additional named subscription is created. - ### Existing name and query ### - If `.subscribe()` is called with a name whose name is taken on a different query, the old subscription is updated with the new query. - - If `.subscribe()` is called with a name that's in already in use by an identical query, no new subscription is created. - - - - Note: This method will wait for all data to be downloaded before returning when `WaitForSyncMode.always` and `.onCreation` (when the subscription is first created) is used. This requires an internet connection if no timeout is set. - - - Note: This method opens a update transaction that creates or updates a subscription. - It's advised to *not* loop over this method in order to create multiple subscriptions. - This could create a performance bottleneck by opening multiple unnecessary update transactions. - To create multiple subscriptions at once use `SyncSubscription.update`. - - - parameter name: The name applied to the subscription - - parameter waitForSync: ``WaitForSyncMode`` Determines the download behavior for the subscription. Defaults to `.onCreation`. - - parameter timeout: An optional client timeout. The client will cancel waiting for subscription downloads after this time has elapsed. Reaching this timeout doesn't imply a server error. - - returns: Returns `self`. - - - warning: This function is only supported for main thread and - actor-isolated Realms. - - warning: This API is currently in `Preview` and may be subject to changes in the future. - */ - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - @_unsafeInheritExecutor - public func subscribe(name: String? = nil, waitForSync: WaitForSyncMode = .onCreation, timeout: TimeInterval? = nil) async throws -> Results { - guard let actor = realm?.rlmRealm.actor as? Actor else { - fatalError("`subscribe` can only be called on main thread or actor-isolated Realms") - } - - var rlmResults = ObjectiveCSupport.convert(object: self) - let scheduler = await RLMScheduler.actor(actor, invoke: actor.invoke, verify: actor.verifier()) - rlmResults = try await rlmResults.subscribe(withName: name, waitForSync: waitForSync, confinedTo: scheduler, timeout: timeout ?? 0) - return self - } -#else - /** - Creates a SyncSubscription matching the Results' local query. - After committing the subscription to the realm's local subscription set, the method - will wait for downloads according to `WaitForSyncMode`. - - ### Unnamed subscriptions ### - If `.subscribe()` is called without a name whose query matches an unnamed subscription, another subscription is not created. - - If `.subscribe()` is called without a name whose query matches a named subscription, an additional unnamed subscription is created. - ### Named Subscriptions ### - If `.subscribe()` is called with a name whose query matches an unnamed subscription, an additional named subscription is created. - ### Existing name and query ### - If `.subscribe()` is called with a name whose name is taken on a different query, the old subscription is updated with the new query. - - If `.subscribe()` is called with a name that's in already in use by an identical query, no new subscription is created. - - - - Note: This method will wait for all data to be downloaded before returning when `WaitForSyncMode.always` and `.onCreation` (when the subscription is first created) is used. This requires an internet connection if no timeout is set. - - - Note: This method opens a update transaction that creates or updates a subscription. - It's advised to *not* loop over this method in order to create multiple subscriptions. - This could create a performance bottleneck by opening multiple unnecessary update transactions. - To create multiple subscriptions at once use `SyncSubscription.update`. - - - parameter name: The name applied to the subscription - - parameter waitForSync: ``WaitForSyncMode`` Determines the download behavior for the subscription. Defaults to `.onCreation`. - - parameter timeout: An optional client timeout. The client will cancel waiting for subscription downloads after this time has elapsed. Reaching this timeout doesn't imply a server error. - - returns: Returns `self`. - - - warning: This function is only supported for main thread and - actor-isolated Realms. - */ - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - public func subscribe( - name: String? = nil, - waitForSync: WaitForSyncMode = .onCreation, - timeout: TimeInterval? = nil, - _isolation: isolated any Actor = #isolation - ) async throws -> Results { - guard let actor = realm?.rlmRealm.actor as? Actor else { - fatalError("`subscribe` can only be called on main thread or actor-isolated Realms") - } - - let rlmResults = ObjectiveCSupport.convert(object: self) - let scheduler = await RLMScheduler.actor(actor, invoke: actor.invoke, verify: actor.verifier()) - _ = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - rlmResults.subscribe(withName: name, waitForSync: waitForSync, confinedTo: scheduler, timeout: timeout ?? 0) { _, error in - if let error { - continuation.resume(throwing: error) - } else { - continuation.resume() - } - } - } - return self - } -#endif - - /** - Removes a SyncSubscription matching the Results' local filter. - - The method returns after committing the subscription removal to the realm's - local subscription set. Calling this method will not wait for objects to - be removed from the realm. - - In order for a named subscription to be removed, the Results - must have previously created the subscription. For example: - ``` - let results1 = try await realm.objects(Dog.self).where { $0.age > 1 }.subscribe(name: "adults") - let results2 = try await realm.objects(Dog.self).where { $0.age > 1 }.subscribe(name: "overOne") - let results3 = try await realm.objects(Dog.self).where { $0.age > 1 }.subscribe() - // This will unsubscribe from the subscription named "overOne". The "adults" and unnamed - // subscription still remain. - results2.unsubscribe() - ``` - - - Note: This method opens an update transaction that removes a subscription. - It is advised to *not* use this method to batch multiple subscription changes - to the server. - To unsubscribe multiple subscriptions at once use `SyncSubscription.update`. - - - warning: Calling unsubscribe on a Results does not remove the local filter from the `Results`. After calling unsubscribe, - Results may still contain objects because other subscriptions may exist in the realm's subscription set. - - warning: This API is currently in `Preview` and may be subject to changes in the future. - */ - public func unsubscribe() { - let rlmResults = ObjectiveCSupport.convert(object: self) - rlmResults.unsubscribe() - } } extension Results: Encodable where Element: Encodable {} diff --git a/RealmSwift/SwiftUI.swift b/RealmSwift/SwiftUI.swift index 548f778f11..92699a721d 100644 --- a/RealmSwift/SwiftUI.swift +++ b/RealmSwift/SwiftUI.swift @@ -1447,10 +1447,6 @@ private struct RealmEnvironmentKey: EnvironmentKey { static let defaultValue = Realm.Configuration.defaultConfiguration } -private struct PartitionValueEnvironmentKey: EnvironmentKey { - static let defaultValue: PartitionValue? = nil -} - @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension EnvironmentValues { /// The current `Realm.Configuration` that the view should use. @@ -1471,442 +1467,6 @@ extension EnvironmentValues { self[RealmEnvironmentKey.self] = newValue.configuration } } - /// The current `PartitionValue` that the view should use. - public var partitionValue: PartitionValue? { - get { - return self[PartitionValueEnvironmentKey.self] - } - set { - self[PartitionValueEnvironmentKey.self] = newValue - } - } -} - -/** -An enum representing different states from `AsyncOpen` and `AutoOpen` process -*/ -public enum AsyncOpenState { - /// Starting the Realm.asyncOpen process. - case connecting - /// Waiting for a user to be logged in before executing Realm.asyncOpen. - case waitingForUser - /// The Realm has been opened and is ready for use. For AsyncOpen this means that the Realm has been fully downloaded, but for AutoOpen the existing local file may have been used if the device is offline. - case open(Realm) - /// The Realm is currently being downloaded from the server. - case progress(Progress) - /// Opening the Realm failed. - case error(Error) -} - -private enum AsyncOpenKind { - case asyncOpen - case autoOpen -} - -@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) -private class ObservableAsyncOpenStorage: ObservableObject { - private var asyncOpenKind: AsyncOpenKind - private var app: App - var configuration: Realm.Configuration? - var partitionValue: AnyBSON? - - // Tracks User State for App for Multi-User Support - enum AppState { - case loggedIn(User) - case loggedOut - } - private var appState: AppState = .loggedOut - - // Cancellables - private var appCancellable = [AnyCancellable]() - private var asyncOpenCancellable = [AnyCancellable]() - - @Published fileprivate var asyncOpenState: AsyncOpenState - - init(asyncOpenKind: AsyncOpenKind, app: App, configuration: Realm.Configuration?, partitionValue: AnyBSON?) { - self.asyncOpenKind = asyncOpenKind - self.app = app - self.configuration = configuration - self.partitionValue = partitionValue - - // Initialising the state value depending on the user status, before first rendering. - if let user = app.currentUser { - appState = .loggedIn(user) - asyncOpenState = .connecting - } else { - asyncOpenState = .waitingForUser - } - } - - var setupHasRun = false - func setup() { - guard !setupHasRun else { return } - initAsyncOpen() - setupHasRun = true - } - - private func initAsyncOpen() { - if case .loggedIn(let user) = appState { - // we only open the realm on initialisation if there is a user logged. - asyncOpenForUser(user) - } - - // we observe the changes in the app state to check for user changes, - // we store an internal state, so we could react to those changes (user login, user change, logout). - app.objectWillChange.sink { [weak self] app in - guard let self = self else { return } - switch self.appState { - case .loggedIn(let user): - if let newUser = app.currentUser, - user != newUser { - self.appState = .loggedIn(newUser) - self.asyncOpenState = .connecting - self.asyncOpenForUser(user) - } else if app.currentUser == nil { - self.asyncOpenState = .waitingForUser - self.appState = .loggedOut - } - case .loggedOut: - if let user = app.currentUser { - self.appState = .loggedIn(user) - self.asyncOpenState = .connecting - self.asyncOpenForUser(user) - } - } - }.store(in: &appCancellable) - } - - private func asyncOpenForUser(_ user: User) { - let initialSubscriptions = configuration?.syncConfiguration?.initialSubscriptions - - // Set the `syncConfiguration` depending if there is partition value (pbs) or not (flx). - var config: Realm.Configuration - if let partitionValue = partitionValue { - config = user.configuration(partitionValue: partitionValue, cancelAsyncOpenOnNonFatalErrors: true) - } else if let initialSubscriptions { - config = user.flexibleSyncConfiguration(cancelAsyncOpenOnNonFatalErrors: true, - initialSubscriptions: ObjectiveCSupport.convert(block: initialSubscriptions.callback), - rerunOnOpen: initialSubscriptions.rerunOnOpen) - } else { - config = user.flexibleSyncConfiguration(cancelAsyncOpenOnNonFatalErrors: true) - } - - // Use the user configuration by default or set configuration with the current user `syncConfiguration`'s. - if var configuration = configuration { - // We want to throw if the configuration doesn't contain a `SyncConfiguration` - guard configuration.syncConfiguration != nil else { - throwRealmException("The used configuration was not configured with sync.") - } - let userSyncConfig = config.syncConfiguration - configuration.syncConfiguration = userSyncConfig - config = configuration - } - - // Cancel any current subscriptions to asyncOpen if there is one - cancelAsyncOpen() - Realm.asyncOpen(configuration: config) - .onProgressNotification { asyncProgress in - // Do not change state to progress if the realm file is already opened or there is an error - switch self.asyncOpenState { - case .connecting, .waitingForUser, .progress: - let progress = Progress(totalUnitCount: Int64(asyncProgress.transferredBytes)) - progress.completedUnitCount = Int64(asyncProgress.transferredBytes) - self.asyncOpenState = .progress(progress) - default: break - } - } - .sink { completion in - if case .failure(let error) = completion { - switch self.asyncOpenKind { - case .asyncOpen: - self.asyncOpenState = .error(error) - case .autoOpen: - if let realm = try? Realm(configuration: config) { - self.asyncOpenState = .open(realm) - } else { - self.asyncOpenState = .error(error) - } - } - } - } receiveValue: { realm in - self.asyncOpenState = .open(realm) - }.store(in: &self.asyncOpenCancellable) - } - - fileprivate func update(_ partitionValue: PartitionValue?, _ configuration: Realm.Configuration) { - if let partitionValue = partitionValue { - let bsonValue = AnyBSON(partitionValue: partitionValue) - if self.partitionValue != bsonValue { - self.partitionValue = bsonValue - } - } - - // We don't want to use the `defaultConfiguration` from the environment, we only want to use this environment value in @AsyncOpen if is not the default one - if configuration != .defaultConfiguration, self.configuration != configuration { - if let partitionValue = configuration.syncConfiguration?.partitionValue { - self.partitionValue = partitionValue - } - self.configuration = configuration - } - } - - private func cancelAsyncOpen() { - asyncOpenCancellable.forEach { $0.cancel() } - asyncOpenCancellable = [] - } - - func cancel() { - cancelAsyncOpen() - appCancellable.forEach { $0.cancel() } - appCancellable = [] - } - - // MARK: - AutoOpen & AsyncOpen Helper - - class func configureApp(appId: String? = nil, timeout: UInt? = nil) -> App { - var app: App - if let appId = appId { - app = App(id: appId) - } else { - // Check if there is a singular cached app - let cachedApps = RLMApp.allApps() - if cachedApps.count > 1 { - throwRealmException("Cannot AsyncOpen the Realm because more than one appId was found. When using multiple Apps you must explicitly pass an appId to indicate which to use.") - } - guard let cachedApp = cachedApps.first else { - throwRealmException("Cannot AsyncOpen the Realm because no appId was found. You must either explicitly pass an appId or initialize an App before displaying your View.") - } - app = cachedApp - } - - // Setup timeout if needed - if let timeout { - app.syncManager.timeoutOptions = SyncTimeoutOptions(connectTimeout: timeout) - } - return app - } -} - -// MARK: - AsyncOpen - -/// A property wrapper type that initiates a `Realm.asyncOpen()` for the current user which asynchronously open a Realm, -/// and notifies states for the given process -/// -/// Add AsyncOpen to your ``SwiftUI/View`` or ``SwiftUI/App``, after a user is already logged in, -/// or if a user is going to be logged in -/// -/// @AsyncOpen(appId: "app_id", partitionValue: ) var asyncOpen -/// -/// This will immediately initiates a `Realm.asyncOpen()` operation which will perform all work needed to get the Realm to -/// a usable state. (see Realm.asyncOpen() documentation) -/// -/// This property wrapper will publish states of the current `Realm.asyncOpen()` process like progress, errors and an opened realm, -/// which can be used to update the view -/// -/// struct AsyncOpenView: View { -/// @AsyncOpen(appId: "app_id", partitionValue: ) var asyncOpen -/// -/// var body: some View { -/// switch asyncOpen { -/// case .notOpen: -/// ProgressView() -/// case .open(let realm): -/// ListView() -/// .environment(\.realm, realm) -/// case .error(_): -/// ErrorView() -/// case .progress(let progress): -/// ProgressView(progress) -/// } -/// } -/// } -/// -/// This opened `realm` can be later injected to the view as an environment value which will be used by our property wrappers -/// to populate the view with data from the opened realm -/// -/// ListView() -/// .environment(\.realm, realm) -/// -@MainActor -@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) -@propertyWrapper public struct AsyncOpen: DynamicProperty { - @Environment(\.realmConfiguration) var configuration - @Environment(\.partitionValue) var partitionValue - @ObservedObject private var storage: ObservableAsyncOpenStorage - - /** - A Publisher for `AsyncOpenState`, emits a state each time the asyncOpen state changes. - */ - public var projectedValue: Published.Publisher { - storage.$asyncOpenState - } - - /// :nodoc: - public var wrappedValue: AsyncOpenState { - storage.setup() - return storage.asyncOpenState - } - - /** - This will cancel any notification from the property wrapper states - */ - public func cancel() { - storage.cancel() - } - - /** - Initialize the property wrapper - - parameter appId: The unique identifier of your Realm app, if empty or `nil` will try to retrieve latest singular cached app. - - parameter partitionValue: The `BSON` value the Realm is partitioned on. - - parameter configuration: The `Realm.Configuration` used when creating the Realm, - user's sync configuration for the given partition value will be set as the `syncConfiguration`, - if empty the user configuration will be used. - - parameter timeout: The maximum number of milliseconds to allow for a connection to - become fully established., if empty or `nil` no connection timeout is set. - */ - public init(appId: String? = nil, - partitionValue: Partition, - configuration: Realm.Configuration? = nil, - timeout: UInt? = nil) where Partition: BSON { - let app = ObservableAsyncOpenStorage.configureApp(appId: appId, timeout: timeout) - // Store property wrapper values on the storage - storage = ObservableAsyncOpenStorage(asyncOpenKind: .asyncOpen, app: app, configuration: configuration, partitionValue: AnyBSON(partitionValue)) - } - - /** - Initialize the property wrapper for a flexible sync configuration. - - parameter appId: The unique identifier of your Realm app, if empty or `nil` will try to retrieve latest singular cached app. - - parameter configuration: The `Realm.Configuration` used when creating the Realm, - user's sync configuration for the given partition value will be set as the `syncConfiguration`, - if empty the user configuration will be used. - - parameter timeout: The maximum number of milliseconds to allow for a connection to - become fully established., if empty or `nil` no connection timeout is set. - */ - public init(appId: String? = nil, - configuration: Realm.Configuration? = nil, - timeout: UInt? = nil) { - let app = ObservableAsyncOpenStorage.configureApp(appId: appId, timeout: timeout) - // Store property wrapper values on the storage - storage = ObservableAsyncOpenStorage(asyncOpenKind: .asyncOpen, app: app, configuration: configuration, partitionValue: nil) - } - - nonisolated public func update() { - MainActor.assumeIsolated { - storage.update(partitionValue, configuration) - } - } -} - -// MARK: - AutoOpen - -/// `AutoOpen` will try once to asynchronously open a Realm, but in case of no internet connection will return an opened realm -/// for the given appId and partitionValue which can be used within our view. - -/// Add AutoOpen to your ``SwiftUI/View`` or ``SwiftUI/App``, after a user is already logged in -/// or if a user is going to be logged in -/// -/// @AutoOpen(appId: "app_id", partitionValue: , timeout: 4000) var autoOpen -/// -/// This will immediately initiates a `Realm.asyncOpen()` operation which will perform all work needed to get the Realm to -/// a usable state. (see Realm.asyncOpen() documentation) -/// -/// This property wrapper will publish states of the current `Realm.asyncOpen()` process like progress, errors and an opened realm, -/// which can be used to update the view -/// -/// struct AutoOpenView: View { -/// @AutoOpen(appId: "app_id", partitionValue: ) var autoOpen -/// -/// var body: some View { -/// switch autoOpen { -/// case .notOpen: -/// ProgressView() -/// case .open(let realm): -/// ListView() -/// .environment(\.realm, realm) -/// case .error(_): -/// ErrorView() -/// case .progress(let progress): -/// ProgressView(progress) -/// } -/// } -/// } -/// -/// This opened `realm` can be later injected to the view as an environment value which will be used by our property wrappers -/// to populate the view with data from the opened realm -/// -/// ListView() -/// .environment(\.realm, realm) -/// -/// This property wrapper behaves similar as `AsyncOpen`, and in terms of declaration and use is completely identical, -/// but with the difference of a offline-first approach. -@MainActor -@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) -@propertyWrapper public struct AutoOpen: DynamicProperty { - @Environment(\.realmConfiguration) var configuration - @Environment(\.partitionValue) var partitionValue - @ObservedObject private var storage: ObservableAsyncOpenStorage - - /** - A Publisher for `AsyncOpenState`, emits a state each time the asyncOpen state changes. - */ - public var projectedValue: Published.Publisher { - storage.$asyncOpenState - } - - /// :nodoc: - public var wrappedValue: AsyncOpenState { - storage.setup() - return storage.asyncOpenState - } - - /** - This will cancel any notification from the property wrapper states - */ - public func cancel() { - storage.cancel() - } - - /** - Initialize the property wrapper - - parameter appId: The unique identifier of your Realm app, if empty or `nil` will try to retrieve latest singular cached app. - - parameter partitionValue: The `BSON` value the Realm is partitioned on. - - parameter configuration: The `Realm.Configuration` used when creating the Realm, - user's sync configuration for the given partition value will be set as the `syncConfiguration`, - if empty the user configuration will be used. - - parameter timeout: The maximum number of milliseconds to allow for a connection to - become fully established, if empty or `nil` no connection timeout is set. - */ - public init(appId: String? = nil, - partitionValue: Partition, - configuration: Realm.Configuration? = nil, - timeout: UInt? = nil) where Partition: BSON { - let app = ObservableAsyncOpenStorage.configureApp(appId: appId, timeout: timeout) - // Store property wrapper values on the storage - storage = ObservableAsyncOpenStorage(asyncOpenKind: .autoOpen, app: app, configuration: configuration, partitionValue: AnyBSON(partitionValue)) - } - - /** - Initialize the property wrapper for a flexible sync configuration. - - parameter appId: The unique identifier of your Realm app, if empty or `nil` will try to retrieve latest singular cached app. - - parameter configuration: The `Realm.Configuration` used when creating the Realm, - user's sync configuration for the given partition value will be set as the `syncConfiguration`, - if empty the user configuration will be used. - - parameter timeout: The maximum number of milliseconds to allow for a connection to - become fully established., if empty or `nil` no connection timeout is set. - */ - public init(appId: String? = nil, - configuration: Realm.Configuration? = nil, - timeout: UInt? = nil) { - let app = ObservableAsyncOpenStorage.configureApp(appId: appId, timeout: timeout) - // Store property wrapper values on the storage - storage = ObservableAsyncOpenStorage(asyncOpenKind: .autoOpen, app: app, configuration: configuration, partitionValue: nil) - } - - nonisolated public func update() { - MainActor.assumeIsolated { - storage.update(partitionValue, configuration) - } - } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) diff --git a/RealmSwift/Sync.swift b/RealmSwift/Sync.swift deleted file mode 100644 index de50726b4d..0000000000 --- a/RealmSwift/Sync.swift +++ /dev/null @@ -1,1266 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import Combine -import Realm -import Realm.Private - -/** - An object representing an Atlas App Services user. - - - see: `RLMUser` - */ -public typealias User = RLMUser - -public extension User { - /// Links the currently authenticated user with a new identity, where the identity is defined by the credential - /// specified as a parameter. This will only be successful if this `User` is the currently authenticated - /// with the client from which it was created. On success a new user will be returned with the new linked credentials. - /// @param credentials The `Credentials` used to link the user to a new identity. - /// @completion A completion that eventually return `Result.success(User)` with user's data or `Result.failure(Error)`. - @preconcurrency - func linkUser(credentials: Credentials, _ completion: @Sendable @escaping (Result) -> Void) { - self.__linkUser(with: ObjectiveCSupport.convert(object: credentials)) { user, error in - if let user = user { - completion(.success(user)) - } else { - completion(.failure(error ?? Realm.Error.callFailed)) - } - } - } - - /// Links the currently authenticated user with a new identity, where the identity is defined by the credential - /// specified as a parameter. This will only be successful if this `User` is the currently authenticated - /// with the client from which it was created. On success a new user will be returned with the new linked credentials. - /// @param credentials The `Credentials` used to link the user to a new identity. - /// @returns A publisher that eventually return `Result.success` or `Error`. - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - func linkUser(credentials: Credentials) -> Future { - return future { self.linkUser(credentials: credentials, $0) } - } - - /// Links the currently authenticated user with a new identity, where the identity is defined by the credential - /// specified as a parameter. This will only be successful if this `User` is the currently authenticated - /// with the client from which it was created. On success a new user will be returned with the new linked credentials. - /// - Parameters: - /// - credentials: The `Credentials` used to link the user to a new identity. - /// - Returns:A `User` after successfully update its identity. - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - func linkUser(credentials: Credentials) async throws -> User { - try await __linkUser(with: ObjectiveCSupport.convert(object: credentials)) - } -} - -/** - A manager which configures and manages Atlas App Services synchronization-related - functionality. - - - see: `RLMSyncManager` - */ -public typealias SyncManager = RLMSyncManager - -/** - Options for configuring timeouts and intervals in the sync client. - - - see: `RLMSyncTimeoutOptions` - */ -public typealias SyncTimeoutOptions = RLMSyncTimeoutOptions -public extension SyncTimeoutOptions { - /** - Memberwise convenience initializer for SyncTimeoutOptions. All values are - in milliseconds, and use a default value if `nil`. - - - Parameters: - - connectTimeout: The maximum 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. - - connectionLingerTime: If session multiplexing is enabled, how long to - keep connections open while there are no active session. - - pingKeepalivePeriod: How long to wait between each ping message sent to - the server. 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). - - pongKeepaliveTimeout: How long to wait for the server to respond to a - ping message. Shorter values make connection state change notifications - more responsive, but increase the chance of spurious disconnections. - - fastReconnectLimit: When a client first connects to the server, it - downloads all data from the server before it begins to upload local - changes. This typically reduces the total amount of merging needed and - gets the local client into a useful state faster. If a disconnect and - reconnect happens within the time span of the fast reconnect limit, - this is skipped and the session behaves as if it were continuously - connected. - */ - convenience init(connectTimeout: UInt? = nil, - connectionLingerTime: UInt? = nil, - pingKeepalivePeriod: UInt? = nil, - pongKeepaliveTimeout: UInt? = nil, - fastReconnectLimit: UInt? = nil) { - self.init() - if let connectTimeout { - self.connectTimeout = connectTimeout - } - if let connectionLingerTime { - self.connectionLingerTime = connectionLingerTime - } - if let pingKeepalivePeriod { - self.pingKeepalivePeriod = pingKeepalivePeriod - } - if let pongKeepaliveTimeout { - self.pongKeepaliveTimeout = pongKeepaliveTimeout - } - if let fastReconnectLimit { - self.fastReconnectLimit = fastReconnectLimit - } - } -} - -/** - A session object which represents communication between the client and server for a specific - Realm. - - - see: `RLMSyncSession` - */ -public typealias SyncSession = RLMSyncSession - -/** - A closure type for a closure which can be set on the `SyncManager` to allow errors to be reported - to the application. - - - see: `RLMSyncErrorReportingBlock` - */ -public typealias ErrorReportingBlock = RLMSyncErrorReportingBlock - -/** - A closure type for a closure which is used by certain APIs to asynchronously return a `SyncUser` - object to the application. - - - see: `RLMUserCompletionBlock` - */ -public typealias UserCompletionBlock = RLMUserCompletionBlock - -/** - An error associated with the SDK's synchronization functionality. All errors reported by - an error handler registered on the `SyncManager` are of this type. - - - see: `RLMSyncError` - */ -public typealias SyncError = RLMSyncError - -extension SyncError { - /** - An opaque token allowing the user to take action after certain types of - errors have been reported. - - - see: `RLMSyncErrorActionToken` - */ - public typealias ActionToken = RLMSyncErrorActionToken - - /** - Given a client reset error, extract and return the recovery file path - and the action token. - - The action token can be passed into `SyncSession.immediatelyHandleError(_:)` - to immediately delete the local copy of the Realm which experienced the - client reset error. The local copy of the Realm must be deleted before - your application attempts to open the Realm again. - - The recovery file path is the path to which the current copy of the Realm - on disk will be saved once the client reset occurs. - - - warning: Do not call `SyncSession.immediatelyHandleError(_:)` until you are - sure that all references to the Realm and managed objects belonging - to the Realm have been nil'ed out, and that all autorelease pools - containing these references have been drained. - - - see: `SyncError.ActionToken`, `SyncSession.immediatelyHandleError(_:)` - */ - public func clientResetInfo() -> (String, SyncError.ActionToken)? { - if code == SyncError.clientResetError, - let recoveryPath = userInfo[kRLMSyncPathOfRealmBackupCopyKey] as? String, - let token = _nsError.__rlmSync_errorActionToken() { - return (recoveryPath, token) - } - return nil - } - - /** - Given a permission denied error, extract and return the action token. - - This action token can be passed into `SyncSession.immediatelyHandleError(_:)` - to immediately delete the local copy of the Realm which experienced the - permission denied error. The local copy of the Realm must be deleted before - your application attempts to open the Realm again. - - - warning: Do not call `SyncSession.immediatelyHandleError(_:)` until you are - sure that all references to the Realm and managed objects belonging - to the Realm have been nil'ed out, and that all autorelease pools - containing these references have been drained. - - - see: `SyncError.ActionToken`, `SyncSession.immediatelyHandleError(_:)` - */ - public func deleteRealmUserInfo() -> SyncError.ActionToken? { - return _nsError.__rlmSync_errorActionToken() - } - - /** - Sync errors which originate from the server also produce server-side logs - which may contain useful information. When applicable, this field contains - the url of those logs, and `nil` otherwise. - */ - public var serverLogURL: URL? { - (userInfo[RLMServerLogURLKey] as? String).flatMap(URL.init) - } - - /// Extended information about what was reverted for `.writeRejected` errors. - public var compensatingWriteInfo: [CompensatingWriteInfo]? { - userInfo[RLMCompensatingWriteInfoKey] as? [CompensatingWriteInfo] - } -} - -/// Extended information about a write which was rejected by the server. -/// -/// The server will sometimes reject writes made by the client for reasons such -/// as permissions, additional server-side validation failing, or because the -/// object didn't match any flexible sync subscriptions. When this happens, a -/// `.writeRejected` error is reported with a non-nil -/// ``SyncError.compensatingWriteInfo`` field with information about what -/// writes were rejected and why. -/// -/// This information is intended for debugging and logging purposes only. The -/// `reason` strings are generated by the server and are not guaranteed to be -/// stable, so attempting to programmatically do anything with them will break -/// without warning. -public typealias CompensatingWriteInfo = RLMCompensatingWriteInfo -extension CompensatingWriteInfo { - /// The primary key of the object being written to. - public var primaryKey: AnyRealmValue { - ObjectiveCSupport.convert(value: __primaryKey) - } -} - -/** - An error which occurred when making a request to Atlas App Services. Most User - and App functions which can fail report errors of this type. - */ -public typealias AppError = RLMAppError - -extension AppError { - /// When applicable, the HTTP status code which resulted in this error. - var httpStatusCode: Int? { - userInfo[RLMHTTPStatusCodeKey] as? Int - } -} - -/** - An enum which can be used to specify the level of logging. - - - see: `RLMSyncLogLevel` - */ -public typealias SyncLogLevel = RLMSyncLogLevel - -/** - A data type whose values represent different authentication providers that can be used with - Atlas App Services. - - - see: `RLMIdentityProvider` - */ -public typealias Provider = RLMIdentityProvider - -/** - An enum used to determines file recovery behavior in the event of a client reset. - Defaults to ``.recoverUnsyncedChanges``. - - - see: `RLMClientResetMode` - - see: https://docs.mongodb.com/realm/sync/error-handling/client-resets/ -*/ -@frozen public enum ClientResetMode { - /// All unsynchronized local changes are automatically discarded and the local state is - /// automatically reverted to the most recent state from the server. Unsynchronized changes - /// can then be recovered in the post-client-reset callback block. - /// - /// If ``.discardLocal`` is enabled but the client reset operation is unable to complete - /// then the client reset process reverts to manual mode. Example: During a destructive schema change this - /// mode will fail and invoke the manual client reset handler. - /// - /// - parameter beforeReset: a function invoked prior to a client reset occurring. - /// - parameter before: a frozen copy of the local Realm state prior to client reset. - /// - seeAlso ``RLMClientResetBeforeBlock`` - - /// Example Usage - /// ``` - /// user.configuration(partitionValue: "myPartition", clientResetMode: .discardUnsyncedChanges({ before in - /// var recoveryConfig = Realm.Configuration() - /// recoveryConfig.fileURL = myRecoveryPath - /// do { - /// before.writeCopy(configuration: recoveryConfig) - /// // The copied realm could be used later for recovery, debugging, reporting, etc. - /// } catch { - /// // handle error - /// } - /// }, nil)) - /// ``` - /// - /// - parameter afterReset: a function invoked after the client reset reset process has occurred. - /// - parameter before: a frozen copy of the local Realm state prior to client reset. - /// - parameter after: a live instance of the realm after client reset. - /// - seeAlso ``RLMClientResetAfterBlock`` - /// - /// Example Usage - /// ``` - /// user.configuration(partitionValue: "myPartition", clientResetMode: .discardUnsyncedChanges( nil, { before, after in - /// // This block could be used to add custom recovery logic, back-up a realm file, send reporting, etc. - /// for object in before.objects(myClass.self) { - /// let res = after.objects(myClass.self) - /// if (res.filter("primaryKey == %@", object.primaryKey).first != nil) { - /// // ...custom recovery logic... - /// } else { - /// // ...custom recovery logic... - /// } - /// } - /// })) - /// ``` - @available(*, deprecated, message: "Use discardUnsyncedChanges") - @preconcurrency - case discardLocal(beforeReset: (@Sendable (_ before: Realm) -> Void)? = nil, - afterReset: (@Sendable (_ before: Realm, _ after: Realm) -> Void)? = nil) - /// All unsynchronized local changes are automatically discarded and the local state is - /// automatically reverted to the most recent state from the server. Unsynchronized changes - /// can then be recovered in the post-client-reset callback block. - /// - /// If ``.discardUnsyncedChanges`` is enabled but the client reset operation is unable to complete - /// then the client reset process reverts to manual mode. Example: During a destructive schema change this - /// mode will fail and invoke the manual client reset handler. - /// - /// - parameter beforeReset: a function invoked prior to a client reset occurring. - /// - parameter before: a frozen copy of the local Realm state prior to client reset. - /// - seeAlso ``RLMClientResetBeforeBlock`` - - /// Example Usage - /// ``` - /// user.configuration(partitionValue: "myPartition", clientResetMode: .discardUnsyncedChanges({ before in - /// var recoveryConfig = Realm.Configuration() - /// recoveryConfig.fileURL = myRecoveryPath - /// do { - /// before.writeCopy(configuration: recoveryConfig) - /// // The copied realm could be used later for recovery, debugging, reporting, etc. - /// } catch { - /// // handle error - /// } - /// }, nil)) - /// ``` - /// - /// - parameter afterReset: a function invoked after the client reset reset process has occurred. - /// - parameter before: a frozen copy of the local Realm state prior to client reset. - /// - parameter after: a live instance of the realm after client reset. - /// - seeAlso ``RLMClientResetAfterBlock`` - /// - /// Example Usage - /// ``` - /// user.configuration(partitionValue: "myPartition", clientResetMode: .discardUnsyncedChanges( nil, { before, after in - /// // This block could be used to add custom recovery logic, back-up a realm file, send reporting, etc. - /// for object in before.objects(myClass.self) { - /// let res = after.objects(myClass.self) - /// if (res.filter("primaryKey == %@", object.primaryKey).first != nil) { - /// // ...custom recovery logic... - /// } else { - /// // ...custom recovery logic... - /// } - /// } - /// })) - /// ``` - @preconcurrency - case discardUnsyncedChanges(beforeReset: (@Sendable (_ before: Realm) -> Void)? = nil, - afterReset: (@Sendable (_ before: Realm, _ after: Realm) -> Void)? = nil) - /// The client device will download a realm realm which reflects the latest - /// state of the server after a client reset. A recovery process is run locally in - /// an attempt to integrate the server version with any local changes from - /// before the client reset occurred. - /// - /// The changes are integrated with the following rules: - /// 1. Objects created locally that were not synced before client reset will be integrated. - /// 2. If an object has been deleted on the server, but was modified on the client, the delete takes precedence and the update is discarded - /// 3. If an object was deleted on the client, but not the server, then the client delete instruction is applied. - /// 4. In the case of conflicting updates to the same field, the client update is applied. - /// - /// If the recovery integration fails, the client reset process falls back to ``ClientResetMode.manual``. - /// The recovery integration will fail if the "Client Recovery" setting is not enabled on the server. - /// Integration may also fail in the event of an incompatible schema change. - /// - /// - parameter beforeReset: a function invoked prior to a client reset occurring. - /// - parameter before: a frozen copy of the local Realm state prior to client reset. - /// - seeAlso ``RLMClientResetBeforeBlock`` - - /// Example Usage - /// ``` - /// user.configuration(partitionValue: "myPartition", clientResetMode: .discardUnsyncedChanges({ before in - /// var recoveryConfig = Realm.Configuration() - /// recoveryConfig.fileURL = myRecoveryPath - /// do { - /// before.writeCopy(configuration: recoveryConfig) - /// // The copied realm could be used later for recovery, debugging, reporting, etc. - /// } catch { - /// // handle error - /// } - /// }, nil)) - /// ``` - /// - /// - parameter afterReset: a function invoked after the client reset reset process has occurred. - /// - parameter before: a frozen copy of the local Realm state prior to client reset. - /// - parameter after: a live instance of the realm after client reset. - /// - seeAlso ``RLMClientResetAfterBlock`` - /// - /// Example Usage - /// ``` - /// user.configuration(partitionValue: "myPartition", clientResetMode: .discardUnsyncedChanges( nil, { before, after in - /// // This block could be used to add custom recovery logic, back-up a realm file, send reporting, etc. - /// for object in before.objects(myClass.self) { - /// let res = after.objects(myClass.self) - /// if (res.filter("primaryKey == %@", object.primaryKey).first != nil) { - /// // ...custom recovery logic... - /// } else { - /// // ...custom recovery logic... - /// } - /// } - /// })) - /// ``` - @preconcurrency - case recoverUnsyncedChanges(beforeReset: (@Sendable (_ before: Realm) -> Void)? = nil, - afterReset: (@Sendable (_ before: Realm, _ after: Realm) -> Void)? = nil) - /// The client device will download a realm with objects reflecting the latest version of the server. A recovery - /// process is run locally in an attempt to integrate the server version with any local changes from before the - /// client reset occurred. - /// - /// The changes are integrated with the following rules: - /// 1. Objects created locally that were not synced before client reset will be integrated. - /// 2. If an object has been deleted on the server, but was modified on the client, the delete takes precedence and the update is discarded - /// 3. If an object was deleted on the client, but not the server, then the client delete instruction is applied. - /// 4. In the case of conflicting updates to the same field, the client update is applied. - /// - /// If the recovery integration fails, the client reset process falls back to ``ClientResetMode.discardUnsyncedChanges``. - /// The recovery integration will fail if the "Client Recovery" setting is not enabled on the server. - /// Integration may also fail in the event of an incompatible schema change. - /// - /// - parameter beforeReset: a function invoked prior to a client reset occurring. - /// - parameter before: a frozen copy of the local Realm state prior to client reset. - /// - seeAlso ``RLMClientResetBeforeBlock`` - - /// Example Usage - /// ``` - /// user.configuration(partitionValue: "myPartition", clientResetMode: .discardUnsyncedChanges({ before in - /// var recoveryConfig = Realm.Configuration() - /// recoveryConfig.fileURL = myRecoveryPath - /// do { - /// before.writeCopy(configuration: recoveryConfig) - /// // The copied realm could be used later for recovery, debugging, reporting, etc. - /// } catch { - /// // handle error - /// } - /// }, nil)) - /// ``` - /// - /// - parameter afterReset: a function invoked after the client reset reset process has occurred. - /// - parameter before: a frozen copy of the local Realm state prior to client reset. - /// - parameter after: a live instance of the realm after client reset. - /// - seeAlso ``RLMClientResetAfterBlock`` - /// - /// Example Usage - /// ``` - /// user.configuration(partitionValue: "myPartition", clientResetMode: .discardUnsyncedChanges( nil, { before, after in - /// // This block could be used to add custom recovery logic, back-up a realm file, send reporting, etc. - /// for object in before.objects(myClass.self) { - /// let res = after.objects(myClass.self) - /// if (res.filter("primaryKey == %@", object.primaryKey).first != nil) { - /// // ...custom recovery logic... - /// } else { - /// // ...custom recovery logic... - /// } - /// } - /// })) - /// ``` - @preconcurrency - case recoverOrDiscardUnsyncedChanges(beforeReset: (@Sendable (_ before: Realm) -> Void)? = nil, - afterReset: (@Sendable (_ before: Realm, _ after: Realm) -> Void)? = nil) - /// - seeAlso: ``RLMClientResetModeManual`` - /// - /// The manual client reset mode handler can be set in two places: - /// 1. As an ErrorReportingBlock argument in the ClientResetMode enum (``ErrorReportingBlock?` = nil`). - /// 2. As an ErrorReportingBlock in the ``SyncManager.errorHandler`` property. - /// - seeAlso: ``RLMSyncManager.errorHandler`` - /// - /// During an ``RLMSyncErrorClientResetError`` the block executed is determined by the following rules - /// - If an error reporting block is set in ``ClientResetMode`` and the ``SyncManager``, the ``ClientResetMode`` block will be executed. - /// - If an error reporting block is set in either the ``ClientResetMode`` or the ``SyncManager``, but not both, the single block will execute. - /// - If no block is set in either location, the client reset will not be handled. The application will likely need to be restarted and unsynced local changes may be lost. - /// - note: The ``SyncManager.errorHandler`` is still invoked under all ``RLMSyncError``s *other than* ``RLMSyncErrorClientResetError``. - /// - seeAlso ``RLMSyncError`` for an exhaustive list. - @preconcurrency - case manual(errorHandler: ErrorReportingBlock? = nil) -} - - -/** - A configuration controlling how the initial subscriptions are populated when a Realm file is first opened. - - - see: `RLMInitialSubscriptionsConfiguration` - */ -public typealias InitialSubscriptionsConfiguration = RLMInitialSubscriptionsConfiguration - -/** - A `SyncConfiguration` represents configuration parameters for Realms intended to sync with - Atlas App Services. - */ -@frozen public struct SyncConfiguration: Sendable { - /// The `SyncUser` who owns the Realm that this configuration should open. - public var user: User { - config.user - } - - /** - The value this Realm is partitioned on. The partition key is a property defined in - Atlas App Services. All classes with a property with this value will be synchronized to the - Realm. - */ - public var partitionValue: AnyBSON? { - ObjectiveCSupport.convert(object: config.partitionValue) - } - - /** - An enum which determines file recovery behavior in the event of a client reset. - - note: Defaults to ``.recoverUnsyncedChanges`` - - - see: ``ClientResetMode`` and ``RLMClientResetMode`` - - see: https://docs.mongodb.com/realm/sync/error-handling/client-resets/ - */ - public var clientResetMode: ClientResetMode { - switch config.clientResetMode { - case .manual: - return .manual(errorHandler: config.manualClientResetHandler) - case .discardUnsyncedChanges, .discardLocal: - return .discardUnsyncedChanges(beforeReset: ObjectiveCSupport.convert(object: config.beforeClientReset), - afterReset: ObjectiveCSupport.convert(object: config.afterClientReset)) - case .recoverUnsyncedChanges: - return .recoverUnsyncedChanges(beforeReset: ObjectiveCSupport.convert(object: config.beforeClientReset), - afterReset: ObjectiveCSupport.convert(object: config.afterClientReset)) - case .recoverOrDiscardUnsyncedChanges: - return .recoverOrDiscardUnsyncedChanges(beforeReset: ObjectiveCSupport.convert(object: config.beforeClientReset), - afterReset: ObjectiveCSupport.convert(object: config.afterClientReset)) - @unknown default: - fatalError() - } - } - - /** - By default, Realm.asyncOpen() swallows non-fatal connection errors such as - a connection attempt timing out and simply retries until it succeeds. If - this is set to `true`, instead the error will be reported to the callback - and the async open will be cancelled. - */ - public var cancelAsyncOpenOnNonFatalErrors: Bool { - config.cancelAsyncOpenOnNonFatalErrors - } - - /** - A configuration that controls how initial subscriptions are populated when the Realm is opened. - */ - public var initialSubscriptions: InitialSubscriptionsConfiguration? { - config.initialSubscriptions - } - - // Although RLMSyncConfiguration objects are mutable, we don't expose a way - // to mutate the one wrapped by this struct, which makes this safe - #if compiler(<6) - internal let config: RLMSyncConfiguration - #else - nonisolated(unsafe) internal let config: RLMSyncConfiguration - #endif - internal init(config: RLMSyncConfiguration) { - self.config = config - } -} - -/// Structure providing an interface to call an Atlas App Services function with the provided name and arguments. -/// -/// user.functions.sum([1, 2, 3, 4, 5]) { sum, error in -/// guard case let .int64(value) = sum else { -/// print(error?.localizedDescription) -/// } -/// -/// assert(value == 15) -/// } -/// -/// The dynamic member name (`sum` in the above example) is directly associated with the function name. -/// The first argument is the `BSONArray` of arguments to be provided to the function. -/// The second and final argument is the completion handler to call when the function call is complete. -/// This handler is executed on a non-main global `DispatchQueue`. -@dynamicMemberLookup -@frozen public struct Functions: Sendable { - private let user: User - fileprivate init(user: User) { - self.user = user - } - - /// A closure type for receiving the completion of a remote function call. - public typealias FunctionCompletionHandler = @Sendable (AnyBSON?, Error?) -> Void - - /// A closure type for the dynamic remote function type. - public typealias Function = @Sendable ([AnyBSON], @escaping FunctionCompletionHandler) -> Void - - /// The implementation of @dynamicMemberLookup that allows for dynamic remote function calls. - public subscript(dynamicMember string: String) -> Function { - return { (arguments: [AnyBSON], completionHandler: @escaping FunctionCompletionHandler) in - let objcArgs = arguments.map(ObjectiveCSupport.convertBson) - self.user.__callFunctionNamed(string, arguments: objcArgs) { (bson: RLMBSON?, error: Error?) in - completionHandler(bson.map(ObjectiveCSupport.convertBson) ?? .none, error) - } - } - } - - /// A closure type for receiving the completion result of a remote function call. - public typealias ResultFunctionCompletionHandler = @Sendable (Result) -> Void - - /// A closure type for the dynamic remote function type. - public typealias ResultFunction = @Sendable ([AnyBSON], @escaping ResultFunctionCompletionHandler) -> Void - - /// The implementation of @dynamicMemberLookup that allows for dynamic remote function calls with a `ResultFunctionCompletionHandler` completion. - @preconcurrency - public subscript(dynamicMember string: String) -> ResultFunction { - return { (arguments: [AnyBSON], completionHandler: @escaping ResultFunctionCompletionHandler) in - let objcArgs = arguments.map(ObjectiveCSupport.convertBson) - self.user.__callFunctionNamed(string, arguments: objcArgs) { (bson: RLMBSON?, error: Error?) in - if let b = bson.map(ObjectiveCSupport.convertBson), let bson = b { - completionHandler(.success(bson)) - } else { - completionHandler(.failure(error ?? Realm.Error.callFailed)) - } - } - } - } - - /// The implementation of @dynamicMemberLookup that allows for dynamic remote function calls with a `callable` return. - public subscript(dynamicMember string: String) -> FunctionCallable { - FunctionCallable(name: string, user: user) - } -} - -/// Structure enabling the following syntactic sugar for user functions: -/// -/// guard case let .int32(sum) = try await user.functions.sum([1, 2, 3, 4, 5]) else { -/// return -/// } -/// -/// The dynamic member name (`sum` in the above example) is provided by `@dynamicMemberLookup` -/// which is directly associated with the function name. -@dynamicCallable -public struct FunctionCallable: Sendable { - fileprivate let name: String - fileprivate let user: User - - /// :nodoc: - @available(*, deprecated, message: "Specify args separately without wrapping them in an array") - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - public func dynamicallyCall(withArguments args: [[AnyBSON]]) -> Future { - return future { promise in - let objcArgs = args.first!.map(ObjectiveCSupport.convertBson) - self.user.__callFunctionNamed(name, arguments: objcArgs) { (bson: RLMBSON?, error: Error?) in - if let b = bson.map(ObjectiveCSupport.convertBson), let bson = b { - promise(.success(bson)) - } else { - promise(.failure(error ?? Realm.Error.callFailed)) - } - } - } - } - - /// The implementation of @dynamicCallable that allows for `Future` callable return. - /// - /// let cancellable = user.functions.sum(1, 2, 3, 4, 5) - /// .sink(receiveCompletion: { result in - /// }, receiveValue: { value in - /// // Returned value from function - /// }) - /// - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - public func dynamicallyCall(withArguments args: [AnyBSON]) -> Future { - return future { promise in - let objcArgs = args.map(ObjectiveCSupport.convertBson) - self.user.__callFunctionNamed(name, arguments: objcArgs) { (bson: RLMBSON?, error: Error?) in - if let b = bson.map(ObjectiveCSupport.convertBson), let bson = b { - promise(.success(bson)) - } else { - promise(.failure(error ?? Realm.Error.callFailed)) - } - } - } - } -} - -private func setSyncFields(_ config: RLMRealmConfiguration, clientResetMode: ClientResetMode, - cancelAsyncOpenOnNonFatalErrors: Bool) { - let syncConfig = config.syncConfiguration! - syncConfig.cancelAsyncOpenOnNonFatalErrors = cancelAsyncOpenOnNonFatalErrors - switch clientResetMode { - case .manual(let block): - syncConfig.clientResetMode = .manual - syncConfig.manualClientResetHandler = block - case .discardUnsyncedChanges(let beforeBlock, let afterBlock), - .discardLocal(let beforeBlock, let afterBlock): - syncConfig.clientResetMode = .discardUnsyncedChanges - syncConfig.beforeClientReset = ObjectiveCSupport.convert(object: beforeBlock) - syncConfig.afterClientReset = ObjectiveCSupport.convert(object: afterBlock) - case .recoverUnsyncedChanges(let beforeBlock, let afterBlock): - syncConfig.clientResetMode = .recoverUnsyncedChanges - syncConfig.beforeClientReset = ObjectiveCSupport.convert(object: beforeBlock) - syncConfig.afterClientReset = ObjectiveCSupport.convert(object: afterBlock) - case .recoverOrDiscardUnsyncedChanges(let beforeBlock, let afterBlock): - syncConfig.clientResetMode = .recoverOrDiscardUnsyncedChanges - syncConfig.beforeClientReset = ObjectiveCSupport.convert(object: beforeBlock) - syncConfig.afterClientReset = ObjectiveCSupport.convert(object: afterBlock) - } - - config.syncConfiguration = syncConfig -} - -public extension User { - /** - Create a sync configuration instance. - - - parameter partitionValue: The `BSON` value the Realm is partitioned on. - - parameter clientResetMode: Determines file recovery behavior during a client reset. `.recoverUnsyncedChanges` by default. - - parameter cancelAsyncOpenOnNonFatalErrors: By default, Realm.asyncOpen() - swallows non-fatal connection errors such as a connection attempt timing - out and simply retries until it succeeds. If this is set to `true`, instead - the error will be reported to the callback and the async open will be - cancelled. - */ - @preconcurrency - func configuration(partitionValue: T, - clientResetMode: ClientResetMode = .recoverUnsyncedChanges(beforeReset: nil, afterReset: nil), - cancelAsyncOpenOnNonFatalErrors: Bool = false) -> Realm.Configuration { - return configuration(partitionValue: AnyBSON(partitionValue), - clientResetMode: clientResetMode, - cancelAsyncOpenOnNonFatalErrors: cancelAsyncOpenOnNonFatalErrors) - } - - /** - Create a sync configuration instance. - - - parameter partitionValue: Takes `nil` as a partition value. - - parameter clientResetMode: Determines file recovery behavior during a client reset. `.recoverUnsyncedChanges` by default. - - parameter cancelAsyncOpenOnNonFatalErrors: By default, Realm.asyncOpen() - swallows non-fatal connection errors such as a connection attempt timing - out and simply retries until it succeeds. If this is set to `true`, instead - the error will be reported to the callback and the async open will be - cancelled. - */ - @preconcurrency - func configuration(partitionValue: AnyBSON, - clientResetMode: ClientResetMode = .recoverUnsyncedChanges(beforeReset: nil, afterReset: nil), - cancelAsyncOpenOnNonFatalErrors: Bool = false) -> Realm.Configuration { - let config = __configuration(withPartitionValue: ObjectiveCSupport.convert(object: partitionValue)) - setSyncFields(config, clientResetMode: clientResetMode, cancelAsyncOpenOnNonFatalErrors: cancelAsyncOpenOnNonFatalErrors) - return ObjectiveCSupport.convert(object: config) - } - - /** - The custom data of the user. - This is configured in your Atlas App Services app. - */ - var customData: Document { - guard let rlmCustomData = self.__customData as RLMBSON?, - let anyBSON = ObjectiveCSupport.convert(object: rlmCustomData), - case let .document(customData) = anyBSON else { - return [:] - } - - return customData - } - - /// A client for interacting with a remote MongoDB instance - /// - Parameter serviceName: The name of the MongoDB service - /// - Returns: A `MongoClient` which is used for interacting with a remote MongoDB service - func mongoClient(_ serviceName: String) -> MongoClient { - return self.__mongoClient(withServiceName: serviceName) - } - - /// Call an Atlas App Services function with the provided name and arguments. - /// - /// user.functions.sum([1, 2, 3, 4, 5]) { sum, error in - /// guard case let .int64(value) = sum else { - /// print(error?.localizedDescription) - /// } - /// - /// assert(value == 15) - /// } - /// - /// The dynamic member name (`sum` in the above example) is directly associated with the function name. - /// The first argument is the `BSONArray` of arguments to be provided to the function. - /// The second and final argument is the completion handler to call when the function call is complete. - /// This handler is executed on a non-main global `DispatchQueue`. - var functions: Functions { - return Functions(user: self) - } -} - -public extension SyncSession { - /** - The current state of the session represented by a session object. - - - see: `RLMSyncSessionState` - */ - typealias State = RLMSyncSessionState - - /** - The current state of a sync session's connection. - - - see: `RLMSyncConnectionState` - */ - typealias ConnectionState = RLMSyncConnectionState - - /** - The transfer direction (upload or download) tracked by a given progress notification block. - - Progress notification blocks can be registered on sessions if your app wishes to be informed - how many bytes have been uploaded or downloaded, for example to show progress indicator UIs. - */ - enum ProgressDirection: Sendable { - /// For monitoring upload progress. - case upload - /// For monitoring download progress. - case download - } - - /** - The desired behavior of a progress notification block. - - Progress notification blocks can be registered on sessions if your app wishes to be informed - how many bytes have been uploaded or downloaded, for example to show progress indicator UIs. - */ - enum ProgressMode: Sendable { - /** - The block will be called forever, or until it is unregistered by calling - `ProgressNotificationToken.invalidate()`. - - Notifications will always report the latest number of transferred bytes, and the - most up-to-date number of total transferrable bytes. - */ - case reportIndefinitely - /** - The block will, upon registration, store the total number of bytes - to be transferred. When invoked, it will always report the most up-to-date number - of transferrable bytes out of that original number of transferrable bytes. - - When the number of transferred bytes reaches or exceeds the - number of transferrable bytes, the block will be unregistered. - */ - case forCurrentlyOutstandingWork - } - - /** - A token corresponding to a progress notification block. - - Call `invalidate()` on the token to stop notifications. If the notification block has already - been automatically stopped, calling `invalidate()` does nothing. `invalidate()` should be called - before the token is destroyed. - */ - typealias ProgressNotificationToken = RLMProgressNotificationToken - - /** - A struct encapsulating progress information. - */ - struct Progress: Sendable { - private let _transferredBytes: Int // NEXT-MAJOR remove storage fields and deprecated props - private let _transferrableBytes: Int // NEXT-MAJOR remove storage fields and deprecated props - - /// The number of bytes that have been transferred. - @available(*, deprecated, message: "Use progressEstimate") - public var transferredBytes: Int { return _transferredBytes } - - /** - The total number of transferrable bytes (bytes that have been transferred, - plus bytes pending transfer). - - If the notification block is tracking downloads, this number represents the size of the - changesets generated by all other clients using the Realm. - If the notification block is tracking uploads, this number represents the size of the - changesets representing the local changes on this client. - */ - @available(*, deprecated, message: "Use progressEstimate") - public var transferrableBytes: Int { return _transferrableBytes } - - /** - A value between 0.0 and 1.0 representing the estimated transfer progress. This value is precise for - uploads, but will be based on historical data and certain heuristics applied by the server for downloads. - - Whenever the progress reporting mode is `forCurrentlyOutstandingWork`, that value - will monotonically increase until it reaches 1.0. If the progress mode is `reportIndefinitely`, the - value may either increase or decrease as new data needs to be transferred. - */ - public let progressEstimate: Double - - /// The fraction of bytes transferred out of all transferrable bytes. If this value is 1, - /// no bytes are waiting to be transferred (either all bytes have already been transferred, - /// or there are no bytes to be transferred in the first place). - @available(*, deprecated, message: "Use progressEstimate", renamed: "progressEstimate") - public var fractionTransferred: Double { - return progressEstimate - } - - /// Whether all pending data has already been transferred. - public var isTransferComplete: Bool { - return progressEstimate == 1.0 - } - - internal init(transferred: UInt, transferrable: UInt, estimate: Double) { - _transferredBytes = Int(transferred) - _transferrableBytes = Int(transferrable) - progressEstimate = estimate - } - } - - /** - Register a progress notification block. - - If the session has already received progress information from the - synchronization subsystem, the block will be called immediately. Otherwise, it - will be called as soon as progress information becomes available. - - Multiple blocks can be registered with the same session at once. Each block - will be invoked on a side queue devoted to progress notifications. - - The token returned by this method must be retained as long as progress - notifications are desired, and the `invalidate()` method should be called on it - when notifications are no longer needed and before the token is destroyed. - - If no token is returned, the notification block will never be called again. - There are a number of reasons this might be true. If the session has previously - experienced a fatal error it will not accept progress notification blocks. If - the block was configured in the `forCurrentlyOutstandingWork` mode but there - is no additional progress to report (for example, the number of transferrable bytes - and transferred bytes are equal), the block will not be called again. - - - parameter direction: The transfer direction (upload or download) to track in this progress notification block. - - parameter mode: The desired behavior of this progress notification block. - - parameter block: The block to invoke when notifications are available. - - - returns: A token which must be held for as long as you want notifications to be delivered. - - - see: `ProgressDirection`, `Progress`, `ProgressNotificationToken` - */ - @preconcurrency - func addProgressNotification(for direction: ProgressDirection, - mode: ProgressMode, - block: @Sendable @escaping (Progress) -> Void) -> ProgressNotificationToken? { - return __addSyncProgressNotification(for: (direction == .upload ? .upload : .download), - mode: (mode == .reportIndefinitely - ? .reportIndefinitely - : .forCurrentlyOutstandingWork)) { progress in - block(Progress(transferred: progress.transferredBytes, transferrable: progress.transferrableBytes, estimate: progress.progressEstimate)) - } - } - - /** - Wait for pending uploads or downloads to complete or the session to expire, and dispatch the callback onto the specified queue. - - parameter direction: The transfer direction (upload or download) to wait for. - - parameter queue: The queue to dispatch the callback onto. - - parameter block: The block to invoke when waiting is complete. - - - see: `ProgressDirection` - - warning: This method is not meant to be used except in special cases, notably for testing. - */ - func wait(for direction: ProgressDirection, - queue: DispatchQueue? = nil, - block: @Sendable @escaping (Error?) -> Void) { - switch direction { - case .upload: - __waitForUploadCompletion(on: queue, callback: block) - case .download: - __waitForDownloadCompletion(on: queue, callback: block) - } - } - - /** - Wait for pending uploads or downloads to complete or the session to expire. - - parameter direction: The transfer direction (upload or download) to wait for. - - - see: `ProgressDirection` - - warning: This method is not meant to be used except in special cases, notably for testing. - */ - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - func wait(for direction: ProgressDirection) async throws { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - wait(for: direction) { error in - if let error = error { - continuation.resume(throwing: error) - } else { - continuation.resume() - } - } - } - } -} - -extension Realm { - /// :nodoc: - @available(*, unavailable, message: "Use Results.subscribe()") - public func subscribe(to objects: T.Type, where: String, - completion: @escaping (Results?, Swift.Error?) -> Void) { - fatalError() - } - - /** - Get the SyncSession used by this Realm. Will be nil if this is not a - synchronized Realm. - */ - public var syncSession: SyncSession? { - return SyncSession(for: rlmRealm) - } -} - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -public extension User { - /// Refresh a user's custom data. This will, in effect, refresh the user's auth session. - /// @returns A publisher that eventually return `Dictionary` with user's data or `Error`. - func refreshCustomData() -> Future<[AnyHashable: Any], Error> { - return future { self.refreshCustomData($0) } - } - - - /// Removes the user - /// This logs out and destroys the session related to this user. The completion block will return an error - /// if the user is not found or is already removed. - /// @returns A publisher that eventually return `Result.success` or `Error`. - func remove() -> Future { - promisify(remove(completion:)) - } - - /// Logs out the current user - /// The users state will be set to `Removed` is they are an anonymous user or `LoggedOut` if they are authenticated by a username / password or third party auth clients - //// If the logout request fails, this method will still clear local authentication state. - /// @returns A publisher that eventually return `Result.success` or `Error`. - func logOut() -> Future { - promisify(logOut(completion:)) - } - - /// Permanently deletes this user from your Atlas App Services app. - /// The users state will be set to `Removed` and the session will be destroyed. - /// If the delete request fails, the local authentication state will be untouched. - /// @returns A publisher that eventually return `Result.success` or `Error`. - func delete() -> Future { - promisify(delete(completion:)) - } -} - -/// :nodoc: -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -@frozen public struct UserSubscription: Subscription { - private let token: RLMUserSubscriptionToken - - internal init(token: RLMUserSubscriptionToken) { - self.token = token - } - - /// A unique identifier for identifying publisher streams. - public var combineIdentifier: CombineIdentifier { - return CombineIdentifier(token) - } - - /// This function is not implemented. - /// - /// Realm publishers do not support backpressure and so this function does nothing. - public func request(_ demand: Subscribers.Demand) { - } - - /// Stop emitting values on this subscription. - public func cancel() { - token.unsubscribe() - } -} - -/// :nodoc: -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -public class UserPublisher: Publisher { - /// This publisher cannot fail. - public typealias Failure = Never - /// This publisher emits User. - public typealias Output = User - - private let user: User - - internal init(_ user: User) { - self.user = user - } - - /// :nodoc: - public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { - let token = user.subscribe { user in - _ = subscriber.receive(user) - } - - subscriber.receive(subscription: UserSubscription(token: token)) - } -} - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -extension User { - /// A publisher that emits Void each time the user changes. - /// - /// Despite the name, this actually emits *after* the user has changed. - public var objectWillChange: AnyPublisher { - return UserPublisher(self).receive(on: DispatchQueue.main).eraseToAnyPublisher() - } -} - -#if compiler(>=6) -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -extension User: @retroactive ObservableObject {} -#else -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -extension User: ObservableObject {} -#endif - -public extension User { - /// Refresh a user's custom data. This will, in effect, refresh the user's auth session. - /// @completion A completion that eventually return `Result.success(Dictionary)` with user's data or `Result.failure(Error)`. - @preconcurrency - func refreshCustomData(_ completion: @escaping @Sendable (Result<[AnyHashable: Any], Error>) -> Void) { - self.__refreshCustomData { customData, error in - if let customData = customData { - completion(.success(customData)) - } else { - completion(.failure(error ?? Realm.Error.callFailed)) - } - } - } - - /// Refresh a user's custom data. This will, in effect, refresh the user's auth session. - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - @discardableResult - func refreshCustomData() async throws -> Document { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - self.__refreshCustomData { _, error in - if let error { - continuation.resume(throwing: error) - } else { - continuation.resume() - } - } - } - return customData - } -} - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -extension FunctionCallable { - /// :nodoc: - @available(*, deprecated, message: "Specify args separately without wrapping them in an array") - public func dynamicallyCall(withArguments args: [[AnyBSON]]) async throws -> AnyBSON { - let objcArgs = args.first!.map(ObjectiveCSupport.convertBson) - let ret = try await user.__callFunctionNamed(name, arguments: objcArgs) - if let bson = ObjectiveCSupport.convertBson(object: ret) { - return bson - } - throw Realm.Error.callFailed - } - - /// The implementation of @dynamicMemberLookup that allows for `async await` callable return. - /// - /// guard case let .int32(sum) = try await user.functions.sum(1, 2, 3, 4, 5) else { - /// return - /// } - /// - public func dynamicallyCall(withArguments args: [AnyBSON]) async throws -> AnyBSON { - let objcArgs = args.map(ObjectiveCSupport.convertBson) - let ret = try await user.__callFunctionNamed(name, arguments: objcArgs) - if let bson = ObjectiveCSupport.convertBson(object: ret) { - return bson - } - throw Realm.Error.callFailed - } -} - -extension User { - /** - Create a flexible sync configuration instance, which can be used to open a realm which - supports flexible sync. - - It won't be possible to combine flexible and partition sync in the same app, which means if you open - a realm with a flexible sync configuration, you won't be able to open a realm with a PBS configuration - and the other way around. - - - parameter clientResetMode: Determines file recovery behavior during a client reset. `.recoverUnsyncedChanges` by default. - - parameter cancelAsyncOpenOnNonFatalErrors: By default, Realm.asyncOpen() - swallows non-fatal connection errors such as a connection attempt timing - out and simply retries until it succeeds. If this is set to `true`, instead - the error will be reported to the callback and the async open will be - cancelled. - - - returns A `Realm.Configuration` instance with a flexible sync configuration. - */ - public func flexibleSyncConfiguration(clientResetMode: ClientResetMode = .recoverUnsyncedChanges(), - cancelAsyncOpenOnNonFatalErrors: Bool = false) -> Realm.Configuration { - let config = __flexibleSyncConfiguration() - setSyncFields(config, clientResetMode: clientResetMode, cancelAsyncOpenOnNonFatalErrors: cancelAsyncOpenOnNonFatalErrors) - return ObjectiveCSupport.convert(object: config) - } - - /** - Create a flexible sync configuration instance, which can be used to open a realm which - supports flexible sync. - - It won't be possible to combine flexible and partition sync in the same app, which means if you open - a realm with a flexible sync configuration, you won't be able to open a realm with a PBS configuration - and the other way around. - - Using `rerunOnOpen` covers the cases where you want to re-run dynamic queries, for example time ranges. - ``` - var config = user.flexibleSyncConfiguration(initialSubscriptions: { subscriptions in - subscriptions.append(QuerySubscription() { - $0.birthdate < Date() && $0.birthdate > Calendar.current.date(byAdding: .year, value: 21)! - }) - }, rerunOnOpen: true) - ``` - - - parameter clientResetMode: Determines file recovery behavior during a client reset. `.recoverUnsyncedChanges` by default. - - parameter initialSubscriptions: A block which receives a subscription set instance, that can be used to add an - initial set of subscriptions which will be executed when the Realm is first opened. - - parameter rerunOnOpen: If true, allows to run the initial set of subscriptions specified, on every app startup. - This can be used to re-run dynamic time ranges and other queries that require a - re-computation of a static variable. - - parameter cancelAsyncOpenOnNonFatalErrors: By default, Realm.asyncOpen() - swallows non-fatal connection errors such as a connection attempt timing - out and simply retries until it succeeds. If this is set to `true`, instead - the error will be reported to the callback and the async open will be - cancelled. - - - - returns A `Realm.Configuration` instance with a flexible sync configuration. - */ - @preconcurrency - public func flexibleSyncConfiguration(clientResetMode: ClientResetMode = .recoverUnsyncedChanges(), - cancelAsyncOpenOnNonFatalErrors: Bool = false, - initialSubscriptions: @escaping @Sendable (SyncSubscriptionSet) -> Void, - rerunOnOpen: Bool = false) -> Realm.Configuration { - let config = __flexibleSyncConfiguration(initialSubscriptions: ObjectiveCSupport.convert(block: initialSubscriptions), - rerunOnOpen: rerunOnOpen) - setSyncFields(config, clientResetMode: clientResetMode, cancelAsyncOpenOnNonFatalErrors: cancelAsyncOpenOnNonFatalErrors) - return ObjectiveCSupport.convert(object: config) - } -} diff --git a/RealmSwift/SyncSubscription.swift b/RealmSwift/SyncSubscription.swift deleted file mode 100644 index d42ec94ad6..0000000000 --- a/RealmSwift/SyncSubscription.swift +++ /dev/null @@ -1,538 +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. - // - //////////////////////////////////////////////////////////////////////////// - -import Combine -import Foundation -import Realm -import Realm.Private - -/// An enum representing different states for the Subscription Set. -@frozen public enum SyncSubscriptionState: Equatable { - /// The subscription is complete and the server has sent all the data that matched the subscription - /// queries at the time the subscription set was updated. The server is now in a steady-state - /// synchronization mode where it will stream update as they come. - case complete - /// The subscription encountered an error and synchronization is paused for this Realm. You can - /// still use the current subscription set to write a subscription. - case error(Error) - /// The subscription is persisted locally but not yet processed by the server, which means - /// the server hasn't yet returned all the data that matched the updated subscription queries. - case pending - /// The subscription set has been superseded by an updated one, this typically means that - /// someone is trying to write a subscription on a different instance of the subscription set. - /// You should not use a superseded subscription set and instead obtain a new instance of - /// the subscription set to write a subscription. - case superseded - - public static func == (lhs: SyncSubscriptionState, rhs: SyncSubscriptionState) -> Bool { - switch (lhs, rhs) { - case (.complete, .complete), (.pending, .pending), (.superseded, .superseded): - return true - case (.error(let error), .error(let error2)): - return error == error2 - default: - return false - } - } -} - -/** - `SyncSubscription` is used to define a Flexible Sync subscription obtained from querying a - subscription set, which can be used to read or remove/update a committed subscription. - */ -@frozen public struct SyncSubscription { - - // MARK: Initializers - fileprivate let _rlmSyncSubscription: RLMSyncSubscription - - fileprivate init(_ rlmSyncSubscription: RLMSyncSubscription) { - self._rlmSyncSubscription = rlmSyncSubscription - } - - /// Name of the subscription, if not specified it will return the value in Query as a String. - public var name: String? { - _rlmSyncSubscription.name - } - - /// When the subscription was created. Recorded automatically. - public var createdAt: Date { - _rlmSyncSubscription.createdAt - } - - /// When the subscription was last updated. Recorded automatically. - public var updatedAt: Date { - _rlmSyncSubscription.updatedAt - } - - /** - Updates a Flexible Sync's subscription with an allowed query which will be used to bootstrap data - from the server when committed. - - - warning: This method may only be called during a write subscription block. - - - parameter type: The type of the object to be queried. - - parameter query: A query which will be used to modify the existing query. - If nil it will set the query to get all documents in the collection. - */ - public func updateQuery(toType type: T.Type, where query: ((Query) -> Query)? = nil) { - guard _rlmSyncSubscription.objectClassName == "\(T.self)" else { - throwRealmException("Updating a subscription query of a different Object Type is not allowed.") - } - _rlmSyncSubscription.update(with: query?(Query()).predicate ?? NSPredicate(format: "TRUEPREDICATE")) - } - - /** - Updates a Flexible Sync's subscription with an allowed query which will be used to bootstrap data - from the server when committed. - - - warning: This method may only be called during a write subscription block. - - - parameter type: The type of the object to be queried. - - parameter query: A query which will be used to modify the existing query. - */ - public func updateQuery(toType type: T.Type, where query: (Query) -> Query) { - guard _rlmSyncSubscription.objectClassName == "\(T.self)" else { - throwRealmException("Updating a subscription query of a different Object Type is not allowed.") - } - _rlmSyncSubscription.update(with: query(Query()).predicate) - } - - /// :nodoc: - @available(*, unavailable, renamed: "updateQuery", message: "SyncSubscription update is unavailable, please use `.updateQuery` instead.") - public func update(toType type: T.Type, where query: @escaping (Query) -> Query) { - fatalError("This API is unavailable, , please use `.updateQuery` instead.") - } - - /** - Updates a Flexible Sync's subscription with an allowed query which will be used to bootstrap data - from the server when committed. - - - warning: This method may only be called during a write subscription block. - - - parameter predicateFormat: A predicate format string, optionally followed by a variable number of arguments, - which will be used to modify the query. - */ - public func updateQuery(to predicateFormat: String, _ args: Any...) { - _rlmSyncSubscription.update(with: NSPredicate(format: predicateFormat, argumentArray: unwrapOptionals(in: args))) - } - - /// :nodoc: - @available(*, unavailable, renamed: "updateQuery", message: "SyncSubscription update is unavailable, please use `.updateQuery` instead.") - public func update(to predicateFormat: String, _ args: Any...) { - fatalError("This API is unavailable, , please use `.updateQuery` instead.") - } - - /** - Updates a Flexible Sync's subscription with an allowed query which will be used to bootstrap data - from the server when committed. - - - warning: This method may only be called during a write subscription block. - - - parameter predicate: The predicate with which to filter the objects on the server, which - will be used to modify the query. - */ - public func updateQuery(to predicate: NSPredicate) { - _rlmSyncSubscription.update(with: predicate) - } - - /// :nodoc: - @available(*, unavailable, renamed: "updateQuery", message: "SyncSubscription update is unavailable, please use `.updateQuery` instead.") - public func update(to predicate: NSPredicate) { - fatalError("This API is unavailable, , please use `.updateQuery` instead.") - } -} - -/** - `SubscriptionQuery` is used to define an named/unnamed query subscription query, which - can be added/remove or updated within a write subscription transaction. - */ -@frozen public struct QuerySubscription { - // MARK: Internal - fileprivate let name: String? - fileprivate var className: String - fileprivate var predicate: NSPredicate - - /// :nodoc: - public typealias QueryFunction = (Query) -> Query - -#if compiler(<6) - /** - Creates a `QuerySubscription` for the given type. - - - parameter name: Name of the subscription. - - parameter query: The query for the subscription, if nil it will set the query to all documents for the collection. - */ - public init(name: String? = nil, query: QueryFunction? = nil) { - self.name = name - self.className = "\(T.self)" - self.predicate = query?(Query()).predicate ?? NSPredicate(format: "TRUEPREDICATE") - } -#else - /** - Creates a `QuerySubscription` which subscribes to all documents of the given type. - - - parameter name: Name of the subscription. - */ - public init(name: String? = nil) { - self.name = name - self.className = "\(T.self)" - self.predicate = NSPredicate(format: "TRUEPREDICATE") - } -#endif - - /** - Creates a `QuerySubscription` for the given type. - - - parameter name: Name of the subscription. - - parameter query: The query for the subscription. - */ - public init(name: String? = nil, query: borrowing QueryFunction) { - // This overload is required to make `query` non-escaping, as optional - // function parameters always are. - self.name = name - self.className = "\(T.self)" - self.predicate = query(Query()).predicate - } - - /** - Creates a `QuerySubscription` for the given type. - - - parameter name: Name of the subscription. - - parameter predicateFormat: A predicate format string, optionally followed by a variable number of arguments, - which will be used to create the subscription. - */ - public init(name: String? = nil, where predicateFormat: String, _ args: Any...) { - self.name = name - self.className = "\(T.self)" - self.predicate = NSPredicate(format: predicateFormat, argumentArray: unwrapOptionals(in: args)) - } - - /** - Creates a `QuerySubscription` for the given type. - - - parameter name: Name of the subscription. - - parameter predicate: The predicate defining the query used to filter the objects on the server.. - */ - public init(name: String? = nil, where predicate: NSPredicate) { - self.name = name - self.className = "\(T.self)" - self.predicate = predicate - } -} - -/** - `SyncSubscriptionSet` is a collection of `SyncSubscription`s. This is the entry point - for adding and removing `SyncSubscription`s. - */ -@frozen public struct SyncSubscriptionSet { - // MARK: Internal - - internal let rlmSyncSubscriptionSet: RLMSyncSubscriptionSet - - // MARK: Initializers - - internal init(_ rlmSyncSubscriptionSet: RLMSyncSubscriptionSet) { - self.rlmSyncSubscriptionSet = rlmSyncSubscriptionSet - } - - /// The number of subscriptions in the subscription set. - public var count: Int { return Int(rlmSyncSubscriptionSet.count) } - - /** - Synchronously performs any transactions (add/remove/update) to the subscription set within the block. - - - parameter block: The block containing the subscriptions transactions to perform. - - parameter onComplete: The block called upon synchronization of subscriptions to the server. Otherwise - an `Error`describing what went wrong will be returned by the block - */ - public func update(_ block: (() -> Void), onComplete: (@Sendable (Error?) -> Void)? = nil) { - rlmSyncSubscriptionSet.update(block, onComplete: onComplete) - } - - /// :nodoc: - @available(*, unavailable, renamed: "update", message: "SyncSubscriptionSet write is unavailable, please use `.update` instead.") - public func write(_ block: (() -> Void), onComplete: ((Error?) -> Void)? = nil) { - fatalError("This API is unavailable, , please use `.update` instead.") - } - - /// Returns the current state for the subscription set. - public var state: SyncSubscriptionState { - switch rlmSyncSubscriptionSet.state { - case .pending: - return .pending - case .complete: - return .complete - case .superseded: - return .superseded - case .error: - return .error(rlmSyncSubscriptionSet.error!) - @unknown default: - fatalError() - } - } - - /** - Returns a subscription by the specified name. - - - parameter named: The name of the subscription searching for. - - returns: A subscription for the given name. - */ - public func first(named: String) -> SyncSubscription? { - return rlmSyncSubscriptionSet.subscription(withName: named).map(SyncSubscription.init) - } - - /** - Returns a subscription by the specified query. - - - parameter type: The type of the object to be queried. - - parameter where: A query builder that produces a subscription which can be used to search - the subscription by query and/or name. - - returns: A query builder that produces a subscription which can used to search for the subscription. - */ - public func first(ofType type: T.Type, `where` query: (Query) -> Query) -> SyncSubscription? { - return rlmSyncSubscriptionSet.subscription(withClassName: "\(T.self)", predicate: query(Query()).predicate).map(SyncSubscription.init) - } - - /** - Returns a subscription by the specified query. - - - parameter type: The type of the object to be queried. - - parameter where: A query builder that produces a subscription which can be used to search - the subscription by query and/or name. - - returns: A query builder that produces a subscription which can used to search for the subscription. - */ - public func first(ofType type: T.Type, `where` predicateFormat: String, _ args: Any...) -> SyncSubscription? { - return rlmSyncSubscriptionSet.subscription(withClassName: "\(T.self)", predicate: NSPredicate(format: predicateFormat, argumentArray: unwrapOptionals(in: args))).map(SyncSubscription.init) - } - - /** - Returns a subscription by the specified query. - - - parameter type: The type of the object to be queried. - - parameter where: A query builder that produces a subscription which can be used to search - the subscription by query and/or name. - - returns: A query builder that produces a subscription which can used to search for the subscription. - */ - public func first(ofType type: T.Type, `where` predicate: NSPredicate) -> SyncSubscription? { - return rlmSyncSubscriptionSet.subscription(withClassName: "\(T.self)", predicate: predicate).map(SyncSubscription.init) - } - - /** - Appends one or several subscriptions to the subscription set. - - - warning: This method may only be called during a write subscription block. - - - parameter subscriptions: The subscriptions to be added to the subscription set. - */ - public func `append`(_ subscriptions: QuerySubscription...) { - subscriptions.forEach { subscription in - rlmSyncSubscriptionSet.addSubscription(withClassName: subscription.className, - subscriptionName: subscription.name, - predicate: subscription.predicate) - } - } - - /** - Removes a subscription with the specified query. - - - warning: This method may only be called during a write subscription block. - - - parameter type: The type of the object to be removed. - - parameter to: A query for the subscription to be removed from the subscription set. - */ - public func remove(ofType type: T.Type, _ query: @escaping (Query) -> Query) { - rlmSyncSubscriptionSet.removeSubscription(withClassName: "\(T.self)", - predicate: query(Query()).predicate) - } - - /** - Removes a subscription with the specified query. - - - warning: This method may only be called during a write subscription block. - - - parameter type: The type of the object to be removed. - - parameter predicateFormat: A predicate format string, optionally followed by a variable number of arguments, - which will be used to identify the subscription to be removed. - */ - public func remove(ofType type: T.Type, where predicateFormat: String, _ args: Any...) { - rlmSyncSubscriptionSet.removeSubscription(withClassName: "\(T.self)", - predicate: NSPredicate(format: predicateFormat, argumentArray: unwrapOptionals(in: args))) - } - - /** - Removes a subscription with the specified query. - - - warning: This method may only be called during a write subscription block. - - - parameter type: The type of the object to be removed. - - parameter predicate: The predicate which will be used to identify the subscription to be removed. - */ - public func remove(ofType type: T.Type, where predicate: NSPredicate) { - rlmSyncSubscriptionSet.removeSubscription(withClassName: "\(T.self)", - predicate: predicate) - } - - /** - Removes one or several subscriptions from the subscription set. - - - warning: This method may only be called during a write subscription block. - - - parameter subscription: The subscription to be removed from the subscription set. - */ - public func remove(_ subscriptions: SyncSubscription...) { - subscriptions.forEach { subscription in - rlmSyncSubscriptionSet.remove(subscription._rlmSyncSubscription) - } - } - - /** - Removes a subscription with the specified name from the subscription set. - - - warning: This method may only be called during a write subscription block. - - - parameter named: The name of the subscription to be removed from the subscription set. - */ - public func remove(named: String) { - rlmSyncSubscriptionSet.removeSubscription(withName: named) - } - - /** - Removes all subscriptions from the subscription set. - - - parameter unnamedOnly: If true, only unnamed subscriptions are removed. - - warning: This method may only be called during a write subscription block. - - warning: Removing all subscriptions will result in an error if no new subscription is added. Server should - acknowledge at least one subscription. - */ - public func removeAll(unnamedOnly: Bool = false) { - if unnamedOnly { - rlmSyncSubscriptionSet.removeAllUnnamedSubscriptions() - } else { - rlmSyncSubscriptionSet.removeAllSubscriptions() - } - } - - /** - Removes zero or none subscriptions of the given type from the subscription set. - - - warning: This method may only be called during a write subscription block. - - - parameter type: The type of the objects to be removed. - */ - public func removeAll(ofType type: T.Type) { - rlmSyncSubscriptionSet.removeAllSubscriptions(withClassName: type.className()) - } - - // MARK: Subscription Retrieval - - /** - Returns the subscription at the given `position`. - - - parameter position: The index for the resulting subscription. - */ - public subscript(position: Int) -> SyncSubscription? { - throwForNegativeIndex(position) - return rlmSyncSubscriptionSet.object(at: UInt(position)).map { SyncSubscription($0) } - } - - /// Returns the first object in the SyncSubscription list, or `nil` if the subscriptions are empty. - public var first: SyncSubscription? { - return rlmSyncSubscriptionSet.firstObject().map { SyncSubscription($0) } - } - - /// Returns the last object in the SyncSubscription list, or `nil` if the subscriptions are empty. - public var last: SyncSubscription? { - return rlmSyncSubscriptionSet.lastObject().map { SyncSubscription($0) } - } -} - -extension SyncSubscriptionSet: Sequence { - // MARK: Sequence Support - - /// Returns a `SyncSubscriptionSetIterator` that yields successive elements in the subscription collection. - public func makeIterator() -> SyncSubscriptionSetIterator { - return SyncSubscriptionSetIterator(rlmSyncSubscriptionSet) - } -} - -/** - This struct enables sequence-style enumeration for `SyncSubscriptionSet`. - */ -@frozen public struct SyncSubscriptionSetIterator: IteratorProtocol { - private let rlmSubscriptionSet: RLMSyncSubscriptionSet - private var index: Int = -1 - - init(_ rlmSubscriptionSet: RLMSyncSubscriptionSet) { - self.rlmSubscriptionSet = rlmSubscriptionSet - } - - private func nextIndex(for index: Int?) -> Int? { - if let index = index, index < self.rlmSubscriptionSet.count - 1 { - return index + 1 - } - return nil - } - - mutating public func next() -> RLMSyncSubscription? { - if let index = self.nextIndex(for: self.index) { - self.index = index - return rlmSubscriptionSet.object(at: UInt(index)) - } - return nil - } -} - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -extension SyncSubscriptionSet { - /** - Creates and commits a transaction, updating the subscription set, - this will continue when the server acknowledge and all the data associated with this - collection of subscriptions is synced. - - - parameter block: The block containing the subscriptions transactions to perform. - - - throws: An `NSError` if the subscription set state changes to an error state or there is and error while committing any changes to the subscriptions. - */ - @MainActor - public func update(_ block: (() -> Void)) async throws { - try await rlmSyncSubscriptionSet.update(block) - } - - /// :nodoc: - @available(*, unavailable, renamed: "update", message: "SyncSubscriptionSet write is unavailable, please use `.update` instead.") - public func write(_ block: (() -> Void)) async throws { - fatalError("This API is unavailable, , please use `.update` instead.") - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension SyncSubscriptionSet { - /** - Creates and commit a transaction, updating the subscription set, - this will return success when the server acknowledge and all the data associated with this - collection of subscriptions is synced. - - - parameter block: The block containing the subscriptions transactions to perform. - - returns: A publisher that eventually returns `Result.success` or `Error`. - */ - public func updateSubscriptions(_ block: @escaping (() -> Void)) -> Future { - promisify { - update(block, onComplete: $0) - } - } -} diff --git a/RealmSwift/Tests/MapTests.swift b/RealmSwift/Tests/MapTests.swift index 60d2325220..44f719d716 100644 --- a/RealmSwift/Tests/MapTests.swift +++ b/RealmSwift/Tests/MapTests.swift @@ -121,38 +121,6 @@ class MapTests: TestCase, @unchecked Sendable { XCTAssertFalse(obj.decimalOpt.contains { $1 == nil }) XCTAssertFalse(obj.objectIdOpt.contains { $1 == nil }) XCTAssertFalse(obj.uuidOpt.contains { $1 == nil }) - - // Map also supports iteration through `SingleMapEntry` being the element. - // We can't create this struct directly so we need to map over the contents of the dictionary - // and test if that element exists. - XCTAssertFalse(obj.int.map(obj.int.contains).contains(true)) - XCTAssertFalse(obj.int8.map(obj.int8.contains).contains(true)) - XCTAssertFalse(obj.int16.map(obj.int16.contains).contains(true)) - XCTAssertFalse(obj.int32.map(obj.int32.contains).contains(true)) - XCTAssertFalse(obj.int64.map(obj.int64.contains).contains(true)) - XCTAssertFalse(obj.float.map(obj.float.contains).contains(true)) - XCTAssertFalse(obj.double.map(obj.double.contains).contains(true)) - XCTAssertFalse(obj.string.map(obj.string.contains).contains(true)) - XCTAssertFalse(obj.data.map(obj.data.contains).contains(true)) - XCTAssertFalse(obj.date.map(obj.date.contains).contains(true)) - XCTAssertFalse(obj.decimal.map(obj.decimal.contains).contains(true)) - XCTAssertFalse(obj.objectId.map(obj.objectId.contains).contains(true)) - XCTAssertFalse(obj.uuid.map(obj.uuid.contains).contains(true)) - XCTAssertFalse(obj.object.map(obj.object.contains).contains(true)) - - XCTAssertFalse(obj.intOpt.map(obj.intOpt.contains).contains(true)) - XCTAssertFalse(obj.int8Opt.map(obj.int8Opt.contains).contains(true)) - XCTAssertFalse(obj.int16Opt.map(obj.int16Opt.contains).contains(true)) - XCTAssertFalse(obj.int32Opt.map(obj.int32Opt.contains).contains(true)) - XCTAssertFalse(obj.int64Opt.map(obj.int64Opt.contains).contains(true)) - XCTAssertFalse(obj.floatOpt.map(obj.floatOpt.contains).contains(true)) - XCTAssertFalse(obj.doubleOpt.map(obj.doubleOpt.contains).contains(true)) - XCTAssertFalse(obj.stringOpt.map(obj.stringOpt.contains).contains(true)) - XCTAssertFalse(obj.dataOpt.map(obj.dataOpt.contains).contains(true)) - XCTAssertFalse(obj.dateOpt.map(obj.dateOpt.contains).contains(true)) - XCTAssertFalse(obj.decimalOpt.map(obj.decimalOpt.contains).contains(true)) - XCTAssertFalse(obj.objectIdOpt.map(obj.objectIdOpt.contains).contains(true)) - XCTAssertFalse(obj.uuidOpt.map(obj.uuidOpt.contains).contains(true)) } func testInvalidated() { @@ -337,7 +305,7 @@ class MapTests: TestCase, @unchecked Sendable { XCTAssertEqual(expected.count, 0) expected = Set((0..<10).map { "key\($0)" }) - for (key, value) in map.asKeyValueSequence() { + for (key, value) in map { XCTAssertEqual(key, value!.stringCol) expected.remove(key) } diff --git a/RealmSwift/Tests/MixedCollectionTest.swift b/RealmSwift/Tests/MixedCollectionTest.swift index 6c4367c022..17a151371f 100644 --- a/RealmSwift/Tests/MixedCollectionTest.swift +++ b/RealmSwift/Tests/MixedCollectionTest.swift @@ -930,7 +930,7 @@ class MixedCollectionTest: TestCase, @unchecked Sendable { iterateNestedCollectionKeyValue(item) } case .dictionary(let d): - for (_, val) in d.asKeyValueSequence() { + for (_, val) in d { iterateNestedCollectionKeyValue(val) } default: diff --git a/RealmSwift/Tests/ModernObjectCreationTests.swift b/RealmSwift/Tests/ModernObjectCreationTests.swift index 01ff9d00f3..7d2b990061 100644 --- a/RealmSwift/Tests/ModernObjectCreationTests.swift +++ b/RealmSwift/Tests/ModernObjectCreationTests.swift @@ -849,14 +849,15 @@ class ModernObjectCreationTests: TestCase, @unchecked Sendable { } func testAddAndUpdateChangedWithExisingNestedObjects() { - try! Realm().beginWrite() - let existingObject = try! Realm().create(SwiftPrimaryStringObject.self, value: ["primary", 1]) - try! Realm().commitWrite() + let realm = try! Realm() + realm.beginWrite() + let existingObject = realm.create(SwiftPrimaryStringObject.self, value: ["primary", 1]) + try! realm.commitWrite() - try! Realm().beginWrite() + realm.beginWrite() let object = SwiftLinkToPrimaryStringObject(value: ["primary", ["primary", 2] as [Any]]) - try! Realm().add(object, update: .modified) - try! Realm().commitWrite() + realm.add(object, update: .modified) + try! realm.commitWrite() XCTAssertNotNil(object.realm) XCTAssertEqual(object.object!, existingObject) // the existing object should be updated diff --git a/RealmSwift/Tests/ObjectCreationTests.swift b/RealmSwift/Tests/ObjectCreationTests.swift index d29770de04..3c1dee4cbd 100644 --- a/RealmSwift/Tests/ObjectCreationTests.swift +++ b/RealmSwift/Tests/ObjectCreationTests.swift @@ -259,17 +259,18 @@ class ObjectCreationTests: TestCase, @unchecked Sendable { ] // test with valid dictionary literals - let props = try! Realm().schema["SwiftObject"]!.properties + let realm = try! Realm() + let props = realm.schema["SwiftObject"]!.properties for propNum in 0.. [Any] { - try! Realm().beginWrite() - let persistedObject = try! Realm().create(SwiftBoolObject.self, value: [true]) - try! Realm().commitWrite() + let realm = try! Realm() + realm.beginWrite() + let persistedObject = realm.create(SwiftBoolObject.self, value: [true]) + try! realm.commitWrite() if map { return [ ["trueVal": ["boolCol": true], "falseVal": ["boolCol": false]], @@ -1451,9 +1459,10 @@ class ObjectCreationTests: TestCase, @unchecked Sendable { } private func invalidValuesForSwiftObjectType(_ type: PropertyType, _ array: Bool, _ map: Bool) -> [Any] { - try! Realm().beginWrite() - let persistedObject = try! Realm().create(SwiftIntObject.self) - try! Realm().commitWrite() + let realm = try! Realm() + realm.beginWrite() + let persistedObject = realm.create(SwiftIntObject.self) + try! realm.commitWrite() if map { return [ ["trueVal": ["boolCol": "invalid"] as [String: Any], "falseVal": ["boolCol": false]], diff --git a/RealmSwift/Tests/ObjectTests.swift b/RealmSwift/Tests/ObjectTests.swift index ec0f220550..f2a5537957 100644 --- a/RealmSwift/Tests/ObjectTests.swift +++ b/RealmSwift/Tests/ObjectTests.swift @@ -1697,7 +1697,6 @@ class ObjectTests: TestCase, @unchecked Sendable { @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) func testCancelTaskWhileWaitingForInitial() async throws { - return; // FIXME // This can't be tested deterministically as it's trying to hit specific // timing windows, so instead spawn a bunch of tasks and hope that at // least one is in each of the interesting states. Not handling all of @@ -1727,7 +1726,7 @@ class ObjectTests: TestCase, @unchecked Sendable { // Actor executors aren't fifo, so we can sometimes prevent the // async opens from ever completing by continuously spawning new // tasks - while waitingForRealm.value > 10 { + while waitingForRealm.value >= 10 { await Task.yield() } } @@ -2054,7 +2053,7 @@ class ObjectTests: TestCase, @unchecked Sendable { let frozen = obj.freeze() XCTAssertTrue(frozen.isFrozen) - try! obj.realm!.write({ obj.boolCol = false }) + try! obj.realm!.write { obj.boolCol = false } XCTAssert(frozen.boolCol, "Frozen objects shouldn't mutate") let thawed = frozen.thaw()! diff --git a/RealmSwift/Tests/ObjectiveCSupportTests.swift b/RealmSwift/Tests/ObjectiveCSupportTests.swift index 523a8ff630..f3a8a58914 100644 --- a/RealmSwift/Tests/ObjectiveCSupportTests.swift +++ b/RealmSwift/Tests/ObjectiveCSupportTests.swift @@ -87,12 +87,6 @@ class ObjectiveCSupportTests: TestCase, @unchecked Sendable { ObjectiveCSupport.convert(object: realm.configuration).inMemoryIdentifier, "Configuration.inMemoryIdentifier must be equal to RLMConfiguration.inMemoryIdentifier") - #if !SWIFT_PACKAGE - XCTAssertEqual(realm.configuration.syncConfiguration?.partitionValue, - ObjectiveCSupport.convert(object: ObjectiveCSupport.convert(object: realm.configuration).syncConfiguration?.partitionValue), - "Configuration.syncConfiguration must be equal to RLMConfiguration.syncConfiguration") - #endif - XCTAssertEqual(realm.configuration.encryptionKey, ObjectiveCSupport.convert(object: realm.configuration).encryptionKey, "Configuration.encryptionKey must be equal to RLMConfiguration.encryptionKey") diff --git a/RealmSwift/Tests/PerformanceTests.swift b/RealmSwift/Tests/PerformanceTests.swift index 5df9ce25c9..6970791628 100644 --- a/RealmSwift/Tests/PerformanceTests.swift +++ b/RealmSwift/Tests/PerformanceTests.swift @@ -435,93 +435,6 @@ class SwiftPerformanceTests: TestCase, @unchecked Sendable { } } - func deleteServerFiles() { - try! FileManager.default.removeItem(at: URL(fileURLWithPath: testDir, isDirectory: true).deletingLastPathComponent().appendingPathComponent("mongodb-realm")) - App.resetAppCache() - } - - func testSyncRealmCacheLookup() { - var config = ObjectiveCSupport.convert(object: RLMRealmConfiguration.fakeSync()) - config.objectTypes = [] - let realm = try! Realm(configuration: config) - - measure(times: 5000) { - _ = try! Realm(configuration: config) - } - realm.invalidate() - deleteServerFiles() - } - - func testSyncRealmCreationCached() { - let config = { - var config = ObjectiveCSupport.convert(object: RLMRealmConfiguration.fakeSync()) - config.objectTypes = [] - return config - }() - nonisolated(unsafe) var realm: Realm! - dispatchSyncNewThread { - realm = try! Realm(configuration: config) - } - - measure(times: 5000) { - _ = try! Realm(configuration: config) - } - _ = realm.configuration - deleteServerFiles() - } - - func testSyncRealmMultithreadedCacheLookup() { - let config = { - var config = ObjectiveCSupport.convert(object: RLMRealmConfiguration.fakeSync()) - config.objectTypes = [] - return config - }() - nonisolated(unsafe) var realm: Realm! - dispatchSyncNewThread { - realm = try! Realm(configuration: config) - } - - measure { - DispatchQueue.concurrentPerform(iterations: 50) { _ in - autoreleasepool { - let realm = try! Realm(configuration: config) - for _ in 0..<500 { - autoreleasepool { - _ = try! Realm(configuration: config) - } - } - realm.invalidate() - } - } - } - _ = realm.configuration - deleteServerFiles() - } - - func testSyncRealmMultithreadedCreationCached() { - let config = { - var config = ObjectiveCSupport.convert(object: RLMRealmConfiguration.fakeSync()) - config.objectTypes = [] - return config - }() - nonisolated(unsafe) var realm: Realm! - dispatchSyncNewThread { - realm = try! Realm(configuration: config) - } - - measure { - DispatchQueue.concurrentPerform(iterations: 50) { _ in - for _ in 0..<500 { - autoreleasepool { - _ = try! Realm(configuration: config) - } - } - } - } - _ = realm.configuration - deleteServerFiles() - } - func testCommitWriteTransaction() { inMeasureBlock { let realm = inMemoryRealm("test") @@ -940,117 +853,3 @@ class SwiftPerformanceTests: TestCase, @unchecked Sendable { } } } - -class SwiftSyncRealmPerformanceTests: TestCase, @unchecked Sendable { - override class var defaultTestSuite: XCTestSuite { -#if !DEBUG && os(iOS) && !targetEnvironment(macCatalyst) - if isRunningOnDevice { - return super.defaultTestSuite - } -#endif - return XCTestSuite(name: "SwiftSyncRealmPerformanceTests") - } - - override func measure(_ block: (() -> Void)) { - super.measure { - autoreleasepool { - block() - } - } - } - - func deleteServerFiles() { - try! FileManager.default.removeItem(at: URL(fileURLWithPath: testDir, isDirectory: true).deletingLastPathComponent().appendingPathComponent("mongodb-realm")) - App.resetAppCache() - } - - var config: Realm.Configuration { - var config = ObjectiveCSupport.convert(object: RLMRealmConfiguration.fakeSync()) - config.objectTypes = [] - return config - } - - func testSyncRealmCacheLookup() { - let config = self.config - let realm = try! Realm(configuration: config) - - measure { - for _ in 0..<1250 { - autoreleasepool { - _ = try! Realm(configuration: config) - } - } - } - realm.invalidate() - deleteServerFiles() - } - - func testSyncRealmCreationCached() { - let config = self.config - nonisolated(unsafe) var realm: Realm! - dispatchSyncNewThread { - // Open on a different thread so that the test hits the path where - // the cache lookup is a miss but there's a cached Realm on a - // different thread - realm = try! Realm(configuration: config) - } - - measure { - for _ in 0..<1250 { - autoreleasepool { - _ = try! Realm(configuration: config) - } - } - } - _ = realm.configuration // ensure realm is still alive until this point - deleteServerFiles() - } - - func testSyncRealmMultithreadedCacheLookup() { - let config = self.config - let realm = try! Realm(configuration: config) - - measure { - DispatchQueue.concurrentPerform(iterations: 50) { _ in - autoreleasepool { - // Ideally we wouldn't measure this and would only measure - // the cache lookups but that'd be much more difficult to set up - let realm = try! Realm(configuration: config) - for _ in 0..<25 { - autoreleasepool { - _ = try! Realm(configuration: config) - } - } - realm.invalidate() - } - } - } - realm.invalidate() // ensure realm is still alive until this point - deleteServerFiles() - } - - func testSyncRealmMultithreadedCreationCached() { - let config = self.config - let realm = try! Realm(configuration: config) - - measure { - DispatchQueue.concurrentPerform(iterations: 50) { _ in - for _ in 0..<25 { - autoreleasepool { - _ = try! Realm(configuration: config) - } - } - } - } - realm.invalidate() // ensure realm is still alive until this point - deleteServerFiles() - } -} - -class SwiftFlexibleSyncRealmPerformanceTests: SwiftSyncRealmPerformanceTests, @unchecked Sendable { - override var config: Realm.Configuration { - var config = ObjectiveCSupport.convert(object: RLMRealmConfiguration.fakeFlexibleSync()) - config.objectTypes = [] - return config - } -} diff --git a/RealmSwift/Tests/RealmTests.swift b/RealmSwift/Tests/RealmTests.swift index ecd0ff6c63..76f625d45b 100644 --- a/RealmSwift/Tests/RealmTests.swift +++ b/RealmSwift/Tests/RealmTests.swift @@ -261,9 +261,10 @@ class RealmTests: TestCase, @unchecked Sendable { } func testDynamicWriteSubscripting() { - try! Realm().beginWrite() - let object = try! Realm().dynamicCreate("SwiftStringObject", value: ["1"]) - try! Realm().commitWrite() + let realm = try! Realm() + realm.beginWrite() + let object = realm.dynamicCreate("SwiftStringObject", value: ["1"]) + try! realm.commitWrite() XCTAssertNotNil(object, "Dynamic Object Creation Failed") @@ -272,12 +273,13 @@ class RealmTests: TestCase, @unchecked Sendable { } func testBeginWrite() { - try! Realm().beginWrite() - assertThrows(try! Realm().beginWrite()) - try! Realm().cancelWrite() - try! Realm().beginWrite() - try! Realm().create(SwiftStringObject.self, value: ["1"]) - XCTAssertEqual(try! Realm().objects(SwiftStringObject.self).count, 1) + let realm = try! Realm() + realm.beginWrite() + assertThrows(realm.beginWrite()) + realm.cancelWrite() + realm.beginWrite() + realm.create(SwiftStringObject.self, value: ["1"]) + XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 1) } func testWriteReturning() { @@ -289,28 +291,30 @@ class RealmTests: TestCase, @unchecked Sendable { } func testCommitWrite() { - try! Realm().beginWrite() - try! Realm().create(SwiftStringObject.self, value: ["1"]) - try! Realm().commitWrite() - XCTAssertEqual(try! Realm().objects(SwiftStringObject.self).count, 1) - try! Realm().beginWrite() + let realm = try! Realm() + realm.beginWrite() + realm.create(SwiftStringObject.self, value: ["1"]) + try! realm.commitWrite() + XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 1) + realm.beginWrite() } func testCancelWrite() { - assertThrows(try! Realm().cancelWrite()) - try! Realm().beginWrite() - try! Realm().create(SwiftStringObject.self, value: ["1"]) - try! Realm().cancelWrite() - XCTAssertEqual(try! Realm().objects(SwiftStringObject.self).count, 0) + let realm = try! Realm() + assertThrows(realm.cancelWrite()) + realm.beginWrite() + realm.create(SwiftStringObject.self, value: ["1"]) + realm.cancelWrite() + XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 0) - try! Realm().write { + try! realm.write { self.assertThrows(self.realmWithTestPath().cancelWrite()) - let object = try! Realm().create(SwiftStringObject.self) - try! Realm().cancelWrite() + let object = realm.create(SwiftStringObject.self) + realm.cancelWrite() XCTAssertTrue(object.isInvalidated) - XCTAssertEqual(try! Realm().objects(SwiftStringObject.self).count, 0) + XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 0) } - XCTAssertEqual(try! Realm().objects(SwiftStringObject.self).count, 0) + XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 0) } func testThrowsWrite() { @@ -1511,16 +1515,6 @@ class RealmTests: TestCase, @unchecked Sendable { @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @available(*, deprecated) // Silence deprecation warnings for RealmOptional extension RealmTests { - @MainActor - func testOpenBehaviorForLocalRealm() async throws { - let realm = try await Realm(downloadBeforeOpen: .always) - _ = try await Realm(downloadBeforeOpen: .always) - _ = try await Task { @CustomGlobalActor in - _ = try await openRealm(actor: CustomGlobalActor.shared, downloadBeforeOpen: .always) - }.value - realm.invalidate() - } - // MARK: - Async Refresh func manuallyAdvancedRealm() throws -> (Realm, String) { diff --git a/RealmSwift/Tests/SwiftBSONTests.swift b/RealmSwift/Tests/SwiftBSONTests.swift deleted file mode 100644 index 7368660cd0..0000000000 --- a/RealmSwift/Tests/SwiftBSONTests.swift +++ /dev/null @@ -1,203 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import XCTest -import Realm -import RealmSwift - -class SwiftBSONTests: XCTestCase { - private func testBSONRoundTrip(_ value: T, - funcName: String = #function, - line: Int = #line, - column: Int = #column) where T: BSON { - let rlmBSON: RLMBSON? = ObjectiveCSupport.convert(object: AnyBSON(value)) - - XCTAssertEqual(rlmBSON as? T, value) - let bson: AnyBSON? = ObjectiveCSupport.convert(object: rlmBSON) - XCTAssertEqual(bson?.value(), value) - } - - func testNilRoundTrip() { - var anyBSONNil: AnyBSON? - var rlmBSONNil: RLMBSON? - XCTAssertNil(ObjectiveCSupport.convert(object: anyBSONNil)) - XCTAssertNil(ObjectiveCSupport.convert(object: rlmBSONNil)) - - anyBSONNil = .null - rlmBSONNil = NSNull() - XCTAssertNil(ObjectiveCSupport.convert(object: anyBSONNil)) - XCTAssertNil(ObjectiveCSupport.convert(object: rlmBSONNil)) - } - - func testIntRoundTrip() { - testBSONRoundTrip(42) - testBSONRoundTrip(Int32(42)) - testBSONRoundTrip(Int64(42)) - } - - func testBoolRoundTrip() { - testBSONRoundTrip(true) - testBSONRoundTrip(false) - } - - func testDoubleRoundTrip() { - testBSONRoundTrip(1.0001220703125) - testBSONRoundTrip(-1.0001220703125) - } - - func testStringRoundTrip() { - testBSONRoundTrip("abc") - } - - func testBinaryRoundTrip() { - testBSONRoundTrip(Data([1, 2, 3])) - } - - func testDatetimeRoundTrip() { - testBSONRoundTrip(Date(timeIntervalSince1970: 42)) - } - - func testObjectIdRoundTxrip() { - testBSONRoundTrip(ObjectId.generate()) - } - - func testDecimal128RoundTrip() { - testBSONRoundTrip(Decimal128("1.234E-3")) - } - - func testRegularExpressionRoundTrip() { - testBSONRoundTrip(try! NSRegularExpression(pattern: "Trolol", options: .caseInsensitive)) - } - - func testMaxKeyRoundTrip() { - testBSONRoundTrip(MaxKey()) - } - - func testMinKeyRoundTrip() { - testBSONRoundTrip(MinKey()) - } - - func testDocumentRoundTrip() throws { - let swiftDocument: Document = [ - "string": "test string", - "true": true, - "false": false, - "int": 25, - "int32": .int32(5), - "int64": .int64(10000000000), - "double": 15.0, - "decimal128": .decimal128(Decimal128("1.2E+10")), - "minkey": .minKey, - "maxkey": .maxKey, - "date": .datetime(Date(timeIntervalSince1970: 500.004)), - "nestedarray": [[.int32(1), .int32(2)], [.int32(3), .int32(4)]], - "nesteddoc": ["a": .int32(1), "b": .int32(2), "c": false, "d": [.int32(3), .int32(4)]], - "oid": .objectId(ObjectId("507f1f77bcf86cd799439011")), - "regex": .regex(try NSRegularExpression(pattern: "^abc", options: [])), - "array1": [.int32(1), .int32(2)], - "array2": ["string1", "string2"], - "null": nil - ] - - let rlmBSON: RLMBSON? = ObjectiveCSupport.convert(object: .document(swiftDocument)) - guard let dictionary = rlmBSON as? NSDictionary else { - XCTFail("RLMBSON was not of type NSDictionary") - return - } - - XCTAssertEqual(dictionary.count, swiftDocument.count) - dictionary.forEach { (arg0) in - guard let key = arg0.key as? String, - let value = arg0.value as? RLMBSON else { - XCTFail("RLMBSON Document has illegal types") - return - } - XCTAssertEqual(swiftDocument[key], ObjectiveCSupport.convert(object: value)) - } - let bson: AnyBSON? = ObjectiveCSupport.convert(object: rlmBSON) - XCTAssertEqual(bson?.value(), swiftDocument) - } - - func testArrayRoundTrip() throws { - // NSNumber does not guarantee that it will preserve the input type of - // ints if the number fits in a smaller type, and in practice it does - // convert everything which fits in Int32 to Int32 on platforms which - // use tagged pointers to represent NSNumber. This means that round-tripping - // to the correct enum case requires Ints to have values that don't fit - // in Int32 on 64-bit platforms, and values which do on 32-bit platforms. -#if arch(i386) || arch(arm) - let swiftArray: Array = [ - "test string", - true, - false, - 25, - .int32(5), - .int64(5000000000), - 15.0, - .decimal128(Decimal128("1.2E+10")), - .minKey, - .maxKey, - .datetime(Date(timeIntervalSince1970: 500.004)), - [[10, 20], [.int32(3), .int32(4)]], - ["a": .int32(1), "b": .int32(2), "c": false, "d": [.int32(3), .int32(4)]], - .objectId(ObjectId("507f1f77bcf86cd799439011")), - .regex(try NSRegularExpression(pattern: "^abc", options: [])), - [10, 20], - ["string1", "string2"], - nil - ] -#else - let swiftArray: Array = [ - "test string", - true, - false, - 25, - .int32(5), - .int64(5000000000), - 15.0, - .decimal128(Decimal128("1.2E+10")), - .minKey, - .maxKey, - .datetime(Date(timeIntervalSince1970: 500.004)), - [[10000000000, 20000000000], [.int32(3), .int32(4)]], - ["a": .int32(1), "b": .int32(2), "c": false, "d": [.int32(3), .int32(4)]], - .objectId(ObjectId("507f1f77bcf86cd799439011")), - .regex(try NSRegularExpression(pattern: "^abc", options: [])), - [10000000000, 20000000000], - ["string1", "string2"], - nil - ] -#endif - let rlmBSON: RLMBSON? = ObjectiveCSupport.convert(object: .array(swiftArray)) - guard let array = rlmBSON as? NSArray else { - XCTFail("RLMBSON was not of type NSDictionary") - return - } - - XCTAssertEqual(array.count, swiftArray.count) - for idx in 0.. Realm { @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) func openRealm(configuration: Realm.Configuration = .defaultConfiguration, - actor: isolated any Actor, - downloadBeforeOpen: Realm.OpenBehavior = .never) async throws -> Realm { + actor: isolated any Actor) async throws -> Realm { #if compiler(<6) - try await Realm(configuration: configuration, actor: actor, downloadBeforeOpen: downloadBeforeOpen) + try await Realm(configuration: configuration, actor: actor) #else - try await Realm.open(configuration: configuration, downloadBeforeOpen: downloadBeforeOpen) + try await Realm.open(configuration: configuration) #endif } @@ -180,13 +179,3 @@ extension Object { self.init(value: value as Any) } } - -extension AsymmetricObject { - public convenience init(value: [String: Any]) { - self.init(value: value as Any) - } - - public convenience init(value: [Any]) { - self.init(value: value as Any) - } -} diff --git a/build.sh b/build.sh index ba76d9743e..f6227f8c4e 100755 --- a/build.sh +++ b/build.sh @@ -6,12 +6,7 @@ # (C) Copyright 2011-2022 by realm.io. ################################################################################## -# Warning: pipefail is not a POSIX compatible option, but on macOS it works just fine. -# macOS uses a POSIX complain version of bash as /bin/sh, but apparently it does -# not strip away this feature. Also, this will fail if somebody forces the script -# to be run with zsh. -set -o pipefail -set -e +set -eo pipefail readonly source_root="$(dirname "$0")" @@ -68,8 +63,7 @@ command: test-catalyst-swift: tests RealmSwift Mac Catalyst framework test-swiftpm: tests ObjC and Swift macOS frameworks via SwiftPM test-ios-swiftui: tests SwiftUI framework UI tests - test-swiftuiserver-osx: tests Server Sync in SwiftUI - verify: verifies docs, cocoapods, swiftpm, xcframework, swiftuiserver-osx, swiftlint, spm-ios, objectserver-osx, watchos in both Debug and Release configurations + verify: verifies docs, cocoapods, swiftpm, xcframework, swiftlint, spm-ios, watchos in both Debug and Release configurations docs: builds docs in docs/output examples: builds all examples @@ -80,7 +74,6 @@ command: examples-tvos-swift: builds all Swift tvOS examples get-version: get the current version - get-ioplatformuuid: get io platform uuid set-version version: set the version set-core-version version: set the version of core to use @@ -95,7 +88,6 @@ command: publish-github: create a Github release for the currently checked-out tag publish-docs: publish a built docs release to the website - publish-update-checker: publish cocoa file with a version to check for update logic publish-cocoapods [tag]: publish the requested tag to CocoaPods prepare-publish-changelog: creates a changelog file to be used in Slack @@ -445,11 +437,6 @@ case "$COMMAND" in exit 0 ;; - "setup-baas") - ruby Realm/ObjectServerTests/setup_baas.rb - exit 0 - ;; - ###################################### # Building ###################################### @@ -554,7 +541,6 @@ case "$COMMAND" in -exec sed -i '' 's/RealmSwift.AsyncOpenTask/RealmSwift.Realm.AsyncOpenTask/g' {} \; \ -exec sed -i '' 's/RealmSwift.UpdatePolicy/RealmSwift.Realm.UpdatePolicy/g' {} \; \ -exec sed -i '' 's/RealmSwift.Notification[[:>:]]/RealmSwift.Realm.Notification/g' {} \; \ - -exec sed -i '' 's/RealmSwift.OpenBehavior/RealmSwift.Realm.OpenBehavior/g' {} \; \ -exec sed -i '' 's/τ_1_0/V/g' {} \; # Generics will use τ_1_0 which needs to be changed to the correct type name. exit 0 @@ -648,11 +634,6 @@ case "$COMMAND" in exit 0 ;; - "test-objectserver-osx") - xctest 'Object Server Tests' -configuration "$CONFIGURATION" -sdk macosx -destination "platform=macOS,arch=$(uname -m)" - exit 0 - ;; - test-swiftpm*) SANITIZER=$(echo "$COMMAND" | cut -d - -f 3) # FIXME: throwing an exception from a property getter corrupts Swift's @@ -683,11 +664,6 @@ case "$COMMAND" in exit 0 ;; - "test-swiftuiserver-osx") - xctest 'SwiftUISyncTestHost' -configuration "$CONFIGURATION" -sdk macosx -destination 'platform=macOS' - exit 0 - ;; - ###################################### # Full verification ###################################### @@ -695,12 +671,10 @@ case "$COMMAND" in sh build.sh verify-cocoapods sh build.sh verify-docs sh build.sh verify-spm-ios - sh build.sh verify-objectserver-osx sh build.sh verify-swiftlint sh build.sh verify-swiftpm sh build.sh verify-watchos sh buils.sh verify-xcframework - sh build.sh verify-swiftuiserver-osx sh build.sh verify-osx sh build.sh verify-osx-debug @@ -801,11 +775,6 @@ case "$COMMAND" in exit 0 ;; - "verify-objectserver-osx") - REALM_TEST_BRANCH="$sha" sh build.sh test-objectserver-osx - exit 0 - ;; - "verify-swiftlint") swiftlint lint --strict exit 0 @@ -933,7 +902,7 @@ case "$COMMAND" in "examples-ios") workspace="examples/ios/objc/RealmExamples.xcworkspace" - examples="Simple TableView Migration Backlink GroupedTableView Encryption Draw" + examples="Simple TableView Migration Backlink GroupedTableView Encryption" versions="0 1 2 3 4 5" for example in $examples; do if [ "$example" = "Migration" ]; then @@ -1016,11 +985,6 @@ case "$COMMAND" in exit 0 ;; - "get-ioplatformuuid") - ioreg -d2 -c IOPlatformExpertDevice | awk -F\" '/IOPlatformUUID/{print $(NF-1)}' - exit 0 - ;; - "set-version") realm_version="$2" version_files="Realm/Realm-Info.plist" @@ -1315,19 +1279,6 @@ case "$COMMAND" in s3cmd put --no-mime-magic --guess-mime-type --recursive --acl-public docs/objc_output/ s3://realm-sdks/docs/realm-sdks/objc/latest/ ;; - "publish-update-checker") - VERSION="$(sed -n 's/^VERSION=\(.*\)$/\1/p' "${source_root}/dependencies.list")" - PRERELEASE_REGEX='alpha|beta|rc|preview' - if [[ $VERSION =~ $PRERELEASE_REGEX ]]; then - exit 0 - fi - - # update static.realm.io/update/cocoa - printf "%s" "${VERSION}" > cocoa - s3cmd put --acl-public cocoa s3://static.realm.io/update/ - exit 0 - ;; - "publish-cocoapods") cd "${ROOT_WORKSPACE}" pod trunk push Realm.podspec --verbose --allow-warnings @@ -1356,7 +1307,6 @@ x.y.z Release notes (yyyy-MM-dd) ### Compatibility * Realm Studio: 15.0.0 or later. -* APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 15.4.0. * CocoaPods: 1.10 or later. * Xcode: 15.3.0-16.1 beta. diff --git a/ci_scripts/ci_post_clone.sh b/ci_scripts/ci_post_clone.sh index 6b867269a7..c8da17df76 100755 --- a/ci_scripts/ci_post_clone.sh +++ b/ci_scripts/ci_post_clone.sh @@ -8,18 +8,12 @@ set -eo pipefail USE_BUNDLE_EXEC='' install_dependencies() { - echo ">>> Installing dependencies for ${CI_WORKFLOW}" - if [[ "$CI_WORKFLOW" == "docs"* ]]; then install_ruby elif [[ "$CI_WORKFLOW" == "swiftlint"* ]]; then brew install swiftlint elif [[ "$CI_WORKFLOW" == "cocoapods"* ]]; then install_ruby - #elif [[ "$CI_WORKFLOW" == "sync"* ]]; then - # elif [[ "$CI_WORKFLOW" == "sync"* ]] || [[ "$CI_WORKFLOW" == "swiftpm"* ]]; then -# sh build.sh setup-baas -# sh build.sh download-core elif [[ "$CI_WORKFLOW" = *"spm"* ]] || [[ "$CI_WORKFLOW" = "xcframework"* ]]; then install_ruby elif [[ "$CI_WORKFLOW" == *"carthage"* ]]; then @@ -30,7 +24,6 @@ install_dependencies() { } install_ruby() { - echo ">>> Installing new Version of ruby" brew install rbenv ruby-build rbenv install eval "$(rbenv init -)" diff --git a/ci_scripts/ci_pre_xcodebuild.sh b/ci_scripts/ci_pre_xcodebuild.sh deleted file mode 100755 index 7b670c9f42..0000000000 --- a/ci_scripts/ci_pre_xcodebuild.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -env - -if [[ "$CI_WORKFLOW" == "sync"* ]]; then - cd .. - sh build.sh setup-baas - sh build.sh download-core -fi diff --git a/contrib/Development.md b/contrib/Development.md index ce1fdfce60..206a0fddde 100644 --- a/contrib/Development.md +++ b/contrib/Development.md @@ -16,16 +16,4 @@ To build Realm against a custom Core branch, update `Package.swift` by updating - .package(url: "https://github.com/realm/realm-core.git", exact: coreVersion) + .package(url: "https://github.com/realm/realm-core.git", branch: "*your-custom-branch*") ], -``` -## Testing - -### Prerequisites - -1. AWS credentials - reach out to your lead or Michael O'Brien. These will need to be added as environment variables to Xcode and/or your shell profile: - - ``` - export AWS_ACCESS_KEY_ID=... - export AWS_SECRET_ACCESS_KEY=... - ``` -2. Run `sh build.sh setup-baas`. This script will dowload go, mongodb, and clone BaaS into `.baas` and prepare everything for the test harness. The version of BaaS that will be run is determined by the commit sha in `dependencies.list/STITCH_VERSION`. diff --git a/dependencies.list b/dependencies.list index d8111aa27e..ca8211e69a 100755 --- a/dependencies.list +++ b/dependencies.list @@ -1,3 +1,2 @@ -VERSION=10.53.1 -REALM_CORE_VERSION=v14.12.1 -STITCH_VERSION=2f308db6f65333728a101d1fecbb792f9659a5ce +VERSION=20.0.0 +REALM_CORE_VERSION=v20.0.0 diff --git a/examples/README.md b/examples/README.md index 6b0739909c..9c3f7069a5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -38,12 +38,6 @@ This simple app shows how to use an encrypted realm. This simple app demonstrates how to define models with inverse relationships using `-linkingObjectsOfClass:forProperty:`. -### Draw - -This is a simple drawing app designed to show off the collaborative features of the [Realm Mobile Platform](https://realm.io/news/introducing-realm-mobile-platform/). - -Any number of users may draw on a single shared canvas in any given moment, with contributions from other devices appearing on the canvas in real-time. - #### Installation Instructions 1. [Download the macOS version](https://realm.io/docs/realm-mobile-platform/get-started/) of the Realm Mobile Platform. @@ -96,8 +90,6 @@ This app demonstrates how to define Projection on Realm Object and how to use it These two targets demonstrate how to use Realm to persist data between an App Clip and its parent. -**Note:** This is only supported for non-synchronized realms. - #### Example Usage For the purpose of this example, the app clip invocation and parent application download is simulated by running each target. diff --git a/examples/ios/objc/Draw/AppDelegate.h b/examples/ios/objc/Draw/AppDelegate.h deleted file mode 100644 index ce76065213..0000000000 --- a/examples/ios/objc/Draw/AppDelegate.h +++ /dev/null @@ -1,25 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -@interface AppDelegate : UIResponder - -@property (strong, nonatomic) UIWindow *window; - -@end diff --git a/examples/ios/objc/Draw/AppDelegate.m b/examples/ios/objc/Draw/AppDelegate.m deleted file mode 100644 index d1fd47b17b..0000000000 --- a/examples/ios/objc/Draw/AppDelegate.m +++ /dev/null @@ -1,105 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "AppDelegate.h" -#import -#import "DrawView.h" -#import "Constants.h" - -@interface AppDelegate () -@property (nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; -@end - -@implementation AppDelegate - -static RLMApp *app; - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - application.applicationSupportsShakeToEdit = YES; - application.idleTimerDisabled = YES; - - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - self.window.rootViewController = [[UIViewController alloc] init]; - - - app = [RLMApp appWithId:@"realm-draw"]; - - // Setup Error Handler - [app syncManager].errorHandler = ^(NSError *error, RLMSyncSession *session) { - NSLog(@"A global error has occurred! %@", error); - }; - - if (app.currentUser) { - RLMRealmConfiguration.defaultConfiguration = [app.currentUser configurationWithPartitionValue:@"foo"]; - self.window.rootViewController.view = [DrawView new]; - } - else { - [self showActivityIndicator]; - [self logIn]; - } - - [self.window makeKeyAndVisible]; - return YES; -} - -- (void)logIn -{ - // The base server path - // Set to connect to local or online host - - // Creating a debug credential since this demo is just using the generated access token - // produced when running the Realm Object Server via the `start-object-server.command` - RLMCredentials *credential = [RLMCredentials credentialsWithEmail:@"demo@realm.io" - password:@"password"]; - - // Log the user in (async, the Realm will start syncing once the user is logged in automatically) - [app loginWithCredential:credential - completion:^(RLMUser *user, NSError *error) { - if (error) { - self.activityIndicatorView.hidden = YES; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Login Failed" message:error.localizedDescription preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:@"Retry" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [self logIn]; - self.activityIndicatorView.hidden = NO; - }]]; - [self.window.rootViewController presentViewController:alertController animated:YES completion:nil]; - } - else { // Logged in setup the default Realm - RLMRealmConfiguration.defaultConfiguration = [app.currentUser configurationWithPartitionValue:@"foo"]; - - self.window.rootViewController.view = [DrawView new]; - } - }]; -} - -- (void)showActivityIndicator -{ - if (self.activityIndicatorView == nil) { - self.activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; - self.activityIndicatorView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | - UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; - } - - [self.window.rootViewController.view addSubview:self.activityIndicatorView]; - self.activityIndicatorView.center = self.window.center; - - [self.activityIndicatorView startAnimating]; -} - -@end diff --git a/examples/ios/objc/Draw/Draw-Info.plist b/examples/ios/objc/Draw/Draw-Info.plist deleted file mode 100644 index ebe95fd6df..0000000000 --- a/examples/ios/objc/Draw/Draw-Info.plist +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - ${PRODUCT_NAME} - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - LSRequiresIPhoneOS - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - UILaunchStoryboardName - LaunchScreen - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - UIInterfaceOrientationPortraitUpsideDown - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/examples/ios/objc/Draw/Draw.entitlements b/examples/ios/objc/Draw/Draw.entitlements deleted file mode 100644 index 806ee819f3..0000000000 --- a/examples/ios/objc/Draw/Draw.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - keychain-access-groups - - $(AppIdentifierPrefix)io.realm.Draw - - - diff --git a/examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/Contents.json b/examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 790ea45439..0000000000 --- a/examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "RealmDraw-60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "RealmDraw-60@3x.png", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "20x20", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "RealmDraw-76.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "RealmDraw-76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "RealmDraw-83.5@2x.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/RealmDraw-60@2x.png b/examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/RealmDraw-60@2x.png deleted file mode 100644 index 6f7813c7b77c0346d1fa9541f6862ea841b31c18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23141 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 diff --git a/examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/RealmDraw-60@3x.png b/examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/RealmDraw-60@3x.png deleted file mode 100644 index a338d63f31780d9ac2b4f48059baf21f290cc1b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28029 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 I#UMfd4}9eG!vFvP diff --git a/examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/RealmDraw-83.5@2x.png b/examples/ios/objc/Draw/Images.xcassets/AppIcon.appiconset/RealmDraw-83.5@2x.png deleted file mode 100644 index 02d4a358ea0196bfa12d740f1e43c6df2148fb29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26860 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 diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Charcoal.imageset/Charcoal@2x.png b/examples/ios/objc/Draw/Images.xcassets/Pencils/Charcoal.imageset/Charcoal@2x.png deleted file mode 100644 index ff66f46932b21a6528f0b6fce5f20c7c59b5795a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21047 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 diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Charcoal.imageset/Contents.json b/examples/ios/objc/Draw/Images.xcassets/Pencils/Charcoal.imageset/Contents.json deleted file mode 100644 index 3b5d0220e9..0000000000 --- a/examples/ios/objc/Draw/Images.xcassets/Pencils/Charcoal.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Charcoal.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "Charcoal@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Contents.json b/examples/ios/objc/Draw/Images.xcassets/Pencils/Contents.json deleted file mode 100644 index da4a164c91..0000000000 --- a/examples/ios/objc/Draw/Images.xcassets/Pencils/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Dove.imageset/Contents.json b/examples/ios/objc/Draw/Images.xcassets/Pencils/Dove.imageset/Contents.json deleted file mode 100644 index d17dde6540..0000000000 --- a/examples/ios/objc/Draw/Images.xcassets/Pencils/Dove.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Dove.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "Dove@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Dove.imageset/Dove.png b/examples/ios/objc/Draw/Images.xcassets/Pencils/Dove.imageset/Dove.png deleted file mode 100644 index 0536f916862ab059733e601be724d96cc9444575..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17278 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 D40!r; diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Dove.imageset/Dove@2x.png b/examples/ios/objc/Draw/Images.xcassets/Pencils/Dove.imageset/Dove@2x.png deleted file mode 100644 index 9b09ff48c8b88a54310e043ee85dbd58686db060..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20953 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 Dfr2C= diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Elephant.imageset/Elephant@2x.png b/examples/ios/objc/Draw/Images.xcassets/Pencils/Elephant.imageset/Elephant@2x.png deleted file mode 100644 index 6668bbeea104b1b8700b8d1e0b523675b42bc5af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21035 zcmeI3c|4Te`^WDwLYAyiR5Oi)EMs2=gN!BnzGY`Hwvl}|6G@69kz_5BCCU;Nsicsy zuZhSO8B4`jf1{pn&GUSp=jZ$TzJ7nYUouzcT%YTl^FG(P?i;WBg1)Xg11%>l000c9 zG*E`5Pj3JK>C#YDQxeA zbwCRTd6J~0OI|U^+uqR~?FV&0yI{N&V1&m{VNi^d0?bTOM?}Y41?`H_2=PH1h3FbP zhPXS*I>8i?wDLg+QUFi1pFK3l)5FUb5u^b75f?$Sx0~TG=noe^cLkX8_JB}x9et<@ z)&~uh6qXWl6crbPO3Mn1O3I4KNC-m3L`0?FA`);>Ss_tz1nEmc1p3DXL(-Bgc^@Zd zgds}pkKsuFQ-Hbp`FSJY@W8-8;XrX=td9#xE-MQc5rd0~36VU6e1pCG?1O~7e7S!H z`Bxki+Skzsf2_;X``18TzJIVIQG^HCd&5PAMc{uY za&r8o@%H!e_%SsnM>yI8?TPmC^Cjs-|5oSiiuJ?#x?=xf$Y0(6I525XbaZ}={kQY= z^!(eQef?AeNC^Hw`nO77<6v(z+z{=H_4jc^s|Jvk!u@xv_ro~<-kd*0+V1(sxub(H z{~+7$`6>I63O{#9p0rkkiVxb}59?!$#d;urW|{uK5JFW{wzEo7SQ5&oWABLZ+9t~{ z5C5y|_n!Zbh(_7_p^@7;DI_8*Bqm}kCW?@hL5Pb8iAW@ z^6=k{WS+k=3!#Gb!1|D;g+_|Y!~d?CO!Xt%5U0F+{p`IQ(Wg*IQiCuC^;bQwrBr;9pZ1o{M%3(7++G329xD3DT9C1Gb8kwKezs?!vpi9E_vJg z_@cK@odWD%$L?1l`&Fm5H9t--!rpPaq9Gl(4+Yvu9{#7+-_!b~{6mKMZ#VE~d@}hz zj}hpK_WIur)Xz{qy#6_eFV@*F(B22F>_W6}n1i6x|=MUx2%_6(8|Jci)t)#~W(i02(*CWfH zxB1s`_+Pk=RL`3eei;JAbj`l9D9TAZ` z?BXJ)v7^0Y3yk4;@S}rxx+3lavD3@ySR2lMDDPQi=4)e_Aag+5s^FW z;v%Q9qrHo3M?~ZfyST_{>}c=e+7S`C!!9mz8avv%xOPND?y!rCoW_p!F0LIBkvr_- zBB!yVy^Cu{MC1;;xX5YjXz$|M5fQn=E-rE!JKDRrc0@$(u#1bF#*X$bt{oAPJM7{j zr?I2`zv80({SqMBi}dDSAn6r9FjS6`^m-uFQNvIN077^G02c)S#4Xb2GywRE0KjKE z06<&^01j-D?MpQPI2d>erEDBDFl#+?_3%r7#XHMK%PNwd9XKV!YG7mra2h)P+c zLl2eV#nkL_(BJxot00erJ)MSGi{X*d;65Pu za~gJwojL5oNS8P+Ucc^TO9^FpGRUK0gy!^w#kDGbM20`T-Ir8FSDyH_^ajpe;aCP86vGY!&$GnYO^HS-+LSbeMAvUZ)bhGbJ_!%JNW)kMBljB zxEPM7`4!#f#++j=!>few8E2}peWRfzR*8UX-)L9-z;2f8SxSc`YdFB`DV0=uT&UJz#o_ zS1;2FtQ1No9uw;wQD<=q9#1u2YnwH!LV51Srp};4yDQKXW%@A#1~xx>Hevyg5}eMv z4`d?4L+F7y{&i_(zYQZjg@=hCL5qm>G3hxdoA~e z?FE~^4d($-E}LXe(AymplUvh!2-csz1cD7Iqt1c^->cp-mAtW!wY+?Il>%(O694UV z_lEgr?CV%(ygE(nve`;_C3Tqr5l~OKS$z(7@7^pOvjv#0n+85Atm zpfE4Ri1nA1^dln=A4neBb31W-{BnR84*a_PR)#040^`~l81GBX1ZSoyZO_*;FeMZ;SG!ld zj>_u;F;p+pp5aY#m<<j@L1KKvz)Kl7xu z>EVXy%n8a!zDlZkCJ~R&%eUB>LgP3qC~Mz+AG8@}s$3=PiH!xlIdLZ|%md8y-t*@C z)w#0uLlm)q6`lqLsA&Lug48$8DaO=IDECppE2p~N@PV(7KUZVl2Y$}ya9jC-X_|zf z6oaOgBgEE%PKVITKuH-)1;sIJeT#(5q5<((^)9Mzq=BTq=!-IJar6wkMo9ukF3oA; znMIZhz!66{#$wl^2XkGh zjW^_PuRteGU1JK!nv<`>gS6>R@0p7##4k*K?OhYL9d@5020c(yEos+iUp3nTF?mzM zt-`K=v{S1rR6016XV~tBBgBjzU|idx%(5^Q0l?<+1;y$F8ZYov6pr8B@I7PO9sTat zmsARBZ96o_B3+wCm8nFUa5d4Fqm5vw5(CI6p7qPdqHjLGVZa1`n|jiFDhu^7&V8qC zLRZ^`=(F3k=Lb*a2ud1KJR5thcPUf6_DEn4Ln2pp*)@oIgv-}7G>)H2KT(_)VjU&p z18rwDGto4V6_P3}#)m{MypHF*U;s*Wv0ua_fs8+0E$5~VTkJjFh{&51E4g4;2Ro z<-tSYMctSOh8L|sw{%z-_eEYox`o=1>vL&fp#1rt57WeQ3X+@?G@ z8V3X>j^#k*BKx!1FzC6G3)b~@@2*$Gs>|IeB-w?iU*6=a9JBHTIOb|KBYM<0@U#Z!>ERe8VA7(dC*K~lG zX6X;mM%yhJKCjbON9u9Ax*+)o%GY2(aQUl@)Vw86`ON)=wRG1sY6r(^-xxLR>pEC`uPG zey%wZC*8nzPY+$i7IHRApqeYvz;&6=p>A4kxc{*beu5xQz4 zm(}=^t$|5Sr?>4nQR&4um`RH+{T`*0u9NN1-gTuJcs3JKKix-wp~z(4NZM8L8PHUw zpw|ub%Z`P;K?t@D86H&0vv?zEg{F7tY1@cdNhj{^Z)&RW;*k9Hc)Q#K9uBwM>J9y#7NcV`pS zfAGBi3qHzILN9qH-LBi19eS?0{;b(8uXf`L99xI_5ULbQ*>qo>NOh3i0Bvh`=*%F) z!yePl583M{Jt_y1d({`>X0WS6X@O=9Y>zY~4(l2}OFpI_T|yJ2Bnm)WTNx`X(xi}W z_g|k=*UHnobq%CJk?+WbKQ>n?8kWtIre=E;g9bEw#m7Fqz-QZ8<(f$IK?>&1BnqA4 zhPVzcje5LnG~=NNxe{Bp0MrFLPlHfa=W{NnHhk%a@k~GS`1Wn;@#<+Q5f2^CyOw+2 z_HtL&Ml?t&ZoSPKZVv*&&j#d2Yxjm{n1-CRK&_poXI-u?Kx>S8MYrCUF1c^hBpA*E zz(!uEA)J*W-f1q&ESKiHM%}bz*4f(2KQa)Boi!T&FqkwR&#^gsF?v(4)%oG)m6t=5 z7q4a7`%Q1m94<^iuclno4ovmNIC#t!&Kyar>C<3fJ3C+zh?uEip5BsTDA%pIM~qHU zH5)q1j0$K}TzIs`C3lX+PHtwSfD2MkbKxYa~J}FV`wRc9Z+G>K@Z0B^r zN8Ku0*#z`!xiR~Fmb&P=H>p2?{U3o1#Ur{GSmV|{5SL4BEFyu#fyPm9CQ-lO{Ic97 zu=Sd6{4vZJuVtr#^l+ghPC{Bd0bsnbQk_dw8X{aA5Dd%yOg#{Hta>#sf5GUPZ1lOu z%-9Wz?_m9VR%gO{Q#GmhazvSLf~jW5rJ-gV4T<1`Ls(?dd`Ie-np2va_kF1v96{HSXDZI^}U$F*JPw>f1D=n z9G9OnuOKnHYF+8H>eOaX*l>C=4-Q+ql4=Xomoz=qxCSpT;b-53e{{O`#`IG*;o%-y zmPW=sbEX1pO2KYjusRtrHjxXU2v+_x`kQ1`0&&XbRx@3euIlmLOU#fhl`cA|6 zZT53-&oBhcc*V2a?9mL*gM?m@iO7yL5R3p!8h$=J^Q~xEKU!9z2%%|49<0vNZM89=11`|)9m!GlZ8M@R+TifQ(GnZ)_Kq4 zgsj)E@<9C;@SrD8!fJH79{U!K%{EgPMhs-HXEaiuP`gyLzeqi4RF!%0(lDeAa!AN- z(}TBkjAAvkDVK}ifwdL-?W7R@QVhYYM+5Eh@PqAFaFdThO(Ub9w=1WHZ^juE->in$ zq@h{W06L+c{cV$^lz>FKN5rJ<=4wig>qzN`En>v*9e;a2koL6Q!tj&#H_9I-`R>28 zajWZTE{nx#s-$*n1&yBbDOj=z1%j}G&j&^-KTBQ-F`2X`mgOwqX$;cswa$N;%6bx( zEfw*az_^bx8$R+;KFr+asaxJWeqd$K>@9J<;=641%Qma&o=Rns5Qr%Y#I>kx<~8-B z(i+)UdVb@f1gl4-h5)8NE5N^4YZ82|*VAPJalV=Az3WXxMB=-Q19r4uGJ?}BCsl&O z?T~E`F1OV+H6=23y`~%I%YwbiTH@=qlA2eCE8gGi$kT#Qv|sNzU0h+#wnb-tvCA_^ z_vI(33IkXx^3m{h8~RquB|F>#H|#>9fV9_#GsWVtMT`_GYA*)52UbpZG>p6U1IS9tG?)hD7B9*V_1L*(!CzU;tT2W3_R&J zzSn=}g-Wfp?FW@Sm;QZxJm;hSC_jUbI{fZ>E|d;-fBSjTjNK zLMLqi8I$L1CkAw1j$7%BW|z7)U&t&1L#C{(;FBxXn#RslS0)f+N}I|s8Wjtl5|Q)k z)t?k#Rs5}$HdEO|JPV``638H!*wZZQpx0wp(75A3t6$hdv`KF9tg zylT8HboQp=Mqn=+Ei_jlsKmnB&>gu2ucbZbNn1Y7WqC2p;_#W4H7!m>U}Q8`#(b8I zmU%5x@w=HQq~M6{ouRD)srWOtg+gp*qbmxpQ`VE3Xcg8vhOtYp2MZ$-k4(q*_8QM% z5WRGg*VFjY(l%aQpAShPsEE9B(8;<(`|9$+<%`Z8N)Z0Rw1+%Pcv5wSi$N7rr`JAl z6|%#{OGuBRYw@IY<+<>amuFJyG@p*e7?C?TIg)x zd4f?5@yvx+PrpTp(8ylyD#0-pJFmBCMNG?ps$;);6R7c5f@0>g?}VnkneST7I<9zv zIK`zhPuKBQ(P*ucMf1F5gvNJj)wsO~Ye|UnUcjhC`HCd}^I7{^}T?1P}M z_1H-STVHc;9<>;ijY`ZL>J-xM_F-v@4uaSy2K#kqIEy#)tv+*W&9*B%pqM7&Q|Ld2 z#Ah9UoAa=jc4WP1_(@uCW1h&`eaCT$o9ZkOUO4H5u0! z_%a!d@+EjGBwVGE;y2HrRZc*j<{UUHg1R{H5uOb7&}m<_U&6obSG_lETfRWoAg8$5 zBz;el@`{Zj0)$&zG%G$_X5;!X4BGHE+}j#FBNaixiN6;l!d!puWgb6_v!(UC#R|d& z`R4OfKk4T^pY9vIs!RB5mxi;n>GOh-l`8uLD@OP-i>??RJ{MiZIY z#km9)w<1D{SZ$w-z3W9zSEK+WKdlw!)-nPv@xnc) zwwx-oyp%Q7xa6x|<;E$axZIV2s$gbf1)t0xIkPHDpmH(w*Bd7o>82YN?fst1z6ei# zKKY!G`Yw^6sHRz+`PFxF1`*NzMHf_`z98w{?>;C!E)mb@H;C#?URaB$Xmv1tU@-W} zNQhY0{g(RkMkg3`M~xXWMGN50)%5Gol(p0`_uQ|xI)bEs&o{Y{#gKFY^+|5?9%f7(ch?|m zy-M^*s$CvtZ&JzwuAW|_6G!h}OO4a*lhDP-b3VP!8NNC0FAdT%8ecj~P%b|Qasn(4 zD6vyr0Xf$^Tx^e`@^)3vXsS2KzMFW<}fr0emb` WJW=-U7f64u0ZysvqVAovJNrMcFv48` diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Flamingo.imageset/Contents.json b/examples/ios/objc/Draw/Images.xcassets/Pencils/Flamingo.imageset/Contents.json deleted file mode 100644 index 0bc5bcf5bc..0000000000 --- a/examples/ios/objc/Draw/Images.xcassets/Pencils/Flamingo.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Flamingo.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "Flamingo@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Flamingo.imageset/Flamingo.png b/examples/ios/objc/Draw/Images.xcassets/Pencils/Flamingo.imageset/Flamingo.png deleted file mode 100644 index e515ad0b1e06a91a44f5e9a98ffcce79d16be965..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17335 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 diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/GrapeJelly.imageset/GrapeJelly@2x.png b/examples/ios/objc/Draw/Images.xcassets/Pencils/GrapeJelly.imageset/GrapeJelly@2x.png deleted file mode 100644 index ba4933ff3c5c871727770bcb87a8ab12cc1240a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21035 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 JC0l&r{}0!m1`z-N diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Indigo.imageset/Indigo@2x.png b/examples/ios/objc/Draw/Images.xcassets/Pencils/Indigo.imageset/Indigo@2x.png deleted file mode 100644 index a7d3e0d76ab6c8f4f7d0a89a41ff70f1f4af292c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21043 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%0AwS6b!ZAsUZ&RT=laJzL8gE@jN)g zIHEi))P%g`(zdcrKC?op(nh+@>&Qp?0ihYmhp=yQ+H$qJWmMhwb4TQ@24wUvHsk4y zK&yR`8$>2hyC7S1!9a}}Kl8P~%#wx&a>M3Bb@9A6Om2N}KFs8>I=r4y;2kEnBr&Tg zc!ZMCE$Uj>^-#z}2AQN%4YwIRf#@wOt3)=|lxT;hBt6uO&67@N77JTdJB-5PgHre< zovfB>%V+y~zO0<`2(|b;jjMn%s-&M7F(J{JAVP-(D|O^S0SAXaPB7CPnr8g;^lnhn zyQ$XYbU`H%@({P`)UM`wC8L#=y;^k0P>lt;;}>_L%#gg8!O_}@u!3Nl=6O(GVv*A7 z$enjU^Se1==tSYPZF{X+Z19cyS#IR;^IyRa4s6i)R3pGkO+1(!zpl8zc!OCCze?+Q_ed^=R-fL@Nj^{1dQJCn>xpZf z*+C9FnrFc<&Z>DXrI=I|++8+O*ZxXBrahLHJ9+O2WDCCQS3(gMT;S?)LL8)c6UCC2sYGalo}IzB$NUyG&Wj`LauS$S}b zuVXU5YiY;l66>|02m1wRH1dP-jDY{Vb)k$?MPn6NIVyFfuN>0uzDg@i%j4zGM{uf# z>##Iw^akIE$j5(uksF&vxgUw2&O2)<&1?E*X>IzYe)6D^{1=kSuGr6y8IB}gHKYSw zSUsA=JZk2c^QB)iPg04+Xa*cZzEJpEt*1^!Lr2IeVcLCBMG&`V>Z5eIy5a$?wbB{e z0Kt0{aT8pJx0~7zpwmksggo({nCFCu{6vVz7SpM_^8U&Mi zlAQuqDyZ(a4hj64c=H8k)cWTNgQI&`8}!8TtT5(^dc)PpP6q_pk9a=ZuT{sXxY*qXQr`=>(JZ_lzS{{G&PVns@l!h;6u?R!x&We?#K_m zrYSWo8Poh3rLF=Y!2!E5lPm;##k}#=J9#CMESQ3pn3eH+CvqabN>f_0@>RD diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Melon.imageset/Contents.json b/examples/ios/objc/Draw/Images.xcassets/Pencils/Melon.imageset/Contents.json deleted file mode 100644 index 207cd17fa2..0000000000 --- a/examples/ios/objc/Draw/Images.xcassets/Pencils/Melon.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Melon.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "Melon@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Melon.imageset/Melon.png b/examples/ios/objc/Draw/Images.xcassets/Pencils/Melon.imageset/Melon.png deleted file mode 100644 index 49d44cecca15b6eb26a60213a4263de750d96555..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17304 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 diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Mulberry.imageset/Contents.json b/examples/ios/objc/Draw/Images.xcassets/Pencils/Mulberry.imageset/Contents.json deleted file mode 100644 index 24fa578dba..0000000000 --- a/examples/ios/objc/Draw/Images.xcassets/Pencils/Mulberry.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Mulberry.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "Mulberry@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Mulberry.imageset/Mulberry.png b/examples/ios/objc/Draw/Images.xcassets/Pencils/Mulberry.imageset/Mulberry.png deleted file mode 100644 index d59b0f9cf733adf009ad70377c849e63f2918425..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17312 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 OMlCfx)e>c^kpBaM2LSN^ diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Peach.imageset/Contents.json b/examples/ios/objc/Draw/Images.xcassets/Pencils/Peach.imageset/Contents.json deleted file mode 100644 index 334d3c3afb..0000000000 --- a/examples/ios/objc/Draw/Images.xcassets/Pencils/Peach.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Peach.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "Peach@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Peach.imageset/Peach.png b/examples/ios/objc/Draw/Images.xcassets/Pencils/Peach.imageset/Peach.png deleted file mode 100644 index a96e1926b5fe0da29d669de9aa2b2a6d696db135..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17303 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 NLPJMA_n7th{{x=JDLDWD diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/SexySalmon.imageset/Contents.json b/examples/ios/objc/Draw/Images.xcassets/Pencils/SexySalmon.imageset/Contents.json deleted file mode 100644 index 95c68cb523..0000000000 --- a/examples/ios/objc/Draw/Images.xcassets/Pencils/SexySalmon.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "SexySalmon.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "SexySalmon@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/SexySalmon.imageset/SexySalmon.png b/examples/ios/objc/Draw/Images.xcassets/Pencils/SexySalmon.imageset/SexySalmon.png deleted file mode 100644 index 0b3c3586c9da6fd257c667d0271f072e417f274f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17301 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 diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/SexySalmon.imageset/SexySalmon@2x.png b/examples/ios/objc/Draw/Images.xcassets/Pencils/SexySalmon.imageset/SexySalmon@2x.png deleted file mode 100644 index 61030e0d6e048375fc3473150e218d400a395c01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21036 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 a1AANDr(E}@?xOs^22fYgRxVXSNBkd{UD#{@ diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Ultramarine.imageset/Contents.json b/examples/ios/objc/Draw/Images.xcassets/Pencils/Ultramarine.imageset/Contents.json deleted file mode 100644 index d25fa27653..0000000000 --- a/examples/ios/objc/Draw/Images.xcassets/Pencils/Ultramarine.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Ultramarine.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "Ultramarine@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Ultramarine.imageset/Ultramarine.png b/examples/ios/objc/Draw/Images.xcassets/Pencils/Ultramarine.imageset/Ultramarine.png deleted file mode 100644 index 21294dbaba60bcb9305fb9ff5525c83ccacedcaf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17321 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 NEX{0)c|W`D|3BeG7|{R# diff --git a/examples/ios/objc/Draw/Images.xcassets/Pencils/Ultramarine.imageset/Ultramarine@2x.png b/examples/ios/objc/Draw/Images.xcassets/Pencils/Ultramarine.imageset/Ultramarine@2x.png deleted file mode 100644 index 9343f96fb1ce0f777f365765330eb732b79f5a69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21023 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¥L`6Zf5mz znB%I~2cJF!gNeyU}Zle=&ey!tgfXb{^Dd(cTCr%BvoRG2Nurz_;r84J)>I_^;ab1e>9L6q@ zv12X$6}tHv?9end5O9_5hNDV=>9>gd>(wJqzG5O@u*LPCc^eBc9G-Q0n*X(-KaeuH z#9M;?h~?x;SZZgZdgiErsbN74#y*N{)cZll-C12H&J8#S7`piReQ57&V0dSSaY%f^ z9XBDroZ6Yt&h3qi{rRXOkW6#Vlyzw`8J+UB_!YP3LAKFZgv~m2kE#DYUvGCBHb&aY zuovxuC^TKGY9ifaIUm71bxWqqB1TRojmKX6!et3YCTur>=|w6ZaQGI8+v>8!jdbqe zBS}$ov0twM$)$xVfyPIbkMWs!WK&S)tjlyg_Q;8#blC@ca?roKaF(a?O~y&HMvfa- zAGZ~fpHh3rFfl=MJF=re?Zgp#&h$!^2=g~X3bk9$?kKnjgPE&h13)+oV8nr -#import -#import "DrawPoint.h" - -@interface DrawPath : RLMObject - -@property BOOL completed; // Set to YES once the user stops drawing this particular line -@property NSString *color; // The name of the color that this path is drawn in -@property RLMArray *points; - -@property (readonly) UIBezierPath *path; - -@end diff --git a/examples/ios/objc/Draw/Models/DrawPath.m b/examples/ios/objc/Draw/Models/DrawPath.m deleted file mode 100644 index 843454aea4..0000000000 --- a/examples/ios/objc/Draw/Models/DrawPath.m +++ /dev/null @@ -1,40 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "DrawPath.h" - -@implementation DrawPath - -@dynamic path; - -- (UIBezierPath *)path { - UIBezierPath *path = [UIBezierPath bezierPath]; - path.lineWidth = 4.0f; - NSUInteger index = 0; - for (DrawPoint *point in self.points) { - if (index == 0) { - [path moveToPoint:point.cgPoint]; - } else { - [path addLineToPoint:point.cgPoint]; - } - index++; - } - return path; -} - -@end diff --git a/examples/ios/objc/Draw/Models/DrawPoint.h b/examples/ios/objc/Draw/Models/DrawPoint.h deleted file mode 100644 index c8c293b5f5..0000000000 --- a/examples/ios/objc/Draw/Models/DrawPoint.h +++ /dev/null @@ -1,31 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import -#import - -@interface DrawPoint : RLMObject - -@property double x; -@property double y; - -@property (readonly) CGPoint cgPoint; - -@end - -RLM_COLLECTION_TYPE(DrawPoint) diff --git a/examples/ios/objc/Draw/Models/DrawPoint.m b/examples/ios/objc/Draw/Models/DrawPoint.m deleted file mode 100644 index 04a656a563..0000000000 --- a/examples/ios/objc/Draw/Models/DrawPoint.m +++ /dev/null @@ -1,27 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "DrawPoint.h" - -@implementation DrawPoint - -- (CGPoint)cgPoint { - return CGPointMake(self.x, self.y); -} - -@end diff --git a/examples/ios/objc/Draw/Models/UIColor+Realm.h b/examples/ios/objc/Draw/Models/UIColor+Realm.h deleted file mode 100644 index 561569bc55..0000000000 --- a/examples/ios/objc/Draw/Models/UIColor+Realm.h +++ /dev/null @@ -1,24 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import -#import - -@interface UIColor (Realm) -+ (NSDictionary *)realmColors; -@end diff --git a/examples/ios/objc/Draw/Models/UIColor+Realm.m b/examples/ios/objc/Draw/Models/UIColor+Realm.m deleted file mode 100644 index 81df5f09c6..0000000000 --- a/examples/ios/objc/Draw/Models/UIColor+Realm.m +++ /dev/null @@ -1,43 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "UIColor+Realm.h" - -@implementation UIColor (Realm) -+ (NSDictionary *)realmColors { - static NSDictionary *realmColors; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - realmColors = - @{ - @"Charcoal": [UIColor colorWithRed:28.0f/255.0f green:35.0f/255.0f blue:63.0f/255.0f alpha:1.0f], - @"Elephant": [UIColor colorWithRed:154.0f/255.0f green:155.0f/255.0f blue:165.0f/255.0f alpha:1.0f], - @"Dove": [UIColor colorWithRed:235.0f/255.0f green:235.0f/255.0f blue:242.0f/255.0f alpha:1.0f], - @"Ultramarine": [UIColor colorWithRed:57.0f/255.0f green:71.0f/255.0f blue:127.0f/255.0f alpha:1.0f], - @"Indigo": [UIColor colorWithRed:89.0f/255.0f green:86.0f/255.0f blue:158.0f/255.0f alpha:1.0f], - @"GrapeJelly": [UIColor colorWithRed:154.0f/255.0f green:80.0f/255.0f blue:165.0f/255.0f alpha:1.0f], - @"Mulberry": [UIColor colorWithRed:211.0f/255.0f green:76.0f/255.0f blue:163.0f/255.0f alpha:1.0f], - @"Flamingo": [UIColor colorWithRed:242.0f/255.0f green:81.0f/255.0f blue:146.0f/255.0f alpha:1.0f], - @"SexySalmon": [UIColor colorWithRed:247.0f/255.0f green:124.0f/255.0f blue:136.0f/255.0f alpha:1.0f], - @"Peach": [UIColor colorWithRed:252.0f/255.0f green:159.0f/255.0f blue:149.0f/255.0f alpha:1.0f], - @"Melon": [UIColor colorWithRed:252.0f/255.0f green:195.0f/255.0f blue:151.0f/255.0f alpha:1.0f] - }; - }); - return realmColors; -} -@end diff --git a/examples/ios/objc/Draw/Views/CanvasView.h b/examples/ios/objc/Draw/Views/CanvasView.h deleted file mode 100644 index 72dc6cdb5b..0000000000 --- a/examples/ios/objc/Draw/Views/CanvasView.h +++ /dev/null @@ -1,30 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import -#import - -@class DrawPath; - -@interface CanvasView : UIView - -@property (nonatomic, strong) RLMResults *paths; - -- (void)clearCanvas; - -@end diff --git a/examples/ios/objc/Draw/Views/CanvasView.m b/examples/ios/objc/Draw/Views/CanvasView.m deleted file mode 100644 index 200c16f1c8..0000000000 --- a/examples/ios/objc/Draw/Views/CanvasView.m +++ /dev/null @@ -1,52 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "CanvasView.h" -#import "DrawPath.h" -#import "UIColor+Realm.h" - -@implementation CanvasView - -- (void)didMoveToSuperview { - [super didMoveToSuperview]; - self.backgroundColor = [UIColor whiteColor]; -} - -- (void)drawPath:(DrawPath *)path withContext:(CGContextRef)context { - UIColor *swatchColor = [UIColor realmColors][path.color]; - CGContextSetStrokeColorWithColor(context, [swatchColor CGColor]); - CGContextSetLineWidth(context, path.path.lineWidth); - CGContextAddPath(context, [path.path CGPath]); - CGContextStrokePath(context); -} - -- (void)drawRect:(CGRect)rect { - CGContextRef context = UIGraphicsGetCurrentContext(); - for (DrawPath *path in self.paths) { - [self drawPath:path withContext:context]; - } -} - -- (void)clearCanvas { - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); - CGContextFillRect(context, self.bounds); - [self setNeedsDisplay]; -} - -@end diff --git a/examples/ios/objc/Draw/Views/DrawView.h b/examples/ios/objc/Draw/Views/DrawView.h deleted file mode 100644 index aeda90ace7..0000000000 --- a/examples/ios/objc/Draw/Views/DrawView.h +++ /dev/null @@ -1,22 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -@interface DrawView : UIView -@end diff --git a/examples/ios/objc/Draw/Views/DrawView.m b/examples/ios/objc/Draw/Views/DrawView.m deleted file mode 100644 index 50d45203ac..0000000000 --- a/examples/ios/objc/Draw/Views/DrawView.m +++ /dev/null @@ -1,181 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "DrawView.h" -#import "DrawPath.h" -#import "SwatchesView.h" -#import "CanvasView.h" -#import "UIColor+Realm.h" -#import - -@interface DrawView () - -@property DrawPath *drawPath; -@property NSString *pathID; -@property RLMResults *paths; -@property RLMNotificationToken *notificationToken; -@property CanvasView *canvasView; -@property SwatchesView *swatchesView; -@property NSString *currentColorName; - -@end - -@implementation DrawView - -- (instancetype)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - typeof(self) __weak weakSelf = self; - self.notificationToken = [[RLMRealm defaultRealm] addNotificationBlock:^(NSString *notification, RLMRealm *realm) { - - if (weakSelf.paths.count == 0) { - [weakSelf.canvasView clearCanvas]; - } - else { - [weakSelf.canvasView setNeedsDisplay]; - } - }]; - self.paths = [DrawPath allObjects]; - - self.canvasView = [[CanvasView alloc] init]; - self.canvasView.paths = self.paths; - [self addSubview:self.canvasView]; - - self.swatchesView = [[SwatchesView alloc] initWithFrame:CGRectZero]; - [self addSubview:self.swatchesView]; - - self.swatchesView.swatchColorChangedHandler = ^{ - weakSelf.currentColorName = weakSelf.swatchesView.selectedColor; - }; - - self.currentColorName = @"Black"; - } - return self; -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - - CGSize boundsSize = self.bounds.size; - CGFloat maxDimension = MAX(boundsSize.width, boundsSize.height); - - CGRect frame = self.canvasView.frame; - frame.size.width = maxDimension; - frame.size.height = maxDimension; - frame.origin.x = (boundsSize.width - maxDimension) * 0.5f; - self.canvasView.frame = CGRectIntegral(frame); - - frame = self.swatchesView.frame; - frame.size.width = CGRectGetWidth(self.frame); - frame.origin.y = CGRectGetHeight(self.frame) - CGRectGetHeight(frame); - self.swatchesView.frame = frame; - [self.swatchesView setNeedsLayout]; -} - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - // Create a draw path object - self.drawPath = [[DrawPath alloc] init]; - self.drawPath.color = self.currentColorName; - - // Create a draw point object - CGPoint point = [[touches anyObject] locationInView:self.canvasView]; - DrawPoint *drawPoint = [[DrawPoint alloc] init]; - drawPoint.x = point.x; - drawPoint.y = point.y; - - // Add the draw point to the draw path - [self.drawPath.points addObject:drawPoint]; - - // Add the draw path to the Realm - RLMRealm *defaultRealm = [RLMRealm defaultRealm]; - [defaultRealm transactionWithBlock:^{ - [defaultRealm addObject:self.drawPath]; - }]; - - [self.canvasView setNeedsDisplay]; -} - -- (void)addPoint:(CGPoint)point -{ - [[RLMRealm defaultRealm] transactionWithBlock:^{ - if (self.drawPath.isInvalidated) { - self.drawPath = [[DrawPath alloc] init]; - self.drawPath.color = self.currentColorName ?: @"Black"; - [[RLMRealm defaultRealm] addObject:self.drawPath]; - } - - DrawPoint *newPoint = [DrawPoint createInDefaultRealmWithValue:@[@(point.x), @(point.y)]]; - [self.drawPath.points addObject:newPoint]; - }]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - CGPoint point = [[touches anyObject] locationInView:self.canvasView]; - [self addPoint:point]; - - [self.canvasView setNeedsDisplay]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - CGPoint point = [[touches anyObject] locationInView:self.canvasView]; - [self addPoint:point]; - [[RLMRealm defaultRealm] transactionWithBlock:^{ self.drawPath.completed = YES; }]; - self.drawPath = nil; -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - [self touchesEnded:touches withEvent:event]; -} - -- (BOOL)canBecomeFirstResponder -{ - return YES; -} - -- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event -{ - if (motion != UIEventSubtypeMotionShake) { - return; - } - - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Reset Canvas?" - message:@"This will clear the Realm database and reset the canvas. Are you sure you wish to proceed?" - preferredStyle:UIAlertControllerStyleAlert]; - - typeof(self) __weak weakSelf = self; - [alertController addAction:[UIAlertAction actionWithTitle:@"Reset" - style:UIAlertActionStyleDefault - handler:^(UIAlertAction *action) { - [[RLMRealm defaultRealm] transactionWithBlock:^{ - [[RLMRealm defaultRealm] deleteAllObjects]; - }]; - - [weakSelf.canvasView clearCanvas]; - }]]; - [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; - - [[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:alertController animated:YES completion:nil]; -} - -@end diff --git a/examples/ios/objc/Draw/Views/SwatchesView.h b/examples/ios/objc/Draw/Views/SwatchesView.h deleted file mode 100644 index c5a61f0fc5..0000000000 --- a/examples/ios/objc/Draw/Views/SwatchesView.h +++ /dev/null @@ -1,27 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import -#import "UIColor+Realm.h" - -@interface SwatchesView : UIScrollView - -@property (nonatomic, strong) NSString *selectedColor; -@property (nonatomic, copy) void (^swatchColorChangedHandler)(void); - -@end diff --git a/examples/ios/objc/Draw/Views/SwatchesView.m b/examples/ios/objc/Draw/Views/SwatchesView.m deleted file mode 100644 index 2d77839593..0000000000 --- a/examples/ios/objc/Draw/Views/SwatchesView.m +++ /dev/null @@ -1,159 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import "SwatchesView.h" - -static CGFloat kSwatchButtonHeightPhone = 85.0f; -static CGFloat kSwatchButtonWidthPhone = 30.0f; - -static CGFloat kSwatchButtonHeightPad= 166.0f; -static CGFloat kSwatchButtonWidthPad = 57.0f; - -static CGFloat kSwatchPencilPadding = 1.0f; - -@interface SwatchesView() - -@property (nonatomic, strong) UIImageView *selectedIconView; -@property (nonatomic, strong) NSDictionary *colors; -@property (nonatomic, strong) NSArray *colorButtons; - -@end - -@implementation SwatchesView - -- (instancetype)initWithFrame:(CGRect)frame -{ - frame.size.height = [SwatchesView sizeForDevice].height; - if (self = [super initWithFrame:frame]) { - [self setupButtons]; - } - - return self; -} - -- (void)setupButtons -{ - self.colors = [UIColor realmColors]; - - NSMutableArray *buttons = [NSMutableArray arrayWithCapacity:self.colors.count]; - NSInteger tag = 0; - for (NSString *color in self.colors.allKeys) { - UIImage *pencilImage = [[UIImage imageNamed:color] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; - - UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; - button.tag = tag++; - button.contentMode = UIViewContentModeScaleAspectFit; - [button setImage:pencilImage forState:UIControlStateNormal]; - [button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; - [self addSubview:button]; - - [buttons addObject:button]; - } - self.colorButtons = buttons; - - CGSize swatchSize = [SwatchesView sizeForDevice]; - CGFloat totalWidth = (self.colors.count * swatchSize.width) + ((self.colors.count-1) * kSwatchPencilPadding); - CGFloat x = 0.0f; - for (UIButton *button in self.colorButtons) { - CGRect frame = button.frame; - frame.origin.x = x; - frame.size = swatchSize; - button.frame = frame; - - x += swatchSize.width + kSwatchPencilPadding; - } - - self.contentSize = (CGSize){totalWidth, swatchSize.height}; - [self updateContentInset]; - - self.selectedIconView = [[UIImageView alloc] initWithImage:[[self class] circleIcon]]; - [self addSelectedIconToButton:self.colorButtons.firstObject]; -} - -- (void)setFrame:(CGRect)frame -{ - [super setFrame:frame]; - [self updateContentInset]; -} - -- (void)updateContentInset -{ - CGSize contentSize = self.contentSize; - CGSize size = self.frame.size; - UIEdgeInsets contentInsets = UIEdgeInsetsZero; - - //Only do content insets if the scroll size is smaller than the window - if (contentSize.width < size.width) { - CGFloat inset = (size.width - contentSize.width) * 0.5f; - contentInsets.left = inset; - contentInsets.right = inset; - } - - self.contentInset = contentInsets; -} - -- (void)addSelectedIconToButton:(UIButton *)button -{ - [button addSubview:self.selectedIconView]; - CGRect frame = self.selectedIconView.frame; - frame.origin.x = (button.frame.size.width - frame.size.width) * 0.5f; - frame.origin.y = button.frame.size.height - 12.0f; - self.selectedIconView.frame = frame; -} - -- (void)buttonTapped:(id)sender -{ - UIButton *button = (UIButton *)sender; - self.selectedColor = self.colors.allKeys[button.tag]; - [self addSelectedIconToButton:sender]; -} - -- (void)setSelectedColor:(NSString *)selectedColor -{ - if (selectedColor == _selectedColor) { - return; - } - - _selectedColor = selectedColor; - - if (self.swatchColorChangedHandler) - self.swatchColorChangedHandler(); -} - -+ (CGSize)sizeForDevice -{ - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - return (CGSize){kSwatchButtonWidthPad, kSwatchButtonHeightPad}; - } - - return (CGSize){kSwatchButtonWidthPhone, kSwatchButtonHeightPhone}; -} - -+ (UIImage *)circleIcon -{ - CGRect rect = CGRectMake(0, 0, 6.0f, 6.0f); - UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0f); - UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect]; - [[UIColor colorWithWhite:1.0f alpha:0.8f] set]; - [path fill]; - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return image; -} - -@end diff --git a/examples/ios/objc/Draw/main.m b/examples/ios/objc/Draw/main.m deleted file mode 100644 index fe9539afc5..0000000000 --- a/examples/ios/objc/Draw/main.m +++ /dev/null @@ -1,28 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#import - -#import "AppDelegate.h" - -int main(int argc, char * argv[]) -{ - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/examples/ios/objc/RealmExamples.xcodeproj/project.pbxproj b/examples/ios/objc/RealmExamples.xcodeproj/project.pbxproj index 90f4dfcbed..e89d1fdbd6 100644 --- a/examples/ios/objc/RealmExamples.xcodeproj/project.pbxproj +++ b/examples/ios/objc/RealmExamples.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ 227D20DF1CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; 227D20E01CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; 227D20E11CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; - 227D20E21CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; 227D20E31CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; 227D20E41CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; 227D20E51CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; @@ -26,21 +25,10 @@ 3F08726027F3C971007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3F08726127F3C977007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3F08726227F3C97D007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; - 3F08726327F3C983007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3F08726427F3C989007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3F08726527F3C98E007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3F08726627F3C993007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3F08726727F3C999007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; - 3F08726827F3D23B007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; - 3F78C9BD1B431CF300A0988E /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AB19BA502500F3EDB4 /* CoreGraphics.framework */; }; - 3F78C9BE1B431CF300A0988E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71A919BA502500F3EDB4 /* Foundation.framework */; }; - 3F78C9BF1B431CF300A0988E /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F70ED019BA52E8006F60D5 /* libc++.dylib */; }; - 3F78C9C01B431CF300A0988E /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0295B8FE19D102880036D6C3 /* Realm.framework */; }; - 3F78C9C11B431CF300A0988E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AD19BA502500F3EDB4 /* UIKit.framework */; }; - 3F78C9C31B431CF300A0988E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E879D9DD1A12AA400035E2EB /* Images.xcassets */; }; - 3F78C9C91B431D6F00A0988E /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F78C9AE1B431CDF00A0988E /* AppDelegate.m */; }; - 3F78C9CA1B431D7400A0988E /* TableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F78C9B01B431CDF00A0988E /* TableViewController.m */; }; - 3F78C9CB1B431D7A00A0988E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F78C9AF1B431CDF00A0988E /* main.m */; }; 3FC898FD1A140F550067CBEC /* LabelViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FC898FC1A140F550067CBEC /* LabelViewController.m */; }; 7DD1D7E225DC2D3D00E63229 /* default-v2.realm in Resources */ = {isa = PBXBuildFile; fileRef = 7DD1D7D225DC2C7400E63229 /* default-v2.realm */; }; 7DD1D7E325DC2D3D00E63229 /* default-v3.realm in Resources */ = {isa = PBXBuildFile; fileRef = 7DD1D7D325DC2C7400E63229 /* default-v3.realm */; }; @@ -114,21 +102,6 @@ E8F70ED519BA530C006F60D5 /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F70ED019BA52E8006F60D5 /* libc++.dylib */; }; E8F70ED919BA534E006F60D5 /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F70ED019BA52E8006F60D5 /* libc++.dylib */; }; E8F70EDD19BA5380006F60D5 /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F70ED019BA52E8006F60D5 /* libc++.dylib */; }; - F1506E991DDD1E2A00D319DC /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0295B8FE19D102880036D6C3 /* Realm.framework */; }; - F18464461DC151F000DAB8B9 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F18464361DC151F000DAB8B9 /* AppDelegate.m */; }; - F184644B1DC151F000DAB8B9 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F184643F1DC151F000DAB8B9 /* Images.xcassets */; }; - F184644D1DC151F000DAB8B9 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F18464411DC151F000DAB8B9 /* main.m */; }; - F18464521DC1551200DAB8B9 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = F18464511DC1551200DAB8B9 /* libc++.tbd */; }; - F18464531DC1551700DAB8B9 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AD19BA502500F3EDB4 /* UIKit.framework */; }; - F18464541DC1551D00DAB8B9 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71A919BA502500F3EDB4 /* Foundation.framework */; }; - F18464551DC1569B00DAB8B9 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AB19BA502500F3EDB4 /* CoreGraphics.framework */; }; - F18464631DC16CDA00DAB8B9 /* DrawPath.m in Sources */ = {isa = PBXBuildFile; fileRef = F18464591DC16CDA00DAB8B9 /* DrawPath.m */; }; - F18464641DC16CDA00DAB8B9 /* DrawPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = F184645B1DC16CDA00DAB8B9 /* DrawPoint.m */; }; - F18464651DC16CDA00DAB8B9 /* UIColor+Realm.m in Sources */ = {isa = PBXBuildFile; fileRef = F184645D1DC16CDA00DAB8B9 /* UIColor+Realm.m */; }; - F18464661DC16CDA00DAB8B9 /* DrawView.m in Sources */ = {isa = PBXBuildFile; fileRef = F18464601DC16CDA00DAB8B9 /* DrawView.m */; }; - F18464671DC16CDA00DAB8B9 /* SwatchesView.m in Sources */ = {isa = PBXBuildFile; fileRef = F18464621DC16CDA00DAB8B9 /* SwatchesView.m */; }; - F184646A1DC16D3700DAB8B9 /* CanvasView.m in Sources */ = {isa = PBXBuildFile; fileRef = F18464691DC16D3700DAB8B9 /* CanvasView.m */; }; - F196C2EF1DD6442400AFB53B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -170,14 +143,6 @@ 0A73D4401B1442B200E1E8EE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../../README.md; sourceTree = SOURCE_ROOT; }; 227D20DD1CB6009D008F641B /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; 3F08725D27F3C8CF007A1175 /* libcompression.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcompression.tbd; path = usr/lib/libcompression.tbd; sourceTree = SDKROOT; }; - 3F78C9AB1B431CDF00A0988E /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = RACTableView/AppDelegate.h; sourceTree = ""; }; - 3F78C9AC1B431CDF00A0988E /* TableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TableViewController.h; path = RACTableView/TableViewController.h; sourceTree = ""; }; - 3F78C9AD1B431CDF00A0988E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = RACTableView/Images.xcassets; sourceTree = ""; }; - 3F78C9AE1B431CDF00A0988E /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = RACTableView/AppDelegate.m; sourceTree = ""; }; - 3F78C9AF1B431CDF00A0988E /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = RACTableView/main.m; sourceTree = ""; }; - 3F78C9B01B431CDF00A0988E /* TableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TableViewController.m; path = RACTableView/TableViewController.m; sourceTree = ""; }; - 3F78C9B11B431CDF00A0988E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = RACTableView/Info.plist; sourceTree = ""; }; - 3F78C9C71B431CF300A0988E /* RACTableView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RACTableView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3FC898FB1A140F550067CBEC /* LabelViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LabelViewController.h; sourceTree = ""; }; 3FC898FC1A140F550067CBEC /* LabelViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LabelViewController.m; sourceTree = ""; }; 7D56426A25DD66160079F4C2 /* Example_v0.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Example_v0.h; sourceTree = ""; }; @@ -256,43 +221,10 @@ E8F70EC219BA50AB006F60D5 /* Encryption-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Encryption-Info.plist"; sourceTree = ""; }; E8F70EC319BA50AB006F60D5 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; E8F70ED019BA52E8006F60D5 /* libc++.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libc++.dylib"; path = "usr/lib/libc++.dylib"; sourceTree = SDKROOT; }; - F184641D1DC151CD00DAB8B9 /* Draw.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Draw.app; sourceTree = BUILT_PRODUCTS_DIR; }; - F18464351DC151F000DAB8B9 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - F18464361DC151F000DAB8B9 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - F184643F1DC151F000DAB8B9 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; - F18464401DC151F000DAB8B9 /* Draw-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Draw-Info.plist"; sourceTree = ""; }; - F18464411DC151F000DAB8B9 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; F18464511DC1551200DAB8B9 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; - F18464561DC1597C00DAB8B9 /* Draw.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Draw.entitlements; sourceTree = ""; }; - F18464581DC16CD900DAB8B9 /* DrawPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DrawPath.h; sourceTree = ""; }; - F18464591DC16CDA00DAB8B9 /* DrawPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DrawPath.m; sourceTree = ""; }; - F184645A1DC16CDA00DAB8B9 /* DrawPoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DrawPoint.h; sourceTree = ""; }; - F184645B1DC16CDA00DAB8B9 /* DrawPoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DrawPoint.m; sourceTree = ""; }; - F184645C1DC16CDA00DAB8B9 /* UIColor+Realm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+Realm.h"; sourceTree = ""; }; - F184645D1DC16CDA00DAB8B9 /* UIColor+Realm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+Realm.m"; sourceTree = ""; }; - F184645F1DC16CDA00DAB8B9 /* DrawView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DrawView.h; sourceTree = ""; }; - F18464601DC16CDA00DAB8B9 /* DrawView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DrawView.m; sourceTree = ""; }; - F18464611DC16CDA00DAB8B9 /* SwatchesView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwatchesView.h; sourceTree = ""; }; - F18464621DC16CDA00DAB8B9 /* SwatchesView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SwatchesView.m; sourceTree = ""; }; - F18464681DC16D3700DAB8B9 /* CanvasView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CanvasView.h; sourceTree = ""; }; - F18464691DC16D3700DAB8B9 /* CanvasView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CanvasView.m; sourceTree = ""; }; - F18DC0A31DC2AFCB007D2D69 /* Constants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 3F78C9BC1B431CF300A0988E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 3F78C9BD1B431CF300A0988E /* CoreGraphics.framework in Frameworks */, - 3F78C9BE1B431CF300A0988E /* Foundation.framework in Frameworks */, - 3F78C9BF1B431CF300A0988E /* libc++.dylib in Frameworks */, - 3F08726327F3C983007A1175 /* libcompression.tbd in Frameworks */, - 3F78C9C01B431CF300A0988E /* Realm.framework in Frameworks */, - 3F78C9C11B431CF300A0988E /* UIKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; C0DC41C91A7072670067156A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -410,19 +342,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F184641A1DC151CD00DAB8B9 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - F18464551DC1569B00DAB8B9 /* CoreGraphics.framework in Frameworks */, - F18464541DC1551D00DAB8B9 /* Foundation.framework in Frameworks */, - F18464521DC1551200DAB8B9 /* libc++.tbd in Frameworks */, - F1506E991DDD1E2A00D319DC /* Realm.framework in Frameworks */, - F18464531DC1551700DAB8B9 /* UIKit.framework in Frameworks */, - 3F08726827F3D23B007A1175 /* libcompression.tbd in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -434,20 +353,6 @@ path = Common; sourceTree = ""; }; - 3F78C9AA1B431CB200A0988E /* RACTableView */ = { - isa = PBXGroup; - children = ( - 3F78C9AB1B431CDF00A0988E /* AppDelegate.h */, - 3F78C9AE1B431CDF00A0988E /* AppDelegate.m */, - 3F78C9AD1B431CDF00A0988E /* Images.xcassets */, - 3F78C9B11B431CDF00A0988E /* Info.plist */, - 3F78C9AF1B431CDF00A0988E /* main.m */, - 3F78C9AC1B431CDF00A0988E /* TableViewController.h */, - 3F78C9B01B431CDF00A0988E /* TableViewController.m */, - ); - name = RACTableView; - sourceTree = ""; - }; 7D718BED25DAADE400A74FDD /* Examples */ = { isa = PBXGroup; children = ( @@ -527,14 +432,12 @@ children = ( E8BDBFC71A116FCB00450CFF /* Backlink */, 227D20DC1CB6009D008F641B /* Common */, - F184641E1DC151CD00DAB8B9 /* Draw */, E8F70EBF19BA50AB006F60D5 /* Encryption */, C0DC41CD1A7072670067156A /* Extension */, E8AB71A819BA502500F3EDB4 /* Frameworks */, E879D9DA1A12AA400035E2EB /* GroupedTableView */, E8F70EAE19BA50A3006F60D5 /* Migration */, E8AB71A719BA502500F3EDB4 /* Products */, - 3F78C9AA1B431CB200A0988E /* RACTableView */, E8F70EA319BA5093006F60D5 /* REST */, E8F70E8E19BA5083006F60D5 /* Simple */, E8F70E9619BA508B006F60D5 /* TableView */, @@ -547,12 +450,10 @@ isa = PBXGroup; children = ( E8BDBFA11A116FAC00450CFF /* Backlink.app */, - F184641D1DC151CD00DAB8B9 /* Draw.app */, E8AB723819BA504000F3EDB4 /* Encryption.app */, C0DC41CC1A7072670067156A /* extension.app */, E879D9D81A12AA120035E2EB /* GroupedTableView.app */, E8AB720A19BA503B00F3EDB4 /* Migration.app */, - 3F78C9C71B431CF300A0988E /* RACTableView.app */, E8AB729419BA504900F3EDB4 /* REST.app */, E8AB71DC19BA503500F3EDB4 /* Simple.app */, E8AB726619BA504500F3EDB4 /* TableView.app */, @@ -653,68 +554,9 @@ path = Encryption; sourceTree = ""; }; - F184641E1DC151CD00DAB8B9 /* Draw */ = { - isa = PBXGroup; - children = ( - F18464571DC16CD900DAB8B9 /* Models */, - F184645E1DC16CDA00DAB8B9 /* Views */, - F18464351DC151F000DAB8B9 /* AppDelegate.h */, - F18464361DC151F000DAB8B9 /* AppDelegate.m */, - F18DC0A31DC2AFCB007D2D69 /* Constants.h */, - F18464401DC151F000DAB8B9 /* Draw-Info.plist */, - F18464561DC1597C00DAB8B9 /* Draw.entitlements */, - F184643F1DC151F000DAB8B9 /* Images.xcassets */, - F18464411DC151F000DAB8B9 /* main.m */, - ); - path = Draw; - sourceTree = ""; - }; - F18464571DC16CD900DAB8B9 /* Models */ = { - isa = PBXGroup; - children = ( - F18464581DC16CD900DAB8B9 /* DrawPath.h */, - F18464591DC16CDA00DAB8B9 /* DrawPath.m */, - F184645A1DC16CDA00DAB8B9 /* DrawPoint.h */, - F184645B1DC16CDA00DAB8B9 /* DrawPoint.m */, - F184645C1DC16CDA00DAB8B9 /* UIColor+Realm.h */, - F184645D1DC16CDA00DAB8B9 /* UIColor+Realm.m */, - ); - path = Models; - sourceTree = ""; - }; - F184645E1DC16CDA00DAB8B9 /* Views */ = { - isa = PBXGroup; - children = ( - F18464681DC16D3700DAB8B9 /* CanvasView.h */, - F18464691DC16D3700DAB8B9 /* CanvasView.m */, - F184645F1DC16CDA00DAB8B9 /* DrawView.h */, - F18464601DC16CDA00DAB8B9 /* DrawView.m */, - F18464611DC16CDA00DAB8B9 /* SwatchesView.h */, - F18464621DC16CDA00DAB8B9 /* SwatchesView.m */, - ); - path = Views; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 3F78C9B51B431CF300A0988E /* RACTableView */ = { - isa = PBXNativeTarget; - buildConfigurationList = 3F78C9C41B431CF300A0988E /* Build configuration list for PBXNativeTarget "RACTableView" */; - buildPhases = ( - 3F78C9B81B431CF300A0988E /* Sources */, - 3F78C9BC1B431CF300A0988E /* Frameworks */, - 3F78C9C21B431CF300A0988E /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = RACTableView; - productName = TableView; - productReference = 3F78C9C71B431CF300A0988E /* RACTableView.app */; - productType = "com.apple.product-type.application"; - }; C0DC41CB1A7072670067156A /* extension */ = { isa = PBXNativeTarget; buildConfigurationList = C0DC41EC1A7072680067156A /* Build configuration list for PBXNativeTarget "extension" */; @@ -871,24 +713,6 @@ productReference = E8BDBFA11A116FAC00450CFF /* Backlink.app */; productType = "com.apple.product-type.application"; }; - F184641C1DC151CD00DAB8B9 /* Draw */ = { - isa = PBXNativeTarget; - buildConfigurationList = F18464331DC151CD00DAB8B9 /* Build configuration list for PBXNativeTarget "Draw" */; - buildPhases = ( - F18DC0A41DC2B197007D2D69 /* Get Device IP Address */, - F18464191DC151CD00DAB8B9 /* Sources */, - F184641A1DC151CD00DAB8B9 /* Frameworks */, - F184641B1DC151CD00DAB8B9 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Draw; - productName = Draw; - productReference = F184641D1DC151CD00DAB8B9 /* Draw.app */; - productType = "com.apple.product-type.application"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -918,16 +742,6 @@ E8BDBFA01A116FAC00450CFF = { CreatedOnToolsVersion = 6.1; }; - F184641C1DC151CD00DAB8B9 = { - CreatedOnToolsVersion = 8.1; - LastSwiftMigration = 0810; - ProvisioningStyle = Automatic; - SystemCapabilities = { - com.apple.Keychain = { - enabled = 1; - }; - }; - }; }; }; buildConfigurationList = E8AB71A119BA502500F3EDB4 /* Build configuration list for PBXProject "RealmExamples" */; @@ -944,12 +758,10 @@ projectRoot = ""; targets = ( E8BDBFA01A116FAC00450CFF /* Backlink */, - F184641C1DC151CD00DAB8B9 /* Draw */, E8AB723719BA504000F3EDB4 /* Encryption */, C0DC41CB1A7072670067156A /* extension */, C0DC42051A7079D00067156A /* TodayExtension */, E879D9C61A12AA120035E2EB /* GroupedTableView */, - 3F78C9B51B431CF300A0988E /* RACTableView */, E8AB720919BA503B00F3EDB4 /* Migration */, E8AB729319BA504900F3EDB4 /* REST */, E8AB71DB19BA503500F3EDB4 /* Simple */, @@ -999,15 +811,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 3F78C9C21B431CF300A0988E /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3F78C9C31B431CF300A0988E /* Images.xcassets in Resources */, - 227D20E21CB6009D008F641B /* LaunchScreen.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; C0DC42041A7079D00067156A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1048,45 +851,9 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F184641B1DC151CD00DAB8B9 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F184644B1DC151F000DAB8B9 /* Images.xcassets in Resources */, - F196C2EF1DD6442400AFB53B /* LaunchScreen.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - F18DC0A41DC2B197007D2D69 /* Get Device IP Address */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Get Device IP Address"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "LOCAL_IP_ADDR=$(ipconfig getifaddr $(netstat -f inet -rant | awk '/default/ { print $6; exit }'))\necho \"static NSString * const kIPAddress = @\\\"${LOCAL_IP_ADDR:-127.0.0.1}\\\";\" > \"$SRCROOT/Draw/Constants.h\""; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ - 3F78C9B81B431CF300A0988E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3F78C9C91B431D6F00A0988E /* AppDelegate.m in Sources */, - 3F78C9CB1B431D7A00A0988E /* main.m in Sources */, - 3F78C9CA1B431D7400A0988E /* TableViewController.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; C0DC41C81A7072670067156A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1173,21 +940,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F18464191DC151CD00DAB8B9 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F18464461DC151F000DAB8B9 /* AppDelegate.m in Sources */, - F184646A1DC16D3700DAB8B9 /* CanvasView.m in Sources */, - F18464631DC16CDA00DAB8B9 /* DrawPath.m in Sources */, - F18464641DC16CDA00DAB8B9 /* DrawPoint.m in Sources */, - F18464661DC16CDA00DAB8B9 /* DrawView.m in Sources */, - F184644D1DC151F000DAB8B9 /* main.m in Sources */, - F18464671DC16CDA00DAB8B9 /* SwatchesView.m in Sources */, - F18464651DC16CDA00DAB8B9 /* UIColor+Realm.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -1199,48 +951,6 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 3F78C9C51B431CF300A0988E /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = ""; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = "$(SRCROOT)/RACTableView/Info.plist"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "\"$PODS_CONFIGURATION_BUILD_DIR\"", - "\"$PODS_CONFIGURATION_BUILD_DIR/ReactiveCocoa\"", - ); - PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = RACTableView; - WRAPPER_EXTENSION = app; - }; - name = Debug; - }; - 3F78C9C61B431CF300A0988E /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = ""; - INFOPLIST_FILE = "$(SRCROOT)/RACTableView/Info.plist"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "\"$PODS_CONFIGURATION_BUILD_DIR\"", - "\"$PODS_CONFIGURATION_BUILD_DIR/ReactiveCocoa\"", - ); - PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = RACTableView; - WRAPPER_EXTENSION = app; - }; - name = Release; - }; AC1CA6392B1FF0BE002167B0 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1307,28 +1017,6 @@ }; name = Static; }; - AC1CA63B2B1FF0BE002167B0 /* Static */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ENABLE_MODULES = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_SUSPICIOUS_MOVES = YES; - CODE_SIGN_ENTITLEMENTS = Draw/Draw.entitlements; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = "$(SRCROOT)/Draw/Draw-Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = NO; - OTHER_LDFLAGS = "-lz"; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.Draw; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Draw/Draw-Bridging-Header.h"; - }; - name = Static; - }; AC1CA63C2B1FF0BE002167B0 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1402,25 +1090,6 @@ }; name = Static; }; - AC1CA6402B1FF0BE002167B0 /* Static */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = ""; - INFOPLIST_FILE = "$(SRCROOT)/RACTableView/Info.plist"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "\"$PODS_CONFIGURATION_BUILD_DIR\"", - "\"$PODS_CONFIGURATION_BUILD_DIR/ReactiveCocoa\"", - ); - PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = RACTableView; - WRAPPER_EXTENSION = app; - }; - name = Static; - }; AC1CA6412B1FF0BE002167B0 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1910,63 +1579,9 @@ }; name = Release; }; - F18464311DC151CD00DAB8B9 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ENABLE_MODULES = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_SUSPICIOUS_MOVES = YES; - CODE_SIGN_ENTITLEMENTS = Draw/Draw.entitlements; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = "$(SRCROOT)/Draw/Draw-Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = YES; - OTHER_LDFLAGS = "-lz"; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.Draw; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Draw/Draw-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - F18464321DC151CD00DAB8B9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ENABLE_MODULES = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_SUSPICIOUS_MOVES = YES; - CODE_SIGN_ENTITLEMENTS = Draw/Draw.entitlements; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = "$(SRCROOT)/Draw/Draw-Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = NO; - OTHER_LDFLAGS = "-lz"; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.Draw; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Draw/Draw-Bridging-Header.h"; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 3F78C9C41B431CF300A0988E /* Build configuration list for PBXNativeTarget "RACTableView" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3F78C9C51B431CF300A0988E /* Debug */, - 3F78C9C61B431CF300A0988E /* Release */, - AC1CA6402B1FF0BE002167B0 /* Static */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; C0DC41EC1A7072680067156A /* Build configuration list for PBXNativeTarget "extension" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2067,16 +1682,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F18464331DC151CD00DAB8B9 /* Build configuration list for PBXNativeTarget "Draw" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - F18464311DC151CD00DAB8B9 /* Debug */, - F18464321DC151CD00DAB8B9 /* Release */, - AC1CA63B2B1FF0BE002167B0 /* Static */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ }; rootObject = E8AB719E19BA502500F3EDB4 /* Project object */; diff --git a/examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/Draw.xcscheme b/examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/Draw.xcscheme deleted file mode 100644 index d0481cd53d..0000000000 --- a/examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/Draw.xcscheme +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/RACTableView.xcscheme b/examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/RACTableView.xcscheme deleted file mode 100644 index 39ea84110e..0000000000 --- a/examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/RACTableView.xcscheme +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/ios/objc/RealmExamples.xcworkspace/contents.xcworkspacedata b/examples/ios/objc/RealmExamples.xcworkspace/contents.xcworkspacedata index 4674d6a623..f92bf716e7 100644 --- a/examples/ios/objc/RealmExamples.xcworkspace/contents.xcworkspacedata +++ b/examples/ios/objc/RealmExamples.xcworkspace/contents.xcworkspacedata @@ -7,7 +7,4 @@ - - diff --git a/examples/ios/swift/AppleAuthentication/AppDelegate.swift b/examples/ios/swift/AppleAuthentication/AppDelegate.swift deleted file mode 100644 index 3c2777dfab..0000000000 --- a/examples/ios/swift/AppleAuthentication/AppDelegate.swift +++ /dev/null @@ -1,30 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import UIKit - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - return true - } - - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { - return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) - } -} diff --git a/examples/ios/swift/AppleAuthentication/AppleAuthentication.entitlements b/examples/ios/swift/AppleAuthentication/AppleAuthentication.entitlements deleted file mode 100644 index a812db506f..0000000000 --- a/examples/ios/swift/AppleAuthentication/AppleAuthentication.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.developer.applesignin - - Default - - - diff --git a/examples/ios/swift/AppleAuthentication/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/ios/swift/AppleAuthentication/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 9221b9bb1a..0000000000 --- a/examples/ios/swift/AppleAuthentication/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/examples/ios/swift/AppleAuthentication/Assets.xcassets/Contents.json b/examples/ios/swift/AppleAuthentication/Assets.xcassets/Contents.json deleted file mode 100644 index 73c00596a7..0000000000 --- a/examples/ios/swift/AppleAuthentication/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/examples/ios/swift/AppleAuthentication/Base.lproj/LaunchScreen.storyboard b/examples/ios/swift/AppleAuthentication/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 865e9329f3..0000000000 --- a/examples/ios/swift/AppleAuthentication/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/ios/swift/AppleAuthentication/ContentView.swift b/examples/ios/swift/AppleAuthentication/ContentView.swift deleted file mode 100644 index f1d8eee64c..0000000000 --- a/examples/ios/swift/AppleAuthentication/ContentView.swift +++ /dev/null @@ -1,92 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import SwiftUI -import AuthenticationServices -import RealmSwift - -/// Your Atlas App Services app ID -let appId = "your-app-id" - -struct ContentView: View { - @State var accessToken: String = "" - @State var error: String = "" - - var body: some View { - VStack { - SignInWithAppleView(accessToken: $accessToken, error: $error) - .frame(width: 200, height: 50, alignment: .center) - Text(self.accessToken) - Text(self.error) - } - } -} - -class SignInCoordinator: ASLoginDelegate { - var parent: SignInWithAppleView - var app: App - - init(parent: SignInWithAppleView) { - self.parent = parent - app = App(id: appId) - app.authorizationDelegate = self - } - - @objc func didTapLogin() { - let appleIDProvider = ASAuthorizationAppleIDProvider() - let request = appleIDProvider.createRequest() - request.requestedScopes = [.fullName, .email] - - let authorizationController = ASAuthorizationController(authorizationRequests: [request]) - app.setASAuthorizationControllerDelegate(for: authorizationController) - authorizationController.performRequests() - } - - func authenticationDidComplete(error: Error) { - parent.error = error.localizedDescription - } - - func authenticationDidComplete(user: User) { - parent.accessToken = user.accessToken ?? "Could not get access token" - } -} - -struct SignInWithAppleView: UIViewRepresentable { - @Binding var accessToken: String - @Binding var error: String - - func makeCoordinator() -> SignInCoordinator { - return SignInCoordinator(parent: self) - } - - func makeUIView(context: Context) -> ASAuthorizationAppleIDButton { - let button = ASAuthorizationAppleIDButton(authorizationButtonType: .signIn, authorizationButtonStyle: .black) - button.addTarget(context.coordinator, action: #selector(context.coordinator.didTapLogin), for: .touchUpInside) - return button - } - - func updateUIView(_ uiView: ASAuthorizationAppleIDButton, context: Context) { - - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/examples/ios/swift/AppleAuthentication/Info.plist b/examples/ios/swift/AppleAuthentication/Info.plist deleted file mode 100644 index 9742bf0f46..0000000000 --- a/examples/ios/swift/AppleAuthentication/Info.plist +++ /dev/null @@ -1,60 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneConfigurationName - Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - - - - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/examples/ios/swift/AppleAuthentication/Preview Content/Preview Assets.xcassets/Contents.json b/examples/ios/swift/AppleAuthentication/Preview Content/Preview Assets.xcassets/Contents.json deleted file mode 100644 index 73c00596a7..0000000000 --- a/examples/ios/swift/AppleAuthentication/Preview Content/Preview Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/examples/ios/swift/AppleAuthentication/README.md b/examples/ios/swift/AppleAuthentication/README.md deleted file mode 100644 index 934b03a480..0000000000 --- a/examples/ios/swift/AppleAuthentication/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# How to enable Apple Authentication via Atlas App Services - -* You first need to setup your app in Atlas App Services. Follow these instructions to get started: https://www.mongodb.com/docs/atlas/app-services/authentication/apple/ -* Once that is done add Apple Sign In to your app's entitlements -* Make sure your bundle ID matches the client id you used in Atlas App Services app. -* Follow the code sample to understand the flow of the authentication cycle. - - diff --git a/examples/ios/swift/AppleAuthentication/SceneDelegate.swift b/examples/ios/swift/AppleAuthentication/SceneDelegate.swift deleted file mode 100644 index e1a9b11236..0000000000 --- a/examples/ios/swift/AppleAuthentication/SceneDelegate.swift +++ /dev/null @@ -1,35 +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. -// -//////////////////////////////////////////////////////////////////////////// - -import UIKit -import SwiftUI - -class SceneDelegate: UIResponder, UIWindowSceneDelegate { - var window: UIWindow? - - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - let contentView = ContentView() - - if let windowScene = scene as? UIWindowScene { - let window = UIWindow(windowScene: windowScene) - window.rootViewController = UIHostingController(rootView: contentView) - self.window = window - window.makeKeyAndVisible() - } - } -} diff --git a/examples/ios/swift/AsyncOpenSwiftUI/Assets.xcassets/AccentColor.colorset/Contents.json b/examples/ios/swift/AsyncOpenSwiftUI/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb87897008..0000000000 --- a/examples/ios/swift/AsyncOpenSwiftUI/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/examples/ios/swift/AsyncOpenSwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/ios/swift/AsyncOpenSwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index c136eaff76..0000000000 --- a/examples/ios/swift/AsyncOpenSwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "16x16" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "16x16" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "32x32" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "32x32" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "128x128" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "128x128" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "256x256" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "256x256" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "512x512" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "512x512" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/examples/ios/swift/AsyncOpenSwiftUI/Assets.xcassets/Contents.json b/examples/ios/swift/AsyncOpenSwiftUI/Assets.xcassets/Contents.json deleted file mode 100644 index 73c00596a7..0000000000 --- a/examples/ios/swift/AsyncOpenSwiftUI/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/examples/ios/swift/AsyncOpenSwiftUI/AsyncOpenSwiftUI--iOS--Info.plist b/examples/ios/swift/AsyncOpenSwiftUI/AsyncOpenSwiftUI--iOS--Info.plist deleted file mode 100644 index 456696a7b0..0000000000 --- a/examples/ios/swift/AsyncOpenSwiftUI/AsyncOpenSwiftUI--iOS--Info.plist +++ /dev/null @@ -1,13 +0,0 @@ - - - - - LSRequiresIPhoneOS - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - - - diff --git a/examples/ios/swift/AsyncOpenSwiftUI/AsyncOpenSwiftUI--macOS--Info.plist b/examples/ios/swift/AsyncOpenSwiftUI/AsyncOpenSwiftUI--macOS--Info.plist deleted file mode 100644 index fca73e24ec..0000000000 --- a/examples/ios/swift/AsyncOpenSwiftUI/AsyncOpenSwiftUI--macOS--Info.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - LSApplicationCategoryType - public.app-category.developer-tools - - diff --git a/examples/ios/swift/AsyncOpenSwiftUI/AsyncOpenSwiftUIApp.swift b/examples/ios/swift/AsyncOpenSwiftUI/AsyncOpenSwiftUIApp.swift deleted file mode 100644 index d44c942c1f..0000000000 --- a/examples/ios/swift/AsyncOpenSwiftUI/AsyncOpenSwiftUIApp.swift +++ /dev/null @@ -1,28 +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. - // - //////////////////////////////////////////////////////////////////////////// - -import SwiftUI - -@main -struct AsyncOpenSwiftUIApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -} diff --git a/examples/ios/swift/AsyncOpenSwiftUI/ContentView.swift b/examples/ios/swift/AsyncOpenSwiftUI/ContentView.swift deleted file mode 100644 index 0fb888597e..0000000000 --- a/examples/ios/swift/AsyncOpenSwiftUI/ContentView.swift +++ /dev/null @@ -1,301 +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. - // - //////////////////////////////////////////////////////////////////////////// - -import SwiftUI -import Combine -import RealmSwift - -class Contact: Object, ObjectKeyIdentifiable { - @Persisted(primaryKey: true) var _id: String - @Persisted var name: String = "" - @Persisted var lastName: String = "" - @Persisted var email: String = "" - @Persisted var phones: RealmSwift.List - @Persisted var birthdate: Date = Date() - @Persisted var notes: String = "" - - var fullName: String { - return "\(name) \(lastName)" - } -} - -class PhoneNumber: EmbeddedObject, ObjectKeyIdentifiable { - enum PhoneNumberType: String, PersistableEnum, Identifiable, CaseIterable { - var id: String { self.rawValue } - case home, mobile, work - } - @Persisted var type: PhoneNumberType = .home - @Persisted var phoneNumber: String = "" -} - -// You can find your Realm app ID in the Realm UI. -let appId = "realm-async-open" -// The partition determines which subset of data to access, this is configured in the Realm UI too. -let partitionValue = "partition-value" -let app = App(id: appId) - -private enum NavigationType: String { - case asyncOpen - case autoOpen -} - -// For the purpose of this example, we have to ways of syncing, using @AsyncOpen and @AutoOpen -struct ContentView: View { - var body: some View { - NavigationView { - VStack(spacing: 20) { - Button(action: {}, label: { - NavigationLink(destination: LoginView(navigationType: .asyncOpen)) { - Text("@AsyncOpen") - } - }) - Button(action: {}, label: { - NavigationLink(destination: LoginView(navigationType: .autoOpen)) { - Text("@AutoOpen") - } - }) - } - } - } -} - -// LoginView, Authenticate User -// When you have enabled anonymous authentication in the Realm UI, users can immediately log into your app without providing any identifying information: -// Documentation of how to login can be found (https://docs.mongodb.com/realm/sdk/ios/quick-start-with-sync/) -struct LoginView: View { - fileprivate var navigationType: NavigationType - - @ObservedObject var loginHelper = LoginHelper() - @State var email: String = "" - @State var password: String = "" - @State var navigationTag: String? - - var body: some View { - VStack { - TextField("Email", text: $email) - .autocapitalization(.none) - SecureField("Password", text: $password) - Spacer() - NavigationLink(destination: LazyView(AsyncOpenView()), tag: "asyncOpen", selection: $navigationTag, label: { EmptyView()}) - NavigationLink(destination: LazyView(AutoOpenView()), tag: "autoOpen", selection: $navigationTag, label: { EmptyView()}) - Button("Login") { - loginHelper.login(email: email, password: password) { - navigationTag = navigationType.rawValue - } - } - } - .padding() - .navigationTitle("Logging View") - } -} - -class LoginHelper: ObservableObject { - var cancellables = Set() - - func login(email: String, password: String, completion: @escaping () -> Void) { - let app = RealmSwift.App(id: appId) - app.login(credentials: Credentials.emailPassword(email: email, password: password)) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in - }, receiveValue: { _ in - completion() - }) - .store(in: &cancellables) - } -} - -// AsyncOpen is as simple as declaring it on your view and wait a notification from the process -struct AsyncOpenView: View { - @AsyncOpen(appId: appId, partitionValue: partitionValue, timeout: 2000) var asyncOpen - - var body: some View { - VStack { - switch asyncOpen { - case .connecting: - ProgressView() - case .waitingForUser: - ProgressView("Waiting for user to logged in...") - case .open(let realm): - ContactsListView() - .environment(\.realm, realm) - case .error(let error): - ErrorView(error: error) - case .progress(let progress): - ProgressView(progress) - } - } - } -} - -// AutoOpen declaration and use is the same as AsyncOpen, but in case of no internet -// connection this will return an opened realm. -struct AutoOpenView: View { - @State var error: Error? - @AutoOpen(appId: appId, partitionValue: partitionValue, timeout: 2000) var autoOpen - - var body: some View { - VStack { - switch autoOpen { - case .connecting: - ProgressView() - case .waitingForUser: - ProgressView("Waiting for user to logged in...") - case .open(let realm): - ContactsListView() - .environment(\.realm, realm) - case .error(let error): - ErrorView(error: error) - case .progress(let progress): - ProgressView(progress) - } - } - } -} - -struct ErrorView: View { - @State var error: Error - var body: some View { - VStack(spacing: 20) { - Text("Error") - Text(error.localizedDescription) - } - .padding() - } -} - -struct ContactsListView: View { - @ObservedResults(Contact.self) var contacts - - var body: some View { - List { - ForEach(contacts) { contact in - NavigationLink(destination: ContactDetailView(contact: contact)) { - ContactCellView(contact: contact) - } - } - } - .navigationBarItems(trailing: HStack { - Button("add") { - let contact = Contact() - contact.name = "New Contact" - $contacts.append(contact) - } - }) - } -} - -struct ContactCellView: View { - @ObservedRealmObject var contact: Contact - - var body: some View { - HStack { - Text(contact.fullName) - Spacer() - Text(contact.phones.first?.phoneNumber ?? "") - } - - } -} - -struct ContactDetailView: View { - @ObservedRealmObject var contact: Contact - - var body: some View { - Form { - if #available(iOS 15.0, *) { - Section("Info") { - TextField("Name", text: $contact.name) - TextField("Lastname", text: $contact.lastName) - TextField("Email", text: $contact.email) - .keyboardType(.emailAddress) - DatePicker("Birthday", selection: $contact.birthdate, displayedComponents: [.date]) - } - } else { - TextField("Name", text: $contact.name) - TextField("Lastname", text: $contact.lastName) - TextField("Email", text: $contact.email) - .keyboardType(.emailAddress) - DatePicker("Birthday", selection: $contact.birthdate, displayedComponents: [.date]) - } - if #available(iOS 15.0, *) { - Section("Phones") { - ForEach($contact.phones) { phone in - HStack { - Picker(selection: phone.type, label: Text("")) { - ForEach(PhoneNumber.PhoneNumberType.allCases) { phoneType in - Text("\(phoneType.rawValue)").tag(phoneType) - } - } - TextField("Phone Number", text: phone.phoneNumber) - } - } - Button { - withAnimation { - $contact.phones.append(PhoneNumber()) - } - } label: { - Label("Add", systemImage: "plus.app") - } - .tint(.red) - } - } else { - ForEach($contact.phones) { phone in - HStack { - Picker(selection: phone.type, label: Text("")) { - ForEach(PhoneNumber.PhoneNumberType.allCases) { phoneType in - Text("\(phoneType.rawValue)").tag(phoneType) - } - } - TextField("Phone Number", text: phone.phoneNumber) - } - } - Button { - withAnimation { - $contact.phones.append(PhoneNumber()) - } - } label: { - Label("Add", systemImage: "plus.app") - } - } - if #available(iOS 15.0, *) { - Section("Notes") { - TextField("Notes", text: $contact.notes) - } - } else { - TextField("Notes", text: $contact.notes) - } - } - .navigationTitle("Contact") - } -} - -struct LazyView: View { - let build: () -> Content - init(_ build: @autoclosure @escaping () -> Content) { - self.build = build - } - var body: Content { - build() - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/examples/ios/swift/RealmExamples.xcodeproj/project.pbxproj b/examples/ios/swift/RealmExamples.xcodeproj/project.pbxproj index ef928c46e6..4e33baf8c1 100644 --- a/examples/ios/swift/RealmExamples.xcodeproj/project.pbxproj +++ b/examples/ios/swift/RealmExamples.xcodeproj/project.pbxproj @@ -55,20 +55,6 @@ 7DFFB15E25C19F0700CA8AE5 /* Example_v0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DFFB15D25C19F0700CA8AE5 /* Example_v0.swift */; }; 7DFFB16F25C1B65A00CA8AE5 /* Example_v1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DFFB16E25C1B65A00CA8AE5 /* Example_v1.swift */; }; 7DFFB17725C1B84E00CA8AE5 /* Example_v2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DFFB17625C1B84E00CA8AE5 /* Example_v2.swift */; }; - AC1004B4269C3D31008C482D /* AsyncOpenSwiftUIApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1004A7269C3D30008C482D /* AsyncOpenSwiftUIApp.swift */; }; - AC1004B5269C3D31008C482D /* AsyncOpenSwiftUIApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1004A7269C3D30008C482D /* AsyncOpenSwiftUIApp.swift */; }; - AC1004B6269C3D31008C482D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1004A8269C3D30008C482D /* ContentView.swift */; }; - AC1004B7269C3D31008C482D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1004A8269C3D30008C482D /* ContentView.swift */; }; - AC1004B8269C3D31008C482D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC1004A9269C3D31008C482D /* Assets.xcassets */; }; - AC1004B9269C3D31008C482D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC1004A9269C3D31008C482D /* Assets.xcassets */; }; - AC2D0C10269C470900126DCF /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC2D0C0E269C470900126DCF /* Realm.framework */; }; - AC2D0C11269C470900126DCF /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AC2D0C0E269C470900126DCF /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - AC2D0C12269C470900126DCF /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC2D0C0F269C470900126DCF /* RealmSwift.framework */; }; - AC2D0C13269C470900126DCF /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AC2D0C0F269C470900126DCF /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - AC2D0C17269C471200126DCF /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC2D0C15269C471200126DCF /* Realm.framework */; }; - AC2D0C18269C471200126DCF /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AC2D0C15269C471200126DCF /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - AC2D0C19269C471200126DCF /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC2D0C16269C471200126DCF /* RealmSwift.framework */; }; - AC2D0C1A269C471200126DCF /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AC2D0C16269C471200126DCF /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C07E5D7A1B15003B00ED625C /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; C07E5D7B1B15003B00ED625C /* Realm.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C07E5D7C1B15004800ED625C /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; @@ -81,16 +67,6 @@ C07E5D831B15005300ED625C /* Realm.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C07E5D841B15005500ED625C /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; C07E5D851B15005500ED625C /* Realm.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - CFEDF00A24B72622007FF10A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFEDF00924B72622007FF10A /* AppDelegate.swift */; }; - CFEDF00C24B72622007FF10A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFEDF00B24B72622007FF10A /* SceneDelegate.swift */; }; - CFEDF00E24B72622007FF10A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFEDF00D24B72622007FF10A /* ContentView.swift */; }; - CFEDF01024B72625007FF10A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CFEDF00F24B72625007FF10A /* Assets.xcassets */; }; - CFEDF01324B72625007FF10A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CFEDF01224B72625007FF10A /* Preview Assets.xcassets */; }; - CFEDF01624B72625007FF10A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CFEDF01424B72625007FF10A /* LaunchScreen.storyboard */; }; - CFF1724B24B7310E00A16A59 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CFF1724924B7310D00A16A59 /* Realm.framework */; }; - CFF1724C24B7310E00A16A59 /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CFF1724924B7310D00A16A59 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - CFF1724D24B7310E00A16A59 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CFF1724A24B7310E00A16A59 /* RealmSwift.framework */; }; - CFF1724E24B7310E00A16A59 /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CFF1724A24B7310E00A16A59 /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E886FB671A12A73E00CB2D0B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E886FB631A12A73E00CB2D0B /* AppDelegate.swift */; }; E886FB681A12A73E00CB2D0B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E886FB641A12A73E00CB2D0B /* Images.xcassets */; }; E886FB6A1A12A73E00CB2D0B /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E886FB661A12A73E00CB2D0B /* TableViewController.swift */; }; @@ -166,30 +142,6 @@ name = "Embed App Clips"; runOnlyForDeploymentPostprocessing = 0; }; - AC2D0C14269C470900126DCF /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - AC2D0C13269C470900126DCF /* RealmSwift.framework in Embed Frameworks */, - AC2D0C11269C470900126DCF /* Realm.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; - AC2D0C1B269C471200126DCF /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - AC2D0C1A269C471200126DCF /* RealmSwift.framework in Embed Frameworks */, - AC2D0C18269C471200126DCF /* Realm.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; C01304E31A60A4A000EB3E1E /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -256,18 +208,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - CFF1724F24B7310E00A16A59 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - CFF1724C24B7310E00A16A59 /* Realm.framework in Embed Frameworks */, - CFF1724E24B7310E00A16A59 /* RealmSwift.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; E8F14D141CF900E500564AF5 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -329,29 +269,12 @@ 7DFFB16E25C1B65A00CA8AE5 /* Example_v1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Example_v1.swift; sourceTree = ""; }; 7DFFB17625C1B84E00CA8AE5 /* Example_v2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Example_v2.swift; sourceTree = ""; }; 9C318E8C1CA42C7800879076 /* GettingStarted.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = GettingStarted.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - AC1004A7269C3D30008C482D /* AsyncOpenSwiftUIApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncOpenSwiftUIApp.swift; sourceTree = ""; }; - AC1004A8269C3D30008C482D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - AC1004A9269C3D31008C482D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - AC1004AE269C3D31008C482D /* AsyncOpenSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AsyncOpenSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AC1004B3269C3D31008C482D /* AsyncOpenSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AsyncOpenSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; }; AC2D0C0E269C470900126DCF /* Realm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AC2D0C0F269C470900126DCF /* RealmSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AC2D0C15269C471200126DCF /* Realm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AC2D0C16269C471200126DCF /* RealmSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - AC6210C2269C5B2C003134E1 /* AsyncOpenSwiftUI--macOS--Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "AsyncOpenSwiftUI--macOS--Info.plist"; sourceTree = ""; }; - AC6210C3269C5B3F003134E1 /* AsyncOpenSwiftUI--iOS--Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "AsyncOpenSwiftUI--iOS--Info.plist"; sourceTree = ""; }; C07E5D791B15001500ED625C /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C09C490F1A605A4800638C9F /* RealmSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - CFEDF00724B72622007FF10A /* AppleAuthentication.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppleAuthentication.app; sourceTree = BUILT_PRODUCTS_DIR; }; - CFEDF00924B72622007FF10A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - CFEDF00B24B72622007FF10A /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - CFEDF00D24B72622007FF10A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - CFEDF00F24B72625007FF10A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - CFEDF01224B72625007FF10A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - CFEDF01524B72625007FF10A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - CFEDF01724B72625007FF10A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - CFEDF01B24B7275D007FF10A /* AppleAuthentication.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AppleAuthentication.entitlements; sourceTree = ""; }; - CFEDF01C24B72883007FF10A /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; CFEDF01D24B72B67007FF10A /* Realm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CFEDF02124B72B6B007FF10A /* RealmSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CFF1724924B7310D00A16A59 /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Realm.framework; path = ../../../../../Desktop/Realm.framework; sourceTree = ""; }; @@ -416,33 +339,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - AC1004AB269C3D31008C482D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AC2D0C12269C470900126DCF /* RealmSwift.framework in Frameworks */, - AC2D0C10269C470900126DCF /* Realm.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AC1004B0269C3D31008C482D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AC2D0C19269C471200126DCF /* RealmSwift.framework in Frameworks */, - AC2D0C17269C471200126DCF /* Realm.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CFEDF00424B72622007FF10A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - CFF1724B24B7310E00A16A59 /* Realm.framework in Frameworks */, - CFF1724D24B7310E00A16A59 /* RealmSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; E886FB581A12A6FC00CB2D0B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -660,52 +556,14 @@ path = RealmTemplates; sourceTree = ""; }; - AC1004A6269C3D30008C482D /* AsyncOpenSwiftUI */ = { - isa = PBXGroup; - children = ( - AC1004A7269C3D30008C482D /* AsyncOpenSwiftUIApp.swift */, - AC1004A8269C3D30008C482D /* ContentView.swift */, - AC1004A9269C3D31008C482D /* Assets.xcassets */, - AC6210C3269C5B3F003134E1 /* AsyncOpenSwiftUI--iOS--Info.plist */, - AC6210C2269C5B2C003134E1 /* AsyncOpenSwiftUI--macOS--Info.plist */, - ); - path = AsyncOpenSwiftUI; - sourceTree = ""; - }; - CFEDF00824B72622007FF10A /* AppleAuthentication */ = { - isa = PBXGroup; - children = ( - CFEDF01124B72625007FF10A /* Preview Content */, - CFEDF00924B72622007FF10A /* AppDelegate.swift */, - CFEDF01B24B7275D007FF10A /* AppleAuthentication.entitlements */, - CFEDF00F24B72625007FF10A /* Assets.xcassets */, - CFEDF00D24B72622007FF10A /* ContentView.swift */, - CFEDF01724B72625007FF10A /* Info.plist */, - CFEDF01424B72625007FF10A /* LaunchScreen.storyboard */, - CFEDF01C24B72883007FF10A /* README.md */, - CFEDF00B24B72622007FF10A /* SceneDelegate.swift */, - ); - path = AppleAuthentication; - sourceTree = ""; - }; - CFEDF01124B72625007FF10A /* Preview Content */ = { - isa = PBXGroup; - children = ( - CFEDF01224B72625007FF10A /* Preview Assets.xcassets */, - ); - path = "Preview Content"; - sourceTree = ""; - }; E805758C19BA55E600376620 = { isa = PBXGroup; children = ( 53454CB824EAC3F900678E48 /* AppClip */, 53454CCB24EAC43500678E48 /* AppClipParent */, - CFEDF00824B72622007FF10A /* AppleAuthentication */, E8D3F2751A11766A00620884 /* Backlink */, 227D20F01CB609A1008F641B /* Common */, E8CB087F19BA6AEE0018434A /* Encryption */, - AC1004A6269C3D30008C482D /* AsyncOpenSwiftUI */, 493759B8230D9F9A0078C28E /* Frameworks */, E886FB621A12A73E00CB2D0B /* GroupedTableView */, 493759A5230D8CA60078C28E /* ListSwiftUI */, @@ -726,7 +584,6 @@ children = ( 53454CDD24EAC45500678E48 /* AppClip.app */, 53454CCA24EAC43500678E48 /* AppClipParent.app */, - CFEDF00724B72622007FF10A /* AppleAuthentication.app */, E8D3F2531A11765200620884 /* Backlink.app */, E8CB07EF19BA6AB60018434A /* Encryption.app */, E886FB601A12A6FC00CB2D0B /* GroupedTableView.app */, @@ -735,8 +592,6 @@ E8F14D0A1CF8FD0C00564AF5 /* PlaygroundFrameworkWrapper.framework */, E8CB085E19BA6ACA0018434A /* Simple.app */, E8CB083919BA6AC60018434A /* TableView.app */, - AC1004AE269C3D31008C482D /* AsyncOpenSwiftUI.app */, - AC1004B3269C3D31008C482D /* AsyncOpenSwiftUI.app */, 0CF50F80272FF6330048A358 /* Projections.app */, ); name = Products; @@ -889,60 +744,6 @@ productReference = 53454CDD24EAC45500678E48 /* AppClip.app */; productType = "com.apple.product-type.application.on-demand-install-capable"; }; - AC1004AD269C3D31008C482D /* AsyncOpenSwiftUI (iOS) */ = { - isa = PBXNativeTarget; - buildConfigurationList = AC1004BE269C3D31008C482D /* Build configuration list for PBXNativeTarget "AsyncOpenSwiftUI (iOS)" */; - buildPhases = ( - AC1004AA269C3D31008C482D /* Sources */, - AC1004AB269C3D31008C482D /* Frameworks */, - AC1004AC269C3D31008C482D /* Resources */, - AC2D0C14269C470900126DCF /* Embed Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "AsyncOpenSwiftUI (iOS)"; - productName = "AsyncOpenSwiftUI (iOS)"; - productReference = AC1004AE269C3D31008C482D /* AsyncOpenSwiftUI.app */; - productType = "com.apple.product-type.application"; - }; - AC1004B2269C3D31008C482D /* AsyncOpenSwiftUI (macOS) */ = { - isa = PBXNativeTarget; - buildConfigurationList = AC1004BF269C3D31008C482D /* Build configuration list for PBXNativeTarget "AsyncOpenSwiftUI (macOS)" */; - buildPhases = ( - AC1004AF269C3D31008C482D /* Sources */, - AC1004B0269C3D31008C482D /* Frameworks */, - AC1004B1269C3D31008C482D /* Resources */, - AC2D0C1B269C471200126DCF /* Embed Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "AsyncOpenSwiftUI (macOS)"; - productName = "AsyncOpenSwiftUI (macOS)"; - productReference = AC1004B3269C3D31008C482D /* AsyncOpenSwiftUI.app */; - productType = "com.apple.product-type.application"; - }; - CFEDF00624B72622007FF10A /* AppleAuthentication */ = { - isa = PBXNativeTarget; - buildConfigurationList = CFEDF01A24B72625007FF10A /* Build configuration list for PBXNativeTarget "AppleAuthentication" */; - buildPhases = ( - CFEDF00324B72622007FF10A /* Sources */, - CFEDF00424B72622007FF10A /* Frameworks */, - CFEDF00524B72622007FF10A /* Resources */, - CFF1724F24B7310E00A16A59 /* Embed Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = AppleAuthentication; - productName = AppleAuthentication; - productReference = CFEDF00724B72622007FF10A /* AppleAuthentication.app */; - productType = "com.apple.product-type.application"; - }; E886FB511A12A6FC00CB2D0B /* GroupedTableView */ = { isa = PBXNativeTarget; buildConfigurationList = E886FB5D1A12A6FC00CB2D0B /* Build configuration list for PBXNativeTarget "GroupedTableView" */; @@ -1097,19 +898,6 @@ LastSwiftMigration = 1300; ProvisioningStyle = Automatic; }; - AC1004AD269C3D31008C482D = { - CreatedOnToolsVersion = 13.0; - ProvisioningStyle = Automatic; - }; - AC1004B2269C3D31008C482D = { - CreatedOnToolsVersion = 13.0; - ProvisioningStyle = Automatic; - }; - CFEDF00624B72622007FF10A = { - CreatedOnToolsVersion = 11.5; - DevelopmentTeam = YYB57M5EKY; - ProvisioningStyle = Automatic; - }; E8CB07EE19BA6AB60018434A = { CreatedOnToolsVersion = 6.0; }; @@ -1152,11 +940,8 @@ E8CB083819BA6AC60018434A /* TableView */, E8F14D091CF8FD0C00564AF5 /* PlaygroundFrameworkWrapper */, 493759A3230D8CA60078C28E /* ListSwiftUI */, - CFEDF00624B72622007FF10A /* AppleAuthentication */, 53454CC924EAC43500678E48 /* AppClipParent */, 53454CDC24EAC45500678E48 /* AppClip */, - AC1004AD269C3D31008C482D /* AsyncOpenSwiftUI (iOS) */, - AC1004B2269C3D31008C482D /* AsyncOpenSwiftUI (macOS) */, 0CF50F7F272FF6330048A358 /* Projections */, ); }; @@ -1221,32 +1006,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - AC1004AC269C3D31008C482D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AC1004B8269C3D31008C482D /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AC1004B1269C3D31008C482D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AC1004B9269C3D31008C482D /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CFEDF00524B72622007FF10A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CFEDF01024B72625007FF10A /* Assets.xcassets in Resources */, - CFEDF01624B72625007FF10A /* LaunchScreen.storyboard in Resources */, - CFEDF01324B72625007FF10A /* Preview Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; E886FB5B1A12A6FC00CB2D0B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1324,34 +1083,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - AC1004AA269C3D31008C482D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AC1004B6269C3D31008C482D /* ContentView.swift in Sources */, - AC1004B4269C3D31008C482D /* AsyncOpenSwiftUIApp.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AC1004AF269C3D31008C482D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AC1004B7269C3D31008C482D /* ContentView.swift in Sources */, - AC1004B5269C3D31008C482D /* AsyncOpenSwiftUIApp.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CFEDF00324B72622007FF10A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CFEDF00A24B72622007FF10A /* AppDelegate.swift in Sources */, - CFEDF00E24B72622007FF10A /* ContentView.swift in Sources */, - CFEDF00C24B72622007FF10A /* SceneDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; E886FB541A12A6FC00CB2D0B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1427,17 +1158,6 @@ }; /* End PBXTargetDependency section */ -/* Begin PBXVariantGroup section */ - CFEDF01424B72625007FF10A /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - CFEDF01524B72625007FF10A /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin XCBuildConfiguration section */ 0CF50F8C272FF6340048A358 /* Debug */ = { isa = XCBuildConfiguration; @@ -1698,222 +1418,6 @@ }; name = Release; }; - AC1004BA269C3D31008C482D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_PREVIEWS = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "AsyncOpenSwiftUI/AsyncOpenSwiftUI--iOS--Info.plist"; - INFOPLIST_KEY_CFBundleExecutable = AsyncOpenSwiftUI; - INFOPLIST_KEY_CFBundleName = AsyncOpenSwiftUI; - INFOPLIST_KEY_CFBundleVersion = 1; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.AsyncOpenSwiftUI; - PRODUCT_NAME = AsyncOpenSwiftUI; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - AC1004BB269C3D31008C482D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_PREVIEWS = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "AsyncOpenSwiftUI/AsyncOpenSwiftUI--iOS--Info.plist"; - INFOPLIST_KEY_CFBundleExecutable = AsyncOpenSwiftUI; - INFOPLIST_KEY_CFBundleName = AsyncOpenSwiftUI; - INFOPLIST_KEY_CFBundleVersion = 1; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.0; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.AsyncOpenSwiftUI; - PRODUCT_NAME = AsyncOpenSwiftUI; - SDKROOT = iphoneos; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - AC1004BC269C3D31008C482D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_APP_SANDBOX = YES; - ENABLE_PREVIEWS = YES; - ENABLE_USER_SELECTED_FILES = readonly; - GCC_C_LANGUAGE_STANDARD = gnu11; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "AsyncOpenSwiftUI/AsyncOpenSwiftUI--macOS--Info.plist"; - INFOPLIST_KEY_CFBundleExecutable = AsyncOpenSwiftUI; - INFOPLIST_KEY_CFBundleName = AsyncOpenSwiftUI; - INFOPLIST_KEY_CFBundleVersion = 1; - INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Realm. All rights reserved."; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.AsyncOpenSwiftUI; - PRODUCT_NAME = AsyncOpenSwiftUI; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - AC1004BD269C3D31008C482D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_APP_SANDBOX = YES; - ENABLE_PREVIEWS = YES; - ENABLE_USER_SELECTED_FILES = readonly; - GCC_C_LANGUAGE_STANDARD = gnu11; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "AsyncOpenSwiftUI/AsyncOpenSwiftUI--macOS--Info.plist"; - INFOPLIST_KEY_CFBundleExecutable = AsyncOpenSwiftUI; - INFOPLIST_KEY_CFBundleName = AsyncOpenSwiftUI; - INFOPLIST_KEY_CFBundleVersion = 1; - INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Realm. All rights reserved."; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.0; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.AsyncOpenSwiftUI; - PRODUCT_NAME = AsyncOpenSwiftUI; - SDKROOT = macosx; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - CFEDF01824B72625007FF10A /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = AppleAuthentication/AppleAuthentication.entitlements; - CODE_SIGN_STYLE = Automatic; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_ASSET_PATHS = "\"AppleAuthentication/Preview Content\""; - DEVELOPMENT_TEAM = YYB57M5EKY; - ENABLE_PREVIEWS = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = AppleAuthentication/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.AppleAuthentication; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - CFEDF01924B72625007FF10A /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = AppleAuthentication/AppleAuthentication.entitlements; - CODE_SIGN_STYLE = Automatic; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_ASSET_PATHS = "\"AppleAuthentication/Preview Content\""; - DEVELOPMENT_TEAM = YYB57M5EKY; - ENABLE_PREVIEWS = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = AppleAuthentication/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.realm.AppleAuthentication; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; E80575AF19BA55E700376620 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2282,33 +1786,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - AC1004BE269C3D31008C482D /* Build configuration list for PBXNativeTarget "AsyncOpenSwiftUI (iOS)" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AC1004BA269C3D31008C482D /* Debug */, - AC1004BB269C3D31008C482D /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AC1004BF269C3D31008C482D /* Build configuration list for PBXNativeTarget "AsyncOpenSwiftUI (macOS)" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AC1004BC269C3D31008C482D /* Debug */, - AC1004BD269C3D31008C482D /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - CFEDF01A24B72625007FF10A /* Build configuration list for PBXNativeTarget "AppleAuthentication" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CFEDF01824B72625007FF10A /* Debug */, - CFEDF01924B72625007FF10A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; E805759019BA55E600376620 /* Build configuration list for PBXProject "RealmExamples" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/include/Realm/NSError+RLMSync.h b/include/Realm/NSError+RLMSync.h deleted file mode 120000 index 78afdad021..0000000000 --- a/include/Realm/NSError+RLMSync.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/NSError+RLMSync.h \ No newline at end of file diff --git a/include/Realm/RLMAPIKeyAuth.h b/include/Realm/RLMAPIKeyAuth.h deleted file mode 120000 index 4e792caeed..0000000000 --- a/include/Realm/RLMAPIKeyAuth.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMAPIKeyAuth.h \ No newline at end of file diff --git a/include/Realm/RLMApp.h b/include/Realm/RLMApp.h deleted file mode 120000 index 0337c190b8..0000000000 --- a/include/Realm/RLMApp.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMApp.h \ No newline at end of file diff --git a/include/Realm/RLMApp_Private.h b/include/Realm/RLMApp_Private.h deleted file mode 120000 index 85de833097..0000000000 --- a/include/Realm/RLMApp_Private.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMApp_Private.h \ No newline at end of file diff --git a/include/Realm/RLMAsymmetricObject.h b/include/Realm/RLMAsymmetricObject.h deleted file mode 120000 index 684ca21d6c..0000000000 --- a/include/Realm/RLMAsymmetricObject.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMAsymmetricObject.h \ No newline at end of file diff --git a/include/Realm/RLMBSON.h b/include/Realm/RLMBSON.h deleted file mode 120000 index bf0a877794..0000000000 --- a/include/Realm/RLMBSON.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMBSON.h \ No newline at end of file diff --git a/include/Realm/RLMCredentials.h b/include/Realm/RLMCredentials.h deleted file mode 120000 index c0f1c946f1..0000000000 --- a/include/Realm/RLMCredentials.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMCredentials.h \ No newline at end of file diff --git a/include/Realm/RLMEmailPasswordAuth.h b/include/Realm/RLMEmailPasswordAuth.h deleted file mode 120000 index d2d988a6f7..0000000000 --- a/include/Realm/RLMEmailPasswordAuth.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMEmailPasswordAuth.h \ No newline at end of file diff --git a/include/Realm/RLMEvent.h b/include/Realm/RLMEvent.h deleted file mode 120000 index 5ab22c88a7..0000000000 --- a/include/Realm/RLMEvent.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMEvent.h \ No newline at end of file diff --git a/include/Realm/RLMFindOneAndModifyOptions.h b/include/Realm/RLMFindOneAndModifyOptions.h deleted file mode 120000 index a224ec4d9b..0000000000 --- a/include/Realm/RLMFindOneAndModifyOptions.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMFindOneAndModifyOptions.h \ No newline at end of file diff --git a/include/Realm/RLMFindOptions.h b/include/Realm/RLMFindOptions.h deleted file mode 120000 index b0d1ba27f5..0000000000 --- a/include/Realm/RLMFindOptions.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMFindOptions.h \ No newline at end of file diff --git a/include/Realm/RLMInitialSubscriptionsConfiguration.h b/include/Realm/RLMInitialSubscriptionsConfiguration.h deleted file mode 120000 index 72d5364d8c..0000000000 --- a/include/Realm/RLMInitialSubscriptionsConfiguration.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMInitialSubscriptionsConfiguration.h \ No newline at end of file diff --git a/include/Realm/RLMMongoClient.h b/include/Realm/RLMMongoClient.h deleted file mode 120000 index b5e6c9aa50..0000000000 --- a/include/Realm/RLMMongoClient.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMMongoClient.h \ No newline at end of file diff --git a/include/Realm/RLMMongoCollection.h b/include/Realm/RLMMongoCollection.h deleted file mode 120000 index 5aa227df0d..0000000000 --- a/include/Realm/RLMMongoCollection.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMMongoCollection.h \ No newline at end of file diff --git a/include/Realm/RLMMongoCollection_Private.h b/include/Realm/RLMMongoCollection_Private.h deleted file mode 120000 index a7c90f6981..0000000000 --- a/include/Realm/RLMMongoCollection_Private.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMMongoCollection_Private.h \ No newline at end of file diff --git a/include/Realm/RLMMongoDatabase.h b/include/Realm/RLMMongoDatabase.h deleted file mode 120000 index 46a4e8ac71..0000000000 --- a/include/Realm/RLMMongoDatabase.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMMongoDatabase.h \ No newline at end of file diff --git a/include/Realm/RLMNetworkTransport.h b/include/Realm/RLMNetworkTransport.h deleted file mode 120000 index 8333c5a1ef..0000000000 --- a/include/Realm/RLMNetworkTransport.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMNetworkTransport.h \ No newline at end of file diff --git a/include/Realm/RLMProviderClient.h b/include/Realm/RLMProviderClient.h deleted file mode 120000 index def5a4c786..0000000000 --- a/include/Realm/RLMProviderClient.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMProviderClient.h \ No newline at end of file diff --git a/include/Realm/RLMPushClient.h b/include/Realm/RLMPushClient.h deleted file mode 120000 index d8952f739f..0000000000 --- a/include/Realm/RLMPushClient.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMPushClient.h \ No newline at end of file diff --git a/include/Realm/RLMRealm+Sync.h b/include/Realm/RLMRealm+Sync.h deleted file mode 120000 index 80dcd81b77..0000000000 --- a/include/Realm/RLMRealm+Sync.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMRealm+Sync.h \ No newline at end of file diff --git a/include/Realm/RLMSyncConfiguration.h b/include/Realm/RLMSyncConfiguration.h deleted file mode 120000 index 6d27816f2f..0000000000 --- a/include/Realm/RLMSyncConfiguration.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMSyncConfiguration.h \ No newline at end of file diff --git a/include/Realm/RLMSyncConfiguration_Private.h b/include/Realm/RLMSyncConfiguration_Private.h deleted file mode 120000 index c70f0a65e3..0000000000 --- a/include/Realm/RLMSyncConfiguration_Private.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMSyncConfiguration_Private.h \ No newline at end of file diff --git a/include/Realm/RLMSyncManager.h b/include/Realm/RLMSyncManager.h deleted file mode 120000 index 00f8c943ba..0000000000 --- a/include/Realm/RLMSyncManager.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMSyncManager.h \ No newline at end of file diff --git a/include/Realm/RLMSyncSession.h b/include/Realm/RLMSyncSession.h deleted file mode 120000 index d87d648268..0000000000 --- a/include/Realm/RLMSyncSession.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMSyncSession.h \ No newline at end of file diff --git a/include/Realm/RLMSyncSubscription.h b/include/Realm/RLMSyncSubscription.h deleted file mode 120000 index 68b3e5fb29..0000000000 --- a/include/Realm/RLMSyncSubscription.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMSyncSubscription.h \ No newline at end of file diff --git a/include/Realm/RLMSyncSubscription_Private.h b/include/Realm/RLMSyncSubscription_Private.h deleted file mode 120000 index d217ac2b8c..0000000000 --- a/include/Realm/RLMSyncSubscription_Private.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMSyncSubscription_Private.h \ No newline at end of file diff --git a/include/Realm/RLMUpdateResult.h b/include/Realm/RLMUpdateResult.h deleted file mode 120000 index b2de907f23..0000000000 --- a/include/Realm/RLMUpdateResult.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMUpdateResult.h \ No newline at end of file diff --git a/include/Realm/RLMUser.h b/include/Realm/RLMUser.h deleted file mode 120000 index c9670603d2..0000000000 --- a/include/Realm/RLMUser.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMUser.h \ No newline at end of file diff --git a/include/Realm/RLMUserAPIKey.h b/include/Realm/RLMUserAPIKey.h deleted file mode 120000 index 857b890064..0000000000 --- a/include/Realm/RLMUserAPIKey.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMUserAPIKey.h \ No newline at end of file diff --git a/include/Realm/RLMUser_Private.h b/include/Realm/RLMUser_Private.h deleted file mode 120000 index 3c0270fe01..0000000000 --- a/include/Realm/RLMUser_Private.h +++ /dev/null @@ -1 +0,0 @@ -../../Realm/RLMUser_Private.h \ No newline at end of file diff --git a/include/module.modulemap b/include/module.modulemap index db2c651a08..d595a5df7e 100644 --- a/include/module.modulemap +++ b/include/module.modulemap @@ -7,14 +7,11 @@ module Realm { explicit module Private { header "Realm/RLMAccessor.h" - header "Realm/RLMApp_Private.h" header "Realm/RLMArray_Private.h" header "Realm/RLMAsyncTask_Private.h" header "Realm/RLMCollection_Private.h" header "Realm/RLMDictionary_Private.h" - header "Realm/RLMEvent.h" header "Realm/RLMLogger_Private.h" - header "Realm/RLMMongoCollection_Private.h" header "Realm/RLMObjectBase_Dynamic.h" header "Realm/RLMObjectBase_Private.h" header "Realm/RLMObjectSchema_Private.h" @@ -31,9 +28,6 @@ module Realm { header "Realm/RLMSwiftCollectionBase.h" header "Realm/RLMSwiftProperty.h" header "Realm/RLMSwiftValueStorage.h" - header "Realm/RLMSyncConfiguration_Private.h" - header "Realm/RLMSyncSubscription_Private.h" - header "Realm/RLMUser_Private.h" } explicit module Dynamic { diff --git a/scripts/setup-cocoapods.sh b/scripts/setup-cocoapods.sh index b5ce182742..d210450079 100644 --- a/scripts/setup-cocoapods.sh +++ b/scripts/setup-cocoapods.sh @@ -14,4 +14,3 @@ cp -R "$source_root/core/realm-monorepo.xcframework/ios-arm64/Headers" "$source_ mkdir -p "$source_root/include" cp "$source_root/Realm/"*.h "$source_root/Realm/"*.hpp "$source_root/include" -echo "#define REALM_IOPLATFORMUUID @\"$(sh $source_root/build.sh get-ioplatformuuid)\"" >> "$source_root/Realm/RLMAnalytics.hpp"