diff --git a/CHANGELOG.md b/CHANGELOG.md index 439fc899c8..e466779a55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,117 @@ +v10 (TBD) +------------------ + +### Breaking Changes +* We no longer support Realm Cloud (legacy), but instead the new [MongoDB Realm Cloud](https://realm.mongodb.com). MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. ([#2011](https://github.com/realm/realm-dotnet/pull/2011)) +* Remove support for Query-based sync, including the configuration parameters and the `SyncSubscription` types. ([#2011](https://github.com/realm/realm-dotnet/pull/2011)) +* Remove everything related to sync permissions, including both the path-based permission system and the object-level privileges for query-based sync. [Permissions in MongoDB Realm](https://docs.mongodb.com/realm/sync/permissions/) are defined serverside. ([#2011](https://github.com/realm/realm-dotnet/pull/2011)) +* Moved all API for dynamic access on the `Realm` class to `Realm.DynamicApi`: + * `Realm.CreateObject(string className, object primaryKey)` is now `Realm.DynamicApi.CreateObject(string className, object primaryKey)`. + * `Realm.All(string className)` is now `Realm.DynamicApi.All(string className)`. + * `Realm.RemoveAll(string className)` is now `Realm.DynamicApi.RemoveAll(string className)`. + * `Realm.Find(string className, long? primaryKey)` is now `Realm.DynamicApi.Find(string className, long? primaryKey)`. + * `Realm.Find(string className, string primaryKey)` is now `Realm.DynamicApi.Find(string className, string primaryKey)`. +* It is now required that all top-level objects in a synchronized Realm have a primary key called `_id`. You can use the `MapTo("_id")` attribute to avoid using unidiomatic names for the model properties. +* Bumped the minimum target for Xamarin.iOS apps to iOS 9. +* Bumped the minimum API level for Xamarin.Android apps to 16 (Android 4.1). +* Renamed `FullSyncConfiguration` to `SyncConfiguration`. + +### Enhancements +* Added support for syncing to MongoDB instead of Realm Object Server. Applications must be created at [realm.mongodb.com](https://realm.mongodb.com). +* Added an `App` class which is the entrypoint for synchronizing with a MongoDB Realm App. +* Added `User.CustomData` containing an unstructured document with additional information about the user. Custom data is configured in your MongoDB Realm App. +* Added `User.Functions`. This is the entry point for calling Remote MongoDB Realm functions. Functions allow you to define and execute server-side logic for your application. Functions are written in modern JavaScript (ES6+) and execute in a serverless manner. When you call a function, you can dynamically access components of the current application as well as information about the request to execute the function and the logged in user that sent the request. +* Added `User.GetMongoClient` exposing an API for CRUD operations on a Remote MongoDB Service. +* Added `User.GetPushClient` exposing an API for registering a device for push notifications. +* Change `SyncConfiguration` to accept partition value instead of a server Uri. Partition values can currently be of types `string`, `long`, or `ObjectId`. Opening a realm by partition value is the equivalent of previously opening a realm by URL. In this case, partitions are meant to be more closely associated with your data. E.g., if you are a large retailer with multiple locations, the partition key can be the store Id and you each Realm will only contain data related to the specified store. +* Add support for the Decimal128 data type. This is a 128-bit IEEE 754 decimal floating point number. Properties of this type can be declared either as `MongoDB.Bson.Decimal128` type or the built-in `decimal` type. Note that .NET's built-in decimal is 96-bit, so it cannot represent the full range of numbers, representable by `Decimal128`. (PR [#2014](https://github.com/realm/realm-dotnet/pull/2014)) +* Add support for the `ObjectId` data type. This is a 12 byte unique identifier that is common as a document id in MongoDB databases. It can be used a primary key. (PR [#2035](https://github.com/realm/realm-dotnet/pull/2035)) +* Add support for embedded objects. Embedded objects are objects which are owned by a single parent object, and are deleted when that parent object is deleted or their parent no longer references them. Embedded objects are declared by subclassing `EmbeddedObject` instead of `RealmObject`. Reassigning an embedded object is not allowed and neither is linking to it from multiple parents. Querying for embedded objects directly is also disallowed as they should be viewed as complex structures belonging to their parents as opposed to standalone objects. A trivial example is: + + ```csharp + public class Address : EmbeddedObject + { + public string Street { get; set; } + + public string City { get; set; } + } + + public class Person : RealmObject + { + public string Name { get; set; } + + // Address is an embedded object - you reference it as usual + public Address Address { get; set; } + } + + public class Company : RealmObject + { + public string PhoneNumber { get; set; } + + // Embedded objects can be contained in lists too + public IList
OfficeAddresses { get; } + } + ``` + +* Added new dynamic methods for instantiating embedded objects: + * `Realm.DynamicApi.CreateEmbeddedObjectForProperty` should be used to create an embedded object and assign it to a parent's property. For example: + + ```csharp + // static API + var person = new Person(); + person.Address = new Address + { + City = "New York" + }; + + // dynamic API + var dynamicPerson = realm.DynamicApi.CreateObject("Person"); + var address = realm.DynamicApi.CreateEmbeddedObjectForProperty(dynamicPerson, "Address") + address.City = "New York"; + ``` + + * `Realm.DynamicApi.AddEmbeddedObjectToList` should be used to create an embedded object and add it to a parent's list property. + * `Realm.DynamicApi.InsertEmbeddedObjectInList` should be used to create an embedded object and insert it in a parent's list property at a specified index. + * `Realm.DynamicApi.SetEmbeddedObjectInList` should be used to create an embedded object and set it at an index in a parent's list property. + + ```csharp + // static API + var company = new Company(); + company.OfficeAddresses.Add(new Address + { + City = "New York" + }); + + company.OfficeAddresses.Insert(0, new Address + { + City = "Palo Alto" + }); + + company.OfficeAddresses[1] = new Address + { + City = "New Jersey" + }; + + // dynamic API + var dynamicCompany = realm.DynamicApi.CreateObject("Company"); + var officeToAdd = realm.DynamicApi.AddEmbeddedObjectToList(dynamicCompany.OfficeAddresses); + officeToAdd.City = "New York"; + + var officeToInsert = realm.DynamicApi.InsertEmbeddedObjectInList(dynamicCompany.OfficeAddresses, 0); + officeToInsert.City = "Palo Alto"; + + var officeToSet = realm.DynamicApi.SetEmbeddedObjectInList(dynamicCompany.OfficeAddresses, 1); + officeToSet.City = "New Jersey"; + ``` + +* The memory mapping scheme for Realm files has changed to better support opening very large files. + +### Compatibility +* Realm Studio: 10.0.0 or later. + +### Internal +* Using Sync 10.0.0 and Core 10.0.0. + ## 5.1.2 (TBD) ------------------ diff --git a/Jenkinsfile b/Jenkinsfile index 58febccd7d..6839811ce5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -23,11 +23,12 @@ stage('Checkout') { userRemoteConfigs: scm.userRemoteConfigs ]) - if (env.BRANCH_NAME == 'master') { - versionSuffix = "alpha-${env.BUILD_ID}" + // V10TODO: temporary set v10 as publishing branch + if (shouldPublishPackage()) { + versionSuffix = "alpha.${env.BUILD_ID}" } else if (env.CHANGE_BRANCH == null || !env.CHANGE_BRANCH.startsWith('release')) { - versionSuffix = "PR-${env.CHANGE_ID}-${env.BUILD_ID}" + versionSuffix = "PR-${env.CHANGE_ID}.${env.BUILD_ID}" } stash includes: '**', excludes: 'wrappers/**', name: 'dotnet-source', useDefaultExcludes: false @@ -38,7 +39,7 @@ stage('Checkout') { stage('Build wrappers') { def jobs = [ 'iOS': { - rlmNode('osx') { + rlmNode('osx || macos-catalina') { unstash 'dotnet-wrappers-source' dir('wrappers') { sh "./build-ios.sh --configuration=${configuration}" @@ -47,7 +48,7 @@ stage('Build wrappers') { } }, 'macOS': { - rlmNode('osx') { + rlmNode('osx || macos-catalina') { unstash 'dotnet-wrappers-source' dir('wrappers') { sh "REALM_CMAKE_CONFIGURATION=${configuration} ./build.sh -GXcode" @@ -59,9 +60,7 @@ stage('Build wrappers') { rlmNode('docker') { unstash 'dotnet-wrappers-source' dir('wrappers') { - buildDockerEnv("ci/realm-dotnet:wrappers", extra_args: "-f centos.Dockerfile").inside() { - sh "REALM_CMAKE_CONFIGURATION=${configuration} ./build.sh" - } + buildWrappersInDocker('wrappers', 'centos.Dockerfile', "REALM_CMAKE_CONFIGURATION=${configuration} ./build.sh") } stash includes: 'wrappers/build/**', name: 'linux-wrappers' } @@ -74,9 +73,7 @@ stage('Build wrappers') { rlmNode('docker') { unstash 'dotnet-wrappers-source' dir('wrappers') { - buildDockerEnv("ci/realm-dotnet:wrappers_android", extra_args: '-f android.Dockerfile').inside() { - sh "./build-android.sh --configuration=${configuration} --ARCH=${localAbi}" - } + buildWrappersInDocker('wrappers_android', 'android.Dockerfile', "./build-android.sh --configuration=${configuration} --ARCH=${localAbi}") } stash includes: 'wrappers/build/**', name: "android-wrappers-${localAbi}" } @@ -86,13 +83,13 @@ stage('Build wrappers') { for(platform in WindowsPlatforms) { def localPlatform = platform jobs["Windows ${localPlatform}"] = { - rlmNode('windows-vs2017') { + rlmNode('windows') { unstash 'dotnet-wrappers-source' dir('wrappers') { powershell ".\\build.ps1 Windows -Configuration ${configuration} -Platforms ${localPlatform}" } stash includes: 'wrappers/build/**', name: "windows-wrappers-${localPlatform}" - if (env.BRANCH_NAME == 'master') { + if (shouldPublishPackage()) { archiveArtifacts 'wrappers/build/**/*.pdb' } } @@ -102,13 +99,13 @@ stage('Build wrappers') { for(platform in WindowsUniversalPlatforms) { def localPlatform = platform jobs["WindowsUniversal ${localPlatform}"] = { - rlmNode('windows-vs2017') { + rlmNode('windows') { unstash 'dotnet-wrappers-source' dir('wrappers') { powershell ".\\build.ps1 WindowsStore -Configuration ${configuration} -Platforms ${localPlatform}" } stash includes: 'wrappers/build/**', name: "windowsuniversal-wrappers-${localPlatform}" - if (env.BRANCH_NAME == 'master') { + if (shouldPublishPackage()) { archiveArtifacts 'wrappers/build/**/*.pdb' } } @@ -164,7 +161,7 @@ stage('Package') { packageVersion = getVersion(packages[0].name); echo "Inferred version is ${packageVersion}" - if (env.BRANCH_NAME == 'master') { + if (shouldPublishPackage()) { withCredentials([usernamePassword(credentialsId: 'github-packages-token', usernameVariable: 'GITHUB_USERNAME', passwordVariable: 'GITHUB_PASSWORD')]) { echo "Publishing Realm.Fody.${packageVersion} to github packages" bat "dotnet nuget add source https://nuget.pkg.github.com/realm/index.json -n github -u ${env.GITHUB_USERNAME} -p ${env.GITHUB_PASSWORD} & exit 0" @@ -329,13 +326,18 @@ def NetCoreTest(String nodeName) { dotnet build -c ${configuration} -f netcoreapp20 -p:RestoreConfigFile=${env.WORKSPACE}/Tests/Test.NuGet.Config -p:UseRealmNupkgsWithVersion=${packageVersion} dotnet run -c ${configuration} -f netcoreapp20 --no-build -- --labels=After --result=${env.WORKSPACE}/TestResults.NetCore.xml """.trim() + + String appLocation = "${env.WORKSPACE}/Tests/TestApps/dotnet-integration-tests" + if (isUnix()) { if (nodeName == 'docker') { def test_runner_image = docker.image('mcr.microsoft.com/dotnet/core/sdk:2.1') test_runner_image.pull() - withRos('3.23.1') { ros -> - test_runner_image.inside("--link ${ros.id}:ros") { - script += ' --ros $ROS_PORT_9080_TCP_ADDR --rosport $ROS_PORT_9080_TCP_PORT' + withRealmCloud(version: '2020-10-12', appsToImport: ["dotnet-integration-tests": appLocation]) { networkName -> + test_runner_image.inside("--network=${networkName}") { + def appId = sh script: "cat ${appLocation}/app_id", returnStdout: true + + script += " --baasurl http://mongodb-realm:9090 --baasappid ${appId.trim()}" // see https://stackoverflow.com/a/53782505 sh """ export HOME=/tmp @@ -401,6 +403,19 @@ def reportTests(spec) { ) } +def buildWrappersInDocker(String label, String image, String invocation) { + String uid = sh(script: 'id -u', returnStdout: true).trim() + String gid = sh(script: 'id -g', returnStdout: true).trim() + + buildDockerEnv("ci/realm-dotnet:${label}", extra_args: "-f ${image}").inside("--mount 'type=bind,src=/tmp,dst=/tmp' -u ${uid}:${gid}") { + sh invocation + } +} + +boolean shouldPublishPackage() { + return env.BRANCH_NAME == 'master' || (env.CHANGE_BRANCH != null && env.CHANGE_BRANCH == 'v10') +} + // Required due to JENKINS-27421 @NonCPS List> mapToList(Map map) { diff --git a/Manual Tests/NuGetReleaseTests/NuGetReleaseTests.sln b/Manual Tests/NuGetReleaseTests/NuGetReleaseTests.sln deleted file mode 100644 index 4b37238807..0000000000 --- a/Manual Tests/NuGetReleaseTests/NuGetReleaseTests.sln +++ /dev/null @@ -1,260 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.12 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Tests.Shared", "..\..\Tests\Tests.Shared\Tests.Shared.shproj", "{06146619-D21C-414C-BFE3-2F59ACBA412E}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Tests.Sync.Shared", "..\..\Tests\Tests.Sync.Shared\Tests.Sync.Shared.shproj", "{C81983E0-58A0-4D67-B0D0-AE91ABA71A2C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Android", "Tests.Android\Tests.Android.csproj", "{AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.iOS", "Tests.iOS\Tests.iOS.csproj", "{D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.UWP", "Tests.UWP\Tests.UWP.csproj", "{159A55AF-9531-4107-8444-CA9C96107EF5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Win32", "Tests.Win32\Tests.Win32.csproj", "{BFC1C1B7-2864-4456-9DEC-309A3F006B66}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.NetCore", "Tests.NetCore\Tests.NetCore.csproj", "{1F426652-FDA6-49AF-B621-7C8B9189B1BD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.XamarinMac", "Tests.XamarinMac\Tests.XamarinMac.csproj", "{3A6CC342-40D0-4487-9964-DDAEBA628199}" -EndProject -Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - ..\..\Tests\Tests.Shared\Tests.Shared.projitems*{06146619-d21c-414c-bfe3-2f59acba412e}*SharedItemsImports = 13 - ..\..\Tests\Tests.Shared\Tests.Shared.projitems*{159a55af-9531-4107-8444-ca9c96107ef5}*SharedItemsImports = 4 - ..\..\Tests\Tests.Sync.Shared\Tests.Sync.Shared.projitems*{159a55af-9531-4107-8444-ca9c96107ef5}*SharedItemsImports = 4 - ..\..\Tests\Tests.Shared\Tests.Shared.projitems*{bfc1c1b7-2864-4456-9dec-309a3f006b66}*SharedItemsImports = 4 - ..\..\Tests\Tests.Sync.Shared\Tests.Sync.Shared.projitems*{bfc1c1b7-2864-4456-9dec-309a3f006b66}*SharedItemsImports = 4 - ..\..\Tests\Tests.Sync.Shared\Tests.Sync.Shared.projitems*{c81983e0-58a0-4d67-b0d0-ae91aba71a2c}*SharedItemsImports = 13 - EndGlobalSection - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|ARM = Debug|ARM - Debug|iPhone = Debug|iPhone - Debug|iPhoneSimulator = Debug|iPhoneSimulator - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - NativeDebug|Any CPU = NativeDebug|Any CPU - NativeDebug|ARM = NativeDebug|ARM - NativeDebug|iPhone = NativeDebug|iPhone - NativeDebug|iPhoneSimulator = NativeDebug|iPhoneSimulator - NativeDebug|x64 = NativeDebug|x64 - NativeDebug|x86 = NativeDebug|x86 - Release|Any CPU = Release|Any CPU - Release|ARM = Release|ARM - Release|iPhone = Release|iPhone - Release|iPhoneSimulator = Release|iPhoneSimulator - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Debug|ARM.ActiveCfg = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Debug|ARM.Build.0 = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Debug|iPhone.Build.0 = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Debug|x64.ActiveCfg = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Debug|x86.ActiveCfg = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.NativeDebug|ARM.ActiveCfg = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.NativeDebug|iPhone.ActiveCfg = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.NativeDebug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.NativeDebug|x64.ActiveCfg = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.NativeDebug|x86.ActiveCfg = Debug|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Release|Any CPU.Build.0 = Release|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Release|ARM.ActiveCfg = Release|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Release|ARM.Build.0 = Release|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Release|iPhone.ActiveCfg = Release|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Release|iPhone.Build.0 = Release|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Release|x64.ActiveCfg = Release|Any CPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA}.Release|x86.ActiveCfg = Release|Any CPU - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Debug|ARM.ActiveCfg = Debug|iPhoneSimulator - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Debug|ARM.Build.0 = Debug|iPhoneSimulator - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Debug|iPhone.ActiveCfg = Debug|iPhone - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Debug|iPhone.Build.0 = Debug|iPhone - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Debug|x64.ActiveCfg = Debug|iPhone - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Debug|x86.ActiveCfg = Debug|iPhone - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.NativeDebug|Any CPU.ActiveCfg = Release|iPhone - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.NativeDebug|ARM.ActiveCfg = Release|iPhone - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.NativeDebug|iPhone.ActiveCfg = Debug|iPhone - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.NativeDebug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.NativeDebug|x64.ActiveCfg = Release|iPhone - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.NativeDebug|x86.ActiveCfg = Release|iPhone - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Release|Any CPU.Build.0 = Release|iPhoneSimulator - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Release|ARM.ActiveCfg = Release|iPhoneSimulator - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Release|ARM.Build.0 = Release|iPhoneSimulator - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Release|iPhone.ActiveCfg = Release|iPhone - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Release|iPhone.Build.0 = Release|iPhone - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Release|x64.ActiveCfg = Release|iPhone - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E}.Release|x86.ActiveCfg = Release|iPhone - {159A55AF-9531-4107-8444-CA9C96107EF5}.Debug|Any CPU.ActiveCfg = Debug|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Debug|ARM.ActiveCfg = Debug|ARM - {159A55AF-9531-4107-8444-CA9C96107EF5}.Debug|ARM.Build.0 = Debug|ARM - {159A55AF-9531-4107-8444-CA9C96107EF5}.Debug|ARM.Deploy.0 = Debug|ARM - {159A55AF-9531-4107-8444-CA9C96107EF5}.Debug|iPhone.ActiveCfg = Debug|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Debug|iPhoneSimulator.ActiveCfg = Debug|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Debug|x64.ActiveCfg = Debug|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Debug|x64.Build.0 = Debug|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Debug|x64.Deploy.0 = Debug|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Debug|x86.ActiveCfg = Debug|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Debug|x86.Build.0 = Debug|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Debug|x86.Deploy.0 = Debug|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|Any CPU.ActiveCfg = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|Any CPU.Build.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|Any CPU.Deploy.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|ARM.ActiveCfg = Debug|ARM - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|ARM.Build.0 = Debug|ARM - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|ARM.Deploy.0 = Debug|ARM - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|iPhone.ActiveCfg = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|iPhone.Build.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|iPhone.Deploy.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|iPhoneSimulator.ActiveCfg = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|iPhoneSimulator.Build.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|iPhoneSimulator.Deploy.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|x64.ActiveCfg = Debug|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|x64.Build.0 = Debug|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|x64.Deploy.0 = Debug|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|x86.ActiveCfg = Debug|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|x86.Build.0 = Debug|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.NativeDebug|x86.Deploy.0 = Debug|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|Any CPU.ActiveCfg = Release|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|ARM.ActiveCfg = Release|ARM - {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|ARM.Build.0 = Release|ARM - {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|ARM.Deploy.0 = Release|ARM - {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|iPhone.ActiveCfg = Release|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|iPhoneSimulator.ActiveCfg = Release|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|x64.ActiveCfg = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|x64.Build.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|x64.Deploy.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|x86.ActiveCfg = Release|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|x86.Build.0 = Release|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|x86.Deploy.0 = Release|x86 - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Debug|ARM.ActiveCfg = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Debug|ARM.Build.0 = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Debug|iPhone.Build.0 = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Debug|x64.ActiveCfg = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Debug|x64.Build.0 = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Debug|x86.ActiveCfg = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Debug|x86.Build.0 = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.NativeDebug|ARM.ActiveCfg = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.NativeDebug|ARM.Build.0 = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.NativeDebug|iPhone.ActiveCfg = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.NativeDebug|iPhone.Build.0 = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.NativeDebug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.NativeDebug|iPhoneSimulator.Build.0 = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.NativeDebug|x64.ActiveCfg = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.NativeDebug|x64.Build.0 = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.NativeDebug|x86.ActiveCfg = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.NativeDebug|x86.Build.0 = Debug|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Release|Any CPU.Build.0 = Release|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Release|ARM.ActiveCfg = Release|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Release|ARM.Build.0 = Release|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Release|iPhone.ActiveCfg = Release|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Release|iPhone.Build.0 = Release|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Release|x64.ActiveCfg = Release|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Release|x64.Build.0 = Release|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Release|x86.ActiveCfg = Release|Any CPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66}.Release|x86.Build.0 = Release|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Debug|ARM.ActiveCfg = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Debug|ARM.Build.0 = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Debug|iPhone.Build.0 = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Debug|x64.ActiveCfg = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Debug|x64.Build.0 = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Debug|x86.ActiveCfg = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Debug|x86.Build.0 = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.NativeDebug|ARM.ActiveCfg = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.NativeDebug|ARM.Build.0 = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.NativeDebug|iPhone.ActiveCfg = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.NativeDebug|iPhone.Build.0 = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.NativeDebug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.NativeDebug|iPhoneSimulator.Build.0 = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.NativeDebug|x64.ActiveCfg = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.NativeDebug|x64.Build.0 = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.NativeDebug|x86.ActiveCfg = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.NativeDebug|x86.Build.0 = Debug|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Release|Any CPU.Build.0 = Release|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Release|ARM.ActiveCfg = Release|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Release|ARM.Build.0 = Release|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Release|iPhone.ActiveCfg = Release|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Release|iPhone.Build.0 = Release|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Release|x64.ActiveCfg = Release|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Release|x64.Build.0 = Release|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Release|x86.ActiveCfg = Release|Any CPU - {1F426652-FDA6-49AF-B621-7C8B9189B1BD}.Release|x86.Build.0 = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|ARM.ActiveCfg = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|ARM.Build.0 = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|iPhone.Build.0 = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|x64.ActiveCfg = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|x64.Build.0 = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|x86.ActiveCfg = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|x86.Build.0 = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.NativeDebug|ARM.ActiveCfg = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.NativeDebug|ARM.Build.0 = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.NativeDebug|iPhone.ActiveCfg = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.NativeDebug|iPhone.Build.0 = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.NativeDebug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.NativeDebug|iPhoneSimulator.Build.0 = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.NativeDebug|x64.ActiveCfg = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.NativeDebug|x64.Build.0 = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.NativeDebug|x86.ActiveCfg = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.NativeDebug|x86.Build.0 = Debug|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|Any CPU.Build.0 = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|ARM.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|ARM.Build.0 = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|iPhone.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|iPhone.Build.0 = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|x64.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|x64.Build.0 = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|x86.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9F2E1B1D-81A3-473E-8633-86CF2EE2BBE5} - EndGlobalSection -EndGlobal diff --git a/Manual Tests/NuGetReleaseTests/README.md b/Manual Tests/NuGetReleaseTests/README.md deleted file mode 100644 index cceafea78c..0000000000 --- a/Manual Tests/NuGetReleaseTests/README.md +++ /dev/null @@ -1,22 +0,0 @@ -NuGetReleaseTests -============= - -The separate solution `NuGetReleaseTests.sln` reuses our shared test code normally run -within the main `Realm.sln` but pulls in Realm via our NuGet packages. - -This means we can easily run the unit test suite again over the release version of the PCL -to ensure no problems would present themselves to users which got past other builds of tests. - -Note as a side-effect of using NuGet to add Realm, it adds the`Realm.PCL` project to the solution, even though we're not using a PCL anywhere. - -Using this Solution -------------------- -The solution as bundled in our source repo does **not** include Realm. - -You should: -1. Duplicate the entire folder `NuGetReleaseTests` -2. In the duplicated `NuGetReleaseTests.sln` use NuGet to add the Realm package to both the IOS and Android projects. -3. Build. - -This ensures we replicate the experience of starting out with NuGet to add packages. - diff --git a/Manual Tests/NuGetReleaseTests/Tests.Android/Constants.cs b/Manual Tests/NuGetReleaseTests/Tests.Android/Constants.cs deleted file mode 100644 index e9a4702e7b..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.Android/Constants.cs +++ /dev/null @@ -1,26 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Tests -{ - public static class Constants - { - public const string ActivityLabel = "NuGet Tests"; - public const string PackageName = "io.realm.nugettests"; - } -} diff --git a/Manual Tests/NuGetReleaseTests/Tests.Android/FodyWeavers.xml b/Manual Tests/NuGetReleaseTests/Tests.Android/FodyWeavers.xml deleted file mode 100644 index 6aa124364c..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.Android/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.Android/Properties/AndroidManifest.xml b/Manual Tests/NuGetReleaseTests/Tests.Android/Properties/AndroidManifest.xml deleted file mode 100644 index a54625988e..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.Android/Properties/AndroidManifest.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-hdpi/Icon.png b/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-hdpi/Icon.png deleted file mode 100644 index 055d8ac77e..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-hdpi/Icon.png and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-ldpi/icon.png b/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-ldpi/icon.png deleted file mode 100644 index 8be15a63dd..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-ldpi/icon.png and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-mdpi/Icon.png b/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-mdpi/Icon.png deleted file mode 100644 index 5ef8d4ca1a..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-mdpi/Icon.png and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-xhdpi/Icon.png b/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-xhdpi/Icon.png deleted file mode 100644 index 7f3ebbeadb..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-xhdpi/Icon.png and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-xxhdpi/Icon.png b/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-xxhdpi/Icon.png deleted file mode 100644 index 91cb35f5df..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-xxhdpi/Icon.png and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-xxxhdpi/Icon.png b/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-xxxhdpi/Icon.png deleted file mode 100644 index 8a3194cd94..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.Android/Resources/drawable-xxxhdpi/Icon.png and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.Android/Settings.StyleCop b/Manual Tests/NuGetReleaseTests/Tests.Android/Settings.StyleCop deleted file mode 100644 index 805dec909e..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.Android/Settings.StyleCop +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - False - - - - - False - - - - - False - - - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.Android/Tests.Android.csproj b/Manual Tests/NuGetReleaseTests/Tests.Android/Tests.Android.csproj deleted file mode 100644 index 9575b04c0f..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.Android/Tests.Android.csproj +++ /dev/null @@ -1,218 +0,0 @@ - - - - - - - Debug - AnyCPU - {AD7B78B0-AE51-4FB7-9B11-4E22A2B538CA} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Properties - Tests - Tests.Android - 512 - true - Resources\Resource.Designer.cs - Off - Properties\AndroidManifest.xml - True - armeabi,armeabi-v7a,x86 - - - - - - - - True - v8.1 - Full - false - nunit.runner.Droid - - - true - full - false - bin\Debug\ - bin\Debug\ - DEBUG;TRACE;ROS_SETUP - prompt - 4 - false - armeabi-v7a;x86;arm64-v8a;x86_64 - - - - true - bin\Release\ - TRACE;ROS_SETUP - prompt - 4 - False - armeabi;armeabi-v7a;x86 - - - - - - ..\packages\NUnit.3.6.1\lib\MonoAndroid\nunit.framework.dll - - - ..\packages\PCLStorage.1.0.2\lib\monoandroid\PCLStorage.dll - - - ..\packages\PCLStorage.1.0.2\lib\monoandroid\PCLStorage.Abstractions.dll - - - - - - - ..\..\..\dependencies\nunit.xamarin.3.6.1\MonoAndroid\nunit.runner.Droid.dll - - - - - - ..\packages\System.Runtime.Loader.4.0.0\lib\netstandard1.5\System.Runtime.Loader.dll - - - ..\packages\Nito.Disposables.1.0.0\lib\portable45-net45+win8+wp8+wpa81\Nito.Disposables.dll - - - ..\packages\Nito.AsyncEx.Tasks.1.1.0\lib\netstandard1.3\Nito.AsyncEx.Tasks.dll - - - ..\packages\Nito.AsyncEx.Context.1.1.0\lib\netstandard1.3\Nito.AsyncEx.Context.dll - - - ..\packages\System.Reactive.Interfaces.3.1.1\lib\netstandard1.0\System.Reactive.Interfaces.dll - - - ..\packages\System.Reactive.Core.3.1.1\lib\netstandard1.3\System.Reactive.Core.dll - - - ..\packages\System.Reactive.Linq.3.1.1\lib\netstandard1.3\System.Reactive.Linq.dll - - - ..\packages\System.Reactive.PlatformServices.3.1.1\lib\netstandard1.3\System.Reactive.PlatformServices.dll - - - ..\packages\Xamarin.Android.Support.v4.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v4.dll - - - ..\packages\Xamarin.Android.Support.v7.CardView.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.CardView.dll - - - ..\packages\Xamarin.Android.Support.v7.RecyclerView.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.RecyclerView.dll - - - ..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.Vector.Drawable.dll - - - ..\packages\Xamarin.Android.Support.Animated.Vector.Drawable.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.Animated.Vector.Drawable.dll - - - ..\packages\Xamarin.Android.Support.v7.AppCompat.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.AppCompat.dll - - - ..\packages\Xamarin.Android.Support.Design.23.3.0\lib\MonoAndroid43\Xamarin.Android.Support.Design.dll - - - ..\packages\Xamarin.Android.Support.v7.MediaRouter.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.MediaRouter.dll - - - ..\packages\Xamarin.Forms.2.3.4.224\lib\MonoAndroid10\FormsViewGroup.dll - - - ..\packages\Xamarin.Forms.2.3.4.224\lib\MonoAndroid10\Xamarin.Forms.Core.dll - - - ..\packages\Xamarin.Forms.2.3.4.224\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll - - - ..\packages\Xamarin.Forms.2.3.4.224\lib\MonoAndroid10\Xamarin.Forms.Platform.dll - - - ..\packages\Xamarin.Forms.2.3.4.224\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll - - - ..\packages\Realm.DataBinding.1.2.0\lib\MonoAndroid44\Realm.DataBinding.dll - - - ..\packages\Remotion.Linq.2.1.2\lib\portable-net45+win+wpa81+wp80\Remotion.Linq.dll - - - ..\packages\Newtonsoft.Json.10.0.3\lib\netstandard1.3\Newtonsoft.Json.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - ..\packages\Realm.Database.3.0.0\lib\netstandard1.4\Realm.dll - - - ..\packages\Realm.3.0.0\lib\netstandard1.4\Realm.Sync.dll - - - - - - - MainActivity.cs - - - TestRunnerInstrumentation.cs - - - - - - app.config - - - - - - - - - - - - - - - - - - - - Assets\ForMigrationsToCopyAndMigrate.realm - - - Assets\nunit3-junit.xslt - - - Assets\trusted_ca.pem - - - Assets\sync-1.x-encrypted.realm - - - Assets\sync-1.x.realm - - - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.Android/packages.config b/Manual Tests/NuGetReleaseTests/Tests.Android/packages.config deleted file mode 100644 index a157b13017..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.Android/packages.config +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.NetCore/FodyWeavers.xml b/Manual Tests/NuGetReleaseTests/Tests.NetCore/FodyWeavers.xml deleted file mode 100644 index e071208def..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.NetCore/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.NetCore/Tests.NetCore.csproj b/Manual Tests/NuGetReleaseTests/Tests.NetCore/Tests.NetCore.csproj deleted file mode 100644 index c78be296ac..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.NetCore/Tests.NetCore.csproj +++ /dev/null @@ -1,56 +0,0 @@ - - - - netcoreapp1.1 - Exe - win-x64;osx-x64;debian.8-x64;ubuntu.16.04-x64 - - - - TRACE;DEBUG;NETCOREAPP1_1;ROS_SETUP - - - - TRACE;RELEASE;NETCOREAPP1_1;ROS_SETUP - - - - - - - - - - - - - - - ForMigrationsToCopyAndMigrate.realm - PreserveNewest - - - PreserveNewest - - - trusted_ca.pem - PreserveNewest - - - sync-1.x-encrypted.realm - PreserveNewest - - - sync-1.x.realm - PreserveNewest - - - - - - Program.cs - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.UWP/Assets/Logo.scale-100.png b/Manual Tests/NuGetReleaseTests/Tests.UWP/Assets/Logo.scale-100.png deleted file mode 100644 index da35ed02cc..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.UWP/Assets/Logo.scale-100.png and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.UWP/Assets/MediumTile.scale-100.png b/Manual Tests/NuGetReleaseTests/Tests.UWP/Assets/MediumTile.scale-100.png deleted file mode 100644 index b3642f9f8e..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.UWP/Assets/MediumTile.scale-100.png and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.UWP/Assets/SmallTile.scale-100.png b/Manual Tests/NuGetReleaseTests/Tests.UWP/Assets/SmallTile.scale-100.png deleted file mode 100644 index f6843af449..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.UWP/Assets/SmallTile.scale-100.png and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.UWP/Assets/StoreLogo.scale-100.png b/Manual Tests/NuGetReleaseTests/Tests.UWP/Assets/StoreLogo.scale-100.png deleted file mode 100644 index 16e77bfdfb..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.UWP/Assets/StoreLogo.scale-100.png and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.UWP/FodyWeavers.xml b/Manual Tests/NuGetReleaseTests/Tests.UWP/FodyWeavers.xml deleted file mode 100644 index eb982b3e95..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.UWP/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.UWP/Package.appxmanifest b/Manual Tests/NuGetReleaseTests/Tests.UWP/Package.appxmanifest deleted file mode 100644 index 28c482f309..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.UWP/Package.appxmanifest +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - io.realm.nugettests - Realm - Assets\StoreLogo.png - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.UWP/Properties/Default.rd.xml b/Manual Tests/NuGetReleaseTests/Tests.UWP/Properties/Default.rd.xml deleted file mode 100644 index d325c1481b..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.UWP/Properties/Default.rd.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.UWP/Tests.UWP.csproj b/Manual Tests/NuGetReleaseTests/Tests.UWP/Tests.UWP.csproj deleted file mode 100644 index d6cde9825f..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.UWP/Tests.UWP.csproj +++ /dev/null @@ -1,184 +0,0 @@ - - - - - Debug - x86 - {159A55AF-9531-4107-8444-CA9C96107EF5} - AppContainerExe - Properties - Tests - Tests.UWP - en-US - UAP - 10.0.15063.0 - 10.0.10586.0 - 14 - 512 - {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - true - Tests.UWP_TemporaryKey.pfx - NETFX_CORE;WINDOWS;WINDOWS_UWP;CODE_ANALYSIS;ENCRYPTION_DISABLED - F3B7F911B519898CE28977B59583CE74514C212C - - - true - bin\x86\Debug\ - TRACE;DEBUG;$(DefineConstants) - ;2008 - full - x86 - false - prompt - true - - - bin\x86\Release\ - TRACE;$(DefineConstants) - true - ;2008 - pdbonly - x86 - false - prompt - true - true - - - true - bin\ARM\Debug\ - TRACE;DEBUG;$(DefineConstants) - ;2008 - full - ARM - false - prompt - true - - - bin\ARM\Release\ - TRACE;$(DefineConstants) - true - ;2008 - pdbonly - ARM - false - prompt - true - true - - - true - bin\x64\Debug\ - TRACE;DEBUG;NETFX_CORE;WINDOWS;WINDOWS_UWP;CODE_ANALYSIS;ENCRYPTION_DISABLED;CODE_ANALYSIS;ROS_SETUP - ;2008 - full - x64 - false - prompt - true - - - bin\x64\Release\ - TRACE;NETFX_CORE;WINDOWS;WINDOWS_UWP;CODE_ANALYSIS;ENCRYPTION_DISABLED;CODE_ANALYSIS;ROS_SETUP - true - ;2008 - pdbonly - x64 - false - prompt - true - true - - - PackageReference - - - - Designer - - - sync-1.x-encrypted.realm - PreserveNewest - - - sync-1.x.realm - PreserveNewest - - - - - - - - - - - - ucrtbased.dll - - - ForMigrationsToCopyAndMigrate.realm - PreserveNewest - - - - - 6.0.6 - - - 1.1.0 - - - 3.9.0 - - - 1.0.2 - - - 3.0.0 - - - 4.3.0 - - - 3.1.1 - - - 2.3.4.231 - - - - - ..\..\..\dependencies\nunit.xamarin.3.6.1\uap10.0\nunit.runner.uwp.dll - - - - - App.xaml - MSBuild:Compile - Designer - - - MainPage.xaml - MSBuild:Compile - Designer - - - - - App.xaml - App.xaml.cs - - - MainPage.xaml - MainPage.xaml.cs - - - - - - 14.0 - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.UWP/Tests.UWP_TemporaryKey.pfx b/Manual Tests/NuGetReleaseTests/Tests.UWP/Tests.UWP_TemporaryKey.pfx deleted file mode 100644 index 32b83e1bc4..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.UWP/Tests.UWP_TemporaryKey.pfx and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.Win32/FodyWeavers.xml b/Manual Tests/NuGetReleaseTests/Tests.Win32/FodyWeavers.xml deleted file mode 100644 index af39d80202..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.Win32/FodyWeavers.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.Win32/Settings.StyleCop b/Manual Tests/NuGetReleaseTests/Tests.Win32/Settings.StyleCop deleted file mode 100644 index 805dec909e..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.Win32/Settings.StyleCop +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - False - - - - - False - - - - - False - - - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.Win32/Tests.Win32.csproj b/Manual Tests/NuGetReleaseTests/Tests.Win32/Tests.Win32.csproj deleted file mode 100644 index bab9fdb4e4..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.Win32/Tests.Win32.csproj +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - Debug - AnyCPU - {BFC1C1B7-2864-4456-9DEC-309A3F006B66} - Library - Properties - Tests.Win32 - Tests.Win32 - v4.6.2 - 512 - false - - - - - - true - full - false - bin\Debug\ - TRACE;DEBUG;WINDOWS;ENCRYPTION_DISABLED;ROS_SETUP - prompt - 4 - MinimumRecommendedRules.ruleset - - - pdbonly - true - bin\Release\ - TRACE;WINDOWS;ENCRYPTION_DISABLED;ROS_SETUP - prompt - 4 - AllRules.ruleset - - - - ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Nito.AsyncEx.Context.1.1.0\lib\net46\Nito.AsyncEx.Context.dll - - - ..\packages\Nito.AsyncEx.Tasks.1.1.0\lib\net46\Nito.AsyncEx.Tasks.dll - - - ..\packages\Nito.Disposables.1.0.0\lib\portable45-net45+win8+wp8+wpa81\Nito.Disposables.dll - - - ..\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll - - - ..\packages\Realm.Database.3.0.0\lib\netstandard1.4\Realm.dll - - - ..\packages\Realm.DataBinding.1.2.0\lib\netstandard1.0\Realm.DataBinding.dll - - - ..\packages\Realm.3.0.0\lib\netstandard1.4\Realm.Sync.dll - - - ..\packages\Remotion.Linq.2.1.2\lib\net45\Remotion.Linq.dll - - - - ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll - - - - ..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll - - - - ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll - - - ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll - - - ..\packages\System.Net.Http.4.3.1\lib\net46\System.Net.Http.dll - - - - ..\packages\System.Reactive.Core.3.1.1\lib\net46\System.Reactive.Core.dll - - - ..\packages\System.Reactive.Interfaces.3.1.1\lib\net45\System.Reactive.Interfaces.dll - - - ..\packages\System.Reactive.Linq.3.1.1\lib\net46\System.Reactive.Linq.dll - - - ..\packages\System.Reactive.PlatformServices.3.1.1\lib\net46\System.Reactive.PlatformServices.dll - - - ..\packages\System.Reactive.Windows.Threading.3.1.1\lib\net45\System.Reactive.Windows.Threading.dll - - - ..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll - - - ..\packages\System.Reflection.TypeExtensions.4.3.0\lib\net462\System.Reflection.TypeExtensions.dll - - - ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - ..\packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll - - - ..\packages\System.Runtime.InteropServices.4.3.0\lib\net462\System.Runtime.InteropServices.dll - - - ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll - - - ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll - - - ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - - - ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - - - ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll - - - - - - - - - - - - - sync-1.x-encrypted.realm - PreserveNewest - - - sync-1.x.realm - PreserveNewest - - - - ForMigrationsToCopyAndMigrate.realm - PreserveNewest - - - - - nunit3-junit.xslt - PreserveNewest - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.Win32/app.config b/Manual Tests/NuGetReleaseTests/Tests.Win32/app.config deleted file mode 100644 index 97a7db934f..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.Win32/app.config +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.Win32/packages.config b/Manual Tests/NuGetReleaseTests/Tests.Win32/packages.config deleted file mode 100644 index 496f9b38b2..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.Win32/packages.config +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Assets.xcassets/AppIcon.appiconset/Contents.json b/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index e550a07eb4..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,214 +0,0 @@ -{ - "images": [ - { - "size": "29x29", - "scale": "1x", - "idiom": "iphone" - }, - { - "size": "29x29", - "scale": "2x", - "idiom": "iphone" - }, - { - "size": "29x29", - "scale": "3x", - "idiom": "iphone" - }, - { - "size": "40x40", - "scale": "2x", - "idiom": "iphone" - }, - { - "size": "40x40", - "scale": "3x", - "idiom": "iphone" - }, - { - "size": "57x57", - "scale": "1x", - "idiom": "iphone" - }, - { - "size": "57x57", - "scale": "2x", - "idiom": "iphone" - }, - { - "size": "60x60", - "scale": "2x", - "idiom": "iphone" - }, - { - "size": "60x60", - "scale": "3x", - "idiom": "iphone" - }, - { - "size": "29x29", - "scale": "1x", - "idiom": "ipad" - }, - { - "size": "29x29", - "scale": "2x", - "idiom": "ipad" - }, - { - "size": "40x40", - "scale": "1x", - "idiom": "ipad" - }, - { - "size": "40x40", - "scale": "2x", - "idiom": "ipad" - }, - { - "size": "50x50", - "scale": "1x", - "idiom": "ipad" - }, - { - "size": "50x50", - "scale": "2x", - "idiom": "ipad" - }, - { - "size": "83.5x83.5", - "scale": "2x", - "idiom": "ipad" - }, - { - "size": "72x72", - "scale": "1x", - "idiom": "ipad" - }, - { - "size": "72x72", - "scale": "2x", - "idiom": "ipad" - }, - { - "size": "76x76", - "scale": "1x", - "idiom": "ipad" - }, - { - "size": "76x76", - "scale": "2x", - "idiom": "ipad" - }, - { - "role": "notificationCenter", - "size": "24x24", - "subtype": "38mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "notificationCenter", - "size": "27.5x27.5", - "subtype": "42mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "companionSettings", - "size": "29x29", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "companionSettings", - "size": "29x29", - "scale": "3x", - "idiom": "watch" - }, - { - "role": "appLauncher", - "size": "40x40", - "subtype": "38mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "longLook", - "size": "44x44", - "subtype": "42mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "quickLook", - "size": "86x86", - "subtype": "38mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "quickLook", - "size": "98x98", - "subtype": "42mm", - "scale": "2x", - "idiom": "watch" - }, - { - "size": "16x16", - "scale": "1x", - "idiom": "mac" - }, - { - "size": "16x16", - "scale": "2x", - "idiom": "mac" - }, - { - "size": "32x32", - "scale": "1x", - "idiom": "mac" - }, - { - "size": "32x32", - "scale": "2x", - "idiom": "mac" - }, - { - "filename": "RealmGenericFilled-128.png", - "size": "128x128", - "scale": "1x", - "idiom": "mac" - }, - { - "filename": "RealmGenericFilled-256.png", - "size": "128x128", - "scale": "2x", - "idiom": "mac" - }, - { - "size": "256x256", - "scale": "1x", - "idiom": "mac" - }, - { - "size": "256x256", - "scale": "2x", - "idiom": "mac" - }, - { - "size": "512x512", - "scale": "1x", - "idiom": "mac" - }, - { - "size": "512x512", - "scale": "2x", - "idiom": "mac" - } - ], - "info": { - "version": 1, - "author": "xcode" - } -} \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Assets.xcassets/AppIcon.appiconset/RealmGenericFilled-128.png b/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Assets.xcassets/AppIcon.appiconset/RealmGenericFilled-128.png deleted file mode 100644 index c85ba55f63..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Assets.xcassets/AppIcon.appiconset/RealmGenericFilled-128.png and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Assets.xcassets/AppIcon.appiconset/RealmGenericFilled-256.png b/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Assets.xcassets/AppIcon.appiconset/RealmGenericFilled-256.png deleted file mode 100644 index 8dd3df0850..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Assets.xcassets/AppIcon.appiconset/RealmGenericFilled-256.png and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Assets.xcassets/Contents.json b/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Assets.xcassets/Contents.json deleted file mode 100644 index 4caf392f92..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/FodyWeavers.xml b/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/FodyWeavers.xml deleted file mode 100644 index 6aa124364c..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Info.plist b/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Info.plist deleted file mode 100644 index a28f7fda32..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Info.plist +++ /dev/null @@ -1,37 +0,0 @@ - - - - - CFBundleIdentifier - io.realm.xamarinmactests - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - LSMinimumSystemVersion - 10.12 - CFBundleDevelopmentRegion - en - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - APPL - CFBundleSignature - ???? - NSHumanReadableCopyright - (c) Nikola Irinchev - NSPrincipalClass - NSApplication - NSMainStoryboardFile - Main - XSAppIconAssets - Assets.xcassets/AppIcon.appiconset - CFBundleName - Realm Tests - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - - diff --git a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Tests.XamarinMac.csproj b/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Tests.XamarinMac.csproj deleted file mode 100644 index d79370b699..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/Tests.XamarinMac.csproj +++ /dev/null @@ -1,174 +0,0 @@ - - - - Debug - AnyCPU - {3A6CC342-40D0-4487-9964-DDAEBA628199} - {A3F8F2AB-B479-4A4A-A458-A89E7DC349F1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Exe - Tests.XamarinMac - Tests.XamarinMac - v2.0 - Xamarin.Mac - Resources - - - true - full - false - bin\Debug - __UNIFIED__;__MACOS__;DEBUG;ROS_SETUP - prompt - 4 - false - Mac Developer - false - false - false - true - true - NSUrlSessionHandler - None - - None - - - - true - bin\Release - __UNIFIED__;__MACOS__;ROS_SETUP - prompt - 4 - false - false - false - true - true - true - None - NSUrlSessionHandler - - None - - - --labels=On - - - - - - - - - ..\packages\Nito.Disposables.1.0.0\lib\netstandard1.0\Nito.Disposables.dll - - - ..\packages\Nito.AsyncEx.Tasks.1.1.0\lib\netstandard1.3\Nito.AsyncEx.Tasks.dll - - - ..\packages\Nito.AsyncEx.Context.1.1.0\lib\netstandard1.3\Nito.AsyncEx.Context.dll - - - ..\packages\Remotion.Linq.2.1.2\lib\netstandard1.0\Remotion.Linq.dll - - - - ..\packages\Newtonsoft.Json.10.0.3\lib\netstandard1.3\Newtonsoft.Json.dll - - - ..\packages\System.Runtime.InteropServices.WindowsRuntime.4.0.1\lib\netstandard1.3\System.Runtime.InteropServices.WindowsRuntime.dll - - - ..\packages\System.Reactive.Interfaces.3.1.1\lib\netstandard1.0\System.Reactive.Interfaces.dll - - - ..\packages\System.Reactive.Core.3.1.1\lib\netstandard1.3\System.Reactive.Core.dll - - - ..\packages\System.Reactive.Linq.3.1.1\lib\netstandard1.3\System.Reactive.Linq.dll - - - ..\packages\System.Reactive.PlatformServices.3.1.1\lib\netstandard1.3\System.Reactive.PlatformServices.dll - - - ..\packages\NUnit.3.6.1\lib\netstandard1.6\nunit.framework.dll - - - ..\packages\NUnitLite.3.7.1\lib\netstandard1.6\nunitlite.dll - - - ..\packages\Realm.DataBinding.1.2.0\lib\Xamarin.Mac20\Realm.DataBinding.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - ..\packages\Realm.Database.3.0.0\lib\netstandard1.4\Realm.dll - - - ..\packages\Realm.3.0.0\lib\netstandard1.4\Realm.Sync.dll - - - - - - - - - - - - - - - - - - - AppDelegate.cs - - - Main.cs - - - ViewController.cs - - - ViewController.designer.cs - ViewController.cs - - - - - Resources\ForMigrationsToCopyAndMigrate.realm - PreserveNewest - - - Resources\nunit3-junit.xslt - PreserveNewest - - - Resources\trusted_ca.pem - - - Resources\sync-1.x-encrypted.realm - PreserveNewest - - - Resources\sync-1.x.realm - PreserveNewest - - - - - Main.storyboard - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/packages.config b/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/packages.config deleted file mode 100644 index 9a25032a46..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.XamarinMac/packages.config +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/Manual Tests/NuGetReleaseTests/Tests.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 86b63ece7b..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,214 +0,0 @@ -{ - "images": [ - { - "size": "29x29", - "scale": "1x", - "idiom": "iphone" - }, - { - "size": "29x29", - "scale": "2x", - "idiom": "iphone" - }, - { - "size": "29x29", - "scale": "3x", - "idiom": "iphone" - }, - { - "size": "40x40", - "scale": "2x", - "idiom": "iphone" - }, - { - "size": "40x40", - "scale": "3x", - "idiom": "iphone" - }, - { - "size": "57x57", - "scale": "1x", - "idiom": "iphone" - }, - { - "size": "57x57", - "scale": "2x", - "idiom": "iphone" - }, - { - "filename": "RealmGenericFilled-60@2x.png", - "size": "60x60", - "scale": "2x", - "idiom": "iphone" - }, - { - "filename": "RealmGenericFilled-60@3x.png", - "size": "60x60", - "scale": "3x", - "idiom": "iphone" - }, - { - "size": "29x29", - "scale": "1x", - "idiom": "ipad" - }, - { - "size": "29x29", - "scale": "2x", - "idiom": "ipad" - }, - { - "size": "40x40", - "scale": "1x", - "idiom": "ipad" - }, - { - "size": "40x40", - "scale": "2x", - "idiom": "ipad" - }, - { - "size": "50x50", - "scale": "1x", - "idiom": "ipad" - }, - { - "size": "50x50", - "scale": "2x", - "idiom": "ipad" - }, - { - "size": "83.5x83.5", - "scale": "2x", - "idiom": "ipad" - }, - { - "size": "72x72", - "scale": "1x", - "idiom": "ipad" - }, - { - "size": "72x72", - "scale": "2x", - "idiom": "ipad" - }, - { - "size": "76x76", - "scale": "1x", - "idiom": "ipad" - }, - { - "size": "76x76", - "scale": "2x", - "idiom": "ipad" - }, - { - "role": "notificationCenter", - "size": "24x24", - "subtype": "38mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "notificationCenter", - "size": "27.5x27.5", - "subtype": "42mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "companionSettings", - "size": "29x29", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "companionSettings", - "size": "29x29", - "scale": "3x", - "idiom": "watch" - }, - { - "role": "appLauncher", - "size": "40x40", - "subtype": "38mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "longLook", - "size": "44x44", - "subtype": "42mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "quickLook", - "size": "86x86", - "subtype": "38mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "quickLook", - "size": "98x98", - "subtype": "42mm", - "scale": "2x", - "idiom": "watch" - }, - { - "size": "16x16", - "scale": "1x", - "idiom": "mac" - }, - { - "size": "16x16", - "scale": "2x", - "idiom": "mac" - }, - { - "size": "32x32", - "scale": "1x", - "idiom": "mac" - }, - { - "size": "32x32", - "scale": "2x", - "idiom": "mac" - }, - { - "size": "128x128", - "scale": "1x", - "idiom": "mac" - }, - { - "size": "128x128", - "scale": "2x", - "idiom": "mac" - }, - { - "size": "256x256", - "scale": "1x", - "idiom": "mac" - }, - { - "size": "256x256", - "scale": "2x", - "idiom": "mac" - }, - { - "size": "512x512", - "scale": "1x", - "idiom": "mac" - }, - { - "size": "512x512", - "scale": "2x", - "idiom": "mac" - } - ], - "info": { - "version": 1, - "author": "xcode" - } -} \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.iOS/Assets.xcassets/AppIcon.appiconset/RealmGenericFilled-60@2x.png b/Manual Tests/NuGetReleaseTests/Tests.iOS/Assets.xcassets/AppIcon.appiconset/RealmGenericFilled-60@2x.png deleted file mode 100644 index 1cd2695f07..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.iOS/Assets.xcassets/AppIcon.appiconset/RealmGenericFilled-60@2x.png and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.iOS/Assets.xcassets/AppIcon.appiconset/RealmGenericFilled-60@3x.png b/Manual Tests/NuGetReleaseTests/Tests.iOS/Assets.xcassets/AppIcon.appiconset/RealmGenericFilled-60@3x.png deleted file mode 100644 index a619e89fc1..0000000000 Binary files a/Manual Tests/NuGetReleaseTests/Tests.iOS/Assets.xcassets/AppIcon.appiconset/RealmGenericFilled-60@3x.png and /dev/null differ diff --git a/Manual Tests/NuGetReleaseTests/Tests.iOS/FodyWeavers.xml b/Manual Tests/NuGetReleaseTests/Tests.iOS/FodyWeavers.xml deleted file mode 100644 index 6aa124364c..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.iOS/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.iOS/Info.plist b/Manual Tests/NuGetReleaseTests/Tests.iOS/Info.plist deleted file mode 100644 index 8eba9eeb4f..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.iOS/Info.plist +++ /dev/null @@ -1,38 +0,0 @@ - - - - - UIDeviceFamily - - 1 - 2 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - MinimumOSVersion - 7.1 - CFBundleIdentifier - io.realm.nugettests - CFBundleVersion - 1.0 - UILaunchStoryboardName - LaunchScreen - CFBundleShortVersionString - 1.0 - XSAppIconAssets - Assets.xcassets/AppIcon.appiconset - CFBundleName - NuGet Tests - - diff --git a/Manual Tests/NuGetReleaseTests/Tests.iOS/Resources/LaunchScreen.storyboard b/Manual Tests/NuGetReleaseTests/Tests.iOS/Resources/LaunchScreen.storyboard deleted file mode 100644 index c42b3b4720..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.iOS/Resources/LaunchScreen.storyboard +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Manual Tests/NuGetReleaseTests/Tests.iOS/Settings.StyleCop b/Manual Tests/NuGetReleaseTests/Tests.iOS/Settings.StyleCop deleted file mode 100644 index 805dec909e..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.iOS/Settings.StyleCop +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - False - - - - - False - - - - - False - - - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.iOS/Tests.iOS.csproj b/Manual Tests/NuGetReleaseTests/Tests.iOS/Tests.iOS.csproj deleted file mode 100644 index 6512de0259..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.iOS/Tests.iOS.csproj +++ /dev/null @@ -1,205 +0,0 @@ - - - - Debug - iPhoneSimulator - {D2CED8F0-7A76-454D-B5F7-9E8B9FE6A58E} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Exe - Tests - Resources - Tests.iOS - false - - - - true - full - false - bin\iPhoneSimulator\Debug\ - __UNIFIED__;__MOBILE__;__IOS__;DEBUG;ROS_SETUP - prompt - 4 - false - x86_64 - Full - true - - iPhone Developer - false - true - --linkskip=nunit.runner.iOS - - - none - true - bin\iPhoneSimulator\Release\ - __UNIFIED__;__MOBILE__;__IOS__;ROS_SETUP - prompt - 4 - Full - x86_64 - false - - iPhone Developer - --linkskip=nunit.runner.iOS - - - true - full - false - bin\iPhone\Debug\ - __UNIFIED__;__MOBILE__;__IOS__;DEBUG;ROS_SETUP - prompt - 4 - false - ARMv7, ARM64 - iPhone Developer - true - - true - Full - --linkskip=nunit.runner.iOS - - - none - true - bin\iPhone\Release\ - __UNIFIED__;__MOBILE__;__IOS__;ROS_SETUP - prompt - 4 - ARMv7, ARM64 - false - iPhone Developer - - Full - --linkskip=nunit.runner.iOS - Visual Studio: NuGet Tests Development - - - - - - - - - - Resources\ForMigrationsToCopyAndMigrate.realm - PreserveNewest - - - Resources\sync-1.x-encrypted.realm - PreserveNewest - - - Resources\sync-1.x.realm - PreserveNewest - - - - - - - - - ..\packages\Xamarin.Forms.2.3.4.224\lib\Xamarin.iOS10\Xamarin.Forms.Core.dll - - - ..\packages\Xamarin.Forms.2.3.4.224\lib\Xamarin.iOS10\Xamarin.Forms.Platform.dll - - - ..\packages\Xamarin.Forms.2.3.4.224\lib\Xamarin.iOS10\Xamarin.Forms.Platform.iOS.dll - - - ..\packages\Xamarin.Forms.2.3.4.224\lib\Xamarin.iOS10\Xamarin.Forms.Xaml.dll - - - - - ..\packages\System.Runtime.Loader.4.0.0\lib\netstandard1.5\System.Runtime.Loader.dll - - - ..\packages\NUnit.3.6.1\lib\Xamarin.iOS10\nunit.framework.dll - - - ..\packages\Nito.Disposables.1.0.0\lib\portable45-net45+win8+wp8+wpa81\Nito.Disposables.dll - - - ..\packages\Nito.AsyncEx.Tasks.1.1.0\lib\netstandard1.3\Nito.AsyncEx.Tasks.dll - - - ..\packages\Nito.AsyncEx.Context.1.1.0\lib\netstandard1.3\Nito.AsyncEx.Context.dll - - - - ..\packages\DotNetCross.Memory.Unsafe.0.2.3.4\lib\netstandard1.0\DotNetCross.Memory.Unsafe.dll - - - ..\packages\PCLStorage.1.0.2\lib\portable-Xamarin.iOS+Xamarin.Mac\PCLStorage.Abstractions.dll - - - ..\packages\PCLStorage.1.0.2\lib\portable-Xamarin.iOS+Xamarin.Mac\PCLStorage.dll - - - ..\packages\System.Reactive.Interfaces.3.1.1\lib\netstandard1.0\System.Reactive.Interfaces.dll - - - ..\packages\System.Reactive.Core.3.1.1\lib\netstandard1.3\System.Reactive.Core.dll - - - ..\packages\System.Reactive.Linq.3.1.1\lib\netstandard1.3\System.Reactive.Linq.dll - - - ..\packages\System.Reactive.PlatformServices.3.1.1\lib\netstandard1.3\System.Reactive.PlatformServices.dll - - - ..\..\..\dependencies\nunit.xamarin.3.6.1\Xamarin.iOS\nunit.runner.iOS.dll - - - ..\packages\Realm.DataBinding.1.2.0\lib\Xamarin.iOS10\Realm.DataBinding.dll - - - ..\packages\Remotion.Linq.2.1.2\lib\portable-net45+win+wpa81+wp80\Remotion.Linq.dll - - - ..\packages\Newtonsoft.Json.10.0.3\lib\netstandard1.3\Newtonsoft.Json.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - ..\packages\Realm.Database.3.0.0\lib\netstandard1.4\Realm.dll - - - ..\packages\Realm.3.0.0\lib\netstandard1.4\Realm.Sync.dll - - - - - - - - - - nunit3-junit.xslt - PreserveNewest - - - - - AppDelegate.cs - - - Main.cs - - - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/NuGetReleaseTests/Tests.iOS/packages.config b/Manual Tests/NuGetReleaseTests/Tests.iOS/packages.config deleted file mode 100644 index 2777eaef86..0000000000 --- a/Manual Tests/NuGetReleaseTests/Tests.iOS/packages.config +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/Readme.md b/Manual Tests/Readme.md deleted file mode 100644 index b441aa7d8a..0000000000 --- a/Manual Tests/Readme.md +++ /dev/null @@ -1,25 +0,0 @@ -# Running manual/integration tests - -The NuGetReleaseTests project is a clone of the regular test projects that references the NuGet packages rather than the local projects. - -## Setting up ROS - -1. Download the latest [public ROS](https://realm.io/products/realm-mobile-platform/). -1. Run `start-object-server.command` and create an admin user `a@a` with password `a`. -1. Edit `configuration.yml`: - 1. In the `https` section: - 1. `enable: true` - 1. `certificate_path: 'keys/127_0_0_1-chain.crt.pem'` - 1. `private_key_path: 'keys/127_0_0_1-server.key.pem'` -1. Copy `Tests/ROS/keys` to `path-to-ros/realm-object-server/object-server` - -## Setting up Android - -You need Android 5.0+ device to run all unit tests. -1. Execute `adb reverse tcp:9080 tcp:9080` -1. Execute `adb reverse tcp:9443 tcp:9443` - -## Setting up iOS - -If you're running a simulator, no additional setup is required. -If you're running tests on a device, you need to update `Constants.ServerUrl` with the url of your server (make sure it's reachable from the device). diff --git a/Manual Tests/Settings.StyleCop b/Manual Tests/Settings.StyleCop deleted file mode 100644 index 805dec909e..0000000000 --- a/Manual Tests/Settings.StyleCop +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - False - - - - - False - - - - - False - - - - - - - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/FodyWeavers.xml b/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/FodyWeavers.xml deleted file mode 100644 index 6aa124364c..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/Properties/AssemblyInfo.cs b/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/Properties/AssemblyInfo.cs deleted file mode 100644 index 801b949b56..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Resources; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("PurePCLViewModel")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("PurePCLViewModel")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/PurePCLViewModel.csproj b/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/PurePCLViewModel.csproj deleted file mode 100644 index f327a49412..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/PurePCLViewModel.csproj +++ /dev/null @@ -1,66 +0,0 @@ - - - - - 10.0 - Debug - AnyCPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62} - Library - Properties - PurePCLViewModel - PurePCLViewModel - en-US - 512 - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Profile259 - v4.5 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - ..\packages\Realm.DataBinding.1.2.0\lib\netstandard1.0\Realm.DataBinding.dll - - - ..\packages\DotNetCross.Memory.Unsafe.0.2.3.4\lib\netstandard1.0\DotNetCross.Memory.Unsafe.dll - - - ..\packages\Remotion.Linq.2.1.2\lib\portable-net45+win+wpa81+wp80\Remotion.Linq.dll - - - ..\packages\Realm.Database.2.2.0\lib\portable-net45+sl5+wp8+wpa81+win8+monoandroid+Xamarin.iOS10+monotouch+Xamarin.Mac\Realm.dll - - - ..\packages\Realm.2.2.0\lib\portable-net45+sl5+wp8+wpa81+win8+monoandroid+Xamarin.iOS10+monotouch+Xamarin.Mac\Realm.Sync.dll - - - - - - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/ViewModel.cs b/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/ViewModel.cs deleted file mode 100644 index 1ecbc21503..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/ViewModel.cs +++ /dev/null @@ -1,75 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Text; -using System.ComponentModel; -using Realms; -using System.Linq; - -namespace PurePCLViewModel -{ - - class Hero : RealmObject - { - public string SuperName { get; set; } - public int SuperScore { get; set; } - } - - public class ViewModel : INotifyPropertyChanged - { - public string TheAnswer { get; private set; } - - public event PropertyChangedEventHandler PropertyChanged; - - public ViewModel() - { - } - - public void TestRealm() - { - Realm.DeleteRealm(RealmConfiguration.DefaultConfiguration); // cleanup previous test run - using (var _realm = Realm.GetInstance()) - { - _realm.Write(() => - { - for (int i = 0; i < 10; ++i) // quick loop to add a few objects - { - _realm.Add(new Hero - { - SuperName = $"Thor {i}", - SuperScore = 10 * i - }); - } - }); - - var numAwe = _realm.All().Count(); - - var timeStamp = DateTimeOffset.Now.ToString(); - var sb = new StringBuilder(); - foreach (var aThor in _realm.All()) - { - sb.AppendLine(aThor.SuperName); // get the names back out to prove that data saved - } - - TheAnswer = $"{timeStamp}\n{numAwe} realm objects created:\n{sb.ToString()}"; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TheAnswer))); // normally would be setter - } - } - } -} diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/packages.config b/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/packages.config deleted file mode 100644 index d7958923db..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/PurePCLViewModel/packages.config +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/README.md b/Manual Tests/ThreeLayerRealmXFNuGet/README.md deleted file mode 100644 index a28f507616..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# About - -Disclaimer: this experiment acts as a testbed for multiple layers of PCL and​_some_ databinding but should not be taken as an example of preferred architecture. It shows what is _technically possible_​rather than what is ​_good_​. - -The solution ThreeLayerRealmXFNuGet was created in VS2015 using _Xamarin Forms Portable_ template. It was later updated to use Xamarin Forms 2.0 which involved no code changes, just selecting a higher version in NuGet. - -Diving into giving PCL layering a full workout, it represents the typical approach of a user who uses Xamarin Forms in a PCL for their portable GUI as well having a separate ViewModel class in another PCL assembly. - -It is based on the adjacent `ThreeLayerRealmXF` but uses NuGet to get the Realm libraries rather than linking to local copies. As such, it allows for quick testing of our NuGet releases in a full application context. - -## The Layers - -* Platform-specific app such as `ThreeLayerRealmXFNuGet.IOS` or `ThreeLayerRealmXFNuGet.Droid`. -* `ThreeLayerRealmXFNuGet` UI Layer of common Xamarin Forms UI. -* `PurePCLViewModel` Sandwich filling invoked by `ThreeLayerRealmXFNuGet`. -* `Realm.PCL` layer which invokes Realm via the _Bait and Switch_ pattern. - -## Build Notes - -`PurePCLViewModel` was created using the VS2015 template _Class Library (Portable)_ with default targets to match the Xamarin Forms PCL default: - -* .Net Framework 4.5 -* Windows 8 -* Windows Phone Silverlight 8 -* Windows Phone 8.1 -* Xamarin.Android -* Xamarin.iOS -* Xamarin.iOS (Classic) - -## Other Notes - -`ThreeLayerRealmXFNuGet.WinPhone` is not yet completed as we lack a Realm build for WinPhone - -### About the Binding - -If you are unfamiliar with the binding models of WPF or Xamarin Forms this may seem convoluted to you (it is!). - -if you look in the `ThreeLayerRealmXFNuGet` project at `App.cs` you can see the binding being created. - -The two key things are: - -1. set the `BindingContext` to something which descends from `INotityPropertyChanged` and -2. use `SetBinding` to bind to a property within that object ( `_model` in this case) - -The `Model.TestRealm` method triggers a `PropertyChanged` with the important signature "TheAnswer" to indicate that property has changed and the binding should refresh our `boundLabel`. \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.sln b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.sln deleted file mode 100644 index d15cce4e71..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.sln +++ /dev/null @@ -1,292 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -VisualStudioVersion = 14.0.23107.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThreeLayerRealmXFNuGet.iOS", "ThreeLayerRealmXFNuGet\ThreeLayerRealmXFNuGet.iOS\ThreeLayerRealmXFNuGet.iOS.csproj", "{F377984A-36A2-454B-AAAB-6E385048D5AB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThreeLayerRealmXFNuGet.WinPhone", "ThreeLayerRealmXFNuGet\ThreeLayerRealmXFNuGet.WinPhone\ThreeLayerRealmXFNuGet.WinPhone.csproj", "{6EEDE0D8-0052-4733-92B9-EE3C40871F30}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThreeLayerRealmXFNuGet", "ThreeLayerRealmXFNuGet\ThreeLayerRealmXFNuGet\ThreeLayerRealmXFNuGet.csproj", "{4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PurePCLViewModel", "PurePCLViewModel\PurePCLViewModel.csproj", "{A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThreeLayerRealmXFNuGet.Droid", "ThreeLayerRealmXFNuGet\ThreeLayerRealmXFNuGet.Droid\ThreeLayerRealmXFNuGet.Droid.csproj", "{4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Ad-Hoc|Any CPU = Ad-Hoc|Any CPU - Ad-Hoc|ARM = Ad-Hoc|ARM - Ad-Hoc|iPhone = Ad-Hoc|iPhone - Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator - Ad-Hoc|x86 = Ad-Hoc|x86 - AppStore|Any CPU = AppStore|Any CPU - AppStore|ARM = AppStore|ARM - AppStore|iPhone = AppStore|iPhone - AppStore|iPhoneSimulator = AppStore|iPhoneSimulator - AppStore|x86 = AppStore|x86 - Debug|Any CPU = Debug|Any CPU - Debug|ARM = Debug|ARM - Debug|iPhone = Debug|iPhone - Debug|iPhoneSimulator = Debug|iPhoneSimulator - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|ARM = Release|ARM - Release|iPhone = Release|iPhone - Release|iPhoneSimulator = Release|iPhoneSimulator - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Ad-Hoc|ARM.Build.0 = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Ad-Hoc|x86.Build.0 = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.AppStore|Any CPU.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.AppStore|Any CPU.Build.0 = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.AppStore|ARM.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.AppStore|ARM.Build.0 = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.AppStore|iPhone.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.AppStore|iPhone.Build.0 = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.AppStore|x86.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.AppStore|x86.Build.0 = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Debug|ARM.ActiveCfg = Debug|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Debug|ARM.Build.0 = Debug|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Debug|iPhone.Build.0 = Debug|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Debug|x86.ActiveCfg = Debug|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Debug|x86.Build.0 = Debug|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Release|Any CPU.Build.0 = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Release|ARM.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Release|ARM.Build.0 = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Release|iPhone.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Release|iPhone.Build.0 = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Release|x86.ActiveCfg = Release|Any CPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35}.Release|x86.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Ad-Hoc|ARM.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Ad-Hoc|x86.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.AppStore|Any CPU.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.AppStore|Any CPU.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.AppStore|ARM.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.AppStore|ARM.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.AppStore|iPhone.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.AppStore|iPhone.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.AppStore|x86.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.AppStore|x86.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Debug|ARM.ActiveCfg = Debug|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Debug|ARM.Build.0 = Debug|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Debug|iPhone.Build.0 = Debug|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Debug|x86.ActiveCfg = Debug|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Debug|x86.Build.0 = Debug|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Release|Any CPU.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Release|ARM.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Release|ARM.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Release|iPhone.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Release|iPhone.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Release|x86.ActiveCfg = Release|Any CPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166}.Release|x86.Build.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|ARM.ActiveCfg = Release|ARM - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|ARM.Build.0 = Release|ARM - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|ARM.Deploy.0 = Release|ARM - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|iPhone.Deploy.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|x86.ActiveCfg = Release|x86 - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|x86.Build.0 = Release|x86 - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Ad-Hoc|x86.Deploy.0 = Release|x86 - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|Any CPU.ActiveCfg = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|Any CPU.Build.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|Any CPU.Deploy.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|ARM.ActiveCfg = Release|ARM - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|ARM.Build.0 = Release|ARM - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|ARM.Deploy.0 = Release|ARM - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|iPhone.ActiveCfg = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|iPhone.Build.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|iPhone.Deploy.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|iPhoneSimulator.Deploy.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|x86.ActiveCfg = Release|x86 - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|x86.Build.0 = Release|x86 - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.AppStore|x86.Deploy.0 = Release|x86 - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|ARM.ActiveCfg = Debug|ARM - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|ARM.Build.0 = Debug|ARM - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|ARM.Deploy.0 = Debug|ARM - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|iPhone.Build.0 = Debug|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|iPhone.Deploy.0 = Debug|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|x86.ActiveCfg = Debug|x86 - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|x86.Build.0 = Debug|x86 - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Debug|x86.Deploy.0 = Debug|x86 - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|Any CPU.Build.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|Any CPU.Deploy.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|ARM.ActiveCfg = Release|ARM - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|ARM.Build.0 = Release|ARM - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|ARM.Deploy.0 = Release|ARM - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|iPhone.ActiveCfg = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|iPhone.Build.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|iPhone.Deploy.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|x86.ActiveCfg = Release|x86 - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|x86.Build.0 = Release|x86 - {6EEDE0D8-0052-4733-92B9-EE3C40871F30}.Release|x86.Deploy.0 = Release|x86 - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Ad-Hoc|ARM.Build.0 = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Ad-Hoc|x86.Build.0 = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.AppStore|Any CPU.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.AppStore|Any CPU.Build.0 = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.AppStore|ARM.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.AppStore|ARM.Build.0 = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.AppStore|iPhone.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.AppStore|iPhone.Build.0 = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.AppStore|x86.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.AppStore|x86.Build.0 = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Debug|ARM.ActiveCfg = Debug|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Debug|ARM.Build.0 = Debug|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Debug|iPhone.Build.0 = Debug|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Debug|x86.ActiveCfg = Debug|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Debug|x86.Build.0 = Debug|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Release|Any CPU.Build.0 = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Release|ARM.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Release|ARM.Build.0 = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Release|iPhone.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Release|iPhone.Build.0 = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Release|x86.ActiveCfg = Release|Any CPU - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62}.Release|x86.Build.0 = Release|Any CPU - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.AppStore|ARM.ActiveCfg = AppStore|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.AppStore|iPhone.ActiveCfg = AppStore|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.AppStore|iPhone.Build.0 = AppStore|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator - {F377984A-36A2-454B-AAAB-6E385048D5AB}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator - {F377984A-36A2-454B-AAAB-6E385048D5AB}.AppStore|x86.ActiveCfg = AppStore|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Debug|Any CPU.ActiveCfg = Debug|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Debug|ARM.ActiveCfg = Debug|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Debug|iPhone.ActiveCfg = Debug|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Debug|iPhone.Build.0 = Debug|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Debug|x86.ActiveCfg = Debug|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Release|Any CPU.ActiveCfg = Release|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Release|ARM.ActiveCfg = Release|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Release|iPhone.ActiveCfg = Release|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Release|iPhone.Build.0 = Release|iPhone - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {F377984A-36A2-454B-AAAB-6E385048D5AB}.Release|x86.ActiveCfg = Release|iPhone - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Ad-Hoc|ARM.Build.0 = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Ad-Hoc|x86.Build.0 = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.AppStore|Any CPU.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.AppStore|Any CPU.Build.0 = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.AppStore|ARM.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.AppStore|ARM.Build.0 = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.AppStore|iPhone.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.AppStore|iPhone.Build.0 = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.AppStore|x86.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.AppStore|x86.Build.0 = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|ARM.ActiveCfg = Debug|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|ARM.Build.0 = Debug|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|iPhone.Build.0 = Debug|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|x86.ActiveCfg = Debug|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|x86.Build.0 = Debug|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|Any CPU.Build.0 = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|ARM.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|ARM.Build.0 = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|iPhone.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|iPhone.Build.0 = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|x86.ActiveCfg = Release|Any CPU - {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Assets/AboutAssets.txt b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Assets/AboutAssets.txt deleted file mode 100644 index 5ddf08729b..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Assets/AboutAssets.txt +++ /dev/null @@ -1,19 +0,0 @@ -Any raw assets you want to be deployed with your application can be placed in -this directory (and child directories) and given a Build Action of "AndroidAsset". - -These files will be deployed with you package and will be accessible using Android's -AssetManager, like this: - -public class ReadAsset : Activity -{ - protected override void OnCreate (Bundle bundle) - { - base.OnCreate (bundle); - - InputStream input = Assets.Open ("my_asset.txt"); - } -} - -Additionally, some Android functions will automatically load asset files: - -Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/FodyWeavers.xml b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/FodyWeavers.xml deleted file mode 100644 index 6aa124364c..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/MainActivity.cs b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/MainActivity.cs deleted file mode 100644 index 6fe9fd0107..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/MainActivity.cs +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -using System; - -using Android.App; -using Android.Content.PM; -using Android.Runtime; -using Android.Views; -using Android.Widget; -using Android.OS; - -namespace ThreeLayerRealmXF.Droid { - [Activity(Label = "ThreeLayerRealmXFNuGet", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] - public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity { - protected override void OnCreate(Bundle bundle) - { - base.OnCreate(bundle); - - global::Xamarin.Forms.Forms.Init(this, bundle); - LoadApplication(new App()); - } - } -} - diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Properties/AndroidManifest.xml b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Properties/AndroidManifest.xml deleted file mode 100644 index 33b6a14be2..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Properties/AndroidManifest.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - ThreeLayerRealmXFNuGet - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Properties/AssemblyInfo.cs b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Properties/AssemblyInfo.cs deleted file mode 100644 index 0cdd3e945f..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Android.App; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ThreeLayerRealmXFNuGet.Droid")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ThreeLayerRealmXFNuGet.Droid")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] - -// Add some common permissions, these can be removed if not needed -[assembly: UsesPermission(Android.Manifest.Permission.Internet)] -[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)] diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/AboutResources.txt b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/AboutResources.txt deleted file mode 100644 index cb30f20b1c..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/AboutResources.txt +++ /dev/null @@ -1,50 +0,0 @@ -Images, layout descriptions, binary blobs and string dictionaries can be included -in your application as resource files. Various Android APIs are designed to -operate on the resource IDs instead of dealing with images, strings or binary blobs -directly. - -For example, a sample Android app that contains a user interface layout (main.xml), -an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) -would keep its resources in the "Resources" directory of the application: - -Resources/ - drawable-hdpi/ - icon.png - - drawable-ldpi/ - icon.png - - drawable-mdpi/ - icon.png - - layout/ - main.xml - - values/ - strings.xml - -In order to get the build system to recognize Android resources, set the build action to -"AndroidResource". The native Android APIs do not operate directly with filenames, but -instead operate on resource IDs. When you compile an Android application that uses resources, -the build system will package the resources for distribution and generate a class called -"Resource" that contains the tokens for each one of the resources included. For example, -for the above Resources layout, this is what the Resource class would expose: - -public class Resource { - public class drawable { - public const int icon = 0x123; - } - - public class layout { - public const int main = 0x456; - } - - public class strings { - public const int first_string = 0xabc; - public const int second_string = 0xbcd; - } -} - -You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main -to reference the layout/main.xml file, or Resource.strings.first_string to reference the first -string in the dictionary file values/strings.xml. diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/drawable-hdpi/icon.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/drawable-hdpi/icon.png deleted file mode 100644 index 964f110abb..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/drawable-hdpi/icon.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/drawable-xhdpi/icon.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/drawable-xhdpi/icon.png deleted file mode 100644 index 3c01e60ced..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/drawable-xhdpi/icon.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/drawable-xxhdpi/icon.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/drawable-xxhdpi/icon.png deleted file mode 100644 index 0d8c1c57dc..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/drawable-xxhdpi/icon.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/drawable/icon.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/drawable/icon.png deleted file mode 100644 index b0ba7150f4..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/Resources/drawable/icon.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/ThreeLayerRealmXFNuGet.Droid.csproj b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/ThreeLayerRealmXFNuGet.Droid.csproj deleted file mode 100644 index a60224c883..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/ThreeLayerRealmXFNuGet.Droid.csproj +++ /dev/null @@ -1,155 +0,0 @@ - - - - - Debug - AnyCPU - {4A3F2108-00E0-4BE6-A4D4-B8C9F573B166} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Properties - ThreeLayerRealmXFNuGet.Droid - ThreeLayerRealmXFNuGet.Droid - 512 - true - Resources\Resource.Designer.cs - Off - Properties\AndroidManifest.xml - True - v8.1 - - - - - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - None - - - full - true - bin\Release\ - TRACE - prompt - 4 - False - armeabi;armeabi-v7a;x86;arm64-v8a;x86_64 - - - - - - - - - - - ..\..\packages\DotNetCross.Memory.Unsafe.0.2.3.4\lib\netstandard1.0\DotNetCross.Memory.Unsafe.dll - - - - - ..\..\packages\Realm.DataBinding.1.2.0\lib\MonoAndroid44\Realm.DataBinding.dll - - - ..\..\packages\Remotion.Linq.2.1.2\lib\portable-net45+win+wpa81+wp80\Remotion.Linq.dll - - - ..\..\packages\Newtonsoft.Json.10.0.3\lib\netstandard1.3\Newtonsoft.Json.dll - - - ..\..\packages\Xamarin.Android.Support.v4.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v4.dll - - - ..\..\packages\Xamarin.Android.Support.v7.CardView.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.CardView.dll - - - ..\..\packages\Xamarin.Android.Support.v7.RecyclerView.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.RecyclerView.dll - - - ..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.Vector.Drawable.dll - - - ..\..\packages\Xamarin.Android.Support.Animated.Vector.Drawable.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.Animated.Vector.Drawable.dll - - - ..\..\packages\Xamarin.Android.Support.v7.AppCompat.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.AppCompat.dll - - - ..\..\packages\Xamarin.Android.Support.Design.23.3.0\lib\MonoAndroid43\Xamarin.Android.Support.Design.dll - - - ..\..\packages\Xamarin.Android.Support.v7.MediaRouter.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.v7.MediaRouter.dll - - - ..\..\packages\Xamarin.Forms.2.4.0.282\lib\MonoAndroid10\FormsViewGroup.dll - - - ..\..\packages\Xamarin.Forms.2.4.0.282\lib\MonoAndroid10\Xamarin.Forms.Core.dll - - - ..\..\packages\Xamarin.Forms.2.4.0.282\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll - - - ..\..\packages\Xamarin.Forms.2.4.0.282\lib\MonoAndroid10\Xamarin.Forms.Platform.dll - - - ..\..\packages\Xamarin.Forms.2.4.0.282\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll - - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - ..\..\packages\Realm.Database.2.2.0\lib\netstandard1.4\Realm.dll - - - ..\..\packages\Realm.2.2.0\lib\netstandard1.4\Realm.Sync.dll - - - - - - - - - - - - - - - - - - - - - - - - - ThreeLayerRealmXFNuGet - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35} - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/packages.config b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/packages.config deleted file mode 100644 index 4af1f8da4b..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.Droid/packages.config +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/App.xaml b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/App.xaml deleted file mode 100644 index 8497aa1b19..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/App.xaml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/App.xaml.cs b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/App.xaml.cs deleted file mode 100644 index fcfa715b43..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/App.xaml.cs +++ /dev/null @@ -1,214 +0,0 @@ -using System; -using System.Diagnostics; -using System.Resources; -using System.Windows; -using System.Windows.Markup; -using System.Windows.Navigation; -using Microsoft.Phone.Controls; -using Microsoft.Phone.Shell; -using ThreeLayerRealmXF.WinPhone.Resources; - -namespace ThreeLayerRealmXF.WinPhone { - public partial class App : Application { - /// - /// Provides easy access to the root frame of the Phone Application. - /// - /// The root frame of the Phone Application. - public static PhoneApplicationFrame RootFrame { get; private set; } - - /// - /// Constructor for the Application object. - /// - public App() - { - // Global handler for uncaught exceptions. - UnhandledException += Application_UnhandledException; - - // Standard XAML initialization - InitializeComponent(); - - // Phone-specific initialization - InitializePhoneApplication(); - - // Language display initialization - InitializeLanguage(); - - // Show graphics profiling information while debugging. - if (Debugger.IsAttached) { - // Display the current frame rate counters. - Application.Current.Host.Settings.EnableFrameRateCounter = true; - - // Show the areas of the app that are being redrawn in each frame. - //Application.Current.Host.Settings.EnableRedrawRegions = true; - - // Enable non-production analysis visualization mode, - // which shows areas of a page that are handed off to GPU with a colored overlay. - //Application.Current.Host.Settings.EnableCacheVisualization = true; - - // Prevent the screen from turning off while under the debugger by disabling - // the application's idle detection. - // Caution:- Use this under debug mode only. Application that disables user idle detection will continue to run - // and consume battery power when the user is not using the phone. - PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled; - } - - } - - // Code to execute when the application is launching (eg, from Start) - // This code will not execute when the application is reactivated - private void Application_Launching(object sender, LaunchingEventArgs e) - { - } - - // Code to execute when the application is activated (brought to foreground) - // This code will not execute when the application is first launched - private void Application_Activated(object sender, ActivatedEventArgs e) - { - } - - // Code to execute when the application is deactivated (sent to background) - // This code will not execute when the application is closing - private void Application_Deactivated(object sender, DeactivatedEventArgs e) - { - } - - // Code to execute when the application is closing (eg, user hit Back) - // This code will not execute when the application is deactivated - private void Application_Closing(object sender, ClosingEventArgs e) - { - } - - // Code to execute if a navigation fails - private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e) - { - if (Debugger.IsAttached) { - // A navigation has failed; break into the debugger - Debugger.Break(); - } - } - - // Code to execute on Unhandled Exceptions - private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e) - { - if (Debugger.IsAttached) { - // An unhandled exception has occurred; break into the debugger - Debugger.Break(); - } - } - - #region Phone application initialization - - // Avoid double-initialization - private bool phoneApplicationInitialized = false; - - // Do not add any additional code to this method - private void InitializePhoneApplication() - { - if (phoneApplicationInitialized) - return; - - // Create the frame but don't set it as RootVisual yet; this allows the splash - // screen to remain active until the application is ready to render. - RootFrame = new PhoneApplicationFrame(); - RootFrame.Navigated += CompleteInitializePhoneApplication; - - // Handle navigation failures - RootFrame.NavigationFailed += RootFrame_NavigationFailed; - - // Handle reset requests for clearing the backstack - RootFrame.Navigated += CheckForResetNavigation; - - // Ensure we don't initialize again - phoneApplicationInitialized = true; - } - - // Do not add any additional code to this method - private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e) - { - // Set the root visual to allow the application to render - if (RootVisual != RootFrame) - RootVisual = RootFrame; - - // Remove this handler since it is no longer needed - RootFrame.Navigated -= CompleteInitializePhoneApplication; - } - - private void CheckForResetNavigation(object sender, NavigationEventArgs e) - { - // If the app has received a 'reset' navigation, then we need to check - // on the next navigation to see if the page stack should be reset - if (e.NavigationMode == NavigationMode.Reset) - RootFrame.Navigated += ClearBackStackAfterReset; - } - - private void ClearBackStackAfterReset(object sender, NavigationEventArgs e) - { - // Unregister the event so it doesn't get called again - RootFrame.Navigated -= ClearBackStackAfterReset; - - // Only clear the stack for 'new' (forward) and 'refresh' navigations - if (e.NavigationMode != NavigationMode.New && e.NavigationMode != NavigationMode.Refresh) - return; - - // For UI consistency, clear the entire page stack - while (RootFrame.RemoveBackEntry() != null) { - ; // do nothing - } - } - - #endregion - - // Initialize the app's font and flow direction as defined in its localized resource strings. - // - // To ensure that the font of your application is aligned with its supported languages and that the - // FlowDirection for each of those languages follows its traditional direction, ResourceLanguage - // and ResourceFlowDirection should be initialized in each resx file to match these values with that - // file's culture. For example: - // - // AppResources.es-ES.resx - // ResourceLanguage's value should be "es-ES" - // ResourceFlowDirection's value should be "LeftToRight" - // - // AppResources.ar-SA.resx - // ResourceLanguage's value should be "ar-SA" - // ResourceFlowDirection's value should be "RightToLeft" - // - // For more info on localizing Windows Phone apps see http://go.microsoft.com/fwlink/?LinkId=262072. - // - private void InitializeLanguage() - { - try { - // Set the font to match the display language defined by the - // ResourceLanguage resource string for each supported language. - // - // Fall back to the font of the neutral language if the Display - // language of the phone is not supported. - // - // If a compiler error is hit then ResourceLanguage is missing from - // the resource file. - RootFrame.Language = XmlLanguage.GetLanguage(AppResources.ResourceLanguage); - - // Set the FlowDirection of all elements under the root frame based - // on the ResourceFlowDirection resource string for each - // supported language. - // - // If a compiler error is hit then ResourceFlowDirection is missing from - // the resource file. - FlowDirection flow = (FlowDirection)Enum.Parse(typeof(FlowDirection), AppResources.ResourceFlowDirection); - RootFrame.FlowDirection = flow; - } - catch { - // If an exception is caught here it is most likely due to either - // ResourceLangauge not being correctly set to a supported language - // code or ResourceFlowDirection is set to a value other than LeftToRight - // or RightToLeft. - - if (Debugger.IsAttached) { - Debugger.Break(); - } - - throw; - } - } - } -} diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/AlignmentGrid.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/AlignmentGrid.png deleted file mode 100644 index f7d2e97804..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/AlignmentGrid.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/ApplicationIcon.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/ApplicationIcon.png deleted file mode 100644 index d2b5c9524d..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/ApplicationIcon.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/FlipCycleTileLarge.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/FlipCycleTileLarge.png deleted file mode 100644 index 8856d9a996..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/FlipCycleTileLarge.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/FlipCycleTileMedium.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/FlipCycleTileMedium.png deleted file mode 100644 index baab0030a7..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/FlipCycleTileMedium.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/FlipCycleTileSmall.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/FlipCycleTileSmall.png deleted file mode 100644 index d44d803a52..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/FlipCycleTileSmall.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/IconicTileMediumLarge.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/IconicTileMediumLarge.png deleted file mode 100644 index 2c904389ac..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/IconicTileMediumLarge.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/IconicTileSmall.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/IconicTileSmall.png deleted file mode 100644 index 76d82e0f08..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Assets/Tiles/IconicTileSmall.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/LocalizedStrings.cs b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/LocalizedStrings.cs deleted file mode 100644 index df28da38f9..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/LocalizedStrings.cs +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -using ThreeLayerRealmXF.WinPhone.Resources; - -namespace ThreeLayerRealmXF.WinPhone { - /// - /// Provides access to string resources. - /// - public class LocalizedStrings { - private static AppResources _localizedResources = new AppResources(); - - public AppResources LocalizedResources { get { return _localizedResources; } } - } -} diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/MainPage.xaml b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/MainPage.xaml deleted file mode 100644 index 94649c80f3..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/MainPage.xaml +++ /dev/null @@ -1,16 +0,0 @@ - - diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/MainPage.xaml.cs b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/MainPage.xaml.cs deleted file mode 100644 index 55fd281b30..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/MainPage.xaml.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Navigation; -using Microsoft.Phone.Controls; -using Microsoft.Phone.Shell; - -namespace ThreeLayerRealmXF.WinPhone { - public partial class MainPage : global::Xamarin.Forms.Platform.WinPhone.FormsApplicationPage { - public MainPage() - { - InitializeComponent(); - SupportedOrientations = SupportedPageOrientation.PortraitOrLandscape; - - global::Xamarin.Forms.Forms.Init(); - LoadApplication(new ThreeLayerRealmXF.App()); - } - } -} diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Properties/AppManifest.xml b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Properties/AppManifest.xml deleted file mode 100644 index a955232752..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Properties/AppManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Properties/AssemblyInfo.cs b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Properties/AssemblyInfo.cs deleted file mode 100644 index 60f22e3035..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Resources; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ThreeLayerRealmXF.WinPhone")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ThreeLayerRealmXF.WinPhone")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("65077432-0c92-466b-b68d-911a8ec84f1d")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Properties/WMAppManifest.xml b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Properties/WMAppManifest.xml deleted file mode 100644 index 2f34344c70..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Properties/WMAppManifest.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - Assets\ApplicationIcon.png - - - - - - - - - - - - - - Assets\Tiles\FlipCycleTileSmall.png - 0 - Assets\Tiles\FlipCycleTileMedium.png - ThreeLayerRealmXF.WinPhone - - - - - - - - - - - - - - - diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/README_FIRST.txt b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/README_FIRST.txt deleted file mode 100644 index ce40c013bb..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/README_FIRST.txt +++ /dev/null @@ -1,3 +0,0 @@ -For the Windows Phone toolkit make sure that you have -marked the icons in the "Toolkit.Content" folder as content. That way they -can be used as the icons for the ApplicationBar control. \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Resources/AppResources.Designer.cs b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Resources/AppResources.Designer.cs deleted file mode 100644 index 6496e492fa..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Resources/AppResources.Designer.cs +++ /dev/null @@ -1,124 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.17626 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace ThreeLayerRealmXF.WinPhone.Resources { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class AppResources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal AppResources() - { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager - { - get - { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ThreeLayerRealmXF.WinPhone.Resources.AppResources", typeof(AppResources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo Culture - { - get - { - return resourceCulture; - } - set - { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to LeftToRight. - /// - public static string ResourceFlowDirection - { - get - { - return ResourceManager.GetString("ResourceFlowDirection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to us-EN. - /// - public static string ResourceLanguage - { - get - { - return ResourceManager.GetString("ResourceLanguage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MY APPLICATION. - /// - public static string ApplicationTitle - { - get - { - return ResourceManager.GetString("ApplicationTitle", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to button. - /// - public static string AppBarButtonText - { - get - { - return ResourceManager.GetString("AppBarButtonText", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to menu item. - /// - public static string AppBarMenuItemText - { - get - { - return ResourceManager.GetString("AppBarMenuItemText", resourceCulture); - } - } - } -} diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Resources/AppResources.resx b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Resources/AppResources.resx deleted file mode 100644 index 569bf520d0..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Resources/AppResources.resx +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - LeftToRight - Controls the FlowDirection for all elements in the RootFrame. Set to the traditional direction of this resource file's language - - - en-US - Controls the Language and ensures that the font for all elements in the RootFrame aligns with the app's language. Set to the language code of this resource file's language. - - - MY APPLICATION - - - add - - - Menu Item - - diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/SplashScreenImage.jpg b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/SplashScreenImage.jpg deleted file mode 100644 index 666f7c537f..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/SplashScreenImage.jpg and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/ThreeLayerRealmXFNuGet.WinPhone.csproj b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/ThreeLayerRealmXFNuGet.WinPhone.csproj deleted file mode 100644 index b045ece24d..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/ThreeLayerRealmXFNuGet.WinPhone.csproj +++ /dev/null @@ -1,200 +0,0 @@ - - - - Debug - AnyCPU - {6EEDE0D8-0052-4733-92B9-EE3C40871F30} - {C089C8C0-30E0-4E22-80C0-CE093F111A43};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - ThreeLayerRealmXFNuGet.WinPhone - ThreeLayerRealmXFNuGet.WinPhone - WindowsPhone - v8.0 - $(TargetFrameworkVersion) - true - - - true - true - PhoneApp1_$(Configuration)_$(Platform).xap - Properties\AppManifest.xml - ThreeLayerRealmXFNuGet.WinPhone.App - true - 11.0 - true - - - - - true - full - false - Bin\Debug - DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE - true - true - prompt - 4 - - - pdbonly - true - Bin\Release - TRACE;SILVERLIGHT;WINDOWS_PHONE - true - true - prompt - 4 - - - true - full - false - Bin\x86\Debug - DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE - true - true - prompt - 4 - - - pdbonly - true - Bin\x86\Release - TRACE;SILVERLIGHT;WINDOWS_PHONE - true - true - prompt - 4 - - - true - full - false - Bin\ARM\Debug - DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE - true - true - prompt - 4 - - - pdbonly - true - Bin\ARM\Release - TRACE;SILVERLIGHT;WINDOWS_PHONE - true - true - prompt - 4 - - - - App.xaml - - - - MainPage.xaml - - - - True - True - AppResources.resx - - - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - - - - - Designer - - - - - - PreserveNewest - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - - - - - - PublicResXFileCodeGenerator - AppResources.Designer.cs - - - - - ThreeLayerRealmXFNuGet - - - - - ..\..\packages\WPtoolkit.4.2013.08.16\lib\wp8\Microsoft.Phone.Controls.Toolkit.dll - True - - - ..\..\packages\Xamarin.Forms.1.3.3.6323\lib\WP80\Xamarin.Forms.Core.dll - True - - - ..\..\packages\Xamarin.Forms.1.3.3.6323\lib\WP80\Xamarin.Forms.Platform.WP8.dll - True - - - ..\..\packages\Xamarin.Forms.1.3.3.6323\lib\WP80\Xamarin.Forms.Xaml.dll - True - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Add.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Add.png deleted file mode 100644 index 4b524d6f14..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Add.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Cancel.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Cancel.png deleted file mode 100644 index 4dd724f087..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Cancel.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Check.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Check.png deleted file mode 100644 index 7a07466687..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Check.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Delete.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Delete.png deleted file mode 100644 index 95bb16dabe..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Delete.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Select.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Select.png deleted file mode 100644 index 995deaaafb..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/Toolkit.Content/ApplicationBar.Select.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/packages.config b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/packages.config deleted file mode 100644 index 25f9dd7967..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.WinPhone/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/AppDelegate.cs b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/AppDelegate.cs deleted file mode 100644 index 192e4dd9c2..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/AppDelegate.cs +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Linq; - -using Foundation; -using UIKit; - -namespace ThreeLayerRealmXF.iOS { - // The UIApplicationDelegate for the application. This class is responsible for launching the - // User Interface of the application, as well as listening (and optionally responding) to - // application events from iOS. - [Register("AppDelegate")] - public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate { - // - // This method is invoked when the application has loaded and is ready to run. In this - // method you should instantiate the window, load the UI into it and then make the window - // visible. - // - // You have 17 seconds to return from this method, or iOS will terminate your application. - // - public override bool FinishedLaunching(UIApplication app, NSDictionary options) - { - global::Xamarin.Forms.Forms.Init(); - LoadApplication(new App()); - - return base.FinishedLaunching(app, options); - } - } -} diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Entitlements.plist b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Entitlements.plist deleted file mode 100644 index e9a3005f78..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Entitlements.plist +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/FodyWeavers.xml b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/FodyWeavers.xml deleted file mode 100644 index 6aa124364c..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Info.plist b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Info.plist deleted file mode 100644 index 2a16c93a4b..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Info.plist +++ /dev/null @@ -1,54 +0,0 @@ - - - - - UIDeviceFamily - - 1 - 2 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - MinimumOSVersion - 8.1 - CFBundleDisplayName - ThreeLayerRealmXF - CFBundleIdentifier - io.realm.ThreeLayerRealmXF - CFBundleVersion - 1.0 - CFBundleIconFiles - - Icon-60@2x - Icon-60@3x - Icon-76 - Icon-76@2x - Default - Default@2x - Default-568h@2x - Default-Portrait - Default-Portrait@2x - Icon-Small-40 - Icon-Small-40@2x - Icon-Small-40@3x - Icon-Small - Icon-Small@2x - Icon-Small@3x - - UILaunchStoryboardName - LaunchScreen - CFBundleShortVersionString - 1.0 - - diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Main.cs b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Main.cs deleted file mode 100644 index de4a58ab54..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Main.cs +++ /dev/null @@ -1,36 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Linq; - -using Foundation; -using UIKit; - -namespace ThreeLayerRealmXF.iOS { - public class Application { - // This is the main entry point of the application. - static void Main(string[] args) - { - // if you want to use a different Application Delegate class from "AppDelegate" - // you can specify it here. - UIApplication.Main(args, null, "AppDelegate"); - } - } -} diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Properties/AssemblyInfo.cs b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Properties/AssemblyInfo.cs deleted file mode 100644 index 8456796ab9..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ThreeLayerRealmXF.iOS")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ThreeLayerRealmXF.iOS")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("72bdc44f-c588-44f3-b6df-9aace7daafdd")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default-568h@2x.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default-568h@2x.png deleted file mode 100644 index 26c6461e50..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default-568h@2x.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default-Portrait.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default-Portrait.png deleted file mode 100644 index 5d0d1ab4c6..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default-Portrait.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default-Portrait@2x.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default-Portrait@2x.png deleted file mode 100644 index 0ee2688e8f..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default-Portrait@2x.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default.png deleted file mode 100644 index b74643c0aa..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default@2x.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default@2x.png deleted file mode 100644 index dbd6bd3e86..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Default@2x.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-60@2x.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-60@2x.png deleted file mode 100644 index 4b03c42703..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-60@2x.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-60@3x.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-60@3x.png deleted file mode 100644 index b03ca1bbc6..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-60@3x.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-76.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-76.png deleted file mode 100644 index 587982e2a6..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-76.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-76@2x.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-76@2x.png deleted file mode 100644 index cd4e2c8fe7..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-76@2x.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small-40.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small-40.png deleted file mode 100644 index 6acff94417..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small-40.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small-40@2x.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small-40@2x.png deleted file mode 100644 index b833aac26f..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small-40@2x.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small-40@3x.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small-40@3x.png deleted file mode 100644 index ab8654e499..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small-40@3x.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small.png deleted file mode 100644 index 33db7e7141..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small@2x.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small@2x.png deleted file mode 100644 index bf45e25929..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small@2x.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small@3x.png b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small@3x.png deleted file mode 100644 index 7ad3891b95..0000000000 Binary files a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/Icon-Small@3x.png and /dev/null differ diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/LaunchScreen.storyboard b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/LaunchScreen.storyboard deleted file mode 100644 index a639c2f1a5..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/Resources/LaunchScreen.storyboard +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/ThreeLayerRealmXFNuGet.iOS.csproj b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/ThreeLayerRealmXFNuGet.iOS.csproj deleted file mode 100644 index 83d689a2b1..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/ThreeLayerRealmXFNuGet.iOS.csproj +++ /dev/null @@ -1,173 +0,0 @@ - - - - - Debug - iPhoneSimulator - {F377984A-36A2-454B-AAAB-6E385048D5AB} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Exe - ThreeLayerRealmXFNuGet.iOS - Resources - ThreeLayerRealmXFNuGetiOS - - - - - true - full - false - bin\iPhoneSimulator\Debug - DEBUG - prompt - 4 - false - i386, x86_64 - None - true - Visual Studio: ThreeLayerRealmXF Development - iPhone Developer: Nikola Irinchev (K299G48R8B) - - - none - true - bin\iPhoneSimulator\Release - prompt - 4 - None - i386, x86_64 - false - - - true - full - false - bin\iPhone\Debug - DEBUG - prompt - 4 - false - ARMv7, ARM64 - iPhone Developer - true - Entitlements.plist - - - none - true - bin\iPhone\Release - prompt - 4 - ARMv7, ARM64 - false - iPhone Developer - Entitlements.plist - - - none - True - bin\iPhone\Ad-Hoc - prompt - 4 - False - ARMv7, ARM64 - True - Automatic:AdHoc - iPhone Distribution - Entitlements.plist - - - none - True - bin\iPhone\AppStore - prompt - 4 - False - ARMv7, ARM64 - Automatic:AppStore - iPhone Distribution - Entitlements.plist - - - - - - - - - - - - - ThreeLayerRealmXFNuGet - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ..\..\packages\Realm.DataBinding.1.2.0\lib\Xamarin.iOS10\Realm.DataBinding.dll - - - ..\..\packages\Remotion.Linq.2.1.2\lib\portable-net45+win+wpa81+wp80\Remotion.Linq.dll - - - ..\..\packages\Newtonsoft.Json.10.0.3\lib\netstandard1.3\Newtonsoft.Json.dll - - - ..\..\packages\Xamarin.Forms.2.4.0.282\lib\Xamarin.iOS10\Xamarin.Forms.Core.dll - - - ..\..\packages\Xamarin.Forms.2.4.0.282\lib\Xamarin.iOS10\Xamarin.Forms.Platform.dll - - - ..\..\packages\Xamarin.Forms.2.4.0.282\lib\Xamarin.iOS10\Xamarin.Forms.Platform.iOS.dll - - - ..\..\packages\Xamarin.Forms.2.4.0.282\lib\Xamarin.iOS10\Xamarin.Forms.Xaml.dll - - - ..\..\packages\DotNetCross.Memory.Unsafe.0.2.3.4\lib\netstandard1.0\DotNetCross.Memory.Unsafe.dll - - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - ..\..\packages\Realm.Database.2.2.0\lib\netstandard1.4\Realm.dll - - - ..\..\packages\Realm.2.2.0\lib\netstandard1.4\Realm.Sync.dll - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/packages.config b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/packages.config deleted file mode 100644 index 0de0551de6..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.iOS/packages.config +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/App.cs b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/App.cs deleted file mode 100644 index 2aa2b36a6d..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/App.cs +++ /dev/null @@ -1,88 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -using Xamarin.Forms; - -namespace ThreeLayerRealmXF { - public class App : Application - { - - private readonly PurePCLViewModel.ViewModel _model; - - public App() - { - _model = new PurePCLViewModel.ViewModel(); - - BindingContext = _model; - var boundLabel = new Label - { - XAlign = TextAlignment.Center, - Text = "" - }; - boundLabel.SetBinding(Label.TextProperty, new Binding("TheAnswer")); - - var button = new Button() - { - Text = "Test Realm", - VerticalOptions = LayoutOptions.CenterAndExpand, - HorizontalOptions = LayoutOptions.CenterAndExpand, - }; - button.Clicked += (s, e) => - { - _model.TestRealm(); - }; - - // The root page of your application - MainPage = new ContentPage - { - Content = new StackLayout - { - VerticalOptions = LayoutOptions.Center, - Children = { - new Label { - XAlign = TextAlignment.Center, - Text = "Welcome to ThreeLayer PCL in Forms!" - }, - button, - boundLabel - } - } - }; - } - - protected override void OnStart() - { - // Handle when your app starts - } - - protected override void OnSleep() - { - // Handle when your app sleeps - } - - protected override void OnResume() - { - // Handle when your app resumes - } - } -} diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/GettingStarted.Xamarin b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/GettingStarted.Xamarin deleted file mode 100644 index f31b0091ce..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/GettingStarted.Xamarin +++ /dev/null @@ -1,4 +0,0 @@ - - GS\XF\CS\App\GettingStarted.html - false - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/Properties/AssemblyInfo.cs b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/Properties/AssemblyInfo.cs deleted file mode 100644 index 7c77a3dc61..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Resources; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ThreeLayerRealmXF")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ThreeLayerRealmXF")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.csproj b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.csproj deleted file mode 100644 index 44bb89f318..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet.csproj +++ /dev/null @@ -1,64 +0,0 @@ - - - - - 10.0 - Debug - AnyCPU - {4115119F-5DDD-4CCA-8A2F-5E9DFC906F35} - Library - Properties - ThreeLayerRealmXFNuGet - ThreeLayerRealmXFNuGet - v4.5 - Profile78 - 512 - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - {A2705D5C-9F10-4A8B-9EBC-D4369E8A4D62} - PurePCLViewModel - - - - - ..\..\packages\Xamarin.Forms.2.4.0.282\lib\portable-win+net45+wp80+win81+wpa81\Xamarin.Forms.Core.dll - - - ..\..\packages\Xamarin.Forms.2.4.0.282\lib\portable-win+net45+wp80+win81+wpa81\Xamarin.Forms.Platform.dll - - - ..\..\packages\Xamarin.Forms.2.4.0.282\lib\portable-win+net45+wp80+win81+wpa81\Xamarin.Forms.Xaml.dll - - - - - \ No newline at end of file diff --git a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/packages.config b/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/packages.config deleted file mode 100644 index f61e8904fb..0000000000 --- a/Manual Tests/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/ThreeLayerRealmXFNuGet/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/README.md b/README.md index 4b06d76536..41a99c7205 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,4 @@ not eligible to receive the product under U.S. law.** **_If you use Realm and are happy with it, all we ask is that you please consider sending out a tweet mentioning [@realm](https://twitter.com/realm) to share your thoughts!_** -**_And if you don't like it, please let us know what you would like improved, so we can fix it!_** - -![analytics](https://ga-beacon.appspot.com/UA-50247013-2/realm-dotnet/README?pixel) +**_And if you don't like it, please let us know what you would like improved, so we can fix it!_** \ No newline at end of file diff --git a/Realm - Windows.sln b/Realm - Windows.sln index b3311f30d0..d9c886ec58 100644 --- a/Realm - Windows.sln +++ b/Realm - Windows.sln @@ -1,6 +1,7 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 -VisualStudioVersion = 16.0.29424.173 +VisualStudioVersion = 16.0.28621.142 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Realm", "Realm", "{50F058DF-2B41-403C-BB73-8B4180D1CF39}" ProjectSection(SolutionItems) = preProject @@ -10,9 +11,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Realm", "Realm\Realm\Realm.csproj", "{C0DB0E07-4FBC-44D1-8E84-A074497385C6}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D10BE048-9C20-4B8B-BE5B-48CC55F8BB07}" - ProjectSection(SolutionItems) = preProject - Tests\Settings.StyleCop = Tests\Settings.StyleCop - EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.UWP", "Tests\Tests.UWP\Tests.UWP.csproj", "{159A55AF-9531-4107-8444-CA9C96107EF5}" ProjectSection(ProjectDependencies) = postProject @@ -26,6 +24,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution CHANGELOG.md = CHANGELOG.md Directory.build.props = Directory.build.props global.json = global.json + global.ruleset = global.ruleset + stylecop.json = stylecop.json EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Realm.Fody", "Realm\Realm.Fody\Realm.Fody.csproj", "{CDAB5FEE-CC5D-4EFA-807F-F1B974047BE7}" @@ -46,14 +46,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssemblyToProcess", "Tests\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Realm.Fody.Tests", "Tests\Weaver\Realm.Fody.Tests\Realm.Fody.Tests.csproj", "{D8DF24A7-E618-4A0F-ABC4-97AC823BD6A3}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Native", "Native", "{0AEA39B5-0D91-4334-81F9-9D45415FD36B}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "realm-object-store", "wrappers\cmake\Windows\Debug-x64\src\object-store\src\realm-object-store.vcxproj", "{C7C69F12-F219-3414-8F95-58D1F5D29158}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "realm-wrappers", "wrappers\cmake\Windows\Debug-Win32\src\realm-wrappers.vcxproj", "{7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "realm-wrappers", "wrappers\cmake\Windows\Debug-x64\src\realm-wrappers.vcxproj", "{C14A2A2C-7CC6-3592-860B-A67089682AA5}" - ProjectSection(ProjectDependencies) = postProject - {C7C69F12-F219-3414-8F95-58D1F5D29158} = {C7C69F12-F219-3414-8F95-58D1F5D29158} - EndProjectSection +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "realm-object-store", "wrappers\cmake\Windows\Debug-Win32\src\object-store\src\realm-object-store.vcxproj", "{BC80B5C6-5AF4-30F4-803D-3844579C103B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -144,23 +139,11 @@ Global {159A55AF-9531-4107-8444-CA9C96107EF5}.Debug|x86.Build.0 = Debug|x86 {159A55AF-9531-4107-8444-CA9C96107EF5}.Debug|x86.Deploy.0 = Debug|x86 {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|Any CPU.ActiveCfg = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|Any CPU.Build.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|Any CPU.Deploy.0 = Release|x64 {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|ARM.ActiveCfg = Release|ARM - {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|ARM.Build.0 = Release|ARM - {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|ARM.Deploy.0 = Release|ARM {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|iPhone.ActiveCfg = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|iPhone.Build.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|iPhone.Deploy.0 = Release|x64 {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|iPhoneSimulator.ActiveCfg = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|iPhoneSimulator.Build.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|iPhoneSimulator.Deploy.0 = Release|x64 {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|x64.ActiveCfg = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|x64.Build.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|x64.Deploy.0 = Release|x64 {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|x86.ActiveCfg = Release|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|x86.Build.0 = Release|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.MinSizeRel|x86.Deploy.0 = Release|x86 {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|Any CPU.ActiveCfg = Release|x86 {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|ARM.ActiveCfg = Release|ARM {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|ARM.Build.0 = Release|ARM @@ -174,23 +157,11 @@ Global {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|x86.Build.0 = Release|x86 {159A55AF-9531-4107-8444-CA9C96107EF5}.Release|x86.Deploy.0 = Release|x86 {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|Any CPU.Build.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|Any CPU.Deploy.0 = Release|x64 {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|ARM.ActiveCfg = Release|ARM - {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|ARM.Build.0 = Release|ARM - {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|ARM.Deploy.0 = Release|ARM {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|iPhone.ActiveCfg = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|iPhone.Build.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|iPhone.Deploy.0 = Release|x64 {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|iPhoneSimulator.ActiveCfg = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|iPhoneSimulator.Build.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|iPhoneSimulator.Deploy.0 = Release|x64 {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|x64.ActiveCfg = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|x64.Build.0 = Release|x64 - {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|x64.Deploy.0 = Release|x64 {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|x86.ActiveCfg = Release|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|x86.Build.0 = Release|x86 - {159A55AF-9531-4107-8444-CA9C96107EF5}.RelWithDebInfo|x86.Deploy.0 = Release|x86 {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|Any CPU.Build.0 = Debug|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -204,17 +175,11 @@ Global {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|x86.ActiveCfg = Debug|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.Debug|x86.Build.0 = Debug|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.MinSizeRel|Any CPU.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.MinSizeRel|Any CPU.Build.0 = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.MinSizeRel|ARM.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.MinSizeRel|ARM.Build.0 = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.MinSizeRel|iPhone.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.MinSizeRel|iPhone.Build.0 = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.MinSizeRel|iPhoneSimulator.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.MinSizeRel|iPhoneSimulator.Build.0 = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.MinSizeRel|x64.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.MinSizeRel|x64.Build.0 = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.MinSizeRel|x86.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.MinSizeRel|x86.Build.0 = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|Any CPU.ActiveCfg = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|Any CPU.Build.0 = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|ARM.ActiveCfg = Release|Any CPU @@ -228,17 +193,11 @@ Global {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|x86.ActiveCfg = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.Release|x86.Build.0 = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.RelWithDebInfo|iPhone.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.RelWithDebInfo|iPhone.Build.0 = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.RelWithDebInfo|iPhoneSimulator.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.RelWithDebInfo|iPhoneSimulator.Build.0 = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {3A6CC342-40D0-4487-9964-DDAEBA628199}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU - {3A6CC342-40D0-4487-9964-DDAEBA628199}.RelWithDebInfo|x86.Build.0 = Release|Any CPU {CDAB5FEE-CC5D-4EFA-807F-F1B974047BE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CDAB5FEE-CC5D-4EFA-807F-F1B974047BE7}.Debug|Any CPU.Build.0 = Debug|Any CPU {CDAB5FEE-CC5D-4EFA-807F-F1B974047BE7}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -300,23 +259,11 @@ Global {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Debug|x86.ActiveCfg = Debug|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Debug|x86.Build.0 = Debug|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|Any CPU.ActiveCfg = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|Any CPU.Build.0 = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|Any CPU.Deploy.0 = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|ARM.ActiveCfg = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|ARM.Build.0 = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|ARM.Deploy.0 = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|iPhone.ActiveCfg = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|iPhone.Build.0 = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|iPhone.Deploy.0 = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|iPhoneSimulator.ActiveCfg = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|iPhoneSimulator.Build.0 = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|iPhoneSimulator.Deploy.0 = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|x64.ActiveCfg = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|x64.Build.0 = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|x64.Deploy.0 = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|x86.ActiveCfg = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|x86.Build.0 = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|x86.Deploy.0 = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Release|Any CPU.ActiveCfg = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Release|Any CPU.Build.0 = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Release|ARM.ActiveCfg = Release|Any CPU @@ -330,23 +277,11 @@ Global {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Release|x86.ActiveCfg = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Release|x86.Build.0 = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|Any CPU.Deploy.0 = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|ARM.Deploy.0 = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|iPhone.ActiveCfg = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|iPhone.Build.0 = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|iPhone.Deploy.0 = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|iPhoneSimulator.ActiveCfg = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|iPhoneSimulator.Build.0 = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|iPhoneSimulator.Deploy.0 = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|x64.Build.0 = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|x64.Deploy.0 = Release|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|x86.Build.0 = Release|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.RelWithDebInfo|x86.Deploy.0 = Release|Any CPU {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.Debug|ARM.ActiveCfg = Debug|iPhoneSimulator @@ -360,17 +295,11 @@ Global {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.Debug|x86.ActiveCfg = Debug|iPhoneSimulator {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.Debug|x86.Build.0 = Debug|iPhoneSimulator {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.MinSizeRel|Any CPU.ActiveCfg = Release|iPhone - {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.MinSizeRel|Any CPU.Build.0 = Release|iPhone {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.MinSizeRel|ARM.ActiveCfg = Release|iPhone - {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.MinSizeRel|ARM.Build.0 = Release|iPhone {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.MinSizeRel|iPhone.ActiveCfg = Release|iPhone - {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.MinSizeRel|iPhone.Build.0 = Release|iPhone {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.MinSizeRel|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.MinSizeRel|iPhoneSimulator.Build.0 = Release|iPhoneSimulator {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.MinSizeRel|x64.ActiveCfg = Release|iPhone - {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.MinSizeRel|x64.Build.0 = Release|iPhone {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.MinSizeRel|x86.ActiveCfg = Release|iPhone - {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.MinSizeRel|x86.Build.0 = Release|iPhone {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.Release|Any CPU.Build.0 = Release|iPhoneSimulator {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.Release|ARM.ActiveCfg = Release|iPhoneSimulator @@ -384,17 +313,11 @@ Global {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.Release|x86.ActiveCfg = Release|iPhoneSimulator {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.Release|x86.Build.0 = Release|iPhoneSimulator {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.RelWithDebInfo|Any CPU.ActiveCfg = Release|iPhone - {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.RelWithDebInfo|Any CPU.Build.0 = Release|iPhone {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.RelWithDebInfo|ARM.ActiveCfg = Release|iPhone - {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.RelWithDebInfo|ARM.Build.0 = Release|iPhone {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.RelWithDebInfo|iPhone.ActiveCfg = Release|iPhone - {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.RelWithDebInfo|iPhone.Build.0 = Release|iPhone {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.RelWithDebInfo|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.RelWithDebInfo|iPhoneSimulator.Build.0 = Release|iPhoneSimulator {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.RelWithDebInfo|x64.ActiveCfg = Release|iPhone - {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.RelWithDebInfo|x64.Build.0 = Release|iPhone {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.RelWithDebInfo|x86.ActiveCfg = Release|iPhone - {F67B1BEB-4157-45A2-B310-4AA8F99C750E}.RelWithDebInfo|x86.Build.0 = Release|iPhone {78799FCD-BE2E-4ECE-A52E-662846615B58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {78799FCD-BE2E-4ECE-A52E-662846615B58}.Debug|Any CPU.Build.0 = Debug|Any CPU {78799FCD-BE2E-4ECE-A52E-662846615B58}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -635,62 +558,62 @@ Global {D8DF24A7-E618-4A0F-ABC4-97AC823BD6A3}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {D8DF24A7-E618-4A0F-ABC4-97AC823BD6A3}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU {D8DF24A7-E618-4A0F-ABC4-97AC823BD6A3}.RelWithDebInfo|x86.Build.0 = Release|Any CPU - {C7C69F12-F219-3414-8F95-58D1F5D29158}.Debug|Any CPU.ActiveCfg = Debug|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.Debug|ARM.ActiveCfg = Debug|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.Debug|iPhone.ActiveCfg = Debug|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.Debug|iPhoneSimulator.ActiveCfg = Debug|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.Debug|x64.ActiveCfg = Debug|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.Debug|x64.Build.0 = Debug|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.Debug|x86.ActiveCfg = Debug|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.MinSizeRel|ARM.ActiveCfg = MinSizeRel|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.MinSizeRel|iPhone.ActiveCfg = MinSizeRel|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.MinSizeRel|iPhoneSimulator.ActiveCfg = MinSizeRel|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.Release|Any CPU.ActiveCfg = Release|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.Release|ARM.ActiveCfg = Release|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.Release|iPhone.ActiveCfg = Release|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.Release|iPhoneSimulator.ActiveCfg = Release|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.Release|x64.ActiveCfg = Release|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.Release|x64.Build.0 = Release|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.Release|x86.ActiveCfg = Release|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.RelWithDebInfo|ARM.ActiveCfg = RelWithDebInfo|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.RelWithDebInfo|iPhone.ActiveCfg = RelWithDebInfo|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.RelWithDebInfo|iPhoneSimulator.ActiveCfg = RelWithDebInfo|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 - {C7C69F12-F219-3414-8F95-58D1F5D29158}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.Debug|Any CPU.ActiveCfg = Debug|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.Debug|ARM.ActiveCfg = Debug|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.Debug|iPhone.ActiveCfg = Debug|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.Debug|iPhoneSimulator.ActiveCfg = Debug|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.Debug|x64.ActiveCfg = Debug|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.Debug|x64.Build.0 = Debug|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.Debug|x86.ActiveCfg = Debug|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.MinSizeRel|ARM.ActiveCfg = MinSizeRel|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.MinSizeRel|iPhone.ActiveCfg = MinSizeRel|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.MinSizeRel|iPhoneSimulator.ActiveCfg = MinSizeRel|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.Release|Any CPU.ActiveCfg = Release|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.Release|ARM.ActiveCfg = Release|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.Release|iPhone.ActiveCfg = Release|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.Release|iPhoneSimulator.ActiveCfg = Release|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.Release|x64.ActiveCfg = Release|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.Release|x64.Build.0 = Release|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.Release|x86.ActiveCfg = Release|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.RelWithDebInfo|ARM.ActiveCfg = RelWithDebInfo|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.RelWithDebInfo|iPhone.ActiveCfg = RelWithDebInfo|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.RelWithDebInfo|iPhoneSimulator.ActiveCfg = RelWithDebInfo|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 - {C14A2A2C-7CC6-3592-860B-A67089682AA5}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.Debug|ARM.ActiveCfg = Debug|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.Debug|iPhone.ActiveCfg = Debug|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.Debug|iPhoneSimulator.ActiveCfg = Debug|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.Debug|x64.ActiveCfg = Debug|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.Debug|x86.ActiveCfg = Debug|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.Debug|x86.Build.0 = Debug|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.MinSizeRel|ARM.ActiveCfg = MinSizeRel|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.MinSizeRel|iPhone.ActiveCfg = MinSizeRel|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.MinSizeRel|iPhoneSimulator.ActiveCfg = MinSizeRel|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.MinSizeRel|x64.ActiveCfg = MinSizeRel|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.MinSizeRel|x86.ActiveCfg = MinSizeRel|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.MinSizeRel|x86.Build.0 = MinSizeRel|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.Release|Any CPU.ActiveCfg = Release|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.Release|ARM.ActiveCfg = Release|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.Release|iPhone.ActiveCfg = Release|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.Release|iPhoneSimulator.ActiveCfg = Release|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.Release|x64.ActiveCfg = Release|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.Release|x86.ActiveCfg = Release|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.Release|x86.Build.0 = Release|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.RelWithDebInfo|ARM.ActiveCfg = RelWithDebInfo|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.RelWithDebInfo|iPhone.ActiveCfg = RelWithDebInfo|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.RelWithDebInfo|iPhoneSimulator.ActiveCfg = RelWithDebInfo|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|Win32 + {7E414A7A-DB48-3EBF-8F4D-0AB7FC2E8721}.RelWithDebInfo|x86.Build.0 = RelWithDebInfo|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.Debug|ARM.ActiveCfg = Debug|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.Debug|iPhone.ActiveCfg = Debug|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.Debug|x64.ActiveCfg = Debug|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.Debug|x86.ActiveCfg = Debug|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.Debug|x86.Build.0 = Debug|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.MinSizeRel|ARM.ActiveCfg = MinSizeRel|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.MinSizeRel|iPhone.ActiveCfg = MinSizeRel|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.MinSizeRel|iPhoneSimulator.ActiveCfg = MinSizeRel|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.MinSizeRel|x64.ActiveCfg = MinSizeRel|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.MinSizeRel|x86.ActiveCfg = MinSizeRel|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.MinSizeRel|x86.Build.0 = MinSizeRel|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.Release|Any CPU.ActiveCfg = Release|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.Release|ARM.ActiveCfg = Release|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.Release|iPhone.ActiveCfg = Release|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.Release|iPhoneSimulator.ActiveCfg = Release|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.Release|x64.ActiveCfg = Release|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.Release|x86.ActiveCfg = Release|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.Release|x86.Build.0 = Release|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.RelWithDebInfo|ARM.ActiveCfg = RelWithDebInfo|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.RelWithDebInfo|iPhone.ActiveCfg = RelWithDebInfo|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.RelWithDebInfo|iPhoneSimulator.ActiveCfg = RelWithDebInfo|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|Win32 + {BC80B5C6-5AF4-30F4-803D-3844579C103B}.RelWithDebInfo|x86.Build.0 = RelWithDebInfo|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -708,10 +631,8 @@ Global {C9753FE6-8022-427E-A612-D9E8EDB3641D} = {4FF9AAE6-210E-41F0-A5E1-8D7C4A864F51} {A416D382-B243-4019-A05D-1D937F1CD73A} = {4FF9AAE6-210E-41F0-A5E1-8D7C4A864F51} {D8DF24A7-E618-4A0F-ABC4-97AC823BD6A3} = {4FF9AAE6-210E-41F0-A5E1-8D7C4A864F51} - {C7C69F12-F219-3414-8F95-58D1F5D29158} = {0AEA39B5-0D91-4334-81F9-9D45415FD36B} - {C14A2A2C-7CC6-3592-860B-A67089682AA5} = {0AEA39B5-0D91-4334-81F9-9D45415FD36B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {DA9D266A-F38B-37CE-8E65-3C773727043C} + SolutionGuid = {BE5E0028-B74D-4BE1-B1DA-5FFCC8469C41} EndGlobalSection EndGlobal diff --git a/Realm/AssemblyInfo.props b/Realm/AssemblyInfo.props index b1ec1ca77a..014d732fbd 100644 --- a/Realm/AssemblyInfo.props +++ b/Realm/AssemblyInfo.props @@ -1,7 +1,7 @@ Realm .NET - 5.1.1 + 10.0.0 Realm is a mobile database: a replacement for SQLite Realm Inc. Copyright © $([System.DateTime]::Now.ToString(yyyy)) Realm Inc. diff --git a/Realm/Realm.Fody/Extensions/PropertyDefinitionExtensions.cs b/Realm/Realm.Fody/Extensions/PropertyDefinitionExtensions.cs index 145af01487..8b50bc7b9e 100644 --- a/Realm/Realm.Fody/Extensions/PropertyDefinitionExtensions.cs +++ b/Realm/Realm.Fody/Extensions/PropertyDefinitionExtensions.cs @@ -24,7 +24,7 @@ using System.Text.RegularExpressions; using Mono.Cecil; using Mono.Cecil.Cil; - +using RealmWeaver; using static ModuleWeaver; [EditorBrowsable(EditorBrowsableState.Never)] @@ -94,6 +94,21 @@ internal static bool IsDouble(this PropertyDefinition property) return property.PropertyType.FullName == DoubleTypeName; } + internal static bool IsDecimal(this PropertyDefinition property) + { + return property.PropertyType.FullName == DecimalTypeName; + } + + internal static bool IsDecimal128(this PropertyDefinition property) + { + return property.PropertyType.FullName == Decimal128TypeName; + } + + internal static bool IsObjectId(this PropertyDefinition property) + { + return property.PropertyType.FullName == ObjectIdTypeName; + } + internal static bool IsString(this PropertyDefinition property) { return property.PropertyType.FullName == StringTypeName; @@ -113,21 +128,21 @@ internal static FieldReference GetBackingField(this PropertyDefinition property) .SingleOrDefault(); } - internal static bool IsPrimaryKey(this PropertyDefinition property) + internal static bool IsPrimaryKey(this PropertyDefinition property, ImportedReferences references) { - Debug.Assert(property.DeclaringType.BaseType.Name == "RealmObject", "Primary key properties only make sense on RealmObject classes"); + Debug.Assert(property.DeclaringType.IsValidRealmObjectBaseInheritor(references), "Primary key properties only make sense on RealmObject/EmbeddedObject classes"); return property.CustomAttributes.Any(a => a.AttributeType.Name == "PrimaryKeyAttribute"); } - internal static bool IsRequired(this PropertyDefinition property) + internal static bool IsRequired(this PropertyDefinition property, ImportedReferences references) { - Debug.Assert(property.DeclaringType.BaseType.Name == "RealmObject", "Required properties only make sense on RealmObject classes"); + Debug.Assert(property.DeclaringType.IsValidRealmObjectBaseInheritor(references), "Required properties only make sense on RealmObject/EmbeddedObject classes"); return property.CustomAttributes.Any(a => a.AttributeType.Name == "RequiredAttribute"); } - internal static bool IsIndexable(this PropertyDefinition property) + internal static bool IsIndexable(this PropertyDefinition property, ImportedReferences references) { - Debug.Assert(property.DeclaringType.BaseType.Name == "RealmObject", "Required properties only make sense on RealmObject classes"); + Debug.Assert(property.DeclaringType.IsValidRealmObjectBaseInheritor(references), "Required properties only make sense on RealmObject/EmbeddedObject classes"); var propertyType = property.PropertyType; if (propertyType.IsRealmInteger(out var isNullable, out var backingType)) { @@ -142,6 +157,16 @@ internal static bool IsIndexable(this PropertyDefinition property) return _indexableTypes.Contains(propertyType.FullName); } + public static bool IsEmbeddedObjectInheritor(this TypeDefinition type, ImportedReferences references) => type.BaseType.IsSameAs(references.EmbeddedObject); + + public static bool IsRealmObjectInheritor(this TypeDefinition type, ImportedReferences references) => type.BaseType.IsSameAs(references.RealmObject); + + public static bool IsValidRealmObjectBaseInheritor(this TypeDefinition type, ImportedReferences references) => type.IsEmbeddedObjectInheritor(references) || type.IsRealmObjectInheritor(references); + + public static bool ContainsRealmObject(this PropertyDefinition property, ImportedReferences references) => property.PropertyType.Resolve().IsRealmObjectInheritor(references); + + public static bool ContainsEmbeddedObject(this PropertyDefinition property, ImportedReferences references) => property.PropertyType.Resolve().IsEmbeddedObjectInheritor(references); + public static bool IsRealmInteger(this TypeReference type, out bool isNullable, out TypeReference genericArgumentType) { var nullableMatch = NullableRegex.Match(type.FullName); diff --git a/Realm/Realm.Fody/ImportedReferences.cs b/Realm/Realm.Fody/ImportedReferences.cs index dd55da0014..9bb15cf5b0 100644 --- a/Realm/Realm.Fody/ImportedReferences.cs +++ b/Realm/Realm.Fody/ImportedReferences.cs @@ -47,10 +47,6 @@ internal abstract class ImportedReferences public MethodReference System_Type_GetTypeFromHandle { get; } - public TypeReference System_DateTimeOffset { get; } - - public MethodReference System_DateTimeOffset_op_Inequality { get; private set; } - public abstract TypeReference System_Collections_Generic_ListOfT { get; } public MethodReference System_Collections_Generic_ListOfT_Constructor { get; private set; } @@ -81,6 +77,10 @@ internal abstract class ImportedReferences public TypeReference RealmObject { get; private set; } + public TypeReference RealmObjectBase { get; private set; } + + public TypeReference EmbeddedObject { get; private set; } + public TypeReference RealmIntegerOfT { get; private set; } public MethodReference RealmIntegerOfT_ConvertToT { get; private set; } @@ -101,6 +101,12 @@ internal abstract class ImportedReferences public MethodReference RealmObject_GetBacklinks { get; private set; } + public MethodReference RealmObject_GetPrimitiveValue { get; private set; } + + public MethodReference RealmObject_SetPrimitiveValue { get; private set; } + + public MethodReference RealmObject_SetPrimitiveValueUnique { get; private set; } + public TypeReference IRealmObjectHelper { get; private set; } public TypeReference PreserveAttribute { get; private set; } @@ -129,6 +135,8 @@ internal abstract class ImportedReferences public MethodReference RealmSchema_AddDefaultTypes { get; private set; } + public TypeReference RealmSchema_PropertyType { get; private set; } + protected ModuleDefinition Module { get; } protected Fody.TypeSystem Types { get; } @@ -184,8 +192,6 @@ protected ImportedReferences(ModuleDefinition module, Fody.TypeSystem types, Fra Parameters = { new ParameterDefinition(runtimeTypeHandle) } }; - System_DateTimeOffset = new TypeReference("System", "DateTimeOffset", Module, Module.TypeSystem.CoreLibrary, valueType: true); - // If the assembly has a reference to PropertyChanged.Fody, let's look up the DoNotNotifyAttribute for use later. var PropertyChanged_Fody = Module.AssemblyReferences.SingleOrDefault(a => a.Name == "PropertyChanged"); if (PropertyChanged_Fody != null) @@ -212,11 +218,6 @@ private void InitializeFrameworkMethods() Parameters = { new ParameterDefinition(Types.Int32Reference) } }; - System_DateTimeOffset_op_Inequality = new MethodReference("op_Inequality", Types.BooleanReference, System_DateTimeOffset) - { - Parameters = { new ParameterDefinition(System_DateTimeOffset), new ParameterDefinition(System_DateTimeOffset) } - }; - System_Collections_Generic_ListOfT_Constructor = new MethodReference(".ctor", Types.VoidReference, System_Collections_Generic_ListOfT) { HasThis = true }; { @@ -238,7 +239,10 @@ private void InitializeFrameworkMethods() private void InitializeRealm(IMetadataScope realmAssembly) { Realm = new TypeReference("Realms", "Realm", Module, realmAssembly); + RealmObjectBase = new TypeReference("Realms", "RealmObjectBase", Module, realmAssembly); RealmObject = new TypeReference("Realms", "RealmObject", Module, realmAssembly); + EmbeddedObject = new TypeReference("Realms", "EmbeddedObject", Module, realmAssembly); + RealmSchema_PropertyType = new TypeReference("Realms.Schema", "PropertyType", Module, realmAssembly, valueType: true); { RealmIntegerOfT = new TypeReference("Realms", "RealmInteger`1", Module, realmAssembly) @@ -271,32 +275,32 @@ private void InitializeRealm(IMetadataScope realmAssembly) Realm_Add.Parameters.Add(new ParameterDefinition(Types.BooleanReference)); } - RealmObject_get_IsManaged = new MethodReference("get_IsManaged", Types.BooleanReference, RealmObject) { HasThis = true }; - RealmObject_get_Realm = new MethodReference("get_Realm", Realm, RealmObject) { HasThis = true }; - RealmObject_RaisePropertyChanged = new MethodReference("RaisePropertyChanged", Types.VoidReference, RealmObject) + RealmObject_get_IsManaged = new MethodReference("get_IsManaged", Types.BooleanReference, RealmObjectBase) { HasThis = true }; + RealmObject_get_Realm = new MethodReference("get_Realm", Realm, RealmObjectBase) { HasThis = true }; + RealmObject_RaisePropertyChanged = new MethodReference("RaisePropertyChanged", Types.VoidReference, RealmObjectBase) { HasThis = true, Parameters = { new ParameterDefinition(Types.StringReference) } }; { - RealmObject_GetObjectValue = new MethodReference("GetObjectValue", Types.VoidReference, RealmObject) { HasThis = true }; - var T = new GenericParameter(RealmObject_GetObjectValue) { Constraints = { new GenericParameterConstraint(RealmObject) } }; + RealmObject_GetObjectValue = new MethodReference("GetObjectValue", Types.VoidReference, RealmObjectBase) { HasThis = true }; + var T = new GenericParameter(RealmObject_GetObjectValue) { Constraints = { new GenericParameterConstraint(RealmObjectBase) } }; RealmObject_GetObjectValue.ReturnType = T; RealmObject_GetObjectValue.GenericParameters.Add(T); RealmObject_GetObjectValue.Parameters.Add(new ParameterDefinition(Types.StringReference)); } { - RealmObject_SetObjectValue = new MethodReference("SetObjectValue", Types.VoidReference, RealmObject) { HasThis = true }; - var T = new GenericParameter(RealmObject_SetObjectValue) { Constraints = { new GenericParameterConstraint(RealmObject) } }; + RealmObject_SetObjectValue = new MethodReference("SetObjectValue", Types.VoidReference, RealmObjectBase) { HasThis = true }; + var T = new GenericParameter(RealmObject_SetObjectValue) { Constraints = { new GenericParameterConstraint(RealmObjectBase) } }; RealmObject_SetObjectValue.GenericParameters.Add(T); RealmObject_SetObjectValue.Parameters.Add(new ParameterDefinition(Types.StringReference)); RealmObject_SetObjectValue.Parameters.Add(new ParameterDefinition(T)); } { - RealmObject_GetListValue = new MethodReference("GetListValue", new GenericInstanceType(IListOfT), RealmObject) { HasThis = true }; + RealmObject_GetListValue = new MethodReference("GetListValue", new GenericInstanceType(IListOfT), RealmObjectBase) { HasThis = true }; var T = new GenericParameter(RealmObject_GetListValue); (RealmObject_GetListValue.ReturnType as GenericInstanceType).GenericArguments.Add(T); RealmObject_GetListValue.GenericParameters.Add(T); @@ -304,13 +308,40 @@ private void InitializeRealm(IMetadataScope realmAssembly) } { - RealmObject_GetBacklinks = new MethodReference("GetBacklinks", new GenericInstanceType(IQueryableOfT), RealmObject) { HasThis = true }; - var T = new GenericParameter(RealmObject_GetBacklinks) { Constraints = { new GenericParameterConstraint(RealmObject) } }; + RealmObject_GetBacklinks = new MethodReference("GetBacklinks", new GenericInstanceType(IQueryableOfT), RealmObjectBase) { HasThis = true }; + var T = new GenericParameter(RealmObject_GetBacklinks) { Constraints = { new GenericParameterConstraint(RealmObjectBase) } }; (RealmObject_GetBacklinks.ReturnType as GenericInstanceType).GenericArguments.Add(T); RealmObject_GetBacklinks.GenericParameters.Add(T); RealmObject_GetBacklinks.Parameters.Add(new ParameterDefinition(Types.StringReference)); } + { + RealmObject_GetPrimitiveValue = new MethodReference("GetPrimitiveValue", Types.VoidReference, RealmObjectBase) { HasThis = true }; + var T = new GenericParameter(RealmObject_GetPrimitiveValue); + RealmObject_GetPrimitiveValue.ReturnType = T; + RealmObject_GetPrimitiveValue.GenericParameters.Add(T); + RealmObject_GetPrimitiveValue.Parameters.Add(new ParameterDefinition(Types.StringReference)); + RealmObject_GetPrimitiveValue.Parameters.Add(new ParameterDefinition(RealmSchema_PropertyType)); + } + + { + RealmObject_SetPrimitiveValue = new MethodReference("SetPrimitiveValue", Types.VoidReference, RealmObjectBase) { HasThis = true }; + var T = new GenericParameter(RealmObject_SetPrimitiveValue); + RealmObject_SetPrimitiveValue.GenericParameters.Add(T); + RealmObject_SetPrimitiveValue.Parameters.Add(new ParameterDefinition(Types.StringReference)); + RealmObject_SetPrimitiveValue.Parameters.Add(new ParameterDefinition(T)); + RealmObject_SetPrimitiveValue.Parameters.Add(new ParameterDefinition(RealmSchema_PropertyType)); + } + + { + RealmObject_SetPrimitiveValueUnique = new MethodReference("SetPrimitiveValueUnique", Types.VoidReference, RealmObjectBase) { HasThis = true }; + var T = new GenericParameter(RealmObject_SetPrimitiveValueUnique); + RealmObject_SetPrimitiveValueUnique.GenericParameters.Add(T); + RealmObject_SetPrimitiveValueUnique.Parameters.Add(new ParameterDefinition(Types.StringReference)); + RealmObject_SetPrimitiveValueUnique.Parameters.Add(new ParameterDefinition(T)); + RealmObject_SetPrimitiveValueUnique.Parameters.Add(new ParameterDefinition(RealmSchema_PropertyType)); + } + IRealmObjectHelper = new TypeReference("Realms.Weaving", "IRealmObjectHelper", Module, realmAssembly); PreserveAttribute = new TypeReference("Realms", "PreserveAttribute", Module, realmAssembly); @@ -373,6 +404,11 @@ protected AssemblyNameReference GetOrAddFrameworkReference(string assemblyName) return assembly; } + public FieldReference GetPropertyTypeField(string name) + { + return new FieldReference(name, RealmSchema_PropertyType, RealmSchema_PropertyType); + } + public GenericParameter GetRealmIntegerGenericParameter(IGenericParameterProvider owner) { var T = new GenericParameter(owner) diff --git a/Realm/Realm.Fody/ModuleWeaver.cs b/Realm/Realm.Fody/ModuleWeaver.cs index 28379cdbdb..6136511fcf 100644 --- a/Realm/Realm.Fody/ModuleWeaver.cs +++ b/Realm/Realm.Fody/ModuleWeaver.cs @@ -40,6 +40,9 @@ public partial class ModuleWeaver : Fody.BaseModuleWeaver internal const string SingleTypeName = "System.Single"; internal const string DoubleTypeName = "System.Double"; internal const string BooleanTypeName = "System.Boolean"; + internal const string DecimalTypeName = "System.Decimal"; + internal const string Decimal128TypeName = "MongoDB.Bson.Decimal128"; + internal const string ObjectIdTypeName = "MongoDB.Bson.ObjectId"; internal const string DateTimeOffsetTypeName = "System.DateTimeOffset"; internal const string NullableCharTypeName = "System.Nullable`1"; internal const string NullableByteTypeName = "System.Nullable`1"; @@ -49,22 +52,35 @@ public partial class ModuleWeaver : Fody.BaseModuleWeaver internal const string NullableSingleTypeName = "System.Nullable`1"; internal const string NullableDoubleTypeName = "System.Nullable`1"; internal const string NullableBooleanTypeName = "System.Nullable`1"; + internal const string NullableDecimalTypeName = "System.Nullable`1"; + internal const string NullableDecimal128TypeName = "System.Nullable`1"; internal const string NullableDateTimeOffsetTypeName = "System.Nullable`1"; + internal const string NullableObjectIdTypeName = "System.Nullable`1"; private static readonly Dictionary _typeTable = new Dictionary { { StringTypeName, "String" }, - { CharTypeName, "Char" }, - { SingleTypeName, "Single" }, - { DoubleTypeName, "Double" }, - { BooleanTypeName, "Boolean" }, - { DateTimeOffsetTypeName, "DateTimeOffset" }, { ByteArrayTypeName, "ByteArray" }, - { NullableCharTypeName, "NullableChar" }, - { NullableSingleTypeName, "NullableSingle" }, + }; + + private static readonly Dictionary _primitiveValueTypes = new Dictionary + { + { CharTypeName, "Int" }, + { SingleTypeName, "Float" }, + { DoubleTypeName, "Double" }, + { BooleanTypeName, "Bool" }, + { DecimalTypeName, "Decimal" }, + { Decimal128TypeName, "Decimal" }, + { ObjectIdTypeName, "ObjectId" }, + { DateTimeOffsetTypeName, "Date" }, + { NullableCharTypeName, "NullableInt" }, + { NullableSingleTypeName, "NullableFloat" }, { NullableDoubleTypeName, "NullableDouble" }, - { NullableBooleanTypeName, "NullableBoolean" }, - { NullableDateTimeOffsetTypeName, "NullableDateTimeOffset" } + { NullableBooleanTypeName, "NullableBool" }, + { NullableDateTimeOffsetTypeName, "NullableDate" }, + { NullableDecimalTypeName, "NullableDecimal" }, + { NullableDecimal128TypeName, "NullableDecimal" }, + { NullableObjectIdTypeName, "NullableObjectId" }, }; private static readonly IEnumerable _realmIntegerBackedTypes = new[] @@ -95,11 +111,13 @@ public partial class ModuleWeaver : Fody.BaseModuleWeaver Int16TypeName, Int32TypeName, Int64TypeName, + ObjectIdTypeName, NullableCharTypeName, NullableByteTypeName, NullableInt16TypeName, NullableInt32TypeName, NullableInt64TypeName, + NullableObjectIdTypeName, }; private static readonly HashSet RealmPropertyAttributes = new HashSet @@ -113,13 +131,13 @@ public partial class ModuleWeaver : Fody.BaseModuleWeaver private IEnumerable GetMatchingTypes() { - foreach (var type in ModuleDefinition.GetTypes().Where(t => t.IsDescendedFrom(_references.RealmObject))) + foreach (var type in ModuleDefinition.GetTypes().Where(t => t.IsDescendedFrom(_references.RealmObject) || t.IsDescendedFrom(_references.EmbeddedObject))) { if (type.CustomAttributes.Any(a => a.AttributeType.Name == "IgnoredAttribute")) { continue; } - else if (type.BaseType.IsSameAs(_references.RealmObject)) + else if (type.IsValidRealmObjectBaseInheritor(_references)) { yield return type; } @@ -256,6 +274,13 @@ private void WeaveType(TypeDefinition type, Dictionary method return; } + var pkProperty = persistedProperties.FirstOrDefault(p => p.IsPrimaryKey); + if (type.IsEmbeddedObjectInheritor(_references) && pkProperty != null) + { + WriteError($"Class {type.Name} is an EmbeddedObject but has a primary key {pkProperty.Property.Name} defined."); + return; + } + if (persistedProperties.Count(p => p.IsPrimaryKey) > 1) { WriteError($"Class {type.Name} has more than one property marked with [PrimaryKey]."); @@ -297,18 +322,18 @@ private WeaveResult WeaveProperty(PropertyDefinition prop, TypeDefinition type, var backingField = prop.GetBackingField(); var isIndexed = prop.CustomAttributes.Any(a => a.AttributeType.Name == "IndexedAttribute"); - if (isIndexed && !prop.IsIndexable()) + if (isIndexed && !prop.IsIndexable(_references)) { return WeaveResult.Error($"{type.Name}.{prop.Name} is marked as [Indexed] which is only allowed on integral types as well as string, bool and DateTimeOffset, not on {prop.PropertyType.FullName}."); } - var isPrimaryKey = prop.IsPrimaryKey(); + var isPrimaryKey = prop.IsPrimaryKey(_references); if (isPrimaryKey && (!_primaryKeyTypes.Contains(prop.PropertyType.FullName))) { return WeaveResult.Error($"{type.Name}.{prop.Name} is marked as [PrimaryKey] which is only allowed on integral and string types, not on {prop.PropertyType.FullName}."); } - var isRequired = prop.IsRequired(); + var isRequired = prop.IsRequired(_references); if (isRequired && !prop.IsIList(typeof(string)) && !prop.IsNullable() && @@ -320,9 +345,9 @@ private WeaveResult WeaveProperty(PropertyDefinition prop, TypeDefinition type, if (!prop.IsAutomatic()) { - if (prop.PropertyType.Resolve().BaseType.IsSameAs(_references.RealmObject)) + if (prop.ContainsRealmObject(_references) || prop.ContainsEmbeddedObject(_references)) { - return WeaveResult.Warning($"{type.Name}.{prop.Name} is not an automatic property but its type is a RealmObject which normally indicates a relationship."); + return WeaveResult.Warning($"{type.Name}.{prop.Name} is not an automatic property but its type is a RealmObject/EmbeddedObject which normally indicates a relationship."); } return WeaveResult.Skipped(); @@ -347,6 +372,30 @@ private WeaveResult WeaveProperty(PropertyDefinition prop, TypeDefinition type, ReplaceGetter(prop, columnName, realmAccessors.Getter); ReplaceSetter(prop, backingField, columnName, realmAccessors.Setter); } + else if (_primitiveValueTypes.TryGetValue(prop.PropertyType.FullName, out var propertyType)) + { + if (prop.SetMethod == null) + { + return WeaveResult.Skipped(); + } + + var suffix = isPrimaryKey ? "Unique" : string.Empty; + var typeId = $"{prop.PropertyType.FullName}{suffix}"; + + if (!methodTable.TryGetValue(typeId, out var accessors)) + { + var getter = new GenericInstanceMethod(_references.RealmObject_GetPrimitiveValue) { GenericArguments = { prop.PropertyType } }; + var setter = new GenericInstanceMethod(isPrimaryKey ? _references.RealmObject_SetPrimitiveValueUnique : _references.RealmObject_SetPrimitiveValue) + { + GenericArguments = { prop.PropertyType } + }; + methodTable[typeId] = accessors = new Accessors { Getter = getter, Setter = setter }; + } + + var propertyTypeRef = _references.GetPropertyTypeField(propertyType); + ReplaceGetter(prop, columnName, accessors.Getter, propertyTypeRef); + ReplaceSetter(prop, backingField, columnName, accessors.Setter, propertyTypeRef); + } else if (_realmIntegerBackedTypes.Contains(prop.PropertyType.FullName)) { // If the property is automatic but doesn't have a setter, we should still ignore it. @@ -375,7 +424,7 @@ private WeaveResult WeaveProperty(PropertyDefinition prop, TypeDefinition type, var typeId = $"{prefix}{integerType.FullName}{suffix}"; if (!methodTable.TryGetValue(typeId, out var accessors)) { - var genericGetter = new MethodReference($"Get{prefix}RealmIntegerValue", ModuleDefinition.TypeSystem.Void, _references.RealmObject) + var genericGetter = new MethodReference($"Get{prefix}RealmIntegerValue", ModuleDefinition.TypeSystem.Void, _references.RealmObjectBase) { HasThis = true, Parameters = { new ParameterDefinition(ModuleDefinition.TypeSystem.String) } @@ -392,7 +441,7 @@ private WeaveResult WeaveProperty(PropertyDefinition prop, TypeDefinition type, genericGetter.ReturnType = returnType; - var genericSetter = new MethodReference($"Set{prefix}RealmIntegerValue{suffix}", ModuleDefinition.TypeSystem.Void, _references.RealmObject) + var genericSetter = new MethodReference($"Set{prefix}RealmIntegerValue{suffix}", ModuleDefinition.TypeSystem.Void, _references.RealmObjectBase) { HasThis = true, Parameters = @@ -423,9 +472,10 @@ private WeaveResult WeaveProperty(PropertyDefinition prop, TypeDefinition type, else if (prop.IsIList()) { var elementType = ((GenericInstanceType)prop.PropertyType).GenericArguments.Single(); - if (!elementType.Resolve().BaseType.IsSameAs(_references.RealmObject) && + if (!elementType.Resolve().IsValidRealmObjectBaseInheritor(_references) && !_realmIntegerBackedTypes.Contains(elementType.FullName) && - !_typeTable.ContainsKey(elementType.FullName)) + !_typeTable.ContainsKey(elementType.FullName) && + !_primitiveValueTypes.ContainsKey(elementType.FullName)) { return WeaveResult.Error($"{type.Name}.{prop.Name} is an IList but its generic type is {elementType.Name} which is not supported by Realm."); } @@ -447,7 +497,7 @@ private WeaveResult WeaveProperty(PropertyDefinition prop, TypeDefinition type, new GenericInstanceMethod(_references.RealmObject_GetListValue) { GenericArguments = { elementType } }, concreteListConstructor); } - else if (prop.PropertyType.Resolve().BaseType.IsSameAs(_references.RealmObject)) + else if (prop.ContainsRealmObject(_references) || prop.ContainsEmbeddedObject(_references)) { // with casting in the _realmObject methods, should just work ReplaceGetter(prop, columnName, @@ -526,12 +576,13 @@ private Accessors GetAccessors(TypeReference backingType, bool isPrimaryKey, IDi if (!methodTable.TryGetValue(typeId, out var realmAccessors)) { - var getter = new MethodReference($"Get{typeName}Value", backingType, _references.RealmObject) + var getter = new MethodReference($"Get{typeName}Value", backingType, _references.RealmObjectBase) { HasThis = true, - Parameters = { new ParameterDefinition(ModuleDefinition.TypeSystem.String) } + Parameters = { new ParameterDefinition(ModuleDefinition.TypeSystem.String) }, }; - var setter = new MethodReference($"Set{typeName}Value" + (isPrimaryKey ? "Unique" : string.Empty), ModuleDefinition.TypeSystem.Void, _references.RealmObject) + + var setter = new MethodReference($"Set{typeName}Value" + (isPrimaryKey ? "Unique" : string.Empty), ModuleDefinition.TypeSystem.Void, _references.RealmObjectBase) { HasThis = true, Parameters = @@ -547,7 +598,7 @@ private Accessors GetAccessors(TypeReference backingType, bool isPrimaryKey, IDi return realmAccessors; } - private void ReplaceGetter(PropertyDefinition prop, string columnName, MethodReference getValueReference) + private void ReplaceGetter(PropertyDefinition prop, string columnName, MethodReference getValueReference, FieldReference propertyTypeRef = null) { //// A synthesized property getter looks like this: //// 0: ldarg.0 @@ -576,6 +627,11 @@ private void ReplaceGetter(PropertyDefinition prop, string columnName, MethodRef il.InsertBefore(start, il.Create(OpCodes.Brfalse_S, start)); il.InsertBefore(start, il.Create(OpCodes.Ldarg_0)); // this for call il.InsertBefore(start, il.Create(OpCodes.Ldstr, columnName)); // [stack = this | name ] + if (propertyTypeRef != null) + { + il.InsertBefore(start, il.Create(OpCodes.Ldc_I4, (int)(byte)propertyTypeRef.Resolve().Constant)); + } + il.InsertBefore(start, il.Create(OpCodes.Call, getValueReference)); il.InsertBefore(start, il.Create(OpCodes.Ret)); @@ -767,7 +823,7 @@ private void ReplaceBacklinksGetter(PropertyDefinition prop, FieldReference back Debug.Write("[get list] "); } - private void ReplaceSetter(PropertyDefinition prop, FieldReference backingField, string columnName, MethodReference setValueReference) + private void ReplaceSetter(PropertyDefinition prop, FieldReference backingField, string columnName, MethodReference setValueReference, FieldReference propertyTypeRef = null) { //// A synthesized property setter looks like this: //// 0: ldarg.0 @@ -829,6 +885,11 @@ private void ReplaceSetter(PropertyDefinition prop, FieldReference backingField, il.Append(managedSetStart); il.Append(il.Create(OpCodes.Ldstr, columnName)); il.Append(il.Create(OpCodes.Ldarg_1)); + if (propertyTypeRef != null) + { + il.Append(il.Create(OpCodes.Ldc_I4, (int)(byte)propertyTypeRef.Resolve().Constant)); + } + il.Append(il.Create(OpCodes.Call, setValueReference)); il.Append(il.Create(OpCodes.Ret)); @@ -926,7 +987,7 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me helperType.Interfaces.Add(new InterfaceImplementation(_references.IRealmObjectHelper)); - var createInstance = new MethodDefinition("CreateInstance", DefaultMethodAttributes, _references.RealmObject); + var createInstance = new MethodDefinition("CreateInstance", DefaultMethodAttributes, _references.RealmObjectBase); { var il = createInstance.Body.GetILProcessor(); il.Emit(OpCodes.Newobj, objectConstructor); @@ -972,7 +1033,7 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me } */ - var instanceParameter = new ParameterDefinition("instance", ParameterAttributes.None, _references.RealmObject); + var instanceParameter = new ParameterDefinition("instance", ParameterAttributes.None, _references.RealmObjectBase); copyToRealm.Parameters.Add(instanceParameter); var updateParameter = new ParameterDefinition("update", ParameterAttributes.None, ModuleDefinition.TypeSystem.Boolean); @@ -984,11 +1045,6 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me copyToRealm.Body.Variables.Add(new VariableDefinition(realmObjectType)); byte currentStloc = 1; - if (properties.Any(p => p.Property.IsDateTimeOffset())) - { - copyToRealm.Body.Variables.Add(new VariableDefinition(_references.System_DateTimeOffset)); - currentStloc++; - } foreach (var prop in properties.Where(p => p.Property.IsIList())) { @@ -1001,7 +1057,7 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me il.Append(il.Create(OpCodes.Castclass, ModuleDefinition.ImportReference(realmObjectType))); il.Append(il.Create(OpCodes.Stloc_0)); - foreach (var prop in properties.Where(p => !p.Property.IsPrimaryKey())) + foreach (var prop in properties.Where(p => !p.Property.IsPrimaryKey(_references))) { var property = prop.Property; var field = prop.Field; @@ -1020,9 +1076,12 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me // We can skip setting properties that have their default values unless: var shouldSetAlways = property.IsNullable() || // The property is nullable - those should be set explicitly to null - property.IsRequired() || // Needed for validating that the property is not null (string) + property.IsRequired(_references) || // Needed for validating that the property is not null (string) property.IsDateTimeOffset() || // Core's DateTimeOffset property defaults to 1970-1-1, so we should override - property.PropertyType.IsRealmInteger(out _, out _); // structs are not implicitly falsy/truthy so the IL is significantly different; we can optimize this case in the future + property.PropertyType.IsRealmInteger(out _, out _) || // structs are not implicitly falsy/truthy so the IL is significantly different; we can optimize this case in the future + property.IsDecimal() || + property.IsDecimal128() || + property.IsObjectId(); // If the property is non-nullable, we want the following code to execute: // if (!skipDefaults || castInstance.field != default(fieldType)) @@ -1034,7 +1093,7 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me // property setting logic. The default check branching instruction is inserted above the *setStartPoint* // instruction later on. Instruction skipDefaultsPlaceholder = null; - if (property.IsDescendantOf(_references.RealmObject)) + if (property.ContainsRealmObject(_references)) { il.Append(il.Create(OpCodes.Ldloc_0)); il.Append(il.Create(OpCodes.Ldfld, field)); @@ -1078,7 +1137,7 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me var setEndPoint = il.Create(OpCodes.Nop); il.Append(setEndPoint); - if (property.IsDescendantOf(_references.RealmObject)) + if (property.ContainsRealmObject(_references)) { if (addPlaceholder != null) { @@ -1154,7 +1213,7 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me var cycleStart = il.Create(OpCodes.Ldloc_0); il.Append(cycleStart); - if (elementType.Resolve().BaseType.IsSameAs(_references.RealmObject)) + if (elementType.Resolve().IsRealmObjectInheritor(_references)) { // castInstance.Realm.Add(list[i], update) il.Append(il.Create(OpCodes.Call, _references.RealmObject_get_Realm)); diff --git a/Realm/Realm/Configurations/FullSyncConfiguration.cs b/Realm/Realm/Configurations/FullSyncConfiguration.cs deleted file mode 100644 index 2b70e74335..0000000000 --- a/Realm/Realm/Configurations/FullSyncConfiguration.cs +++ /dev/null @@ -1,61 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; - -namespace Realms.Sync -{ - /// - /// A is used to setup a that can be synchronized - /// in "full" mode between devices using the Realm Object Server. The entirety of the Realm will be kept - /// in sync between the server and the client. - /// - /// - /// Full Synchronization docs. - /// - /// - public class FullSyncConfiguration : SyncConfigurationBase - { - internal override bool IsFullSync => true; - - internal override ClientResyncMode ResyncMode => ClientResyncMode; - - /// - /// Gets or sets a value controlling the behavior in case of a Client Resync. Default is . - /// - public ClientResyncMode ClientResyncMode { get; set; } = ClientResyncMode.RecoverLocalRealm; - - /// - /// Initializes a new instance of the class. - /// - /// - /// A unique that identifies the Realm. In URIs, ~ can be used as a placeholder for a user Id. - /// If a relative Uri is provided, it will be resolved using the user's as baseUri. - /// - /// - /// A valid . If not provided, the currently logged-in user will be used. - /// - /// - /// Path to the realm, must be a valid full path for the current platform, relative subdirectory, or just filename. - /// - public FullSyncConfiguration(Uri serverUri, User user = null, string optionalPath = null) - : base(serverUri, user, optionalPath) - { - } - } -} diff --git a/Realm/Realm/Configurations/QueryBasedSyncConfiguration.cs b/Realm/Realm/Configurations/QueryBasedSyncConfiguration.cs deleted file mode 100644 index d716e0f52c..0000000000 --- a/Realm/Realm/Configurations/QueryBasedSyncConfiguration.cs +++ /dev/null @@ -1,84 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Threading; -using System.Threading.Tasks; -using Realms.Schema; - -namespace Realms.Sync -{ - /// - /// A is used to setup a that can be synchronized - /// in "query-based" mode between devices using the Realm Object Server. Only objects that match the subscribed - /// queries will be synchronized to the client. - /// - /// - /// Query-based Synchronization docs. - /// - /// - /// - public class QueryBasedSyncConfiguration : SyncConfigurationBase - { - internal override bool IsFullSync => false; - - internal override ClientResyncMode ResyncMode => ClientResyncMode.Manual; - - internal static readonly Type[] _queryBasedPermissionTypes = - { - typeof(ClassPermission), - typeof(Permission), - typeof(PermissionRole), - typeof(PermissionUser), - typeof(RealmPermission), - typeof(NamedSubscription) - }; - - /// - /// Initializes a new instance of the class. - /// - /// - /// A unique that identifies the Realm. In URIs, ~ can be used as a placeholder for a user Id. - /// If a relative Uri is provided, it will be resolved using the user's as baseUri. - /// If null is passed, a Uri will be constructed from the user's , combined with - /// /default. - /// - /// - /// A valid . If not provided, the currently logged-in user will be used. - /// - /// - /// Path to the realm, must be a valid full path for the current platform, relative subdirectory, or just filename. - /// - public QueryBasedSyncConfiguration(Uri serverUri = null, User user = null, string optionalPath = null) - : base(serverUri ?? new Uri("/default", UriKind.Relative), user, optionalPath) - { - } - - internal override Task CreateRealmAsync(RealmSchema schema, CancellationToken cancellationToken) - { - schema = RealmSchema.CreateSchemaForClasses(_queryBasedPermissionTypes, schema); - return base.CreateRealmAsync(schema, cancellationToken); - } - - internal override Realm CreateRealm(RealmSchema schema) - { - schema = RealmSchema.CreateSchemaForClasses(_queryBasedPermissionTypes, schema); - return base.CreateRealm(schema); - } - } -} diff --git a/Realm/Realm/Configurations/RealmConfigurationBase.cs b/Realm/Realm/Configurations/RealmConfigurationBase.cs index 119b5bef3c..92207532bf 100644 --- a/Realm/Realm/Configurations/RealmConfigurationBase.cs +++ b/Realm/Realm/Configurations/RealmConfigurationBase.cs @@ -57,7 +57,7 @@ public abstract class RealmConfigurationBase /// Gets or sets the list of classes persisted in a Realm opened with this configuration. /// /// - /// Typically left null so by default all s will be able to be stored in all Realms. + /// Typically left null so by default all s and s will be able to be stored in all Realms. /// /// /// diff --git a/Realm/Realm/Configurations/SyncConfiguration.cs b/Realm/Realm/Configurations/SyncConfiguration.cs new file mode 100644 index 0000000000..52eabc2d98 --- /dev/null +++ b/Realm/Realm/Configurations/SyncConfiguration.cs @@ -0,0 +1,199 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2018 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Bson; +using Realms.Helpers; +using Realms.Schema; + +namespace Realms.Sync +{ + /// + /// A is used to setup a that can be synchronized between devices using MongoDB Realm. + /// + /// + public class SyncConfiguration : RealmConfigurationBase + { + /// + /// Gets the used to create this . + /// + /// The whose s will be synced. + public User User { get; } + + /// + /// Gets or sets a callback that is invoked when download progress is made when using . + /// This will only be invoked for the initial download of the Realm and will not be invoked as futher download + /// progress is made during the lifetime of the Realm. It is ignored when using + /// . + /// + public Action OnProgress { get; set; } + + /// + /// Gets the partition identifying the Realm this configuration is describing. + /// + /// The partition value for the Realm. + public object Partition { get; } + + internal SessionStopPolicy SessionStopPolicy { get; set; } = SessionStopPolicy.AfterChangesUploaded; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The partition identifying the remote Realm that will be synchronized. + /// + /// + /// A valid . If not provided, the currently logged-in user will be used. + /// + /// + /// Path to the realm, must be a valid full path for the current platform, relative subdirectory, or just filename. + /// + [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Arguments are validated in the private ctor.")] + public SyncConfiguration(string partition, User user, string optionalPath = null) + : this((object)partition, user, optionalPath) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The partition identifying the remote Realm that will be synchronized. + /// + /// + /// A valid . If not provided, the currently logged-in user will be used. + /// + /// + /// Path to the realm, must be a valid full path for the current platform, relative subdirectory, or just filename. + /// + [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Arguments are validated in the private ctor.")] + public SyncConfiguration(long? partition, User user, string optionalPath = null) + : this((object)partition, user, optionalPath) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The partition identifying the remote Realm that will be synchronized. + /// + /// + /// A valid . If not provided, the currently logged-in user will be used. + /// + /// + /// Path to the realm, must be a valid full path for the current platform, relative subdirectory, or just filename. + /// + [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Arguments are validated in the private ctor.")] + public SyncConfiguration(ObjectId? partition, User user, string optionalPath = null) + : this((object)partition, user, optionalPath) + { + } + + private SyncConfiguration(object partition, User user, string path) + { + Argument.NotNull(user, nameof(user)); + + User = user; + Partition = partition; + DatabasePath = GetPathToRealm(path ?? user.App.Handle.GetRealmPath(User, partition.ToNativeJson())); + } + + internal override Realm CreateRealm(RealmSchema schema) + { + var configuration = CreateConfiguration(); + + var srHandle = SharedRealmHandle.OpenWithSync(configuration, ToNative(), schema, EncryptionKey); + if (IsDynamic && !schema.Any()) + { + srHandle.GetSchema(nativeSchema => schema = RealmSchema.CreateFromObjectStoreSchema(nativeSchema)); + } + + return new Realm(srHandle, this, schema); + } + + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The Realm instance will own its handle")] + internal override async Task CreateRealmAsync(RealmSchema schema, CancellationToken cancellationToken) + { + var configuration = CreateConfiguration(); + + var tcs = new TaskCompletionSource(); + var tcsHandle = GCHandle.Alloc(tcs); + ProgressNotificationToken progressToken = null; + try + { + using var handle = SharedRealmHandle.OpenWithSyncAsync(configuration, ToNative(), schema, EncryptionKey, tcsHandle); + cancellationToken.Register(() => + { + if (!handle.IsClosed) + { + handle.Cancel(); + tcs.TrySetCanceled(); + } + }); + + if (OnProgress != null) + { + progressToken = new ProgressNotificationToken( + observer: (progress) => + { + OnProgress(progress); + }, + register: handle.RegisterProgressNotifier, + unregister: (token) => + { + if (!handle.IsClosed) + { + handle.UnregisterProgressNotifier(token); + } + }); + } + + using var realmReference = await tcs.Task; + var realmPtr = SharedRealmHandle.ResolveFromReference(realmReference); + var sharedRealmHandle = new SharedRealmHandle(realmPtr); + if (IsDynamic && !schema.Any()) + { + sharedRealmHandle.GetSchema(nativeSchema => schema = RealmSchema.CreateFromObjectStoreSchema(nativeSchema)); + } + + return new Realm(sharedRealmHandle, this, schema); + } + finally + { + tcsHandle.Free(); + progressToken?.Dispose(); + } + } + + internal Native.SyncConfiguration ToNative() + { + return new Native.SyncConfiguration + { + SyncUserHandle = User.Handle, + Partition = Partition.ToNativeJson(), + session_stop_policy = SessionStopPolicy, + }; + } + } +} diff --git a/Realm/Realm/Configurations/SyncConfigurationBase.cs b/Realm/Realm/Configurations/SyncConfigurationBase.cs deleted file mode 100644 index 07b63d1cd7..0000000000 --- a/Realm/Realm/Configurations/SyncConfigurationBase.cs +++ /dev/null @@ -1,312 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using Realms.Helpers; -using Realms.Schema; - -namespace Realms.Sync -{ - /// - /// A is used to setup a that can be synchronized between devices using the - /// Realm Object Server. - /// - /// - /// - /// - /// - public abstract class SyncConfigurationBase : RealmConfigurationBase - { - internal abstract bool IsFullSync { get; } - - /// - /// Gets the used to create this . - /// - /// The where the Realm Object Server is hosted. - public Uri ServerUri { get; } - - /// - /// Gets the used to create this . - /// - /// The whose s will be synced. - public User User { get; } - - /// - /// Gets or sets a value indicating whether SSL certificate validation is enabled for the connection associated - /// with this configuration value. - /// - /// true if SSL validation is enabled; otherwise, false. Default value is true. - public bool EnableSSLValidation { get; set; } = true; - - /// - /// Gets or sets the path to the trusted root certificate(s) authority (CA) in PEM format, that should - /// be used to validate the TLS connections to the Realm Object Server. - /// - /// The path to the certificate. - /// - /// The file will be copied at runtime into the internal storage. - ///
- /// It is recommended to include only the root CA you trust, and not the entire list of root CA as this file - /// will be loaded at runtime. It is your responsibility to download and verify the correct PEM for the root CA - /// you trust. - ///
- /// This property is ignored on Apple platforms - you should use the KeyChain API to install your certificate - /// instead. - ///
- /// - /// OpenSSL documentation for SSL_CTX_load_verify_locations. - /// - /// - /// Mozilla Included CA Certificate List - /// - public string TrustedCAPath { get; set; } - - /// - /// Gets or sets a callback that is invoked when download progress is made when using . - /// This will only be invoked for the initial download of the Realm and will not be invoked as futher download - /// progress is made during the lifetime of the Realm. It is ignored when using - /// . - /// - public Action OnProgress { get; set; } - - internal abstract ClientResyncMode ResyncMode { get; } - - /// - /// Gets or sets a value indicating how detailed the sync client's logs will be. - /// - public static LogLevel LogLevel - { - get => SharedRealmHandleExtensions.GetLogLevel(); - set => SharedRealmHandleExtensions.SetLogLevel(value); - } - - private static Action _customLogger; - - /// - /// Gets or sets a custom log function that will be invoked by Sync instead of writing - /// to the standard error. This must be set before using any of the sync API. - /// - /// - /// This callback will not be invoked in a thread-safe manner, so it's up to the implementor to ensure - /// that log messages arriving from multiple threads are processed without garbling the final output. - /// - /// The custom log function. - public static Action CustomLogger - { - get => _customLogger; - set - { - _customLogger = value; - SharedRealmHandleExtensions.InstallLogCallback(); - } - } - - private static string _userAgent; - - /// - /// Gets or sets a string identifying this application which is included in the User-Agent - /// header of sync connections. - /// - /// - /// 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. - /// - /// - /// The custom user agent that will be appended to the one generated by the SDK. - /// - public static string UserAgent - { - get => _userAgent; - set - { - Argument.NotNull(value, nameof(value)); - SharedRealmHandleExtensions.SetUserAgent(value); - _userAgent = value; - } - } - - internal SyncConfigurationBase(Uri serverUri, User user = null, string optionalPath = null) - { - Argument.NotNull(serverUri, nameof(serverUri)); - Argument.Ensure(user != null || User.AllLoggedIn.Length == 1, - "The user must be explicitly specified when the number of logged-in users is not 1.", - nameof(user)); - - User = user ?? User.Current; - if (!serverUri.IsAbsoluteUri) - { - ServerUri = User.GetUriForRealm(serverUri); - } - else - { - Argument.Ensure(serverUri.Scheme.StartsWith("realm"), "Unexpected protocol for server url. Expected realm:// or realms://.", nameof(serverUri)); - ServerUri = serverUri; - } - - DatabasePath = GetPathToRealm(optionalPath ?? SharedRealmHandleExtensions.GetRealmPath(User, ServerUri)); - } - - /// - /// Configures various parameters of the sync system, such as the way users are persisted or the base - /// path relative to which files will be saved. - /// - /// The user persistence mode. - /// The key to encrypt the persistent user store with. - /// If set to true reset the persistent user store on error. - /// The base folder relative to which Realm files will be stored. - /// - /// Users are persisted in a realm file within the application's sandbox. - /// - /// By default objects are persisted and are additionally protected with an encryption key stored - /// in the iOS Keychain when running on an iOS device (but not on a Simulator). - /// On Android users are persisted in plaintext, because the AndroidKeyStore API is only supported on API level 18 and up. - /// You might want to provide your own encryption key on Android or disable persistence for security reasons. - /// - /// - public static void Initialize(UserPersistenceMode mode, byte[] encryptionKey = null, bool resetOnError = false, string basePath = null) - { - if (mode == UserPersistenceMode.Encrypted && encryptionKey != null && encryptionKey.Length != 64) - { - throw new ArgumentException("The encryption key must be 64 bytes long", nameof(encryptionKey)); - } - - SharedRealmHandleExtensions.Configure(mode, encryptionKey, resetOnError, basePath); - } - - /// - /// Enable multiplexing multiple sync sessions over a single connection. - /// - /// - /// When having a lot of synchronized realms open, or when using - /// the system might run out of file descriptors because of all the open sockets to the server. - /// Session multiplexing is designed to alleviate that, but it might not work with a server configured with fail-over. - /// Only use if you're seeing errors about reaching the file descriptor limit and you know you are using many sync sessions. - /// - /// Only call this method before opening any synchronized realms. - /// - /// - public static void EnableSessionMultiplexing() - { - SharedRealmHandleExtensions.EnableSessionMultiplexing(); - } - - internal override Realm CreateRealm(RealmSchema schema) - { - var configuration = CreateConfiguration(); - - var srHandle = SharedRealmHandleExtensions.OpenWithSync(configuration, ToNative(), schema, EncryptionKey); - if (IsDynamic && !schema.Any()) - { - srHandle.GetSchema(nativeSchema => schema = RealmSchema.CreateFromObjectStoreSchema(nativeSchema)); - } - - return new Realm(srHandle, this, schema); - } - - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The Realm instance will own its handle")] - internal override async Task CreateRealmAsync(RealmSchema schema, CancellationToken cancellationToken) - { - var configuration = CreateConfiguration(); - - var tcs = new TaskCompletionSource(); - var tcsHandle = GCHandle.Alloc(tcs); - ProgressNotificationToken progressToken = null; - try - { - using (var handle = SharedRealmHandleExtensions.OpenWithSyncAsync(configuration, ToNative(), schema, EncryptionKey, tcsHandle)) - { - cancellationToken.Register(() => - { - if (!handle.IsClosed) - { - handle.Cancel(); - tcs.TrySetCanceled(); - } - }); - - if (OnProgress != null) - { - progressToken = new ProgressNotificationToken( - observer: (progress) => - { - OnProgress(progress); - }, - register: handle.RegisterProgressNotifier, - unregister: (token) => - { - if (!handle.IsClosed) - { - handle.UnregisterProgressNotifier(token); - } - }); - } - - using (var realmReference = await tcs.Task) - { - var realmPtr = SharedRealmHandle.ResolveFromReference(realmReference); - var sharedRealmHandle = new SharedRealmHandle(realmPtr); - if (IsDynamic && !schema.Any()) - { - sharedRealmHandle.GetSchema(nativeSchema => schema = RealmSchema.CreateFromObjectStoreSchema(nativeSchema)); - } - - return new Realm(sharedRealmHandle, this, schema); - } - } - } - finally - { - tcsHandle.Free(); - progressToken?.Dispose(); - } - } - - internal Native.SyncConfiguration ToNative() - { - if (!string.IsNullOrEmpty(TrustedCAPath) && - !File.Exists(TrustedCAPath)) - { - throw new FileNotFoundException($"{nameof(TrustedCAPath)} has been specified, but the file was not found.", TrustedCAPath); - } - - return new Native.SyncConfiguration - { - SyncUserHandle = User.Handle, - Url = ServerUri.ToString(), - client_validate_ssl = EnableSSLValidation, - TrustedCAPath = TrustedCAPath, - is_partial = !IsFullSync, - PartialSyncIdentifier = null, - client_resync_mode = ResyncMode, - }; - } - - internal static string GetSDKUserAgent() - { - var version = typeof(SyncConfigurationBase).GetTypeInfo().Assembly.GetName().Version; - return $"RealmDotNet/{version} ({RuntimeInformation.FrameworkDescription})"; - } - } -} diff --git a/Realm/Realm/DataBinding/TypeInfoHelper.cs b/Realm/Realm/DataBinding/TypeInfoHelper.cs index f492771a0d..ee20f52755 100644 --- a/Realm/Realm/DataBinding/TypeInfoHelper.cs +++ b/Realm/Realm/DataBinding/TypeInfoHelper.cs @@ -28,7 +28,7 @@ internal static class TypeInfoHelper // Holds Type -> RealmObjectTypeInfo map to avoid creating a new TypeDelegator for each IReflectableType.GetTypeInfo invocation. private static readonly ConcurrentDictionary TypeCache = new ConcurrentDictionary(); - public static TypeInfo GetInfo(RealmObject obj) + public static TypeInfo GetInfo(RealmObjectBase obj) { Argument.NotNull(obj, nameof(obj)); return TypeCache.GetOrAdd(obj.GetType(), t => new RealmObjectTypeDelegator(t)); diff --git a/Realm/Realm/DataBinding/WovenGetterMethodInfo.cs b/Realm/Realm/DataBinding/WovenGetterMethodInfo.cs index 5e4e5085c6..f6bfa5d024 100644 --- a/Realm/Realm/DataBinding/WovenGetterMethodInfo.cs +++ b/Realm/Realm/DataBinding/WovenGetterMethodInfo.cs @@ -59,7 +59,7 @@ public WovenGetterMethodInfo(MethodInfo mi) public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) { - var ro = obj as RealmObject; + var ro = obj as RealmObjectBase; if (ro == null || ro.IsValid) { return _mi.Invoke(obj, invokeAttr, binder, parameters, culture); diff --git a/Realm/Realm/DataBinding/WovenSetterMethodInfo.cs b/Realm/Realm/DataBinding/WovenSetterMethodInfo.cs index dfab545fac..9e87490e74 100644 --- a/Realm/Realm/DataBinding/WovenSetterMethodInfo.cs +++ b/Realm/Realm/DataBinding/WovenSetterMethodInfo.cs @@ -59,7 +59,7 @@ public WovenSetterMethodInfo(MethodInfo mi) public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) { - var managingRealm = (obj as RealmObject)?.Realm; + var managingRealm = (obj as RealmObjectBase)?.Realm; Transaction writeTransaction = null; if (managingRealm != null && !managingRealm.IsInTransaction) { diff --git a/Tests/Realm.Tests/Sync/Constants.cs b/Realm/Realm/Dynamic/DynamicEmbeddedObject.cs similarity index 65% rename from Tests/Realm.Tests/Sync/Constants.cs rename to Realm/Realm/Dynamic/DynamicEmbeddedObject.cs index b23712fe65..3b404b31a6 100644 --- a/Tests/Realm.Tests/Sync/Constants.cs +++ b/Realm/Realm/Dynamic/DynamicEmbeddedObject.cs @@ -16,15 +16,19 @@ // //////////////////////////////////////////////////////////////////////////// -namespace Realms.Tests.Sync +using System.ComponentModel; +using System.Dynamic; +using System.Linq.Expressions; + +namespace Realms.Dynamic { - public static class Constants + [EditorBrowsable(EditorBrowsableState.Never)] + [Ignored] + public class DynamicEmbeddedObject : EmbeddedObject, IDynamicMetaObjectProvider { - public const string AdminUsername = "realm-admin"; - public const string AdminPassword = ""; - - public static string RosUrl; - public static string RosPort; - public static string RosSecurePort; + public DynamicMetaObject GetMetaObject(Expression parameter) + { + return new MetaRealmObject(parameter, this); + } } } \ No newline at end of file diff --git a/Realm/Realm/Dynamic/DynamicRealmObjectHelper.cs b/Realm/Realm/Dynamic/DynamicRealmObjectHelper.cs index 73385d40c2..c2b8a3c954 100644 --- a/Realm/Realm/Dynamic/DynamicRealmObjectHelper.cs +++ b/Realm/Realm/Dynamic/DynamicRealmObjectHelper.cs @@ -23,15 +23,30 @@ namespace Realms.Dynamic { internal class DynamicRealmObjectHelper : IRealmObjectHelper { - internal static readonly DynamicRealmObjectHelper Instance = new DynamicRealmObjectHelper(); + private static readonly DynamicRealmObjectHelper _embeddedInstance = new DynamicRealmObjectHelper(embedded: true); + private static readonly DynamicRealmObjectHelper _objectInstance = new DynamicRealmObjectHelper(embedded: false); - public void CopyToRealm(RealmObject instance, bool update, bool setPrimaryKey) + private readonly bool _embedded; + + internal static DynamicRealmObjectHelper Instance(bool embedded) => embedded ? _embeddedInstance : _objectInstance; + + private DynamicRealmObjectHelper(bool embedded) + { + _embedded = embedded; + } + + public void CopyToRealm(RealmObjectBase instance, bool update, bool setPrimaryKey) { throw new NotSupportedException("DynamicRealmObjectHelper cannot exist in unmanaged state, so CopyToRealm should not be called ever."); } - public RealmObject CreateInstance() + public RealmObjectBase CreateInstance() { + if (_embedded) + { + return new DynamicEmbeddedObject(); + } + return new DynamicRealmObject(); } diff --git a/Realm/Realm/Dynamic/MetaRealmList.cs b/Realm/Realm/Dynamic/MetaRealmList.cs index c2778c09ad..d49fc2d86a 100644 --- a/Realm/Realm/Dynamic/MetaRealmList.cs +++ b/Realm/Realm/Dynamic/MetaRealmList.cs @@ -16,10 +16,10 @@ // //////////////////////////////////////////////////////////////////////////// +using System; using System.Dynamic; using System.Linq; using System.Linq.Expressions; -using System.Reflection; namespace Realms.Dynamic { @@ -46,5 +46,31 @@ public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMet return new DynamicMetaObject(expression, BindingRestrictions.GetTypeRestriction(Expression, LimitType)); } + + public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) + { + if (Value is IRealmCollection) + { + throw new NotSupportedException("Can't set embedded objects directly. Instead use Realm.DynamicApi.SetEmbeddedObjectInList."); + } + + return base.BindSetIndex(binder, indexes, value); + } + + public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) + { + if (Value is IRealmCollection) + { + switch (binder.Name) + { + case "Add": + throw new NotSupportedException("Can't add embedded objects directly. Instead use Realm.DynamicApi.AddEmbeddedObjectToList."); + case "Insert": + throw new NotSupportedException("Can't insert embedded objects directly. Instead use Realm.DynamicApi.InsertEmbeddedObjectInList."); + } + } + + return base.BindInvokeMember(binder, args); + } } } \ No newline at end of file diff --git a/Realm/Realm/Dynamic/MetaRealmObject.cs b/Realm/Realm/Dynamic/MetaRealmObject.cs index 96df08931f..beb7efb506 100644 --- a/Realm/Realm/Dynamic/MetaRealmObject.cs +++ b/Realm/Realm/Dynamic/MetaRealmObject.cs @@ -21,6 +21,8 @@ using System.Dynamic; using System.Linq.Expressions; using System.Reflection; +using Realms.Exceptions; +using Realms.Native; using Realms.Schema; namespace Realms.Dynamic @@ -30,16 +32,23 @@ internal class MetaRealmObject : DynamicMetaObject private const BindingFlags PrivateBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; private readonly Realm _realm; - private readonly RealmObject.Metadata _metadata; + private readonly RealmObjectBase.Metadata _metadata; - private static readonly FieldInfo RealmObjectRealmField = typeof(RealmObject).GetField("_realm", PrivateBindingFlags); - private static readonly FieldInfo RealmObjectObjectHandleField = typeof(RealmObject).GetField("_objectHandle", PrivateBindingFlags); - private static readonly MethodInfo RealmObjectGetBacklinksForHandleMethod = typeof(RealmObject).GetMethod("GetBacklinksForHandle", PrivateBindingFlags) - .MakeGenericMethod(typeof(DynamicRealmObject)); + private static readonly FieldInfo RealmObjectRealmField = typeof(RealmObjectBase).GetField("_realm", PrivateBindingFlags); + private static readonly FieldInfo RealmObjectObjectHandleField = typeof(RealmObjectBase).GetField("_objectHandle", PrivateBindingFlags); + + private static readonly MethodInfo RealmObjectGetBacklinksForHandle_RealmObject = typeof(DynamicRealmObject).GetMethod("GetBacklinksForHandle", PrivateBindingFlags) + .MakeGenericMethod(typeof(DynamicRealmObject)); + + private static readonly MethodInfo RealmObjectGetBacklinksForHandle_EmbeddedObject = typeof(DynamicRealmObject).GetMethod("GetBacklinksForHandle", PrivateBindingFlags) + .MakeGenericMethod(typeof(DynamicEmbeddedObject)); + + private static readonly MethodInfo PrimitiveValueGetMethod = typeof(PrimitiveValue).GetMethod(nameof(PrimitiveValue.Get), BindingFlags.Public | BindingFlags.Instance); + private static readonly MethodInfo CreatePrimitiveMethod = typeof(PrimitiveValue).GetMethod(nameof(PrimitiveValue.Create), BindingFlags.Public | BindingFlags.Static); private static readonly ObjectHandle DummyHandle = new ObjectHandle(null, IntPtr.Zero); - public MetaRealmObject(Expression expression, DynamicRealmObject value) + public MetaRealmObject(Expression expression, RealmObjectBase value) : base(expression, BindingRestrictions.Empty, value) { _realm = value.Realm; @@ -53,15 +62,17 @@ public override DynamicMetaObject BindGetMember(GetMemberBinder binder) return base.BindGetMember(binder); } - var arguments = new List - { - Expression.Constant(_metadata.PropertyIndices[property.Name]) - }; - + var arguments = new List(); MethodInfo getter = null; - if (property.Type.IsArray()) + if (property.Type.UnderlyingType() == PropertyType.LinkingObjects) + { + arguments.Add(Expression.Constant(_metadata.PropertyIndices[property.Name])); + getter = GetGetMethod(DummyHandle.GetBacklinks); + } + else if (property.Type.IsArray()) { - arguments.Insert(0, Expression.Field(GetLimitedSelf(), RealmObjectRealmField)); + arguments.Add(Expression.Field(GetLimitedSelf(), RealmObjectRealmField)); + arguments.Add(Expression.Constant(_metadata.PropertyIndices[property.Name])); arguments.Add(Expression.Constant(property.ObjectType, typeof(string))); switch (property.Type.UnderlyingType()) { @@ -87,7 +98,7 @@ public override DynamicMetaObject BindGetMember(GetMemberBinder binder) } break; - case Schema.PropertyType.Float: + case PropertyType.Float: if (property.Type.IsNullable()) { getter = GetGetMethod(DummyHandle.GetList); @@ -127,85 +138,36 @@ public override DynamicMetaObject BindGetMember(GetMemberBinder binder) break; case PropertyType.Object: - getter = GetGetMethod(DummyHandle.GetList); - break; - case PropertyType.LinkingObjects: - // ObjectHandle.GetBacklinks has only one argument. - arguments.Clear(); - arguments.Add(Expression.Constant(_metadata.PropertyIndices[property.Name])); - getter = GetGetMethod(DummyHandle.GetBacklinks); + getter = IsTargetEmbedded(property) ? GetGetMethod(DummyHandle.GetList) : GetGetMethod(DummyHandle.GetList); break; } } else { + arguments.Add(Expression.Constant(_metadata.PropertyIndices[property.Name])); switch (property.Type.UnderlyingType()) { case PropertyType.Int: - if (property.Type.IsNullable()) - { - getter = GetGetMethod(DummyHandle.GetNullableInt64); - } - else - { - getter = GetGetMethod(DummyHandle.GetInt64); - } - - break; case PropertyType.Bool: - if (property.Type.IsNullable()) - { - getter = GetGetMethod(DummyHandle.GetNullableBoolean); - } - else - { - getter = GetGetMethod(DummyHandle.GetBoolean); - } - - break; - case Schema.PropertyType.Float: - if (property.Type.IsNullable()) - { - getter = GetGetMethod(DummyHandle.GetNullableSingle); - } - else - { - getter = GetGetMethod(DummyHandle.GetSingle); - } - - break; + case PropertyType.Float: case PropertyType.Double: - if (property.Type.IsNullable()) - { - getter = GetGetMethod(DummyHandle.GetNullableDouble); - } - else - { - getter = GetGetMethod(DummyHandle.GetDouble); - } - + case PropertyType.Date: + case PropertyType.Decimal: + case PropertyType.ObjectId: + arguments.Add(Expression.Constant(property.Type)); + getter = GetGetMethod(DummyHandle.GetPrimitive); break; case PropertyType.String: getter = GetGetMethod(DummyHandle.GetString); break; case PropertyType.Data: getter = GetGetMethod(DummyHandle.GetByteArray); - break; - case PropertyType.Date: - if (property.Type.IsNullable()) - { - getter = GetGetMethod(DummyHandle.GetNullableDateTimeOffset); - } - else - { - getter = GetGetMethod(DummyHandle.GetDateTimeOffset); - } - break; case PropertyType.Object: arguments.Insert(0, Expression.Field(GetLimitedSelf(), RealmObjectRealmField)); arguments.Add(Expression.Constant(property.ObjectType)); - getter = GetGetMethod(DummyHandle.GetObject); + + getter = IsTargetEmbedded(property) ? GetGetMethod(DummyHandle.GetObject) : GetGetMethod(DummyHandle.GetObject); break; } } @@ -216,7 +178,20 @@ public override DynamicMetaObject BindGetMember(GetMemberBinder binder) if (property.Type.UnderlyingType() == PropertyType.LinkingObjects) { - expression = Expression.Call(self, RealmObjectGetBacklinksForHandleMethod, Expression.Constant(binder.Name), expression); + var relatedMeta = _realm.Metadata[property.ObjectType]; + if (relatedMeta.Schema.IsEmbedded) + { + expression = Expression.Call(self, RealmObjectGetBacklinksForHandle_EmbeddedObject, Expression.Constant(binder.Name), expression); + } + else + { + expression = Expression.Call(self, RealmObjectGetBacklinksForHandle_RealmObject, Expression.Constant(binder.Name), expression); + } + } + + if (expression.Type == typeof(PrimitiveValue)) + { + expression = Expression.Call(expression, PrimitiveValueGetMethod.MakeGenericMethod(property.PropertyInfo.PropertyType)); } if (binder.ReturnType != expression.Type) @@ -224,7 +199,7 @@ public override DynamicMetaObject BindGetMember(GetMemberBinder binder) expression = Expression.Convert(expression, binder.ReturnType); } - var argumentShouldBeDynamicRealmObject = BindingRestrictions.GetTypeRestriction(Expression, typeof(DynamicRealmObject)); + var argumentShouldBeDynamicRealmObject = BindingRestrictions.GetTypeRestriction(Expression, _metadata.Schema.IsEmbedded ? typeof(DynamicEmbeddedObject) : typeof(DynamicRealmObject)); var argumentShouldBeInTheSameRealm = BindingRestrictions.GetInstanceRestriction(Expression.Field(self, RealmObjectRealmField), _realm); return new DynamicMetaObject(expression, argumentShouldBeDynamicRealmObject.Merge(argumentShouldBeInTheSameRealm)); } @@ -244,57 +219,26 @@ public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicM MethodInfo setter = null; Type argumentType = null; + var valueExpression = value.Expression; switch (property.Type.UnderlyingType()) { + // TODO: split these into individual cases to avoid calling the generic method case PropertyType.Int: - argumentType = typeof(long); - if (property.Type.IsNullable()) - { - setter = GetSetMethod(DummyHandle.SetNullableInt64); - } - else if (property.IsPrimaryKey) - { - setter = GetSetMethod(DummyHandle.SetInt64Unique); - } - else - { - setter = GetSetMethod(DummyHandle.SetInt64); - } - - break; case PropertyType.Bool: - argumentType = typeof(bool); - if (property.Type.IsNullable()) - { - setter = GetSetMethod(DummyHandle.SetNullableBoolean); - } - else - { - setter = GetSetMethod(DummyHandle.SetBoolean); - } - - break; case PropertyType.Float: - argumentType = typeof(float); - if (property.Type.IsNullable()) - { - setter = GetSetMethod(DummyHandle.SetNullableSingle); - } - else - { - setter = GetSetMethod(DummyHandle.SetSingle); - } - - break; case PropertyType.Double: - argumentType = typeof(double); - if (property.Type.IsNullable()) + case PropertyType.Date: + case PropertyType.Decimal: + case PropertyType.ObjectId: + argumentType = typeof(PrimitiveValue); + valueExpression = Expression.Call(CreatePrimitiveMethod.MakeGenericMethod(valueExpression.Type), new[] { valueExpression, Expression.Constant(property.Type) }); + if (property.IsPrimaryKey) { - setter = GetSetMethod(DummyHandle.SetNullableDouble); + setter = GetSetMethod(DummyHandle.SetPrimitiveUnique); } else { - setter = GetSetMethod(DummyHandle.SetDouble); + setter = GetSetMethod(DummyHandle.SetPrimitive); } break; @@ -313,32 +257,14 @@ public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicM case PropertyType.Data: argumentType = typeof(byte[]); setter = GetSetMethod(DummyHandle.SetByteArray); - break; - case PropertyType.Date: - argumentType = typeof(DateTimeOffset); - if (property.Type.IsNullable()) - { - setter = GetSetMethod(DummyHandle.SetNullableDateTimeOffset); - } - else - { - setter = GetSetMethod(DummyHandle.SetDateTimeOffset); - } - break; case PropertyType.Object: - argumentType = typeof(RealmObject); + argumentType = typeof(RealmObjectBase); arguments.Insert(0, Expression.Field(GetLimitedSelf(), RealmObjectRealmField)); - setter = GetSetMethod(DummyHandle.SetObject); + setter = GetSetMethod(DummyHandle.SetObject); break; } - if (property.Type.IsNullable() && argumentType.GetTypeInfo().IsValueType) - { - argumentType = typeof(Nullable<>).MakeGenericType(argumentType); - } - - var valueExpression = value.Expression; if (valueExpression.Type != argumentType) { valueExpression = Expression.Convert(valueExpression, argumentType); @@ -348,7 +274,7 @@ public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicM var expression = Expression.Block(Expression.Call(Expression.Field(GetLimitedSelf(), RealmObjectObjectHandleField), setter, arguments), Expression.Default(binder.ReturnType)); - var argumentShouldBeDynamicRealmObject = BindingRestrictions.GetTypeRestriction(Expression, typeof(DynamicRealmObject)); + var argumentShouldBeDynamicRealmObject = BindingRestrictions.GetTypeRestriction(Expression, _metadata.Schema.IsEmbedded ? typeof(DynamicEmbeddedObject) : typeof(DynamicRealmObject)); var argumentShouldBeInTheSameRealm = BindingRestrictions.GetInstanceRestriction(Expression.Field(GetLimitedSelf(), RealmObjectRealmField), _realm); return new DynamicMetaObject(expression, argumentShouldBeDynamicRealmObject.Merge(argumentShouldBeInTheSameRealm)); } @@ -369,12 +295,32 @@ private Expression GetLimitedSelf() return convertedExpression; } + private bool IsTargetEmbedded(Property property) + { + if (!_realm.Metadata.TryGetValue(property.ObjectType, out var metadata)) + { + throw new RealmException($"Couldn't find metadata for type {property.ObjectType}."); + } + + return metadata.Schema.IsEmbedded; + } + + // GetString(propertyIndex) + // GetByteArray(propertyIndex) + // GetBacklinks(propertyIndex) private static MethodInfo GetGetMethod(Func @delegate) => @delegate.GetMethodInfo(); - private static MethodInfo GetSetMethod(Action @delegate) => @delegate.GetMethodInfo(); + // GetPrimitive(propertyIndex, propertyType) + private static MethodInfo GetGetMethod(Func @delegate) => @delegate.GetMethodInfo(); + // GetList(realm, propertyIndex, objectType) + // GetObject(realm, propertyIndex, objectType) private static MethodInfo GetGetMethod(Func @delegate) => @delegate.GetMethodInfo(); + // SetXXX(propertyIndex) + private static MethodInfo GetSetMethod(Action @delegate) => @delegate.GetMethodInfo(); + + // SetObject(this, propertyIndex) private static MethodInfo GetSetMethod(Action @delegate) => @delegate.GetMethodInfo(); } } \ No newline at end of file diff --git a/Realm/Realm/Sync/Permissions/Recipient.cs b/Realm/Realm/EmbeddedObject.cs similarity index 65% rename from Realm/Realm/Sync/Permissions/Recipient.cs rename to Realm/Realm/EmbeddedObject.cs index e6d4df5171..cf08bf8f3f 100644 --- a/Realm/Realm/Sync/Permissions/Recipient.cs +++ b/Realm/Realm/EmbeddedObject.cs @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2017 Realm Inc. +// 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. @@ -16,26 +16,20 @@ // //////////////////////////////////////////////////////////////////////////// -namespace Realms.Sync +using System; + +namespace Realms { /// - /// The user affected by the permission change. + /// Base for any embedded object that can be persisted in a . /// - public enum Recipient + [Serializable] + public class EmbeddedObject : RealmObjectBase { /// - /// Any user - either current or someone else. - /// - Any, - - /// - /// The current user. - /// - CurrentUser, - - /// - /// User other than the current one. + /// Gets the parent of this . It can be either another + /// or a standalone . /// - OtherUser, + public RealmObjectBase Parent { get; } } } diff --git a/Realm/Realm/Exceptions/AppException.cs b/Realm/Realm/Exceptions/AppException.cs new file mode 100644 index 0000000000..0e4e0410e8 --- /dev/null +++ b/Realm/Realm/Exceptions/AppException.cs @@ -0,0 +1,51 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Net; +using Realms.Sync.Native; + +namespace Realms.Sync.Exceptions +{ + /// + /// An exception thrown from operations interacting with a MongoDB Realm app. + /// + public class AppException : Exception + { + /// + /// Gets the HTTP status code returned by the remote operation. + /// + /// The HTTP status code of the operation that failed or null if the error was not an http one. + public HttpStatusCode? StatusCode { get; } + + internal AppException(AppError appError) + : this($"{appError.ErrorCategory}: {appError.Message}", appError.LogsLink, appError.http_status_code) + { + } + + internal AppException(string message, string helpLink, int httpStatusCode) + : base(message) + { + HelpLink = helpLink; + if (httpStatusCode != 0) + { + StatusCode = (HttpStatusCode)httpStatusCode; + } + } + } +} diff --git a/Realm/Realm/Exceptions/ClientResetException.cs b/Realm/Realm/Exceptions/ClientResetException.cs index 51987ebbca..b8e1539e71 100644 --- a/Realm/Realm/Exceptions/ClientResetException.cs +++ b/Realm/Realm/Exceptions/ClientResetException.cs @@ -27,6 +27,7 @@ namespace Realms.Sync.Exceptions public class ClientResetException : SessionException { private readonly string _originalFilePath; + private readonly App _app; /// /// Gets the path where the backup copy of the realm will be placed once the client reset process is complete. @@ -34,11 +35,12 @@ public class ClientResetException : SessionException /// The path to the backup realm. public string BackupFilePath { get; } - internal ClientResetException(string message, IDictionary userInfo) + internal ClientResetException(App app, string message, IDictionary userInfo) : base(message, ErrorCode.DivergingHistories) { // Using Path.GetFullPath to normalize path separators on Windows _originalFilePath = Path.GetFullPath(userInfo[OriginalFilePathKey]); + _app = app; BackupFilePath = Path.GetFullPath(userInfo[BackupFilePathKey]); HelpLink = "https://realm.io/docs/xamarin/latest/#client-reset"; } @@ -53,7 +55,7 @@ internal ClientResetException(string message, IDictionary userIn /// public bool InitiateClientReset() { - return SharedRealmHandleExtensions.ImmediatelyRunFileActions(_originalFilePath); + return _app.Handle.ImmediatelyRunFileActions(_originalFilePath); } } } diff --git a/Realm/Realm/Exceptions/ErrorCode.cs b/Realm/Realm/Exceptions/ErrorCode.cs index 52663453e3..a7938bb946 100644 --- a/Realm/Realm/Exceptions/ErrorCode.cs +++ b/Realm/Realm/Exceptions/ErrorCode.cs @@ -98,6 +98,30 @@ public enum ErrorCode /// DisabledSession = 213, + /// + /// The client file is invalid. + /// + BadClientFile = 217, + + /// + /// The user for this session doesn't match the user who originally created the file. This can happen + /// if you explicitly specify the Realm file path in the configuration and you open the Realm first with + /// user A, then with user B without changing the on-disk path. + /// + UserMismatch = 223, + + /// + /// The server has received too many sessions from this client. This is typically a transient error + /// but can also indicate that the client has too many Realms open at the same time. + /// + TooManySessions = 224, + + /// + /// The client attempted to upload an invalid schema change - either an additive schema change + /// when developer mode is off or a destructive schema change. + /// + InvalidSchemaChange = 225, + /// /// Your request parameters did not validate. /// diff --git a/Realm/Realm/Exceptions/HttpException.cs b/Realm/Realm/Exceptions/HttpException.cs deleted file mode 100644 index 6d4d8f154d..0000000000 --- a/Realm/Realm/Exceptions/HttpException.cs +++ /dev/null @@ -1,63 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Net; - -using ErrorCodeEnum = Realms.Sync.Exceptions.ErrorCode; - -namespace Realms.Sync.Exceptions -{ - /// - /// An exception thrown when a transport error occurs during authentication. - /// - public class HttpException : Exception - { - /// - /// Gets the of the error. - /// - /// An enum value indicating the error code. - public ErrorCodeEnum? ErrorCode { get; } - - /// - /// Gets the of the response. - /// - /// A well known . - public HttpStatusCode StatusCode { get; } - - /// - /// Gets the Reason-Phrase of the HTTP response. - /// - /// The Reason-Phrase of the HTTP response. - public string ReasonPhrase { get; } - - /// - /// Gets the body of the HTTP response. - /// - /// The body of the HTTP response. - public string Payload { get; } - - internal HttpException(HttpStatusCode statusCode, string reasonPhrase, string payload, string message, ErrorCodeEnum errorCode = ErrorCodeEnum.Unknown) : base(message) - { - StatusCode = statusCode; - ReasonPhrase = reasonPhrase; - Payload = payload; - ErrorCode = errorCode; - } - } -} diff --git a/Realm/Realm/Exceptions/NotifierStartException.cs b/Realm/Realm/Exceptions/NotifierStartException.cs deleted file mode 100644 index c1e1fd8758..0000000000 --- a/Realm/Realm/Exceptions/NotifierStartException.cs +++ /dev/null @@ -1,39 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using Realms.Exceptions; - -namespace Realms.Server.Exceptions -{ - /// - /// An exception thrown while attempting to start the Notifier. - /// - public class NotifierStartException : RealmException - { - /// - /// Gets an integer value representing the code of the error. - /// - /// An integer error code. - public int ErrorCode { get; } - - internal NotifierStartException(int errorCode, string detailMessage) : base(detailMessage) - { - ErrorCode = errorCode; - } - } -} diff --git a/Realm/Realm/Exceptions/PermissionDeniedException.cs b/Realm/Realm/Exceptions/PermissionDeniedException.cs index 41e012593e..ad3aab4a11 100644 --- a/Realm/Realm/Exceptions/PermissionDeniedException.cs +++ b/Realm/Realm/Exceptions/PermissionDeniedException.cs @@ -42,13 +42,15 @@ namespace Realms.Sync.Exceptions public class PermissionDeniedException : SessionException { private readonly string _originalFilePath; + private readonly App _app; private bool _actionInvoked; - internal PermissionDeniedException(string message, IDictionary userInfo) + internal PermissionDeniedException(App app, string message, IDictionary userInfo) : base(message, ErrorCode.PermissionDenied) { _originalFilePath = userInfo[OriginalFilePathKey]; + _app = app; HelpLink = "https://realm.io/docs/xamarin/latest/#access-control"; } @@ -63,7 +65,7 @@ public bool DeleteRealmUserInfo() Argument.Ensure(!_actionInvoked, $"{nameof(DeleteRealmUserInfo)} can only be called once."); _actionInvoked = true; - return SharedRealmHandleExtensions.ImmediatelyRunFileActions(_originalFilePath); + return _app.Handle.ImmediatelyRunFileActions(_originalFilePath); } } } diff --git a/Realm/Realm/Exceptions/RealmAppException.cs b/Realm/Realm/Exceptions/RealmAppException.cs new file mode 100644 index 0000000000..6d81e765f8 --- /dev/null +++ b/Realm/Realm/Exceptions/RealmAppException.cs @@ -0,0 +1,52 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using Realms.Exceptions; + +namespace Realms.Sync.Exceptions +{ + /// + /// An exception being thrown when performing app-related operations. + /// + public class RealmAppException : RealmException + { + /// + /// Gets the category (type) of this exception. + /// + /// The exception category - for example Client Error, Http Error, etc. + public string Category { get; } + + internal RealmAppException(RealmExceptionCodes code, string detailMessage) : base(detailMessage) + { + Category = GetCategory(code); + } + + private static string GetCategory(RealmExceptionCodes code) + { + return code switch + { + RealmExceptionCodes.AppClientError => "Client Error", + RealmExceptionCodes.AppCustomError => "Custom Error", + RealmExceptionCodes.AppHttpError => "Http Error", + RealmExceptionCodes.AppJsonError => "Json Error", + RealmExceptionCodes.AppServiceError => "Service Error", + _ => "Unknown" + }; + } + } +} diff --git a/Realm/Realm/Exceptions/RealmException.cs b/Realm/Realm/Exceptions/RealmException.cs index 577308d5e0..be7fbe78c5 100644 --- a/Realm/Realm/Exceptions/RealmException.cs +++ b/Realm/Realm/Exceptions/RealmException.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using Realms.Sync.Exceptions; namespace Realms.Exceptions { @@ -105,6 +106,14 @@ internal static Exception Create(RealmExceptionCodes exceptionCode, string messa case RealmExceptionCodes.RealmClosed: return new RealmClosedException(message); + case RealmExceptionCodes.AppClientError: + case RealmExceptionCodes.AppCustomError: + case RealmExceptionCodes.AppHttpError: + case RealmExceptionCodes.AppJsonError: + case RealmExceptionCodes.AppServiceError: + case RealmExceptionCodes.AppUnknownError: + return new RealmAppException(exceptionCode, message); + case RealmExceptionCodes.StdArgumentOutOfRange: case RealmExceptionCodes.StdIndexOutOfRange: return new ArgumentOutOfRangeException(message); @@ -115,9 +124,6 @@ internal static Exception Create(RealmExceptionCodes exceptionCode, string messa case RealmExceptionCodes.ObjectManagedByAnotherRealm: return new RealmObjectManagedByAnotherRealmException(message); - case RealmExceptionCodes.RealmFeatureUnavailable: - return new RealmFeatureUnavailableException(message); - default: return new Exception(message); } diff --git a/Realm/Realm/Exceptions/RealmExceptionCodes.cs b/Realm/Realm/Exceptions/RealmExceptionCodes.cs index efd676aee5..6175983219 100644 --- a/Realm/Realm/Exceptions/RealmExceptionCodes.cs +++ b/Realm/Realm/Exceptions/RealmExceptionCodes.cs @@ -41,11 +41,16 @@ internal enum RealmExceptionCodes : sbyte RealmDuplicatePrimaryKeyValue = 23, RealmClosed = 24, ObjectManagedByAnotherRealm = 25, - RealmFeatureUnavailable = 26, - RealmIncompatibleSyncedFile = 27, RealmDotNetExceptionDuringMigration = 30, + AppClientError = 50, + AppCustomError = 51, + AppHttpError = 52, + AppJsonError = 53, + AppServiceError = 54, + AppUnknownError = 59, + StdArgumentOutOfRange = 100, StdIndexOutOfRange = 101, StdInvalidOperation = 102 diff --git a/Realm/Realm/Exceptions/RealmFeatureUnavailableException.cs b/Realm/Realm/Exceptions/RealmFeatureUnavailableException.cs deleted file mode 100644 index 1f8571c54b..0000000000 --- a/Realm/Realm/Exceptions/RealmFeatureUnavailableException.cs +++ /dev/null @@ -1,36 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Exceptions -{ - /// - /// An exception thrown when attempting to use a feature that is not available at your edition level. - /// If you're using a paid edition of the Realm Platform, make sure you call - /// SyncConfiguration.SetFeatureToken before any calls to . - /// - /// - /// See more details on Enabling Professional and Enterprise APIs in the documentation. - /// - public class RealmFeatureUnavailableException : RealmException - { - internal RealmFeatureUnavailableException(string message) : base(message) - { - HelpLink = "https://realm.io/docs/realm-object-server/pe-ee/#enabling-professional-and-enterprise-apis"; - } - } -} \ No newline at end of file diff --git a/Realm/Realm/Exceptions/RealmFrozenException.cs b/Realm/Realm/Exceptions/RealmFrozenException.cs index 06222690c4..9fcb7d7a1e 100644 --- a/Realm/Realm/Exceptions/RealmFrozenException.cs +++ b/Realm/Realm/Exceptions/RealmFrozenException.cs @@ -19,8 +19,8 @@ namespace Realms.Exceptions { /// - /// Exception thrown when trying to subscribe to changes or modify a frozen , , or - /// . + /// Exception thrown when trying to subscribe to changes or modify a frozen , , + /// , or . /// public class RealmFrozenException : RealmException { diff --git a/Realm/Realm/Exceptions/RealmObjectManagedByAnotherRealmException.cs b/Realm/Realm/Exceptions/RealmObjectManagedByAnotherRealmException.cs index c71203a7f3..ecda663bcc 100644 --- a/Realm/Realm/Exceptions/RealmObjectManagedByAnotherRealmException.cs +++ b/Realm/Realm/Exceptions/RealmObjectManagedByAnotherRealmException.cs @@ -19,7 +19,7 @@ namespace Realms.Exceptions { /// - /// Exception thrown when you're trying to use but the object is already managed by a + /// Exception thrown when you're trying to use but the object is already managed by a /// different . /// public class RealmObjectManagedByAnotherRealmException : RealmException diff --git a/Realm/Realm/Exceptions/SessionException.cs b/Realm/Realm/Exceptions/SessionException.cs index a1a61332d3..b4235c148d 100644 --- a/Realm/Realm/Exceptions/SessionException.cs +++ b/Realm/Realm/Exceptions/SessionException.cs @@ -37,7 +37,6 @@ public class SessionException : Exception internal SessionException(string message, ErrorCode errorCode, Exception innerException = null) : base(message, innerException) { ErrorCode = errorCode; - HelpLink = "https://realm.io/docs/realm-object-server/#session-specific-errors"; } } } diff --git a/Realm/Realm/Extensions/CollectionNotificationsExtensions.cs b/Realm/Realm/Extensions/CollectionNotificationsExtensions.cs index 46efb67ee1..fa31b2d7cc 100644 --- a/Realm/Realm/Extensions/CollectionNotificationsExtensions.cs +++ b/Realm/Realm/Extensions/CollectionNotificationsExtensions.cs @@ -36,11 +36,11 @@ public static class CollectionNotificationsExtensions /// implements . /// /// The to observe for changes. - /// Type of the in the results. + /// Type of the or in the results. /// /// The collection, implementing . public static IRealmCollection AsRealmCollection(this IQueryable query) - where T : RealmObject + where T : RealmObjectBase { Argument.NotNull(query, nameof(query)); @@ -56,7 +56,7 @@ public static IRealmCollection AsRealmCollection(this IQueryable query) /// A convenience method that casts to and subscribes for change notifications. /// /// The to observe for changes. - /// Type of the in the results. + /// Type of the or in the results. /// /// The callback to be invoked with the updated . /// @@ -64,7 +64,7 @@ public static IRealmCollection AsRealmCollection(this IQueryable query) /// To stop receiving notifications, call . /// public static IDisposable SubscribeForNotifications(this IQueryable results, NotificationCallbackDelegate callback) - where T : RealmObject + where T : RealmObjectBase { return results.AsRealmCollection().SubscribeForNotifications(callback); } diff --git a/Realm/Realm/Extensions/ErrorCodeExtensions.cs b/Realm/Realm/Extensions/ErrorCodeExtensions.cs deleted file mode 100644 index 6d87365b6d..0000000000 --- a/Realm/Realm/Extensions/ErrorCodeExtensions.cs +++ /dev/null @@ -1,49 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; - -namespace Realms.Sync.Exceptions -{ - /// - /// A set of extensions that simplify checking for common error scenarios. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public static class ErrorCodeExtensions - { - private static readonly IEnumerable ClientResetCodes = new[] - { - ErrorCode.BadServerFileIdentifier, - ErrorCode.BadClientFileIdentifier, - ErrorCode.BadServerVersion, - ErrorCode.DivergingHistories, - }; - - /// - /// Checks if an error code indicates that a client reset is needed. - /// - /// true, if the code indicates a client reset error, false otherwise. - /// The error code. - public static bool IsClientResetError(this ErrorCode code) - { - return ClientResetCodes.Contains(code); - } - } -} diff --git a/Realm/Realm/Extensions/FrozenObjectsExtensions.cs b/Realm/Realm/Extensions/FrozenObjectsExtensions.cs index ec6af2e716..a805f5cdd1 100644 --- a/Realm/Realm/Extensions/FrozenObjectsExtensions.cs +++ b/Realm/Realm/Extensions/FrozenObjectsExtensions.cs @@ -25,7 +25,7 @@ namespace Realms { /// - /// A set of extension methods on top of RealmObject. + /// A set of extension methods on top of RealmObjectBase. /// [EditorBrowsable(EditorBrowsableState.Never)] public static class FrozenObjectsExtensions @@ -33,7 +33,7 @@ public static class FrozenObjectsExtensions /// /// Returns a frozen snapshot of this object. The frozen copy can be read and queried from any thread without throwing an exception. /// - /// Freezing a RealmObject also creates a frozen Realm which has its own lifecycle, but if the live Realm that spawned the + /// Freezing a RealmObjectBase also creates a frozen Realm which has its own lifecycle, but if the live Realm that spawned the /// original object is fully closed (i.e. all instances across all threads are closed), the frozen Realm and /// object will be closed as well. /// @@ -43,12 +43,12 @@ public static class FrozenObjectsExtensions /// Note: Keeping a large number of frozen objects with different versions alive can have a negative impact on the filesize /// of the Realm. In order to avoid such a situation it is possible to set . /// - /// The instance that you want to create a frozen version of. - /// The type of the . + /// The or instance that you want to create a frozen version of. + /// The type of the /. /// A new frozen instance of the passed in object or the object itself if it was already frozen. - /// + /// public static T Freeze(this T realmObj) - where T : RealmObject + where T : RealmObjectBase { Argument.NotNull(realmObj, nameof(realmObj)); @@ -105,10 +105,10 @@ public static IList Freeze(this IList list) /// of the Realm. In order to avoid such a situation it is possible to set . /// /// The query you want to create a frozen copy of. - /// The type of the in the query. + /// The type of the / in the query. /// A frozen copy of this query. public static IQueryable Freeze(this IQueryable query) - where T : RealmObject + where T : RealmObjectBase { Argument.NotNull(query, nameof(query)); diff --git a/Realm/Realm/Extensions/RealmSyncExtensions.cs b/Realm/Realm/Extensions/RealmSyncExtensions.cs index c13e21a7aa..332dfad44b 100644 --- a/Realm/Realm/Extensions/RealmSyncExtensions.cs +++ b/Realm/Realm/Extensions/RealmSyncExtensions.cs @@ -31,16 +31,17 @@ public static class RealmSyncExtensions /// /// Gets the for the realm file behind this . /// - /// The that is responsible for synchronizing with a Realm Object Server instance. - /// An instance of the class created with a object. - /// Thrown if realm is null. - /// Thrown if the realm was not created with a object. + /// The that is responsible for synchronizing with the MongoDB Realm server. + /// An instance of the class created with a object. + /// Thrown if is null. + /// Thrown if the was not created with a object. public static Session GetSession(this Realm realm) { Argument.NotNull(realm, nameof(realm)); - Argument.Ensure(realm.Config is SyncConfigurationBase, "Cannot get a Session for a Realm without a SyncConfiguration", nameof(realm)); + var syncConfig = Argument.EnsureType(realm.Config, "Cannot get a Session for a Realm without a SyncConfiguration", nameof(realm)); - return new Session(realm.Config.DatabasePath); + var session = syncConfig.User.App.Handle.GetSessionForPath(realm.SharedRealmHandle); + return new Session(session); } } } diff --git a/Realm/Realm/Extensions/ReflectionExtensions.cs b/Realm/Realm/Extensions/ReflectionExtensions.cs index da18279298..ffd30f3ab9 100644 --- a/Realm/Realm/Extensions/ReflectionExtensions.cs +++ b/Realm/Realm/Extensions/ReflectionExtensions.cs @@ -44,5 +44,9 @@ public static bool HasCustomAttribute(this MemberInfo member) => member.CustomAttributes.Any(a => a.AttributeType == typeof(T)); public static string GetMappedOrOriginalName(this MemberInfo member) => member?.GetCustomAttribute()?.Mapping ?? member?.Name; + + public static bool IsEmbeddedObject(this Type type) => type == typeof(EmbeddedObject) || type.BaseType == typeof(EmbeddedObject); + + public static bool IsRealmObject(this Type type) => type == typeof(RealmObject) || type.BaseType == typeof(RealmObject); } } diff --git a/Realm/Realm/Extensions/StringExtensions.cs b/Realm/Realm/Extensions/StringExtensions.cs index a1ea646681..27a5661d53 100644 --- a/Realm/Realm/Extensions/StringExtensions.cs +++ b/Realm/Realm/Extensions/StringExtensions.cs @@ -77,5 +77,7 @@ public static bool Like(this string str, string pattern, bool caseSensitive = tr var options = caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase; return Regex.Match(str, $"^{pattern}$", options).Success; } + + internal static IntPtr IntPtrLength(this string str) => (IntPtr)(str?.Length ?? 0); } } diff --git a/Realm/Realm/Extensions/TestingExtensions.cs b/Realm/Realm/Extensions/TestingExtensions.cs index c98eea6aad..fb5db5517a 100644 --- a/Realm/Realm/Extensions/TestingExtensions.cs +++ b/Realm/Realm/Extensions/TestingExtensions.cs @@ -34,7 +34,7 @@ public static class TestingExtensions /// Error message. /// If set to true the error will be marked as fatal. /// - /// Use this method to test your error handling code without connecting to a Realm Object Server. + /// Use this method to test your error handling code without connecting to a MongoDB Realm Server. /// Some error codes, such as will be ignored and will not be reported /// to subscribers. /// diff --git a/Realm/Realm/GlobalSuppressions.cs b/Realm/Realm/GlobalSuppressions.cs index 2353b4b289..123f85b1f2 100644 --- a/Realm/Realm/GlobalSuppressions.cs +++ b/Realm/Realm/GlobalSuppressions.cs @@ -2,14 +2,12 @@ [assembly: SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "Copied from Xamarin's implementation", Scope = "type", Target = "~T:Realms.PreserveAttribute")] [assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Copied from Xamarin's implementation", Scope = "type", Target = "~T:Realms.PreserveAttribute")] -[assembly: SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "This is checked in the base class.", Scope = "member", Target = "~M:Realms.Sync.FullSyncConfiguration.#ctor(System.Uri,Realms.Sync.User,System.String)")] [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Shouldn't be used directly.", Scope = "type", Target = "~T:Realms.Dynamic.DynamicRealmObject")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Shouldn't be used directly.", Scope = "type", Target = "~T:Realms.Dynamic.DynamicEmbeddedObject")] [assembly: SuppressMessage("Performance", "CA1820:Test for empty strings using string length", Justification = "We're only capturing the method group.", Scope = "type", Target = "~T:Realms.RealmResultsVisitor.Methods.String")] [assembly: SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "index is the argument name for ElementAt.", Scope = "member", Target = "~M:Realms.RealmResultsVisitor.VisitMethodCall(System.Linq.Expressions.MethodCallExpression)~System.Linq.Expressions.Expression")] [assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1121:Use built-in type alias", Justification = "Native structs use verbose names.", Scope = "namespaceanddescendants", Target = "Realms.Native")] [assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Native structs use snake_case fields.", Scope = "namespaceanddescendants", Target = "Realms.Native")] -[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1121:Use built-in type alias", Justification = "Native structs use verbose names.", Scope = "namespaceanddescendants", Target = "Realms.Server.Native")] -[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Native structs use snake_case fields.", Scope = "namespaceanddescendants", Target = "Realms.Server.Native")] [assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Native methods are snake_case.", Scope = "type", Target = "~T:Realms.NativeCommon")] [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Native methods are snake_case.", Scope = "type", Target = "~T:Realms.NativeCommon")] [assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Native structs use snake_case fields.", Scope = "type", Target = "~T:Realms.NativeException")] @@ -20,5 +18,5 @@ [assembly: SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "Will be a breaking change to rename.", Scope = "type", Target = "~T:Realms.Schema.Property")] [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is the private event", Scope = "member", Target = "~E:Realms.RealmCollectionBase`1._collectionChanged")] [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is the private event", Scope = "member", Target = "~E:Realms.RealmCollectionBase`1._propertyChanged")] -[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is the private event", Scope = "member", Target = "~E:Realms.RealmObject._propertyChanged")] +[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is the private event", Scope = "member", Target = "~E:Realms.RealmObjectBase._propertyChanged")] [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is the private event", Scope = "member", Target = "~E:Realms.Realm._realmChanged")] diff --git a/Realm/Realm/Handles/AppHandle.EmailPassword.cs b/Realm/Realm/Handles/AppHandle.EmailPassword.cs new file mode 100644 index 0000000000..3b49a72a95 --- /dev/null +++ b/Realm/Realm/Handles/AppHandle.EmailPassword.cs @@ -0,0 +1,135 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Realms.Sync +{ + internal partial class AppHandle : RealmHandle + { + private static class EmailNativeMethods + { +#pragma warning disable IDE1006 // Naming Styles +#pragma warning disable SA1121 // Use built-in type alias + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_register_user", CallingConvention = CallingConvention.Cdecl)] + public static extern void register_user(AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string username, IntPtr username_len, + [MarshalAs(UnmanagedType.LPWStr)] string password, IntPtr password_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_confirm_user", CallingConvention = CallingConvention.Cdecl)] + public static extern void confirm_user(AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string token, IntPtr token_len, + [MarshalAs(UnmanagedType.LPWStr)] string token_id, IntPtr token_id_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_resend_confirmation_email", CallingConvention = CallingConvention.Cdecl)] + public static extern void resend_confirmation_email(AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string email, IntPtr email_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_send_reset_password_email", CallingConvention = CallingConvention.Cdecl)] + public static extern void send_reset_password_email(AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string email, IntPtr email_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_reset_password", CallingConvention = CallingConvention.Cdecl)] + public static extern void reset_password(AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string password, IntPtr password_len, + [MarshalAs(UnmanagedType.LPWStr)] string token, IntPtr token_len, + [MarshalAs(UnmanagedType.LPWStr)] string token_id, IntPtr token_id_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_call_reset_password_function", CallingConvention = CallingConvention.Cdecl)] + public static extern void call_reset_password_function(AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string username, IntPtr username_len, + [MarshalAs(UnmanagedType.LPWStr)] string password, IntPtr password_len, + [MarshalAs(UnmanagedType.LPWStr)] string function_args, IntPtr function_args_len, + IntPtr tcs_ptr, out NativeException ex); + +#pragma warning restore IDE1006 // Naming Styles +#pragma warning restore SA1121 // Use built-in type alias + } + + public readonly EmailPasswordApi EmailPassword; + + public class EmailPasswordApi + { + private readonly AppHandle _appHandle; + + public EmailPasswordApi(AppHandle handle) + { + _appHandle = handle; + } + + public void RegisterUser(string username, string password, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); + EmailNativeMethods.register_user(_appHandle, username, (IntPtr)username.Length, password, (IntPtr)password.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(); + } + + public void ConfirmUser(string token, string tokenId, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); + EmailNativeMethods.confirm_user(_appHandle, token, (IntPtr)token.Length, tokenId, (IntPtr)tokenId.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(); + } + + public void ResendConfirmationEmail(string email, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); + EmailNativeMethods.resend_confirmation_email(_appHandle, email, (IntPtr)email.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(); + } + + public void SendResetPasswordEmail(string username, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); + EmailNativeMethods.send_reset_password_email(_appHandle, username, (IntPtr)username.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(); + } + + public void ResetPassword(string password, string token, string tokenId, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); + EmailNativeMethods.reset_password( + _appHandle, + password, (IntPtr)password.Length, + token, (IntPtr)token.Length, + tokenId, (IntPtr)tokenId.Length, + GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(); + } + + public void CallResetPasswordFunction(string username, string password, string functionArgs, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); + EmailNativeMethods.call_reset_password_function(_appHandle, + username, (IntPtr)username.Length, + password, (IntPtr)password.Length, + functionArgs, (IntPtr)functionArgs.Length, + GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(); + } + } + } +} diff --git a/Realm/Realm/Handles/AppHandle.cs b/Realm/Realm/Handles/AppHandle.cs new file mode 100644 index 0000000000..cd5fe484c5 --- /dev/null +++ b/Realm/Realm/Handles/AppHandle.cs @@ -0,0 +1,380 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Realms.Native; +using Realms.Sync.Exceptions; +using Realms.Sync.Native; + +namespace Realms.Sync +{ + internal partial class AppHandle : RealmHandle + { + private static class NativeMethods + { +#pragma warning disable IDE1006 // Naming Styles +#pragma warning disable SA1121 // Use built-in type alias + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public unsafe delegate void LogMessageCallback(IntPtr managed_handler, byte* message_buf, IntPtr message_len, LogLevel logLevel); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void UserCallback(IntPtr tcs_ptr, IntPtr user_ptr, AppError error); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void VoidTaskCallback(IntPtr tcs_ptr, AppError error); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public unsafe delegate void BsonCallback(IntPtr tcs_ptr, BsonPayload response, AppError error); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_initialize", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr initialize( + [MarshalAs(UnmanagedType.LPWStr)] string platform, IntPtr platform_len, + [MarshalAs(UnmanagedType.LPWStr)] string platform_version, IntPtr platform_version_len, + [MarshalAs(UnmanagedType.LPWStr)] string sdk_version, IntPtr sdk_version_len, + UserCallback user_callback, VoidTaskCallback void_callback, BsonCallback bson_callback, LogMessageCallback log_message_callback); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_create", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr create_app(Native.AppConfiguration app_config, byte[] encryptionKey, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_destroy", CallingConvention = CallingConvention.Cdecl)] + public static extern void destroy(IntPtr syncuserHandle); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_sync_get_session_from_path", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr get_session(AppHandle app, SharedRealmHandle realm, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_sync_get_path_for_realm", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr get_path_for_realm(AppHandle app, SyncUserHandle user, [MarshalAs(UnmanagedType.LPWStr)] string partition, IntPtr partition_len, IntPtr buffer, IntPtr bufsize, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_sync_immediately_run_file_actions", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.U1)] + public static extern bool immediately_run_file_actions(AppHandle app, [MarshalAs(UnmanagedType.LPWStr)] string path, IntPtr path_len, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_sync_reconnect", CallingConvention = CallingConvention.Cdecl)] + public static extern void reconnect(AppHandle app); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_current_user", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr get_current_user(AppHandle app, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_logged_in_users", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr get_logged_in_users(AppHandle app, [Out] IntPtr[] users, IntPtr bufsize, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_switch_user", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr switch_user(AppHandle app, SyncUserHandle user, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_login_user", CallingConvention = CallingConvention.Cdecl)] + public static extern void login_user(AppHandle app, Native.Credentials credentials, IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_remove_user", CallingConvention = CallingConvention.Cdecl)] + public static extern void remove_user(AppHandle app, SyncUserHandle user, IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_reset_for_testing", CallingConvention = CallingConvention.Cdecl)] + public static extern void reset_for_testing(AppHandle app); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_user_for_testing", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr get_user_for_testing( + AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string id_buf, IntPtr id_len, + [MarshalAs(UnmanagedType.LPWStr)] string refresh_token_buf, IntPtr refresh_token_len, + [MarshalAs(UnmanagedType.LPWStr)] string access_token_buf, IntPtr access_token_len, + out NativeException ex); + + public static class EmailPassword + { + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_register_user", CallingConvention = CallingConvention.Cdecl)] + public static extern void register_user(AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string username, IntPtr username_len, + [MarshalAs(UnmanagedType.LPWStr)] string password, IntPtr password_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_confirm_user", CallingConvention = CallingConvention.Cdecl)] + public static extern void confirm_user(AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string token, IntPtr token_len, + [MarshalAs(UnmanagedType.LPWStr)] string token_id, IntPtr token_id_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_resend_confirmation_email", CallingConvention = CallingConvention.Cdecl)] + public static extern void resent_confirmation_email(AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string email, IntPtr email_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_send_reset_password_email", CallingConvention = CallingConvention.Cdecl)] + public static extern void send_reset_password_email(AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string email, IntPtr email_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_reset_password", CallingConvention = CallingConvention.Cdecl)] + public static extern void reset_password(AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string password, IntPtr password_len, + [MarshalAs(UnmanagedType.LPWStr)] string token, IntPtr token_len, + [MarshalAs(UnmanagedType.LPWStr)] string token_id, IntPtr token_id_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_call_reset_password_function", CallingConvention = CallingConvention.Cdecl)] + public static extern void call_reset_password_function(AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string username, IntPtr username_len, + [MarshalAs(UnmanagedType.LPWStr)] string password, IntPtr password_len, + IntPtr tcs_ptr, out NativeException ex); + } + +#pragma warning restore IDE1006 // Naming Styles +#pragma warning restore SA1121 // Use built-in type alias + } + + static unsafe AppHandle() + { + NativeCommon.Initialize(); + SessionHandle.InstallCallbacks(); + + NativeMethods.LogMessageCallback logMessage = HandleLogMessage; + NativeMethods.UserCallback userLogin = HandleUserCallback; + NativeMethods.VoidTaskCallback taskCallback = HandleTaskCompletion; + NativeMethods.BsonCallback bsonCallback = HandleBsonCallback; + + GCHandle.Alloc(logMessage); + GCHandle.Alloc(userLogin); + GCHandle.Alloc(taskCallback); + GCHandle.Alloc(bsonCallback); + + //// This is a hack due to a mixup of what OS uses as platform/SDK and what is displayed in the UI. + //// The original code is below: + //// + //// string platform; + //// string platformVersion; + //// var platformRegex = new Regex("^(?[^0-9]*) (?[^ ]*)", RegexOptions.Compiled); + //// var osDescription = platformRegex.Match(RuntimeInformation.OSDescription); + //// if (osDescription.Success) + //// { + //// platform = osDescription.Groups["platform"].Value; + //// platformVersion = osDescription.Groups["version"].Value; + //// } + //// else + //// { + //// platform = Environment.OSVersion.Platform.ToString(); + //// platformVersion = Environment.OSVersion.VersionString; + //// } + + var platform = "Realm .NET"; + var platformVersion = RuntimeInformation.OSDescription; + + var sdkVersion = typeof(AppHandle).GetTypeInfo().Assembly.GetName().Version.ToString(3); + NativeMethods.initialize( + platform, (IntPtr)platform.Length, + platformVersion, (IntPtr)platformVersion.Length, + sdkVersion, (IntPtr)sdkVersion.Length, + userLogin, taskCallback, bsonCallback, logMessage); + + HttpClientTransport.Install(); + } + + internal AppHandle(IntPtr handle) : base(null, handle) + { + EmailPassword = new EmailPasswordApi(this); + } + + public static AppHandle CreateApp(Native.AppConfiguration config, byte[] encryptionKey) + { + var handle = NativeMethods.create_app(config, encryptionKey, out var ex); + ex.ThrowIfNecessary(); + return new AppHandle(handle); + } + + public string GetRealmPath(User user, string partition) + { + return MarshalHelpers.GetString((IntPtr buffer, IntPtr bufferLength, out bool isNull, out NativeException ex) => + { + isNull = false; + return NativeMethods.get_path_for_realm(this, user.Handle, partition, (IntPtr)partition.Length, buffer, bufferLength, out ex); + }); + } + + public SessionHandle GetSessionForPath(SharedRealmHandle realm) + { + var ptr = NativeMethods.get_session(this, realm, out var ex); + ex.ThrowIfNecessary(); + return new SessionHandle(ptr); + } + + public bool ImmediatelyRunFileActions(string path) + { + var result = NativeMethods.immediately_run_file_actions(this, path, (IntPtr)path.Length, out var ex); + ex.ThrowIfNecessary(); + + return result; + } + + public void Reconnect() + { + NativeMethods.reconnect(this); + } + + public bool TryGetCurrentUser(out SyncUserHandle handle) + { + var userPtr = NativeMethods.get_current_user(this, out var ex); + ex.ThrowIfNecessary(); + + if (userPtr == IntPtr.Zero) + { + handle = null; + return false; + } + + handle = new SyncUserHandle(userPtr); + return true; + } + + public IEnumerable GetAllLoggedInUsers() + { + return MarshalHelpers.GetCollection((IntPtr[] buf, IntPtr len, out NativeException ex) => NativeMethods.get_logged_in_users(this, buf, len, out ex), bufferSize: 8) + .Select(h => new SyncUserHandle(h)); + } + + public void SwitchUser(SyncUserHandle user) + { + NativeMethods.switch_user(this, user, out var ex); + ex.ThrowIfNecessary(); + } + + public void LogIn(Native.Credentials credentials, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); + NativeMethods.login_user(this, credentials, GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(tcsHandle); + } + + public void Remove(SyncUserHandle user, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); + NativeMethods.remove_user(this, user, GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(tcsHandle); + } + + public void ResetForTesting() + { + NativeMethods.reset_for_testing(this); + } + + public SyncUserHandle GetUserForTesting(string id, string refreshToken, string accessToken) + { + var result = NativeMethods.get_user_for_testing( + this, + id, (IntPtr)id.Length, + refreshToken, (IntPtr)refreshToken.Length, + accessToken, (IntPtr)accessToken.Length, + out var ex); + ex.ThrowIfNecessary(); + return new SyncUserHandle(result); + } + + protected override void Unbind() + { + NativeMethods.destroy(handle); + } + + [MonoPInvokeCallback(typeof(NativeMethods.LogMessageCallback))] + private static unsafe void HandleLogMessage(IntPtr managedHandler, byte* messageBuffer, IntPtr messageLength, LogLevel level) + { + try + { + var message = Encoding.UTF8.GetString(messageBuffer, (int)messageLength); + var logCallback = (Action)GCHandle.FromIntPtr(managedHandler).Target; + logCallback.Invoke(message, level); + } + catch (Exception ex) + { + var errorMessage = $"An error occurred while trying to log a message: {ex}"; + Console.Error.WriteLine(errorMessage); + } + } + + [MonoPInvokeCallback(typeof(NativeMethods.UserCallback))] + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The user will own its handle.")] + private static unsafe void HandleUserCallback(IntPtr tcs_ptr, IntPtr user_ptr, AppError error) + { + var tcsHandle = GCHandle.FromIntPtr(tcs_ptr); + try + { + var tcs = (TaskCompletionSource)tcsHandle.Target; + if (error.is_null) + { + var userHandle = new SyncUserHandle(user_ptr); + tcs.TrySetResult(userHandle); + } + else + { + tcs.TrySetException(new AppException(error)); + } + } + finally + { + tcsHandle.Free(); + } + } + + [MonoPInvokeCallback(typeof(NativeMethods.VoidTaskCallback))] + private static unsafe void HandleTaskCompletion(IntPtr tcs_ptr, AppError error) + { + var tcsHandle = GCHandle.FromIntPtr(tcs_ptr); + try + { + var tcs = (TaskCompletionSource)tcsHandle.Target; + if (error.is_null) + { + tcs.TrySetResult(null); + } + else + { + tcs.TrySetException(new AppException(error)); + } + } + finally + { + tcsHandle.Free(); + } + } + + [MonoPInvokeCallback(typeof(NativeMethods.BsonCallback))] + private static unsafe void HandleBsonCallback(IntPtr tcs_ptr, BsonPayload response, AppError error) + { + var tcsHandle = GCHandle.FromIntPtr(tcs_ptr); + try + { + var tcs = (TaskCompletionSource)tcsHandle.Target; + if (error.is_null) + { + tcs.TrySetResult(response); + } + else + { + tcs.TrySetException(new AppException(error)); + } + } + finally + { + tcsHandle.Free(); + } + } + } +} diff --git a/Realm/Realm/Handles/CollectionHandleBase.cs b/Realm/Realm/Handles/CollectionHandleBase.cs index ed9f57eec1..6550045044 100644 --- a/Realm/Realm/Handles/CollectionHandleBase.cs +++ b/Realm/Realm/Handles/CollectionHandleBase.cs @@ -50,7 +50,7 @@ public PrimitiveValue GetPrimitiveAtIndex(int index, PropertyType type) { var result = new PrimitiveValue { - type = type + Type = type }; GetPrimitiveAtIndexCore((IntPtr)index, ref result, out var nativeException); diff --git a/Realm/Realm/Handles/ListHandle.cs b/Realm/Realm/Handles/ListHandle.cs index 0a8c3e6f82..eef44fb005 100644 --- a/Realm/Realm/Handles/ListHandle.cs +++ b/Realm/Realm/Handles/ListHandle.cs @@ -40,11 +40,14 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_add_string", CallingConvention = CallingConvention.Cdecl)] public static extern void add_string(ListHandle listHandle, [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLength, - [MarshalAs(UnmanagedType.I1)] bool has_value, out NativeException ex); + [MarshalAs(UnmanagedType.U1)] bool has_value, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_add_binary", CallingConvention = CallingConvention.Cdecl)] public static extern void add_binary(ListHandle listHandle, IntPtr buffer, IntPtr bufferLength, - [MarshalAs(UnmanagedType.I1)] bool has_value, out NativeException ex); + [MarshalAs(UnmanagedType.U1)] bool has_value, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_add_embedded", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr add_embedded(ListHandle listHandle, out NativeException ex); #endregion @@ -60,11 +63,14 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_set_string", CallingConvention = CallingConvention.Cdecl)] public static extern void set_string(ListHandle listHandle, IntPtr targetIndex, [MarshalAs(UnmanagedType.LPWStr)] string value, - IntPtr valueLen, [MarshalAs(UnmanagedType.I1)] bool has_value, out NativeException ex); + IntPtr valueLen, [MarshalAs(UnmanagedType.U1)] bool has_value, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_set_binary", CallingConvention = CallingConvention.Cdecl)] public static extern void set_binary(ListHandle listHandle, IntPtr targetIndex, IntPtr buffer, IntPtr bufferLength, - [MarshalAs(UnmanagedType.I1)] bool has_value, out NativeException ex); + [MarshalAs(UnmanagedType.U1)] bool has_value, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_set_embedded", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr set_embedded(ListHandle listHandle, IntPtr targetIndex, out NativeException ex); #endregion @@ -80,11 +86,14 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_insert_string", CallingConvention = CallingConvention.Cdecl)] public static extern void insert_string(ListHandle listHandle, IntPtr targetIndex, [MarshalAs(UnmanagedType.LPWStr)] string value, - IntPtr valueLen, [MarshalAs(UnmanagedType.I1)] bool has_value, out NativeException ex); + IntPtr valueLen, [MarshalAs(UnmanagedType.U1)] bool has_value, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_insert_binary", CallingConvention = CallingConvention.Cdecl)] public static extern void insert_binary(ListHandle listHandle, IntPtr targetIndex, IntPtr buffer, IntPtr bufferLength, - [MarshalAs(UnmanagedType.I1)] bool has_value, out NativeException ex); + [MarshalAs(UnmanagedType.U1)] bool has_value, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_insert_embedded", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr insert_embedded(ListHandle listHandle, IntPtr targetIndex, out NativeException ex); #endregion @@ -98,11 +107,11 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_get_string", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_string(ListHandle listHandle, IntPtr link_ndx, IntPtr buffer, IntPtr bufsize, - [MarshalAs(UnmanagedType.I1)] out bool isNull, out NativeException ex); + [MarshalAs(UnmanagedType.U1)] out bool isNull, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_get_binary", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_binary(ListHandle listHandle, IntPtr link_ndx, IntPtr buffer, IntPtr bufsize, - [MarshalAs(UnmanagedType.I1)] out bool isNull, out NativeException ex); + [MarshalAs(UnmanagedType.U1)] out bool isNull, out NativeException ex); #endregion @@ -118,11 +127,11 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_find_string", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr find_string(ListHandle listHandle, [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, - [MarshalAs(UnmanagedType.I1)] bool has_value, out NativeException ex); + [MarshalAs(UnmanagedType.U1)] bool has_value, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_find_binary", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr find_binary(ListHandle listHandle, IntPtr buffer, IntPtr bufsize, - [MarshalAs(UnmanagedType.I1)] bool has_value, out NativeException ex); + [MarshalAs(UnmanagedType.U1)] bool has_value, out NativeException ex); #endregion @@ -145,7 +154,7 @@ private static class NativeMethods public static extern IntPtr move(ListHandle listHandle, IntPtr sourceIndex, IntPtr targetIndex, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_get_is_valid", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] public static extern bool get_is_valid(ListHandle listHandle, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_get_thread_safe_reference", CallingConvention = CallingConvention.Cdecl)] @@ -155,7 +164,7 @@ private static class NativeMethods public static extern IntPtr snapshot(ListHandle list, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_get_is_frozen", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] public static extern bool get_is_frozen(ListHandle list, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "list_freeze", CallingConvention = CallingConvention.Cdecl)] @@ -232,6 +241,13 @@ public unsafe void Add(byte[] value) NativeMethods.add_binary(this, buffer, bufferSize, hasValue, out ex)); } + public ObjectHandle AddEmbedded() + { + var result = NativeMethods.add_embedded(this, out var nativeException); + nativeException.ThrowIfNecessary(); + return new ObjectHandle(Root, result); + } + #endregion #region Set @@ -263,6 +279,13 @@ public unsafe void Set(int targetIndex, byte[] value) NativeMethods.set_binary(this, (IntPtr)targetIndex, buffer, bufferSize, hasValue, out ex)); } + public ObjectHandle SetEmbedded(int targetIndex) + { + var result = NativeMethods.set_embedded(this, (IntPtr)targetIndex, out var nativeException); + nativeException.ThrowIfNecessary(); + return new ObjectHandle(Root, result); + } + #endregion #region Insert @@ -294,6 +317,13 @@ public unsafe void Insert(int targetIndex, byte[] value) NativeMethods.insert_binary(this, (IntPtr)targetIndex, buffer, bufferSize, hasValue, out ex)); } + public ObjectHandle InsertEmbedded(int targetIndex) + { + var result = NativeMethods.insert_embedded(this, (IntPtr)targetIndex, out var nativeException); + nativeException.ThrowIfNecessary(); + return new ObjectHandle(Root, result); + } + #endregion #region Find diff --git a/Realm/Realm/Handles/MongoCollectionHandle.cs b/Realm/Realm/Handles/MongoCollectionHandle.cs new file mode 100644 index 0000000000..cb792e6a78 --- /dev/null +++ b/Realm/Realm/Handles/MongoCollectionHandle.cs @@ -0,0 +1,221 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Realms.Native; +using Realms.Sync; + +namespace Realms +{ + internal class MongoCollectionHandle : RealmHandle + { + private static class NativeMethods + { +#pragma warning disable IDE1006 // Naming Styles +#pragma warning disable SA1121 // Use built-in type alias + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_get", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr get(SyncUserHandle user, + [MarshalAs(UnmanagedType.LPWStr)] string service, IntPtr service_len, + [MarshalAs(UnmanagedType.LPWStr)] string database, IntPtr database_len, + [MarshalAs(UnmanagedType.LPWStr)] string collection, IntPtr collection_len, + out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_destroy", CallingConvention = CallingConvention.Cdecl)] + public static extern void destroy(IntPtr handle); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_find_one", CallingConvention = CallingConvention.Cdecl)] + public static extern void find_one(MongoCollectionHandle handle, + [MarshalAs(UnmanagedType.LPWStr)] string filter, IntPtr filter_len, + FindAndModifyOptions options, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_find", CallingConvention = CallingConvention.Cdecl)] + public static extern void find(MongoCollectionHandle handle, + [MarshalAs(UnmanagedType.LPWStr)] string filter, IntPtr filter_len, + FindAndModifyOptions options, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_aggregate", CallingConvention = CallingConvention.Cdecl)] + public static extern void aggregate(MongoCollectionHandle handle, + [MarshalAs(UnmanagedType.LPWStr)] string pipeline, IntPtr pipeline_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_count", CallingConvention = CallingConvention.Cdecl)] + public static extern void count(MongoCollectionHandle handle, + [MarshalAs(UnmanagedType.LPWStr)] string filter, IntPtr filter_len, + Int64 limit, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_insert_one", CallingConvention = CallingConvention.Cdecl)] + public static extern void insert_one(MongoCollectionHandle handle, + [MarshalAs(UnmanagedType.LPWStr)] string doc, IntPtr doc_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_insert_many", CallingConvention = CallingConvention.Cdecl)] + public static extern void insert_many(MongoCollectionHandle handle, + [MarshalAs(UnmanagedType.LPWStr)] string docs, IntPtr docs_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_delete_one", CallingConvention = CallingConvention.Cdecl)] + public static extern void delete_one(MongoCollectionHandle handle, + [MarshalAs(UnmanagedType.LPWStr)] string filter, IntPtr filter_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_delete_many", CallingConvention = CallingConvention.Cdecl)] + public static extern void delete_many(MongoCollectionHandle handle, + [MarshalAs(UnmanagedType.LPWStr)] string filter, IntPtr filter_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_update_one", CallingConvention = CallingConvention.Cdecl)] + public static extern void update_one(MongoCollectionHandle handle, + [MarshalAs(UnmanagedType.LPWStr)] string filter, IntPtr filter_len, + [MarshalAs(UnmanagedType.LPWStr)] string update, IntPtr update_len, + [MarshalAs(UnmanagedType.U1)] bool upsert, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_update_many", CallingConvention = CallingConvention.Cdecl)] + public static extern void update_many(MongoCollectionHandle handle, + [MarshalAs(UnmanagedType.LPWStr)] string filter, IntPtr filter_len, + [MarshalAs(UnmanagedType.LPWStr)] string update, IntPtr update_len, + [MarshalAs(UnmanagedType.U1)] bool upsert, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_find_one_and_update", CallingConvention = CallingConvention.Cdecl)] + public static extern void find_one_and_update(MongoCollectionHandle handle, + [MarshalAs(UnmanagedType.LPWStr)] string filter, IntPtr filter_len, + [MarshalAs(UnmanagedType.LPWStr)] string update, IntPtr update_len, + FindAndModifyOptions options, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_find_one_and_replace", CallingConvention = CallingConvention.Cdecl)] + public static extern void find_one_and_replace(MongoCollectionHandle handle, + [MarshalAs(UnmanagedType.LPWStr)] string filter, IntPtr filter_len, + [MarshalAs(UnmanagedType.LPWStr)] string update, IntPtr update_len, + FindAndModifyOptions options, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_mongo_collection_find_one_and_delete", CallingConvention = CallingConvention.Cdecl)] + public static extern void find_one_and_delete(MongoCollectionHandle handle, + [MarshalAs(UnmanagedType.LPWStr)] string filter, IntPtr filter_len, + FindAndModifyOptions options, + IntPtr tcs_ptr, out NativeException ex); + +#pragma warning restore SA1121 // Use built-in type alias +#pragma warning restore IDE1006 // Naming Styles + } + + private MongoCollectionHandle(SyncUserHandle root, IntPtr handle) : base(root, handle) + { + } + + public static MongoCollectionHandle Create(SyncUserHandle user, string service, string database, string collection) + { + var handle = NativeMethods.get(user, service, (IntPtr)service.Length, database, (IntPtr)database.Length, collection, (IntPtr)collection.Length, out var ex); + ex.ThrowIfNecessary(); + + return new MongoCollectionHandle(user, handle); + } + + public Task FindOne(string filter, FindAndModifyOptions options) + { + return CallNativeMethod((IntPtr tcs, out NativeException ex) => NativeMethods.find_one(this, filter, filter.IntPtrLength(), options, tcs, out ex)); + } + + public Task Find(string filter, FindAndModifyOptions options) + { + return CallNativeMethod((IntPtr tcs, out NativeException ex) => NativeMethods.find(this, filter, filter.IntPtrLength(), options, tcs, out ex)); + } + + public Task Aggregate(string pipeline) + { + return CallNativeMethod((IntPtr tcs, out NativeException ex) => NativeMethods.aggregate(this, pipeline, pipeline.IntPtrLength(), tcs, out ex)); + } + + public Task Count(string filter, long? limit) + { + return CallNativeMethod((IntPtr tcs, out NativeException ex) => NativeMethods.count(this, filter, filter.IntPtrLength(), limit ?? 0, tcs, out ex)); + } + + public Task InsertOne(string doc) + { + return CallNativeMethod((IntPtr tcs, out NativeException ex) => NativeMethods.insert_one(this, doc, doc.IntPtrLength(), tcs, out ex)); + } + + public Task InsertMany(string docs) + { + return CallNativeMethod((IntPtr tcs, out NativeException ex) => NativeMethods.insert_many(this, docs, docs.IntPtrLength(), tcs, out ex)); + } + + public Task DeleteOne(string filter) + { + return CallNativeMethod((IntPtr tcs, out NativeException ex) => NativeMethods.delete_one(this, filter, filter.IntPtrLength(), tcs, out ex)); + } + + public Task DeleteMany(string filter) + { + return CallNativeMethod((IntPtr tcs, out NativeException ex) => NativeMethods.delete_many(this, filter, filter.IntPtrLength(), tcs, out ex)); + } + + public Task UpdateOne(string filter, string update, bool upsert) + { + return CallNativeMethod((IntPtr tcs, out NativeException ex) => NativeMethods.update_one(this, filter, filter.IntPtrLength(), update, update.IntPtrLength(), upsert, tcs, out ex)); + } + + public Task UpdateMany(string filter, string update, bool upsert) + { + return CallNativeMethod((IntPtr tcs, out NativeException ex) => NativeMethods.update_many(this, filter, filter.IntPtrLength(), update, update.IntPtrLength(), upsert, tcs, out ex)); + } + + public Task FindOneAndUpdate(string filter, string update, FindAndModifyOptions options) + { + return CallNativeMethod((IntPtr tcs, out NativeException ex) => + NativeMethods.find_one_and_update(this, filter, filter.IntPtrLength(), update, update.IntPtrLength(), options, tcs, out ex)); + } + + public Task FindOneAndReplace(string filter, string replacement, FindAndModifyOptions options) + { + return CallNativeMethod((IntPtr tcs, out NativeException ex) => + NativeMethods.find_one_and_replace(this, filter, filter.IntPtrLength(), replacement, replacement.IntPtrLength(), options, tcs, out ex)); + } + + public Task FindOneAndDelete(string filter, FindAndModifyOptions options) + { + return CallNativeMethod((IntPtr tcs, out NativeException ex) => + NativeMethods.find_one_and_delete(this, filter, filter.IntPtrLength(), options, tcs, out ex)); + } + + protected override void Unbind() + { + NativeMethods.destroy(handle); + } + + private delegate void NativeMethod(IntPtr tcsPtr, out NativeException ex); + + private static Task CallNativeMethod(NativeMethod method) + { + var tcs = new TaskCompletionSource(); + var tcsHandle = GCHandle.Alloc(tcs); + method(GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(); + return tcs.Task; + } + } +} \ No newline at end of file diff --git a/Realm/Realm/Handles/NotifierHandle.cs b/Realm/Realm/Handles/NotifierHandle.cs deleted file mode 100644 index a76bfb1ede..0000000000 --- a/Realm/Realm/Handles/NotifierHandle.cs +++ /dev/null @@ -1,103 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -using NativeSyncConfiguration = Realms.Sync.Native.SyncConfiguration; - -namespace Realms.Server -{ - internal class NotifierHandle : RealmHandle - { - private static class NativeMethods - { - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public unsafe delegate bool ShouldHandleCallback(IntPtr managedNotifier, byte* path_buf, IntPtr path_len); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public unsafe delegate void EnqueueCalculationCallback(IntPtr managedNotifier, byte* path_buf, IntPtr path_len, IntPtr calculator_ptr); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public unsafe delegate void StartCallback(IntPtr task_completion_source, int error_code, byte* message_buf, IntPtr message_len); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_server_install_callbacks", CallingConvention = CallingConvention.Cdecl)] - public static extern void install_callbacks( - ShouldHandleCallback should_handle_callback, - EnqueueCalculationCallback enqueue_calculation_callback, - StartCallback start_callback, - NotifierNotificationHandle.CalculationCompleteCallback calculation_complete_callback); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_server_create_global_notifier", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr create_notifier(IntPtr managedInstance, - NativeSyncConfiguration sync_configuration, - byte[] encryptionKey, - out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_server_global_notifier_get_realm_for_writing", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_realm_for_writing(SharedRealmHandle currentRealm, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_server_global_notifier_destroy", CallingConvention = CallingConvention.Cdecl)] - public static extern void destroy(IntPtr handle); - } - - static unsafe NotifierHandle() - { - NativeCommon.Initialize(); - - NativeMethods.ShouldHandleCallback shouldHandle = Notifier.ShouldHandle; - NativeMethods.EnqueueCalculationCallback enqueueCalculation = Notifier.EnqueueCalculation; - NativeMethods.StartCallback start = Notifier.OnStarted; - NotifierNotificationHandle.CalculationCompleteCallback calculationComplete = Notifier.OnCalculationCompleted; - - GCHandle.Alloc(shouldHandle); - GCHandle.Alloc(enqueueCalculation); - GCHandle.Alloc(start); - GCHandle.Alloc(calculationComplete); - - NativeMethods.install_callbacks(shouldHandle, enqueueCalculation, start, calculationComplete); - } - - public static NotifierHandle CreateHandle(GCHandle managedNotifier, NotifierConfiguration configuration) - { - var nativeConfig = configuration.ToNative(); - var result = NativeMethods.create_notifier(GCHandle.ToIntPtr(managedNotifier), nativeConfig, configuration.EncryptionKey, - out var nativeException); - nativeException.ThrowIfNecessary(); - - return new NotifierHandle(result); - } - - public static IntPtr GetRealmForWriting(SharedRealmHandle currentRealm) - { - var result = NativeMethods.get_realm_for_writing(currentRealm, out var nativeException); - nativeException.ThrowIfNecessary(); - return result; - } - - protected NotifierHandle(IntPtr handle) : base(null, handle) - { - } - - protected override void Unbind() - { - NativeMethods.destroy(handle); - } - } -} diff --git a/Realm/Realm/Handles/NotifierNotificationHandle.cs b/Realm/Realm/Handles/NotifierNotificationHandle.cs deleted file mode 100644 index 475063b24a..0000000000 --- a/Realm/Realm/Handles/NotifierNotificationHandle.cs +++ /dev/null @@ -1,55 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Runtime.InteropServices; -using Realms.Server.Native; - -namespace Realms.Server -{ - internal class NotifierNotificationHandle : RealmHandle - { - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void CalculationCompleteCallback(IntPtr details, IntPtr managedCallbackPtr); - - private static class NativeMethods - { - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_server_global_notifier_notification_get_changes", CallingConvention = CallingConvention.Cdecl)] - public static extern void get_changes(NotifierNotificationHandle handle, IntPtr callback, out NativeException nativeException); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_server_global_notifier_notification_destroy", CallingConvention = CallingConvention.Cdecl)] - public static extern void destroy(IntPtr handle); - } - - public void GetChanges(Action callback) - { - var handle = GCHandle.Alloc(callback); - NativeMethods.get_changes(this, GCHandle.ToIntPtr(handle), out var nativeException); - nativeException.ThrowIfNecessary(); - } - - internal NotifierNotificationHandle(IntPtr handle) : base(null, handle) - { - } - - protected override void Unbind() - { - NativeMethods.destroy(handle); - } - } -} diff --git a/Realm/Realm/Handles/ObjectHandle.cs b/Realm/Realm/Handles/ObjectHandle.cs index 411907a46d..22e9c2bf04 100644 --- a/Realm/Realm/Handles/ObjectHandle.cs +++ b/Realm/Realm/Handles/ObjectHandle.cs @@ -19,7 +19,9 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using Realms.Exceptions; using Realms.Native; +using Realms.Schema; namespace Realms { @@ -31,7 +33,7 @@ private static class NativeMethods #pragma warning disable SA1121 // Use built-in type alias [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_is_valid", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] public static extern bool get_is_valid(ObjectHandle objectHandle, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_key", CallingConvention = CallingConvention.Cdecl)] @@ -40,15 +42,13 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_destroy", CallingConvention = CallingConvention.Cdecl)] public static extern void destroy(IntPtr objectHandle); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_set_timestamp_ticks", CallingConvention = CallingConvention.Cdecl)] - public static extern void set_timestamp_ticks(ObjectHandle handle, IntPtr propertyIndex, Int64 value, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_primitive", CallingConvention = CallingConvention.Cdecl)] + public static extern void get_primitive(ObjectHandle handle, IntPtr propertyIndex, ref PrimitiveValue value, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_timestamp_ticks", CallingConvention = CallingConvention.Cdecl)] - public static extern Int64 get_timestamp_ticks(ObjectHandle handle, IntPtr propertyIndex, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_nullable_timestamp_ticks", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool get_nullable_timestamp_ticks(ObjectHandle handle, IntPtr propertyIndex, out Int64 retVal, out NativeException ex); + // value is IntPtr rather than PrimitiveValue due to a bug in .NET Core on Linux and Mac + // that causes incorrect marshalling of the struct. + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_set_primitive", CallingConvention = CallingConvention.Cdecl)] + public static extern void set_primitive(ObjectHandle handle, IntPtr propertyIndex, IntPtr value, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_set_string", CallingConvention = CallingConvention.Cdecl)] public static extern void set_string(ObjectHandle handle, IntPtr propertyIndex, @@ -56,7 +56,7 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_string", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_string(ObjectHandle handle, IntPtr propertyIndex, - IntPtr buffer, IntPtr bufsize, [MarshalAs(UnmanagedType.I1)] out bool isNull, out NativeException ex); + IntPtr buffer, IntPtr bufsize, [MarshalAs(UnmanagedType.U1)] out bool isNull, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_set_link", CallingConvention = CallingConvention.Cdecl)] public static extern void set_link(ObjectHandle handle, IntPtr propertyIndex, ObjectHandle targetHandle, out NativeException ex); @@ -64,6 +64,9 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_link", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_link(ObjectHandle handle, IntPtr propertyIndex, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_create_embedded", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr create_embedded_link(ObjectHandle handle, IntPtr propertyIndex, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_clear_link", CallingConvention = CallingConvention.Cdecl)] public static extern void clear_link(ObjectHandle handle, IntPtr propertyIndex, out NativeException ex); @@ -73,67 +76,26 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_set_null", CallingConvention = CallingConvention.Cdecl)] public static extern void set_null(ObjectHandle handle, IntPtr propertyIndex, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_set_bool", CallingConvention = CallingConvention.Cdecl)] - public static extern void set_bool(ObjectHandle handle, IntPtr propertyIndex, [MarshalAs(UnmanagedType.U1)] bool value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_bool", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool get_bool(ObjectHandle handle, IntPtr propertyIndex, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_nullable_bool", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool get_nullable_bool(ObjectHandle handle, IntPtr propertyIndex, out IntPtr retVal, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_set_int64", CallingConvention = CallingConvention.Cdecl)] - public static extern void set_int64(ObjectHandle handle, IntPtr propertyIndex, Int64 value, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_add_int64", CallingConvention = CallingConvention.Cdecl)] public static extern void add_int64(ObjectHandle handle, IntPtr propertyIndex, Int64 value, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_int64", CallingConvention = CallingConvention.Cdecl)] - public static extern Int64 get_int64(ObjectHandle handle, IntPtr propertyIndex, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_nullable_int64", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool get_nullable_int64(ObjectHandle handle, IntPtr propertyIndex, out Int64 retVal, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_set_float", CallingConvention = CallingConvention.Cdecl)] - public static extern void set_float(ObjectHandle handle, IntPtr propertyIndex, Single value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_float", CallingConvention = CallingConvention.Cdecl)] - public static extern Single get_float(ObjectHandle handle, IntPtr propertyIndex, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_nullable_float", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool get_nullable_float(ObjectHandle handle, IntPtr propertyIndex, out Single retVal, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_set_double", CallingConvention = CallingConvention.Cdecl)] - public static extern void set_double(ObjectHandle handle, IntPtr propertyIndex, Double value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_double", CallingConvention = CallingConvention.Cdecl)] - public static extern Double get_double(ObjectHandle handle, IntPtr propertyIndex, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_nullable_double", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool get_nullable_double(ObjectHandle handle, IntPtr propertyIndex, out Double retVal, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_set_binary", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr set_binary(ObjectHandle handle, IntPtr propertyIndex, IntPtr buffer, IntPtr bufferLength, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_binary", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_binary(ObjectHandle handle, IntPtr propertyIndex, - IntPtr buffer, IntPtr bufferLength, [MarshalAs(UnmanagedType.I1)] out bool is_null, out NativeException ex); + IntPtr buffer, IntPtr bufferLength, [MarshalAs(UnmanagedType.U1)] out bool is_null, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_remove", CallingConvention = CallingConvention.Cdecl)] public static extern void remove(ObjectHandle handle, RealmHandle realmHandle, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_equals_object", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] public static extern bool equals_object(ObjectHandle handle, ObjectHandle otherHandle, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_backlinks", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_backlinks(ObjectHandle objectHandle, IntPtr propertyIndex, out NativeException nativeException); + public static extern IntPtr get_backlinks(ObjectHandle objectHandle, IntPtr property_index, out NativeException nativeException); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_backlinks_for_type", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_backlinks_for_type(ObjectHandle objectHandle, TableHandle source_table, IntPtr source_property_index, out NativeException nativeException); @@ -148,7 +110,7 @@ private static class NativeMethods public static extern IntPtr get_backlink_count(ObjectHandle objectHandle, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_is_frozen", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] public static extern bool get_is_frozen(ObjectHandle objectHandle, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_freeze", CallingConvention = CallingConvention.Cdecl)] @@ -213,41 +175,21 @@ protected override void Unbind() NativeMethods.destroy(handle); } - public void SetDateTimeOffset(IntPtr propertyIndex, DateTimeOffset value) + public PrimitiveValue GetPrimitive(IntPtr propertyIndex, PropertyType type) { - var ticks = value.ToUniversalTime().Ticks; - NativeMethods.set_timestamp_ticks(this, propertyIndex, ticks, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void SetNullableDateTimeOffset(IntPtr propertyIndex, DateTimeOffset? value) - { - NativeException nativeException; - if (value.HasValue) - { - var ticks = value.Value.ToUniversalTime().Ticks; - NativeMethods.set_timestamp_ticks(this, propertyIndex, ticks, out nativeException); - } - else - { - NativeMethods.set_null(this, propertyIndex, out nativeException); - } + var result = new PrimitiveValue { Type = type }; + NativeMethods.get_primitive(this, propertyIndex, ref result, out var nativeException); nativeException.ThrowIfNecessary(); - } - public DateTimeOffset GetDateTimeOffset(IntPtr propertyIndex) - { - var ticks = NativeMethods.get_timestamp_ticks(this, propertyIndex, out var nativeException); - nativeException.ThrowIfNecessary(); - return new DateTimeOffset(ticks, TimeSpan.Zero); + return result; } - public DateTimeOffset? GetNullableDateTimeOffset(IntPtr propertyIndex) + public unsafe void SetPrimitive(IntPtr propertyIndex, PrimitiveValue value) { - var hasValue = NativeMethods.get_nullable_timestamp_ticks(this, propertyIndex, out var ticks, out var nativeException); + PrimitiveValue* valuePtr = &value; + NativeMethods.set_primitive(this, propertyIndex, new IntPtr(valuePtr), out var nativeException); nativeException.ThrowIfNecessary(); - return hasValue ? new DateTimeOffset(ticks, TimeSpan.Zero) : (DateTimeOffset?)null; } public void SetString(IntPtr propertyIndex, string value) @@ -267,11 +209,6 @@ public void SetString(IntPtr propertyIndex, string value) public void SetStringUnique(IntPtr propertyIndex, string value) { - if (value == null) - { - throw new ArgumentNullException(nameof(value), "Object identifiers cannot be null"); - } - if (GetString(propertyIndex) != value) { throw new InvalidOperationException("Once set, primary key properties may not be modified."); @@ -317,168 +254,20 @@ public IntPtr GetLinklist(IntPtr propertyIndex) return result; } - public void SetBoolean(IntPtr propertyIndex, bool value) - { - NativeMethods.set_bool(this, propertyIndex, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void SetNullableBoolean(IntPtr propertyIndex, bool? value) - { - NativeException nativeException; - if (value.HasValue) - { - NativeMethods.set_bool(this, propertyIndex, value.Value, out nativeException); - } - else - { - NativeMethods.set_null(this, propertyIndex, out nativeException); - } - - nativeException.ThrowIfNecessary(); - } - - public bool GetBoolean(IntPtr propertyIndex) - { - var result = NativeMethods.get_bool(this, propertyIndex, out var nativeException); - nativeException.ThrowIfNecessary(); - return result; - } - - public bool? GetNullableBoolean(IntPtr propertyIndex) - { - var hasValue = NativeMethods.get_nullable_bool(this, propertyIndex, out var value, out var nativeException); - nativeException.ThrowIfNecessary(); - return hasValue ? MarshalHelpers.IntPtrToBool(value) : (bool?)null; - } - - public void SetInt64(IntPtr propertyIndex, long value) - { - NativeMethods.set_int64(this, propertyIndex, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - public void AddInt64(IntPtr propertyIndex, long value) { NativeMethods.add_int64(this, propertyIndex, value, out var nativeException); nativeException.ThrowIfNecessary(); } - public void SetNullableInt64(IntPtr propertyIndex, long? value) - { - NativeException nativeException; - if (value.HasValue) - { - NativeMethods.set_int64(this, propertyIndex, value.Value, out nativeException); - } - else - { - NativeMethods.set_null(this, propertyIndex, out nativeException); - } - - nativeException.ThrowIfNecessary(); - } - - public void SetInt64Unique(IntPtr propertyIndex, long value) - { - if (GetInt64(propertyIndex) != value) - { - throw new InvalidOperationException("Once set, primary key properties may not be modified."); - } - } - - public void SetNullableInt64Unique(IntPtr propertyIndex, long? value) + public void SetPrimitiveUnique(IntPtr propertyIndex, PrimitiveValue value) { - if (GetNullableInt64(propertyIndex) != value) + if (!GetPrimitive(propertyIndex, value.Type).Equals(value)) { throw new InvalidOperationException("Once set, primary key properties may not be modified."); } } - public long GetInt64(IntPtr propertyIndex) - { - var result = NativeMethods.get_int64(this, propertyIndex, out var nativeException); - nativeException.ThrowIfNecessary(); - return result; - } - - public long? GetNullableInt64(IntPtr propertyIndex) - { - var hasValue = NativeMethods.get_nullable_int64(this, propertyIndex, out var value, out var nativeException); - nativeException.ThrowIfNecessary(); - return hasValue ? value : (long?)null; - } - - public void SetSingle(IntPtr propertyIndex, float value) - { - NativeMethods.set_float(this, propertyIndex, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void SetNullableSingle(IntPtr propertyIndex, float? value) - { - NativeException nativeException; - if (value.HasValue) - { - NativeMethods.set_float(this, propertyIndex, value.Value, out nativeException); - } - else - { - NativeMethods.set_null(this, propertyIndex, out nativeException); - } - - nativeException.ThrowIfNecessary(); - } - - public float GetSingle(IntPtr propertyIndex) - { - var result = NativeMethods.get_float(this, propertyIndex, out var nativeException); - nativeException.ThrowIfNecessary(); - return result; - } - - public float? GetNullableSingle(IntPtr propertyIndex) - { - var hasValue = NativeMethods.get_nullable_float(this, propertyIndex, out var value, out var nativeException); - nativeException.ThrowIfNecessary(); - return hasValue ? value : (float?)null; - } - - public void SetDouble(IntPtr propertyIndex, double value) - { - NativeMethods.set_double(this, propertyIndex, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void SetNullableDouble(IntPtr propertyIndex, double? value) - { - NativeException nativeException; - if (value.HasValue) - { - NativeMethods.set_double(this, propertyIndex, value.Value, out nativeException); - } - else - { - NativeMethods.set_null(this, propertyIndex, out nativeException); - } - - nativeException.ThrowIfNecessary(); - } - - public double GetDouble(IntPtr propertyIndex) - { - var result = NativeMethods.get_double(this, propertyIndex, out var nativeException); - nativeException.ThrowIfNecessary(); - return result; - } - - public double? GetNullableDouble(IntPtr propertyIndex) - { - var hasValue = NativeMethods.get_nullable_double(this, propertyIndex, out var value, out var nativeException); - nativeException.ThrowIfNecessary(); - return hasValue ? value : (double?)null; - } - public unsafe void SetByteArray(IntPtr propertyIndex, byte[] value) { MarshalHelpers.SetByteArray(value, (IntPtr buffer, IntPtr bufferSize, bool hasValue, out NativeException ex) => @@ -508,14 +297,14 @@ public void RemoveFromRealm(SharedRealmHandle realmHandle) public RealmList GetList(Realm realm, IntPtr propertyIndex, string objectType) { - var listHandle = new ListHandle(Root ?? this, GetLinklist(propertyIndex)); + var listHandle = new ListHandle(Root, GetLinklist(propertyIndex)); var metadata = objectType == null ? null : realm.Metadata[objectType]; return new RealmList(realm, listHandle, metadata); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObject instance will own its handle.")] + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObjectBase instance will own its handle.")] public T GetObject(Realm realm, IntPtr propertyIndex, string objectType) - where T : RealmObject + where T : RealmObjectBase { if (TryGetLink(propertyIndex, out var objectHandle)) { @@ -525,21 +314,42 @@ public T GetObject(Realm realm, IntPtr propertyIndex, string objectType) return null; } - public void SetObject(Realm realm, IntPtr propertyIndex, RealmObject @object) + public void SetObject(Realm realm, IntPtr propertyIndex, RealmObjectBase @object) { if (@object == null) { ClearLink(propertyIndex); } - else + else if (@object is RealmObject realmObj) + { + if (!realmObj.IsManaged) + { + realm.Add(realmObj); + } + + SetLink(propertyIndex, realmObj.ObjectHandle); + } + else if (@object is EmbeddedObject embeddedObj) { - if (!@object.IsManaged) + if (embeddedObj.IsManaged) { - realm.Add(@object); + throw new RealmException("Can't link to an embedded object that is already managed."); } - SetLink(propertyIndex, @object.ObjectHandle); + var handle = CreateEmbeddedObjectForProperty(propertyIndex); + realm.ManageEmbedded(embeddedObj, handle); } + else + { + throw new NotSupportedException($"Tried to add an object of type {@object.GetType().FullName} which does not inherit from RealmObject or EmbeddedObject"); + } + } + + public ObjectHandle CreateEmbeddedObjectForProperty(IntPtr propertyIndex) + { + var objPtr = NativeMethods.create_embedded_link(this, propertyIndex, out var ex); + ex.ThrowIfNecessary(); + return new ObjectHandle(Root, objPtr); } public ResultsHandle GetBacklinks(IntPtr propertyIndex) @@ -552,7 +362,7 @@ public ResultsHandle GetBacklinks(IntPtr propertyIndex) public ResultsHandle GetBacklinksForType(TableHandle table, IntPtr propertyIndex) { - var resultsPtr = NativeMethods.get_backlinks_for_type(this, table, (IntPtr)propertyIndex, out var nativeException); + var resultsPtr = NativeMethods.get_backlinks_for_type(this, table, propertyIndex, out var nativeException); nativeException.ThrowIfNecessary(); return new ResultsHandle(this, resultsPtr); diff --git a/Realm/Realm/Handles/QueryHandle.cs b/Realm/Realm/Handles/QueryHandle.cs index 616620c2e0..7e7a71fc18 100644 --- a/Realm/Realm/Handles/QueryHandle.cs +++ b/Realm/Realm/Handles/QueryHandle.cs @@ -31,7 +31,7 @@ namespace Realms internal class QueryHandle : RealmHandle { // This is a delegate type meant to represent one of the "query operator" methods such as float_less and bool_equal - internal delegate void Operation(QueryHandle queryPtr, ColumnKey columnKey, T value); + internal delegate void Operation(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr propertyIndex, T value); private static class NativeMethods { @@ -39,143 +39,63 @@ private static class NativeMethods #pragma warning disable SA1121 // Use built-in type alias [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_binary_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void binary_equal(QueryHandle queryPtr, ColumnKey columnKey, IntPtr buffer, IntPtr bufferLength, out NativeException ex); + public static extern void binary_equal(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, IntPtr buffer, IntPtr bufferLength, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_binary_not_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void binary_not_equal(QueryHandle queryPtr, ColumnKey columnKey, IntPtr buffer, IntPtr bufferLength, out NativeException ex); + public static extern void binary_not_equal(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, IntPtr buffer, IntPtr bufferLength, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_string_contains", CallingConvention = CallingConvention.Cdecl)] - public static extern void string_contains(QueryHandle queryPtr, ColumnKey columnKey, - [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.I1)] bool caseSensitive, out NativeException ex); + public static extern void string_contains(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, + [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.U1)] bool caseSensitive, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_string_starts_with", CallingConvention = CallingConvention.Cdecl)] - public static extern void string_starts_with(QueryHandle queryPtr, ColumnKey columnKey, - [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.I1)] bool caseSensitive, out NativeException ex); + public static extern void string_starts_with(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, + [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.U1)] bool caseSensitive, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_string_ends_with", CallingConvention = CallingConvention.Cdecl)] - public static extern void string_ends_with(QueryHandle queryPtr, ColumnKey columnKey, - [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.I1)] bool caseSensitive, out NativeException ex); + public static extern void string_ends_with(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, + [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.U1)] bool caseSensitive, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_string_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void string_equal(QueryHandle queryPtr, ColumnKey columnKey, - [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.I1)] bool caseSensitive, out NativeException ex); + public static extern void string_equal(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, + [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.U1)] bool caseSensitive, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_string_not_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void string_not_equal(QueryHandle queryPtr, ColumnKey columnKey, - [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.I1)] bool caseSensitive, out NativeException ex); + public static extern void string_not_equal(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, + [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.U1)] bool caseSensitive, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_string_like", CallingConvention = CallingConvention.Cdecl)] - public static extern void string_like(QueryHandle queryPtr, ColumnKey columnKey, - [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.I1)] bool caseSensitive, out NativeException ex); + public static extern void string_like(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, + [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.U1)] bool caseSensitive, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_bool_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void bool_equal(QueryHandle queryPtr, ColumnKey columnKey, IntPtr value, out NativeException ex); + // primitive is IntPtr rather than PrimitiveValue due to a bug in .NET Core on Linux and Mac + // that causes incorrect marshalling of the struct. + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_primitive_equal", CallingConvention = CallingConvention.Cdecl)] + public static extern void primitive_equal(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, IntPtr primitive, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_bool_not_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void bool_not_equal(QueryHandle queryPtr, ColumnKey columnKey, IntPtr value, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_primitive_not_equal", CallingConvention = CallingConvention.Cdecl)] + public static extern void primitive_not_equal(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, IntPtr primitive, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_int_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void int_equal(QueryHandle queryPtr, ColumnKey columnKey, IntPtr value, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_primitive_less", CallingConvention = CallingConvention.Cdecl)] + public static extern void primitive_less(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, IntPtr primitive, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_int_not_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void int_not_equal(QueryHandle queryPtr, ColumnKey columnKey, IntPtr value, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_primitive_less_equal", CallingConvention = CallingConvention.Cdecl)] + public static extern void primitive_less_equal(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, IntPtr primitive, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_int_less", CallingConvention = CallingConvention.Cdecl)] - public static extern void int_less(QueryHandle queryPtr, ColumnKey columnKey, IntPtr value, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_primitive_greater", CallingConvention = CallingConvention.Cdecl)] + public static extern void primitive_greater(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, IntPtr primitive, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_int_less_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void int_less_equal(QueryHandle queryPtr, ColumnKey columnKey, IntPtr value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_int_greater", CallingConvention = CallingConvention.Cdecl)] - public static extern void int_greater(QueryHandle queryPtr, ColumnKey columnKey, IntPtr value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_int_greater_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void int_greater_equal(QueryHandle queryPtr, ColumnKey columnKey, IntPtr value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_long_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void long_equal(QueryHandle queryPtr, ColumnKey columnKey, Int64 value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_long_not_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void long_not_equal(QueryHandle queryPtr, ColumnKey columnKey, Int64 value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_long_less", CallingConvention = CallingConvention.Cdecl)] - public static extern void long_less(QueryHandle queryPtr, ColumnKey columnKey, Int64 value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_long_less_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void long_less_equal(QueryHandle queryPtr, ColumnKey columnKey, Int64 value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_long_greater", CallingConvention = CallingConvention.Cdecl)] - public static extern void long_greater(QueryHandle queryPtr, ColumnKey columnKey, Int64 value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_long_greater_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void long_greater_equal(QueryHandle queryPtr, ColumnKey columnKey, Int64 value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_float_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void float_equal(QueryHandle queryPtr, ColumnKey columnKey, Single value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_float_not_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void float_not_equal(QueryHandle queryPtr, ColumnKey columnKey, Single value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_float_less", CallingConvention = CallingConvention.Cdecl)] - public static extern void float_less(QueryHandle queryPtr, ColumnKey columnKey, Single value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_float_less_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void float_less_equal(QueryHandle queryPtr, ColumnKey columnKey, Single value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_float_greater", CallingConvention = CallingConvention.Cdecl)] - public static extern void float_greater(QueryHandle queryPtr, ColumnKey columnKey, Single value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_float_greater_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void float_greater_equal(QueryHandle queryPtr, ColumnKey columnKey, Single value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_double_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void double_equal(QueryHandle queryPtr, ColumnKey columnKey, Double value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_double_not_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void double_not_equal(QueryHandle queryPtr, ColumnKey columnKey, Double value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_double_less", CallingConvention = CallingConvention.Cdecl)] - public static extern void double_less(QueryHandle queryPtr, ColumnKey columnKey, Double value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_double_less_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void double_less_equal(QueryHandle queryPtr, ColumnKey columnKey, Double value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_double_greater", CallingConvention = CallingConvention.Cdecl)] - public static extern void double_greater(QueryHandle queryPtr, ColumnKey columnKey, Double value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_double_greater_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void double_greater_equal(QueryHandle queryPtr, ColumnKey columnKey, Double value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_timestamp_ticks_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void timestamp_ticks_equal(QueryHandle queryPtr, ColumnKey columnKey, Int64 value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_timestamp_ticks_not_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void timestamp_ticks_not_equal(QueryHandle queryPtr, ColumnKey columnKey, Int64 value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_timestamp_ticks_less", CallingConvention = CallingConvention.Cdecl)] - public static extern void timestamp_ticks_less(QueryHandle queryPtr, ColumnKey columnKey, Int64 value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_timestamp_ticks_less_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void timestamp_ticks_less_equal(QueryHandle queryPtr, ColumnKey columnKey, Int64 value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_timestamp_ticks_greater", CallingConvention = CallingConvention.Cdecl)] - public static extern void timestamp_ticks_greater(QueryHandle queryPtr, ColumnKey columnKey, Int64 value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_timestamp_ticks_greater_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void timestamp_ticks_greater_equal(QueryHandle queryPtr, ColumnKey columnKey, Int64 value, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_primitive_greater_equal", CallingConvention = CallingConvention.Cdecl)] + public static extern void primitive_greater_equal(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, IntPtr primitive, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_object_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void query_object_equal(QueryHandle queryPtr, ColumnKey columnKey, ObjectHandle objectHandle, out NativeException ex); + public static extern void query_object_equal(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, ObjectHandle objectHandle, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_null_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void null_equal(QueryHandle queryPtr, ColumnKey columnKey, out NativeException ex); + public static extern void null_equal(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_null_not_equal", CallingConvention = CallingConvention.Cdecl)] - public static extern void null_not_equal(QueryHandle queryPtr, ColumnKey columnKey, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_get_column_key", CallingConvention = CallingConvention.Cdecl)] - public static extern void get_column_key(QueryHandle queryPtr, - [MarshalAs(UnmanagedType.LPWStr)] string columnName, IntPtr columnNameLen, out ColumnKey key, out NativeException ex); + public static extern void null_not_equal(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_not", CallingConvention = CallingConvention.Cdecl)] public static extern void not(QueryHandle queryHandle, out NativeException ex); @@ -211,307 +131,138 @@ protected override void Unbind() NativeMethods.destroy(handle); } - public NumericQueryMethods NumericEqualMethods => new NumericQueryMethods(IntEqual, LongEqual, FloatEqual, DoubleEqual); - - public NumericQueryMethods NumericNotEqualMethods => new NumericQueryMethods(IntNotEqual, LongNotEqual, FloatNotEqual, DoubleNotEqual); - - public NumericQueryMethods NumericLessMethods => new NumericQueryMethods(IntLess, LongLess, FloatLess, DoubleLess); - - public NumericQueryMethods NumericLessEqualMethods => new NumericQueryMethods(IntLessEqual, LongLessEqual, FloatLessEqual, DoubleLessEqual); - - public NumericQueryMethods NumericGreaterMethods => new NumericQueryMethods(IntGreater, LongGreater, FloatGreater, DoubleGreater); - - public NumericQueryMethods NumericGreaterEqualMethods => new NumericQueryMethods(IntGreaterEqual, LongGreaterEqual, FloatGreaterEqual, DoubleGreaterEqual); - - public void BinaryEqual(ColumnKey columnKey, IntPtr buffer, IntPtr bufferLength) + public void BinaryEqual(SharedRealmHandle realm, IntPtr propertyIndex, IntPtr buffer, IntPtr bufferLength) { - NativeMethods.binary_equal(this, columnKey, buffer, bufferLength, out var nativeException); + NativeMethods.binary_equal(this, realm, propertyIndex, buffer, bufferLength, out var nativeException); nativeException.ThrowIfNecessary(); } - public void BinaryNotEqual(ColumnKey columnKey, IntPtr buffer, IntPtr bufferLength) + public void BinaryNotEqual(SharedRealmHandle realm, IntPtr propertyIndex, IntPtr buffer, IntPtr bufferLength) { - NativeMethods.binary_not_equal(this, columnKey, buffer, bufferLength, out var nativeException); + NativeMethods.binary_not_equal(this, realm, propertyIndex, buffer, bufferLength, out var nativeException); nativeException.ThrowIfNecessary(); } /// /// If the user hasn't specified it, should be caseSensitive=true. /// - public void StringContains(ColumnKey columnKey, string value, bool caseSensitive) + public void StringContains(SharedRealmHandle realm, IntPtr propertyIndex, string value, bool caseSensitive) { - NativeMethods.string_contains(this, columnKey, value, (IntPtr)value.Length, caseSensitive, out var nativeException); + NativeMethods.string_contains(this, realm, propertyIndex, value, (IntPtr)value.Length, caseSensitive, out var nativeException); nativeException.ThrowIfNecessary(); } /// /// If the user hasn't specified it, should be caseSensitive = true. /// - public void StringStartsWith(ColumnKey columnKey, string value, bool caseSensitive) + public void StringStartsWith(SharedRealmHandle realm, IntPtr propertyIndex, string value, bool caseSensitive) { - NativeMethods.string_starts_with(this, columnKey, value, (IntPtr)value.Length, caseSensitive, out var nativeException); + NativeMethods.string_starts_with(this, realm, propertyIndex, value, (IntPtr)value.Length, caseSensitive, out var nativeException); nativeException.ThrowIfNecessary(); } /// /// If the user hasn't specified it, should be caseSensitive = true. /// - public void StringEndsWith(ColumnKey columnKey, string value, bool caseSensitive) + public void StringEndsWith(SharedRealmHandle realm, IntPtr propertyIndex, string value, bool caseSensitive) { - NativeMethods.string_ends_with(this, columnKey, value, (IntPtr)value.Length, caseSensitive, out var nativeException); + NativeMethods.string_ends_with(this, realm, propertyIndex, value, (IntPtr)value.Length, caseSensitive, out var nativeException); nativeException.ThrowIfNecessary(); } /// /// If the user hasn't specified it, should be caseSensitive = true. /// - public void StringEqual(ColumnKey columnKey, string value, bool caseSensitive) + public void StringEqual(SharedRealmHandle realm, IntPtr propertyIndex, string value, bool caseSensitive) { - NativeMethods.string_equal(this, columnKey, value, (IntPtr)value.Length, caseSensitive, out var nativeException); + NativeMethods.string_equal(this, realm, propertyIndex, value, (IntPtr)value.Length, caseSensitive, out var nativeException); nativeException.ThrowIfNecessary(); } /// /// If the user hasn't specified it, should be caseSensitive = true. /// - public void StringNotEqual(ColumnKey columnKey, string value, bool caseSensitive) + public void StringNotEqual(SharedRealmHandle realm, IntPtr propertyIndex, string value, bool caseSensitive) { - NativeMethods.string_not_equal(this, columnKey, value, (IntPtr)value.Length, caseSensitive, out var nativeException); + NativeMethods.string_not_equal(this, realm, propertyIndex, value, (IntPtr)value.Length, caseSensitive, out var nativeException); nativeException.ThrowIfNecessary(); } - public void StringLike(ColumnKey columnKey, string value, bool caseSensitive) + public void StringLike(SharedRealmHandle realm, IntPtr propertyIndex, string value, bool caseSensitive) { NativeException nativeException; if (value == null) { - NativeMethods.null_equal(this, columnKey, out nativeException); + NativeMethods.null_equal(this, realm, propertyIndex, out nativeException); } else { - NativeMethods.string_like(this, columnKey, value, (IntPtr)value.Length, caseSensitive, out nativeException); + NativeMethods.string_like(this, realm, propertyIndex, value, (IntPtr)value.Length, caseSensitive, out nativeException); } nativeException.ThrowIfNecessary(); } - public void BoolEqual(ColumnKey columnKey, bool value) - { - NativeMethods.bool_equal(this, columnKey, MarshalHelpers.BoolToIntPtr(value), out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void BoolNotEqual(ColumnKey columnKey, bool value) - { - NativeMethods.bool_not_equal(this, columnKey, MarshalHelpers.BoolToIntPtr(value), out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void IntEqual(ColumnKey columnKey, int value) - { - NativeMethods.int_equal(this, columnKey, (IntPtr)value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void IntNotEqual(ColumnKey columnKey, int value) - { - NativeMethods.int_not_equal(this, columnKey, (IntPtr)value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void IntLess(ColumnKey columnKey, int value) - { - NativeMethods.int_less(this, columnKey, (IntPtr)value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void IntLessEqual(ColumnKey columnKey, int value) - { - NativeMethods.int_less_equal(this, columnKey, (IntPtr)value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void IntGreater(ColumnKey columnKey, int value) - { - NativeMethods.int_greater(this, columnKey, (IntPtr)value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void IntGreaterEqual(ColumnKey columnKey, int value) - { - NativeMethods.int_greater_equal(this, columnKey, (IntPtr)value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void LongEqual(ColumnKey columnKey, long value) - { - NativeMethods.long_equal(this, columnKey, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void LongNotEqual(ColumnKey columnKey, long value) - { - NativeMethods.long_not_equal(this, columnKey, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void LongLess(ColumnKey columnKey, long value) - { - NativeMethods.long_less(this, columnKey, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void LongLessEqual(ColumnKey columnKey, long value) - { - NativeMethods.long_less_equal(this, columnKey, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void LongGreater(ColumnKey columnKey, long value) - { - NativeMethods.long_greater(this, columnKey, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void LongGreaterEqual(ColumnKey columnKey, long value) - { - NativeMethods.long_greater_equal(this, columnKey, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void FloatEqual(ColumnKey columnKey, float value) - { - NativeMethods.float_equal(this, columnKey, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void FloatNotEqual(ColumnKey columnKey, float value) - { - NativeMethods.float_not_equal(this, columnKey, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void FloatLess(ColumnKey columnKey, float value) - { - NativeMethods.float_less(this, columnKey, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void FloatLessEqual(ColumnKey columnKey, float value) - { - NativeMethods.float_less_equal(this, columnKey, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void FloatGreater(ColumnKey columnKey, float value) - { - NativeMethods.float_greater(this, columnKey, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void FloatGreaterEqual(ColumnKey columnKey, float value) - { - NativeMethods.float_greater_equal(this, columnKey, value, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void DoubleEqual(ColumnKey columnKey, double value) + public unsafe void PrimitiveEqual(SharedRealmHandle realm, IntPtr propertyIndex, PrimitiveValue value) { - NativeMethods.double_equal(this, columnKey, value, out var nativeException); + PrimitiveValue* valuePtr = &value; + NativeMethods.primitive_equal(this, realm, propertyIndex, new IntPtr(valuePtr), out var nativeException); nativeException.ThrowIfNecessary(); } - public void DoubleNotEqual(ColumnKey columnKey, double value) + public unsafe void PrimitiveNotEqual(SharedRealmHandle realm, IntPtr propertyIndex, PrimitiveValue value) { - NativeMethods.double_not_equal(this, columnKey, value, out var nativeException); + PrimitiveValue* valuePtr = &value; + NativeMethods.primitive_not_equal(this, realm, propertyIndex, new IntPtr(valuePtr), out var nativeException); nativeException.ThrowIfNecessary(); } - public void DoubleLess(ColumnKey columnKey, double value) + public unsafe void PrimitiveLess(SharedRealmHandle realm, IntPtr propertyIndex, PrimitiveValue value) { - NativeMethods.double_less(this, columnKey, value, out var nativeException); + PrimitiveValue* valuePtr = &value; + NativeMethods.primitive_less(this, realm, propertyIndex, new IntPtr(valuePtr), out var nativeException); nativeException.ThrowIfNecessary(); } - public void DoubleLessEqual(ColumnKey columnKey, double value) + public unsafe void PrimitiveLessEqual(SharedRealmHandle realm, IntPtr propertyIndex, PrimitiveValue value) { - NativeMethods.double_less_equal(this, columnKey, value, out var nativeException); + PrimitiveValue* valuePtr = &value; + NativeMethods.primitive_less_equal(this, realm, propertyIndex, new IntPtr(valuePtr), out var nativeException); nativeException.ThrowIfNecessary(); } - public void DoubleGreater(ColumnKey columnKey, double value) + public unsafe void PrimitiveGreater(SharedRealmHandle realm, IntPtr propertyIndex, PrimitiveValue value) { - NativeMethods.double_greater(this, columnKey, value, out var nativeException); + PrimitiveValue* valuePtr = &value; + NativeMethods.primitive_greater(this, realm, propertyIndex, new IntPtr(valuePtr), out var nativeException); nativeException.ThrowIfNecessary(); } - public void DoubleGreaterEqual(ColumnKey columnKey, double value) + public unsafe void PrimitiveGreaterEqual(SharedRealmHandle realm, IntPtr propertyIndex, PrimitiveValue value) { - NativeMethods.double_greater_equal(this, columnKey, value, out var nativeException); + PrimitiveValue* valuePtr = &value; + NativeMethods.primitive_greater_equal(this, realm, propertyIndex, new IntPtr(valuePtr), out var nativeException); nativeException.ThrowIfNecessary(); } - public void TimestampTicksEqual(ColumnKey columnKey, DateTimeOffset value) + public void ObjectEqual(SharedRealmHandle realm, IntPtr propertyIndex, ObjectHandle objectHandle) { - NativeMethods.timestamp_ticks_equal(this, columnKey, value.ToUniversalTime().Ticks, out var nativeException); + NativeMethods.query_object_equal(this, realm, propertyIndex, objectHandle, out var nativeException); nativeException.ThrowIfNecessary(); } - public void TimestampTicksNotEqual(ColumnKey columnKey, DateTimeOffset value) + public void NullEqual(SharedRealmHandle realm, IntPtr propertyIndex) { - NativeMethods.timestamp_ticks_not_equal(this, columnKey, value.ToUniversalTime().Ticks, out var nativeException); + NativeMethods.null_equal(this, realm, propertyIndex, out var nativeException); nativeException.ThrowIfNecessary(); } - public void TimestampTicksLess(ColumnKey columnKey, DateTimeOffset value) + public void NullNotEqual(SharedRealmHandle realm, IntPtr propertyIndex) { - NativeMethods.timestamp_ticks_less(this, columnKey, value.ToUniversalTime().Ticks, out var nativeException); + NativeMethods.null_not_equal(this, realm, propertyIndex, out var nativeException); nativeException.ThrowIfNecessary(); } - public void TimestampTicksLessEqual(ColumnKey columnKey, DateTimeOffset value) - { - NativeMethods.timestamp_ticks_less_equal(this, columnKey, value.ToUniversalTime().Ticks, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void TimestampTicksGreater(ColumnKey columnKey, DateTimeOffset value) - { - NativeMethods.timestamp_ticks_greater(this, columnKey, value.ToUniversalTime().Ticks, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void TimestampTicksGreaterEqual(ColumnKey columnKey, DateTimeOffset value) - { - NativeMethods.timestamp_ticks_greater_equal(this, columnKey, value.ToUniversalTime().Ticks, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void ObjectEqual(ColumnKey columnKey, ObjectHandle objectHandle) - { - NativeMethods.query_object_equal(this, columnKey, objectHandle, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void NullEqual(ColumnKey columnKey) - { - NativeMethods.null_equal(this, columnKey, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void NullNotEqual(ColumnKey columnKey) - { - NativeMethods.null_not_equal(this, columnKey, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public ColumnKey GetColumnKey(string columnName) - { - NativeMethods.get_column_key(this, columnName, (IntPtr)columnName.Length, out var result, out var nativeException); - nativeException.ThrowIfNecessary(); - return result; - } - public void Not() { NativeMethods.not(this, out var nativeException); @@ -549,25 +300,5 @@ public ResultsHandle CreateResults(SharedRealmHandle sharedRealm, SortDescriptor nativeException.ThrowIfNecessary(); return new ResultsHandle(sharedRealm, result); } - - public struct NumericQueryMethods - { - public readonly Action Int; - - public readonly Action Long; - - public readonly Action Float; - - public readonly Action Double; - - public NumericQueryMethods(Action intQuery, Action longQuery, - Action floatQuery, Action doubleQuery) - { - Int = intQuery; - Long = longQuery; - Float = floatQuery; - Double = doubleQuery; - } - } } } diff --git a/Realm/Realm/Handles/ResultsHandle.cs b/Realm/Realm/Handles/ResultsHandle.cs index 4ec658d48c..78ce6da694 100644 --- a/Realm/Realm/Handles/ResultsHandle.cs +++ b/Realm/Realm/Handles/ResultsHandle.cs @@ -46,11 +46,11 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "results_get_string", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_string(ResultsHandle results, IntPtr link_ndx, IntPtr buffer, IntPtr bufsize, - [MarshalAs(UnmanagedType.I1)] out bool isNull, out NativeException ex); + [MarshalAs(UnmanagedType.U1)] out bool isNull, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "results_get_binary", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_binary(ResultsHandle results, IntPtr link_ndx, IntPtr buffer, IntPtr bufsize, - [MarshalAs(UnmanagedType.I1)] out bool isNull, out NativeException ex); + [MarshalAs(UnmanagedType.U1)] out bool isNull, out NativeException ex); #endregion @@ -67,7 +67,7 @@ private static class NativeMethods public static extern IntPtr get_query(ResultsHandle results, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "results_get_is_valid", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] public static extern bool get_is_valid(ResultsHandle results, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "results_get_thread_safe_reference", CallingConvention = CallingConvention.Cdecl)] @@ -86,7 +86,7 @@ private static class NativeMethods public static extern IntPtr get_sort_descriptor(ResultsHandle results, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "results_get_is_frozen", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] public static extern bool get_is_frozen(ResultsHandle results, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "results_freeze", CallingConvention = CallingConvention.Cdecl)] diff --git a/Realm/Realm/Handles/SessionHandle.cs b/Realm/Realm/Handles/SessionHandle.cs index 68f5382744..cddb893656 100644 --- a/Realm/Realm/Handles/SessionHandle.cs +++ b/Realm/Realm/Handles/SessionHandle.cs @@ -18,7 +18,12 @@ using System; using System.Runtime.InteropServices; +using System.Text; using System.Threading.Tasks; +using Realms.Exceptions; +using Realms.Native; +using Realms.Sync.Exceptions; +using Realms.Sync.Native; namespace Realms.Sync { @@ -26,27 +31,29 @@ internal class SessionHandle : RealmHandle { private static class NativeMethods { - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_refresh_access_token", CallingConvention = CallingConvention.Cdecl)] - public static extern void refresh_access_token(SessionHandle session, - [MarshalAs(UnmanagedType.LPWStr)] string access_token, IntPtr access_token_len, - [MarshalAs(UnmanagedType.LPWStr)] string server_path, IntPtr server_path_len, - out NativeException ex); +#pragma warning disable IDE1006 // Naming Styles +#pragma warning disable SA1121 // Use built-in type alias + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public unsafe delegate void SessionErrorCallback(IntPtr session_handle_ptr, ErrorCode error_code, byte* message_buf, IntPtr message_len, IntPtr user_info_pairs, int user_info_pairs_len, [MarshalAs(UnmanagedType.U1)] bool is_client_reset); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void SessionProgressCallback(IntPtr progress_token_ptr, ulong transferred_bytes, ulong transferable_bytes); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public unsafe delegate void SessionWaitCallback(IntPtr task_completion_source, int error_code, byte* message_buf, IntPtr message_len); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_install_callbacks", CallingConvention = CallingConvention.Cdecl)] + public static extern void install_syncsession_callbacks(SessionErrorCallback error_callback, SessionProgressCallback progress_callback, SessionWaitCallback wait_callback); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_get_user", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_user(SessionHandle session); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_get_uri", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_uri(SessionHandle session, IntPtr buffer, IntPtr buffer_length, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_get_state", CallingConvention = CallingConvention.Cdecl)] public static extern SessionState get_state(SessionHandle session, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_get_path", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_path(SessionHandle session, IntPtr buffer, IntPtr buffer_length, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_get_from_path", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_from_path([MarshalAs(UnmanagedType.LPWStr)] string path, IntPtr path_len, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_get_raw_pointer", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_raw_pointer(SessionHandle session); @@ -57,18 +64,18 @@ private static class NativeMethods public static extern ulong register_progress_notifier(SessionHandle session, IntPtr token_ptr, ProgressDirection direction, - [MarshalAs(UnmanagedType.I1)] bool is_streaming, + [MarshalAs(UnmanagedType.U1)] bool is_streaming, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_unregister_progress_notifier", CallingConvention = CallingConvention.Cdecl)] public static extern void unregister_progress_notifier(SessionHandle session, ulong token, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_wait", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] public static extern void wait(SessionHandle session, IntPtr task_completion_source, ProgressDirection direction, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_report_error_for_testing", CallingConvention = CallingConvention.Cdecl)] - public static extern void report_error_for_testing(SessionHandle session, int error_code, [MarshalAs(UnmanagedType.LPWStr)] string message, IntPtr message_len, [MarshalAs(UnmanagedType.I1)] bool is_fatal); + public static extern void report_error_for_testing(SessionHandle session, int error_code, [MarshalAs(UnmanagedType.LPWStr)] string message, IntPtr message_len, [MarshalAs(UnmanagedType.U1)] bool is_fatal); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_stop", CallingConvention = CallingConvention.Cdecl)] public static extern void stop(SessionHandle session, out NativeException ex); @@ -76,11 +83,8 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_start", CallingConvention = CallingConvention.Cdecl)] public static extern void start(SessionHandle session, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_set_multiplex_identifier", CallingConvention = CallingConvention.Cdecl)] - public static extern void set_multiplex_identifier(SessionHandle session, [MarshalAs(UnmanagedType.LPWStr)] string identifier, IntPtr identifier_len, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_set_url_prefix", CallingConvention = CallingConvention.Cdecl)] - public static extern void set_url_prefix(SessionHandle session, [MarshalAs(UnmanagedType.LPWStr)] string prefix, IntPtr prefix_len, out NativeException ex); +#pragma warning restore IDE1006 // Naming Styles +#pragma warning restore SA1121 // Use built-in type alias } static SessionHandle() @@ -93,6 +97,19 @@ public SessionHandle(IntPtr handle) : base(null, handle) { } + public static unsafe void InstallCallbacks() + { + NativeMethods.SessionErrorCallback error = HandleSessionError; + NativeMethods.SessionProgressCallback progress = HandleSessionProgress; + NativeMethods.SessionWaitCallback wait = HandleSessionWaitCallback; + + GCHandle.Alloc(error); + GCHandle.Alloc(progress); + GCHandle.Alloc(wait); + + NativeMethods.install_syncsession_callbacks(error, progress, wait); + } + public bool TryGetUser(out SyncUserHandle handle) { var ptr = NativeMethods.get_user(this); @@ -106,15 +123,6 @@ public bool TryGetUser(out SyncUserHandle handle) return true; } - public string GetServerUri() - { - return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => - { - isNull = false; - return NativeMethods.get_uri(this, buffer, length, out ex); - }); - } - public SessionState GetState() { var state = NativeMethods.get_state(this, out var ex); @@ -131,24 +139,6 @@ public string GetPath() }); } - public void SetMultiplexIdentifier(string multiplexIdentifier) - { - NativeMethods.set_multiplex_identifier(this, multiplexIdentifier, (IntPtr)multiplexIdentifier.Length, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void SetUrlPrefix(string urlPrefix) - { - NativeMethods.set_url_prefix(this, urlPrefix, (IntPtr)urlPrefix.Length, out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public void RefreshAccessToken(string accessToken, string serverPath) - { - NativeMethods.refresh_access_token(this, accessToken, (IntPtr)accessToken.Length, serverPath, (IntPtr)serverPath.Length, out var ex); - ex.ThrowIfNecessary(); - } - public ulong RegisterProgressNotifier(GCHandle managedHandle, ProgressDirection direction, ProgressMode mode) { var isStreaming = mode == ProgressMode.ReportIndefinitely; @@ -192,16 +182,68 @@ public void Start() ex.ThrowIfNecessary(); } - public static SessionHandle GetSessionForPath(string path) + protected override void Unbind() { - var ptr = NativeMethods.get_from_path(path, (IntPtr)path.Length, out var ex); - ex.ThrowIfNecessary(); - return new SessionHandle(ptr); + NativeMethods.destroy(handle); } - protected override void Unbind() + [MonoPInvokeCallback(typeof(NativeMethods.SessionErrorCallback))] + private static unsafe void HandleSessionError(IntPtr sessionHandlePtr, ErrorCode errorCode, byte* messageBuffer, IntPtr messageLength, IntPtr userInfoPairs, int userInfoPairsLength, bool isClientReset) { - NativeMethods.destroy(handle); + var handle = new SessionHandle(sessionHandlePtr); + var session = new Session(handle); + var message = Encoding.UTF8.GetString(messageBuffer, (int)messageLength); + + SessionException exception; + + if (isClientReset) + { + var userInfo = StringStringPair.UnmarshalDictionary(userInfoPairs, userInfoPairsLength); + exception = new ClientResetException(session.User.App, message, userInfo); + } + else if (errorCode == ErrorCode.PermissionDenied) + { + var userInfo = StringStringPair.UnmarshalDictionary(userInfoPairs, userInfoPairsLength); + exception = new PermissionDeniedException(session.User.App, message, userInfo); + } + else + { + exception = new SessionException(message, errorCode); + } + + Session.RaiseError(session, exception); + } + + [MonoPInvokeCallback(typeof(NativeMethods.SessionProgressCallback))] + private static void HandleSessionProgress(IntPtr tokenPtr, ulong transferredBytes, ulong transferableBytes) + { + var token = (ProgressNotificationToken)GCHandle.FromIntPtr(tokenPtr).Target; + token.Notify(transferredBytes, transferableBytes); + } + + [MonoPInvokeCallback(typeof(NativeMethods.SessionWaitCallback))] + private static unsafe void HandleSessionWaitCallback(IntPtr taskCompletionSource, int error_code, byte* messageBuffer, IntPtr messageLength) + { + var handle = GCHandle.FromIntPtr(taskCompletionSource); + var tcs = (TaskCompletionSource)handle.Target; + + try + { + if (error_code == 0) + { + tcs.TrySetResult(null); + } + else + { + var inner = new SessionException(Encoding.UTF8.GetString(messageBuffer, (int)messageLength), (ErrorCode)error_code); + const string OuterMessage = "A system error occurred while waiting for completion. See InnerException for more details"; + tcs.TrySetException(new RealmException(OuterMessage, inner)); + } + } + finally + { + handle.Free(); + } } } } diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index 06a08b1e4a..7c01221d48 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -18,11 +18,16 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using MongoDB.Bson; using Realms.Exceptions; using Realms.Native; using Realms.Schema; +using Realms.Sync.Exceptions; namespace Realms { @@ -39,6 +44,9 @@ private static class NativeMethods [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void GetNativeSchemaCallback(Native.Schema schema, IntPtr managed_callback); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public unsafe delegate void OpenRealmCallback(IntPtr task_completion_source, IntPtr shared_realm, int error_code, byte* message_buf, IntPtr message_len); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_open", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr open(Configuration configuration, [MarshalAs(UnmanagedType.LPArray), In] SchemaObject[] objects, int objects_length, @@ -46,6 +54,21 @@ private static class NativeMethods byte[] encryptionKey, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_open_with_sync", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr open_with_sync(Configuration configuration, Sync.Native.SyncConfiguration sync_configuration, + [MarshalAs(UnmanagedType.LPArray), In] SchemaObject[] objects, int objects_length, + [MarshalAs(UnmanagedType.LPArray), In] SchemaProperty[] properties, + byte[] encryptionKey, + out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_open_with_sync_async", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr open_with_sync_async(Configuration configuration, Sync.Native.SyncConfiguration sync_configuration, + [MarshalAs(UnmanagedType.LPArray), In] SchemaObject[] objects, int objects_length, + [MarshalAs(UnmanagedType.LPArray), In] SchemaProperty[] properties, + byte[] encryptionKey, + IntPtr task_completion_source, + out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_set_managed_state_handle", CallingConvention = CallingConvention.Cdecl)] public static extern void set_managed_state_handle(SharedRealmHandle sharedRealm, IntPtr managedStateHandle, out NativeException ex); @@ -68,25 +91,25 @@ private static class NativeMethods public static extern void cancel_transaction(SharedRealmHandle sharedRealm, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_is_in_transaction", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] public static extern bool is_in_transaction(SharedRealmHandle sharedRealm, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_refresh", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] public static extern bool refresh(SharedRealmHandle sharedRealm, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_table", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_table(SharedRealmHandle sharedRealm, [MarshalAs(UnmanagedType.LPWStr)] string tableName, IntPtr tableNameLength, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_is_same_instance", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] public static extern bool is_same_instance(SharedRealmHandle lhs, SharedRealmHandle rhs, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_schema_version", CallingConvention = CallingConvention.Cdecl)] public static extern ulong get_schema_version(SharedRealmHandle sharedRealm, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_compact", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] public static extern bool compact(SharedRealmHandle sharedRealm, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_resolve_object_reference", CallingConvention = CallingConvention.Cdecl)] @@ -107,30 +130,31 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_create_object", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr create_object(SharedRealmHandle sharedRealm, TableHandle table, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_create_object_int_unique", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr create_object_unique(SharedRealmHandle sharedRealm, TableHandle table, long key, [MarshalAs(UnmanagedType.I1)] bool has_value, - [MarshalAs(UnmanagedType.I1)] bool is_nullable, - [MarshalAs(UnmanagedType.I1)] bool update, - [MarshalAs(UnmanagedType.I1)] out bool is_new, out NativeException ex); + // value is IntPtr rather than PrimitiveValue due to a bug in .NET Core on Linux and Mac + // that causes incorrect marshalling of the struct. + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_create_object_primitive_unique", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr create_object_unique(SharedRealmHandle sharedRealm, TableHandle table, IntPtr value, + [MarshalAs(UnmanagedType.U1)] bool update, + [MarshalAs(UnmanagedType.U1)] out bool is_new, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_create_object_string_unique", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr create_object_unique(SharedRealmHandle sharedRealm, TableHandle table, [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, - [MarshalAs(UnmanagedType.I1)] bool update, - [MarshalAs(UnmanagedType.I1)] out bool is_new, out NativeException ex); + [MarshalAs(UnmanagedType.U1)] bool update, + [MarshalAs(UnmanagedType.U1)] out bool is_new, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_schema", CallingConvention = CallingConvention.Cdecl)] public static extern void get_schema(SharedRealmHandle sharedRealm, IntPtr callback, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_install_callbacks", CallingConvention = CallingConvention.Cdecl)] - public static extern void install_callbacks(NotifyRealmCallback notifyRealmCallback, GetNativeSchemaCallback nativeSchemaCallback); + public static extern void install_callbacks(NotifyRealmCallback notifyRealmCallback, GetNativeSchemaCallback nativeSchemaCallback, OpenRealmCallback open_callback); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_has_changed", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] public static extern bool has_changed(SharedRealmHandle sharedRealm); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_is_frozen", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] public static extern bool get_is_frozen(SharedRealmHandle sharedRealm, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_freeze", CallingConvention = CallingConvention.Cdecl)] @@ -140,17 +164,19 @@ private static class NativeMethods #pragma warning restore SA1121 // Use built-in type alias } - static SharedRealmHandle() + static unsafe SharedRealmHandle() { NativeCommon.Initialize(); NativeMethods.NotifyRealmCallback notifyRealm = NotifyRealmChanged; NativeMethods.GetNativeSchemaCallback getNativeSchema = GetNativeSchema; + NativeMethods.OpenRealmCallback openRealm = HandleOpenRealmCallback; GCHandle.Alloc(notifyRealm); GCHandle.Alloc(getNativeSchema); + GCHandle.Alloc(openRealm); - NativeMethods.install_callbacks(notifyRealm, getNativeSchema); + NativeMethods.install_callbacks(notifyRealm, getNativeSchema, openRealm); } [Preserve] @@ -172,6 +198,26 @@ public static IntPtr Open(Configuration configuration, RealmSchema schema, byte[ return result; } + public static SharedRealmHandle OpenWithSync(Configuration configuration, Sync.Native.SyncConfiguration syncConfiguration, RealmSchema schema, byte[] encryptionKey) + { + var marshaledSchema = new SchemaMarshaler(schema); + + var result = NativeMethods.open_with_sync(configuration, syncConfiguration, marshaledSchema.Objects, marshaledSchema.Objects.Length, marshaledSchema.Properties, encryptionKey, out var nativeException); + nativeException.ThrowIfNecessary(); + + return new SharedRealmHandle(result); + } + + public static AsyncOpenTaskHandle OpenWithSyncAsync(Configuration configuration, Sync.Native.SyncConfiguration syncConfiguration, RealmSchema schema, byte[] encryptionKey, GCHandle tcsHandle) + { + var marshaledSchema = new SchemaMarshaler(schema); + + var asyncTaskPtr = NativeMethods.open_with_sync_async(configuration, syncConfiguration, marshaledSchema.Objects, marshaledSchema.Objects.Length, marshaledSchema.Properties, encryptionKey, GCHandle.ToIntPtr(tcsHandle), out var nativeException); + nativeException.ThrowIfNecessary(); + var asyncTaskHandle = new AsyncOpenTaskHandle(asyncTaskPtr); + return asyncTaskHandle; + } + public static IntPtr ResolveFromReference(ThreadSafeReferenceHandle referenceHandle) { var result = NativeMethods.resolve_realm_reference(referenceHandle, out var nativeException); @@ -322,26 +368,48 @@ public ObjectHandle CreateObject(TableHandle table) return new ObjectHandle(this, result); } - public ObjectHandle CreateObjectWithPrimaryKey(Property pkProperty, object primaryKey, TableHandle table, string parentType, bool update, out bool isNew) + public unsafe ObjectHandle CreateObjectWithPrimaryKey(Property pkProperty, object primaryKey, TableHandle table, string parentType, bool update, out bool isNew) { if (primaryKey == null && !pkProperty.Type.IsNullable()) { throw new ArgumentException($"{parentType}'s primary key is defined as non-nullable, but the value passed is null"); } - switch (pkProperty.Type.UnderlyingType()) + PrimitiveValue primitiveValue; + + switch (pkProperty.Type) { case PropertyType.String: + case PropertyType.String | PropertyType.Nullable: var stringKey = (string)primaryKey; - return CreateObjectWithPrimaryKey(table, stringKey, update, out isNew); + var handle = NativeMethods.create_object_unique(this, table, stringKey, (IntPtr)(stringKey?.Length ?? 0), update, out isNew, out var nativeEx); + nativeEx.ThrowIfNecessary(); + return new ObjectHandle(this, handle); case PropertyType.Int: - var longKey = primaryKey == null ? (long?)null : Convert.ToInt64(primaryKey); - return CreateObjectWithPrimaryKey(table, longKey, pkProperty.Type.IsNullable(), update, out isNew); + primitiveValue = PrimitiveValue.Int(Convert.ToInt64(primaryKey)); + break; + + case PropertyType.NullableInt: + primitiveValue = PrimitiveValue.NullableInt(primaryKey == null ? (long?)null : Convert.ToInt64(primaryKey)); + break; + + case PropertyType.ObjectId: + primitiveValue = PrimitiveValue.ObjectId((ObjectId)primaryKey); + break; + + case PropertyType.NullableObjectId: + primitiveValue = PrimitiveValue.NullableObjectId(primaryKey == null ? (ObjectId?)null : (ObjectId)primaryKey); + break; default: throw new NotSupportedException($"Unexpected primary key of type: {pkProperty.Type}"); } + + PrimitiveValue* valuePtr = &primitiveValue; + var result = NativeMethods.create_object_unique(this, table, new IntPtr(valuePtr), update, out isNew, out var ex); + ex.ThrowIfNecessary(); + return new ObjectHandle(this, result); } public bool HasChanged() @@ -356,20 +424,6 @@ public SharedRealmHandle Freeze() return new SharedRealmHandle(result); } - private ObjectHandle CreateObjectWithPrimaryKey(TableHandle table, long? key, bool isNullable, bool update, out bool isNew) - { - var result = NativeMethods.create_object_unique(this, table, key ?? 0, key.HasValue, isNullable, update, out isNew, out var ex); - ex.ThrowIfNecessary(); - return new ObjectHandle(this, result); - } - - private ObjectHandle CreateObjectWithPrimaryKey(TableHandle table, string key, bool update, out bool isNew) - { - var result = NativeMethods.create_object_unique(this, table, key, (IntPtr)(key?.Length ?? 0), update, out isNew, out var ex); - ex.ThrowIfNecessary(); - return new ObjectHandle(this, result); - } - [MonoPInvokeCallback(typeof(NativeMethods.GetNativeSchemaCallback))] private static void GetNativeSchema(Native.Schema schema, IntPtr managedCallbackPtr) { @@ -386,6 +440,25 @@ public static void NotifyRealmChanged(IntPtr stateHandle) ((Realm.State)gch.Target).NotifyChanged(EventArgs.Empty); } + [MonoPInvokeCallback(typeof(NativeMethods.OpenRealmCallback))] + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The task awaiter will own the ThreadSafeReference handle.")] + private static unsafe void HandleOpenRealmCallback(IntPtr taskCompletionSource, IntPtr realm_reference, int error_code, byte* messageBuffer, IntPtr messageLength) + { + var handle = GCHandle.FromIntPtr(taskCompletionSource); + var tcs = (TaskCompletionSource)handle.Target; + + if (error_code == 0) + { + tcs.TrySetResult(new ThreadSafeReferenceHandle(realm_reference, isRealmReference: true)); + } + else + { + var inner = new SessionException(Encoding.UTF8.GetString(messageBuffer, (int)messageLength), (ErrorCode)error_code); + const string OuterMessage = "A system error occurred while opening a Realm. See InnerException for more details"; + tcs.TrySetException(new RealmException(OuterMessage, inner)); + } + } + public class SchemaMarshaler { public readonly SchemaObject[] Objects; @@ -405,7 +478,8 @@ public SchemaMarshaler(RealmSchema schema) { name = @object.Name, properties_start = start, - properties_end = properties.Count + properties_end = properties.Count, + is_embedded = @object.IsEmbedded, }; }).ToArray(); Properties = properties.ToArray(); diff --git a/Realm/Realm/Handles/SharedRealmHandleExtensions.cs b/Realm/Realm/Handles/SharedRealmHandleExtensions.cs deleted file mode 100644 index 573c87a7c5..0000000000 --- a/Realm/Realm/Handles/SharedRealmHandleExtensions.cs +++ /dev/null @@ -1,441 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Realms.Exceptions; -using Realms.Native; -using Realms.Schema; -using Realms.Sync.Exceptions; -using Realms.Sync.Native; - -namespace Realms.Sync -{ - internal static class SharedRealmHandleExtensions - { - // We only save it to avoid allocating the GCHandle multiple times. - private static readonly NativeMethods.LogMessageCallback _logCallback; - - // This is int, because Interlocked.Exchange cannot work with narrower types such as bool. - private static int _metadataConfigured; - - public static bool IsMetadataConfigured => _metadataConfigured == 1; - - private static class NativeMethods - { -#pragma warning disable IDE1006 // Naming Styles -#pragma warning disable SA1121 // Use built-in type alias - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_open_with_sync", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr open_with_sync(Configuration configuration, SyncConfiguration sync_configuration, - [MarshalAs(UnmanagedType.LPArray), In] SchemaObject[] objects, int objects_length, - [MarshalAs(UnmanagedType.LPArray), In] SchemaProperty[] properties, - byte[] encryptionKey, - out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_open_with_sync_async", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr open_with_sync_async(Configuration configuration, SyncConfiguration sync_configuration, - [MarshalAs(UnmanagedType.LPArray), In] SchemaObject[] objects, int objects_length, - [MarshalAs(UnmanagedType.LPArray), In] SchemaProperty[] properties, - byte[] encryptionKey, - IntPtr task_completion_source, - out NativeException ex); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void RefreshAccessTokenCallbackDelegate(IntPtr session_handle_ptr); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public unsafe delegate void SessionErrorCallback(IntPtr session_handle_ptr, ErrorCode error_code, byte* message_buf, IntPtr message_len, IntPtr user_info_pairs, int user_info_pairs_len); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void SessionProgressCallback(IntPtr progress_token_ptr, ulong transferred_bytes, ulong transferable_bytes); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public unsafe delegate void SessionWaitCallback(IntPtr task_completion_source, int error_code, byte* message_buf, IntPtr message_len); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public unsafe delegate void LogMessageCallback(byte* message_buf, IntPtr message_len, LogLevel logLevel); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public unsafe delegate void OpenRealmCallback(IntPtr task_completion_source, IntPtr shared_realm, int error_code, byte* message_buf, IntPtr message_len); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncmanager_configure", CallingConvention = CallingConvention.Cdecl)] - public static extern unsafe void configure([MarshalAs(UnmanagedType.LPWStr)] string base_path, IntPtr base_path_length, - [MarshalAs(UnmanagedType.LPWStr)] string user_agent, IntPtr user_agent_length, - UserPersistenceMode* userPersistence, byte[] encryptionKey, - [MarshalAs(UnmanagedType.I1)] bool resetMetadataOnError, - out NativeException exception); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_install_syncmanager_callbacks", CallingConvention = CallingConvention.Cdecl)] - public static extern void install_syncmanager_callbacks(OpenRealmCallback open_callback); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_install_syncsession_callbacks", CallingConvention = CallingConvention.Cdecl)] - public static extern void install_syncsession_callbacks(RefreshAccessTokenCallbackDelegate refresh_callback, SessionErrorCallback error_callback, SessionProgressCallback progress_callback, SessionWaitCallback wait_callback); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncmanager_get_path_for_realm", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_path_for_realm(SyncUserHandle user, [MarshalAs(UnmanagedType.LPWStr)] string url, IntPtr url_len, IntPtr buffer, IntPtr bufsize, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncmanager_immediately_run_file_actions", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool immediately_run_file_actions([MarshalAs(UnmanagedType.LPWStr)] string path, IntPtr path_len, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncmanager_reconnect", CallingConvention = CallingConvention.Cdecl)] - public static extern void reconnect(); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncmanager_get_session", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_session([MarshalAs(UnmanagedType.LPWStr)] string path, IntPtr path_len, SyncConfiguration configuration, byte[] encryptionKey, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncmanager_set_log_level", CallingConvention = CallingConvention.Cdecl)] - public static extern unsafe void set_log_level(LogLevel* level, out NativeException exception); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncmanager_set_user_agent", CallingConvention = CallingConvention.Cdecl)] - public static extern unsafe void set_user_agent([MarshalAs(UnmanagedType.LPWStr)] string user_agent, IntPtr user_agent_length, out NativeException exception); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncmanager_get_log_level", CallingConvention = CallingConvention.Cdecl)] - public static extern LogLevel get_log_level(); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncmanager_set_log_callback", CallingConvention = CallingConvention.Cdecl)] - public static extern void set_log_callback(LogMessageCallback callback, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncmanager_get_realm_privileges", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.U1)] - public static extern byte get_realm_privileges(SharedRealmHandle handle, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncmanager_get_class_privileges", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.U1)] - public static extern byte get_class_privileges(SharedRealmHandle handle, - [MarshalAs(UnmanagedType.LPWStr)] string class_name, IntPtr class_name_len, - out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncmanager_get_object_privileges", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.U1)] - public static extern byte get_object_privileges(SharedRealmHandle handle, ObjectHandle objectHandle, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncmanager_enable_session_multiplexing", CallingConvention = CallingConvention.Cdecl)] - public static extern void enable_session_multiplexing(out NativeException ex); - -#pragma warning restore IDE1006 // Naming Styles -#pragma warning restore SA1121 // Use built-in type alias - } - - static unsafe SharedRealmHandleExtensions() - { - NativeCommon.Initialize(); - - NativeMethods.RefreshAccessTokenCallbackDelegate refresh = RefreshAccessTokenCallback; - NativeMethods.SessionErrorCallback error = HandleSessionError; - NativeMethods.SessionProgressCallback progress = HandleSessionProgress; - NativeMethods.SessionWaitCallback wait = HandleSessionWaitCallback; - - GCHandle.Alloc(refresh); - GCHandle.Alloc(error); - GCHandle.Alloc(progress); - GCHandle.Alloc(wait); - - NativeMethods.install_syncsession_callbacks(refresh, error, progress, wait); - - NativeMethods.OpenRealmCallback openRealm = HandleOpenRealmCallback; - - GCHandle.Alloc(openRealm); - - NativeMethods.install_syncmanager_callbacks(openRealm); - - _logCallback = HandleLogMessage; - GCHandle.Alloc(_logCallback); - } - - public static SharedRealmHandle OpenWithSync(Configuration configuration, SyncConfiguration syncConfiguration, RealmSchema schema, byte[] encryptionKey) - { - DoInitialMetadataConfiguration(); - - var marshaledSchema = new SharedRealmHandle.SchemaMarshaler(schema); - - var result = NativeMethods.open_with_sync(configuration, syncConfiguration, marshaledSchema.Objects, marshaledSchema.Objects.Length, marshaledSchema.Properties, encryptionKey, out var nativeException); - nativeException.ThrowIfNecessary(); - - return new SharedRealmHandle(result); - } - - public static AsyncOpenTaskHandle OpenWithSyncAsync(Configuration configuration, SyncConfiguration syncConfiguration, RealmSchema schema, byte[] encryptionKey, GCHandle tcsHandle) - { - DoInitialMetadataConfiguration(); - - var marshaledSchema = new SharedRealmHandle.SchemaMarshaler(schema); - - var asyncTaskPtr = NativeMethods.open_with_sync_async(configuration, syncConfiguration, marshaledSchema.Objects, marshaledSchema.Objects.Length, marshaledSchema.Properties, encryptionKey, GCHandle.ToIntPtr(tcsHandle), out var nativeException); - nativeException.ThrowIfNecessary(); - var asyncTaskHandle = new AsyncOpenTaskHandle(asyncTaskPtr); - return asyncTaskHandle; - } - - public static string GetRealmPath(User user, Uri serverUri) - { - DoInitialMetadataConfiguration(); - return MarshalHelpers.GetString((IntPtr buffer, IntPtr bufferLength, out bool isNull, out NativeException ex) => - { - isNull = false; - return NativeMethods.get_path_for_realm(user.Handle, serverUri.AbsoluteUri, (IntPtr)serverUri.AbsoluteUri.Length, buffer, bufferLength, out ex); - }); - } - - // Configure the SyncMetadataManager with default values if it hasn't been configured already - public static void DoInitialMetadataConfiguration() - { - if (Interlocked.Exchange(ref _metadataConfigured, 1) == 0) - { - Configure(null, null, false); - } - } - - public static void EnableSessionMultiplexing() - { - NativeMethods.enable_session_multiplexing(out var nativeException); - nativeException.ThrowIfNecessary(); - } - - public static unsafe void Configure(UserPersistenceMode? userPersistenceMode, byte[] encryptionKey, bool resetMetadataOnError, string basePath = null) - { - // mark the file system as configured in case this is called directly - // so that it isn't reconfigured with default values in DoInitialMetadataConfiguration - Interlocked.Exchange(ref _metadataConfigured, 1); - - RealmException.AddOverrider(RealmExceptionCodes.RealmIncompatibleSyncedFile, (message, path) => new IncompatibleSyncedFileException(message, path)); - - if (basePath == null) - { - basePath = InteropConfig.DefaultStorageFolder; - } - - UserPersistenceMode mode; - UserPersistenceMode* modePtr = null; - if (userPersistenceMode.HasValue) - { - mode = userPersistenceMode.Value; - modePtr = &mode; - } - - var userAgent = SyncConfigurationBase.GetSDKUserAgent(); - NativeMethods.configure( - basePath, (IntPtr)basePath.Length, - userAgent, (IntPtr)userAgent.Length, - modePtr, encryptionKey, resetMetadataOnError, out var ex); - ex.ThrowIfNecessary(); - - InstallLogCallback(); - } - - public static unsafe void SetLogLevel(LogLevel level) - { - var levelPtr = &level; - - NativeMethods.set_log_level(levelPtr, out var ex); - ex.ThrowIfNecessary(); - } - - public static void SetUserAgent(string userAgent) - { - NativeMethods.set_user_agent(userAgent, (IntPtr)userAgent.Length, out var ex); - ex.ThrowIfNecessary(); - } - - public static LogLevel GetLogLevel() - { - return NativeMethods.get_log_level(); - } - - public static void InstallLogCallback() - { - if (SyncConfigurationBase.CustomLogger != null) - { - NativeMethods.set_log_callback(_logCallback, out var ex); - ex.ThrowIfNecessary(); - } - } - - public static void ResetForTesting() - { - NativeCommon.reset_for_testing(); - _metadataConfigured = 0; - } - - public static bool ImmediatelyRunFileActions(string path) - { - var result = NativeMethods.immediately_run_file_actions(path, (IntPtr)path.Length, out var ex); - ex.ThrowIfNecessary(); - - return result; - } - - public static void ReconnectSessions() - { - NativeMethods.reconnect(); - } - - public static SessionHandle GetSession(string path, SyncConfiguration configuration, byte[] encryptionKey) - { - DoInitialMetadataConfiguration(); - - var result = NativeMethods.get_session(path, (IntPtr)path.Length, configuration, encryptionKey, out var nativeException); - nativeException.ThrowIfNecessary(); - - return new SessionHandle(result); - } - - public static RealmPrivileges GetPrivileges(this SharedRealmHandle handle) - { - var result = NativeMethods.get_realm_privileges(handle, out var ex); - ex.ThrowIfNecessary(); - return (RealmPrivileges)result; - } - - public static ClassPrivileges GetPrivileges(this SharedRealmHandle handle, string className) - { - var result = NativeMethods.get_class_privileges(handle, className, (IntPtr)className.Length, out var ex); - ex.ThrowIfNecessary(); - return (ClassPrivileges)result; - } - - public static ObjectPrivileges GetPrivileges(this SharedRealmHandle handle, ObjectHandle objectHandle) - { - var result = NativeMethods.get_object_privileges(handle, objectHandle, out var ex); - ex.ThrowIfNecessary(); - return (ObjectPrivileges)result; - } - - [MonoPInvokeCallback(typeof(NativeMethods.RefreshAccessTokenCallbackDelegate))] - private static void RefreshAccessTokenCallback(IntPtr sessionHandlePtr) - { - var handle = new SessionHandle(sessionHandlePtr); - var session = new Session(handle); - AuthenticationHelper.RefreshAccessTokenAsync(session).ContinueWith(_ => session.CloseHandle()); - } - - [MonoPInvokeCallback(typeof(NativeMethods.SessionErrorCallback))] - private static unsafe void HandleSessionError(IntPtr sessionHandlePtr, ErrorCode errorCode, byte* messageBuffer, IntPtr messageLength, IntPtr userInfoPairs, int userInfoPairsLength) - { - var handle = new SessionHandle(sessionHandlePtr); - var session = new Session(handle); - var message = Encoding.UTF8.GetString(messageBuffer, (int)messageLength); - - SessionException exception; - - if (errorCode.IsClientResetError()) - { - var userInfo = MarshalErrorUserInfo(userInfoPairs, userInfoPairsLength); - exception = new ClientResetException(message, userInfo); - } - else if (errorCode == ErrorCode.PermissionDenied) - { - var userInfo = MarshalErrorUserInfo(userInfoPairs, userInfoPairsLength); - exception = new PermissionDeniedException(message, userInfo); - } - else - { - exception = new SessionException(message, errorCode); - } - - Session.RaiseError(session, exception); - } - - private static Dictionary MarshalErrorUserInfo(IntPtr userInfoPairs, int userInfoPairsLength) - { - return Enumerable.Range(0, userInfoPairsLength) - .Select(i => Marshal.PtrToStructure(IntPtr.Add(userInfoPairs, i * StringStringPair.Size))) - .ToDictionary(pair => pair.Key, pair => pair.Value); - } - - [MonoPInvokeCallback(typeof(NativeMethods.SessionProgressCallback))] - private static void HandleSessionProgress(IntPtr tokenPtr, ulong transferredBytes, ulong transferableBytes) - { - var token = (ProgressNotificationToken)GCHandle.FromIntPtr(tokenPtr).Target; - token.Notify(transferredBytes, transferableBytes); - } - - [MonoPInvokeCallback(typeof(NativeMethods.SessionWaitCallback))] - private static unsafe void HandleSessionWaitCallback(IntPtr taskCompletionSource, int error_code, byte* messageBuffer, IntPtr messageLength) - { - var handle = GCHandle.FromIntPtr(taskCompletionSource); - var tcs = (TaskCompletionSource)handle.Target; - - try - { - if (error_code == 0) - { - tcs.TrySetResult(null); - } - else - { - var inner = new SessionException(Encoding.UTF8.GetString(messageBuffer, (int)messageLength), (ErrorCode)error_code); - const string OuterMessage = "A system error occurred while waiting for completion. See InnerException for more details"; - tcs.TrySetException(new RealmException(OuterMessage, inner)); - } - } - finally - { - handle.Free(); - } - } - - [MonoPInvokeCallback(typeof(NativeMethods.OpenRealmCallback))] - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The task awaiter will own the ThreadSafeReference handle.")] - private static unsafe void HandleOpenRealmCallback(IntPtr taskCompletionSource, IntPtr realm_reference, int error_code, byte* messageBuffer, IntPtr messageLength) - { - var handle = GCHandle.FromIntPtr(taskCompletionSource); - var tcs = (TaskCompletionSource)handle.Target; - - if (error_code == 0) - { - tcs.TrySetResult(new ThreadSafeReferenceHandle(realm_reference, isRealmReference: true)); - } - else - { - var inner = new SessionException(Encoding.UTF8.GetString(messageBuffer, (int)messageLength), (ErrorCode)error_code); - const string OuterMessage = "A system error occurred while opening a Realm. See InnerException for more details"; - tcs.TrySetException(new RealmException(OuterMessage, inner)); - } - } - - [MonoPInvokeCallback(typeof(NativeMethods.LogMessageCallback))] - private static unsafe void HandleLogMessage(byte* messageBuffer, IntPtr messageLength, LogLevel level) - { - try - { - var message = Encoding.UTF8.GetString(messageBuffer, (int)messageLength); - SyncConfigurationBase.CustomLogger?.Invoke(message, level); - } - catch (Exception ex) - { - var errorMessage = $"An error occurred while trying to log a message: {ex}"; - try - { - SyncConfigurationBase.CustomLogger(errorMessage, LogLevel.Error); - } - catch - { - Console.Error.WriteLine(errorMessage); - } - } - } - } -} diff --git a/Realm/Realm/Handles/SortDescriptorHandle.cs b/Realm/Realm/Handles/SortDescriptorHandle.cs index 86976e7f88..f0985e1fb2 100644 --- a/Realm/Realm/Handles/SortDescriptorHandle.cs +++ b/Realm/Realm/Handles/SortDescriptorHandle.cs @@ -18,15 +18,11 @@ using System; using System.Runtime.InteropServices; -using Realms.Native; namespace Realms { internal class SortDescriptorHandle : RealmHandle { - // This is a delegate type meant to represent one of the "query operator" methods such as float_less and bool_equal - internal delegate void Operation(QueryHandle queryPtr, ColumnKey columnKey, T value); - private static class NativeMethods { [DllImport(InteropConfig.DLL_NAME, EntryPoint = "sort_descriptor_destroy", CallingConvention = CallingConvention.Cdecl)] @@ -34,8 +30,8 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "sort_descriptor_add_clause", CallingConvention = CallingConvention.Cdecl)] public static extern void add_clause(SortDescriptorHandle descriptor, TableHandle query, SharedRealmHandle realm, - [MarshalAs(UnmanagedType.LPArray), In] IntPtr[] property_chain, IntPtr properties_count, - [MarshalAs(UnmanagedType.I1)] bool ascending, + [MarshalAs(UnmanagedType.LPArray), In] IntPtr[] property_index_chain, IntPtr column_keys_count, + [MarshalAs(UnmanagedType.U1)] bool ascending, out NativeException ex); } @@ -43,9 +39,9 @@ public SortDescriptorHandle(RealmHandle root, IntPtr handle) : base(root, handle { } - public void AddClause(TableHandle table, SharedRealmHandle realm, IntPtr[] propertyChain, bool ascending) + public void AddClause(TableHandle table, SharedRealmHandle realm, IntPtr[] propertyIndexChain, bool ascending) { - NativeMethods.add_clause(this, table, realm, propertyChain, (IntPtr)propertyChain.Length, ascending, out var nativeException); + NativeMethods.add_clause(this, table, realm, propertyIndexChain, (IntPtr)propertyIndexChain.Length, ascending, out var nativeException); nativeException.ThrowIfNecessary(); } diff --git a/Realm/Realm/Handles/SubscriptionHandle.cs b/Realm/Realm/Handles/SubscriptionHandle.cs deleted file mode 100644 index 0b8464eb87..0000000000 --- a/Realm/Realm/Handles/SubscriptionHandle.cs +++ /dev/null @@ -1,128 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Linq; -using System.Runtime.InteropServices; -using Realms.Exceptions; - -namespace Realms.Sync -{ - internal class SubscriptionHandle : RealmHandle - { - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void SubscriptionCallbackDelegate(IntPtr managedHandle); - - private static class NativeMethods - { -#pragma warning disable IDE1006 // Naming Styles -#pragma warning disable SA1121 // Use built-in type alias - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscription_destroy", CallingConvention = CallingConvention.Cdecl)] - public static extern void destroy(IntPtr handle); - - // -1 for name_len and time_to_live means "no value" as both would be meaningless with negative numbers - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscription_create", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr subscribe( - ResultsHandle results, - [MarshalAs(UnmanagedType.LPWStr)] string name, int name_len, - long time_to_live, - [MarshalAs(UnmanagedType.I1)] bool update, - [MarshalAs(UnmanagedType.LPArray), In] Native.StringValue[] inclusions, int inclusions_length, - out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscription_get_state", CallingConvention = CallingConvention.Cdecl)] - public static extern sbyte get_state(SubscriptionHandle subscription, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscription_get_error", CallingConvention = CallingConvention.Cdecl)] - public static extern NativeException get_error(SubscriptionHandle subscription); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscription_add_notification_callback", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr add_notification_callback(SubscriptionHandle subscription, IntPtr managedSubscriptionHandle, SubscriptionCallbackDelegate callback, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscription_unsubscribe", CallingConvention = CallingConvention.Cdecl)] - public static extern void unsubscribe(SubscriptionHandle subscription, out NativeException ex); - -#pragma warning restore IDE1006 // Naming Styles -#pragma warning restore SA1121 // Use built-in type alias - } - - [Preserve] - private SubscriptionHandle(IntPtr handle) : base(null, handle) - { - } - - public static SubscriptionHandle Create(ResultsHandle results, string name, long? timeToLive, bool update, string[] inclusions) - { - var nativeInclusions = Array.Empty(); - if (inclusions != null) - { - nativeInclusions = inclusions.Select(i => new Native.StringValue { Value = i }).ToArray(); - } - - // We use -1 to signal "no value" - var handle = NativeMethods.subscribe( - results, - name, name?.Length ?? -1, - timeToLive ?? -1, - update, - nativeInclusions, inclusions?.Length ?? -1, - out var ex); - - ex.ThrowIfNecessary(); - - return new SubscriptionHandle(handle); - } - - public SubscriptionState GetState() - { - var result = NativeMethods.get_state(this, out var ex); - ex.ThrowIfNecessary(); - return (SubscriptionState)result; - } - - public Exception GetError() - { - var result = NativeMethods.get_error(this); - if (result.type != RealmExceptionCodes.NoError) - { - return result.Convert(); - } - - return null; - } - - public SubscriptionTokenHandle AddNotificationCallback(IntPtr managedObjectHandle, SubscriptionCallbackDelegate callback) - { - var result = NativeMethods.add_notification_callback(this, managedObjectHandle, callback, out var ex); - ex.ThrowIfNecessary(); - return new SubscriptionTokenHandle(this, result); - } - - public void Unsubscribe() - { - NativeMethods.unsubscribe(this, out var ex); - ex.ThrowIfNecessary(); - } - - protected override void Unbind() - { - NativeMethods.destroy(handle); - } - } -} diff --git a/Realm/Realm/Handles/SubscriptionTokenHandle.cs b/Realm/Realm/Handles/SubscriptionTokenHandle.cs deleted file mode 100644 index cef0185986..0000000000 --- a/Realm/Realm/Handles/SubscriptionTokenHandle.cs +++ /dev/null @@ -1,49 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Runtime.InteropServices; - -namespace Realms.Sync -{ - internal class SubscriptionTokenHandle : RealmHandle - { - private static class NativeMethods - { -#pragma warning disable IDE1006 // Naming Styles -#pragma warning disable SA1121 // Use built-in type alias - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscription_destroy_notification_token", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr destroy_notificationtoken(IntPtr token, out NativeException ex); - -#pragma warning restore IDE1006 // Naming Styles -#pragma warning restore SA1121 // Use built-in type alias - } - - public SubscriptionTokenHandle(SubscriptionHandle root, IntPtr handle) : base(root, handle) - { - } - - protected override void Unbind() - { - var managedObjectHandle = NativeMethods.destroy_notificationtoken(handle, out var ex); - ex.ThrowIfNecessary(); - GCHandle.FromIntPtr(managedObjectHandle).Free(); - } - } -} \ No newline at end of file diff --git a/Realm/Realm/Handles/SyncUserHandle.cs b/Realm/Realm/Handles/SyncUserHandle.cs index 8ad2de734c..ae00f24bda 100644 --- a/Realm/Realm/Handles/SyncUserHandle.cs +++ b/Realm/Realm/Handles/SyncUserHandle.cs @@ -17,9 +17,12 @@ //////////////////////////////////////////////////////////////////////////// using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; +using System.Threading.Tasks; +using MongoDB.Bson; +using Realms.Native; +using Realms.Sync.Exceptions; +using Realms.Sync.Native; namespace Realms.Sync { @@ -27,60 +30,126 @@ internal class SyncUserHandle : RealmHandle { private static class NativeMethods { - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_get_sync_user", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_sync_user([MarshalAs(UnmanagedType.LPWStr)] string identity, IntPtr identity_len, - [MarshalAs(UnmanagedType.LPWStr)] string auth_server_url, IntPtr auth_server_url_len, - [MarshalAs(UnmanagedType.LPWStr)] string refresh_token, IntPtr refresh_token_len, - [MarshalAs(UnmanagedType.I1)] bool is_admin, out NativeException ex); +#pragma warning disable IDE1006 // Naming Styles +#pragma warning disable SA1121 // Use built-in type alias - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_get_admintoken_user", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_admintoken_user([MarshalAs(UnmanagedType.LPWStr)] string auth_server_url, IntPtr auth_server_url_len, - [MarshalAs(UnmanagedType.LPWStr)] string token, IntPtr token_len, - out NativeException ex); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void ApiKeysCallback(IntPtr tcs_ptr, /* UserApiKey[] */ IntPtr api_keys, int api_keys_len, AppError error); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_identity", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_identity(SyncUserHandle user, IntPtr buffer, IntPtr buffer_length, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_id", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr get_user_id(SyncUserHandle user, IntPtr buffer, IntPtr buffer_length, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_refresh_token", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_refresh_token(SyncUserHandle user, IntPtr buffer, IntPtr buffer_length, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_set_refresh_token", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr set_refresh_token(SyncUserHandle user, [MarshalAs(UnmanagedType.LPWStr)] string refresh_token, IntPtr refresh_token_len, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_access_token", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr get_access_token(SyncUserHandle user, IntPtr buffer, IntPtr buffer_length, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_server_url", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_server_url(SyncUserHandle user, IntPtr buffer, IntPtr buffer_length, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_device_id", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr get_device_id(SyncUserHandle user, IntPtr buffer, IntPtr buffer_length, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_state", CallingConvention = CallingConvention.Cdecl)] public static extern UserState get_state(SyncUserHandle user, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_is_admin", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool get_is_admin(SyncUserHandle user); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_auth_provider", CallingConvention = CallingConvention.Cdecl)] + public static extern Credentials.AuthProvider get_auth_provider(SyncUserHandle user, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_profile_data", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr get_profile_data(SyncUserHandle user, UserProfileField field, + IntPtr buffer, IntPtr buffer_length, [MarshalAs(UnmanagedType.U1)] out bool isNull, + out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_custom_data", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr get_custom_data(SyncUserHandle user, IntPtr buffer, IntPtr buffer_length, + [MarshalAs(UnmanagedType.U1)] out bool isNull, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_app", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr get_app(SyncUserHandle user, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_log_out", CallingConvention = CallingConvention.Cdecl)] public static extern void log_out(SyncUserHandle user, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_get_current_sync_user", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_current_user(out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_get_logged_in_users", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_logged_in_users([Out] IntPtr[] users, IntPtr bufsize, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_refresh_custom_data", CallingConvention = CallingConvention.Cdecl)] + public static extern void refresh_custom_data(SyncUserHandle user, IntPtr tcs_ptr, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_destroy", CallingConvention = CallingConvention.Cdecl)] public static extern void destroy(IntPtr syncuserHandle); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_get_logged_in_user", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_logged_in_user([MarshalAs(UnmanagedType.LPWStr)] string identity, IntPtr identity_len, - [MarshalAs(UnmanagedType.LPWStr)] string auth_server_url, IntPtr auth_server_url_len, - out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_sync_user_initialize", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr initialize(ApiKeysCallback api_keys_callback); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_call_function", CallingConvention = CallingConvention.Cdecl)] + public static extern void call_function(SyncUserHandle handle, AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string function_name, IntPtr function_name_len, + [MarshalAs(UnmanagedType.LPWStr)] string args, IntPtr args_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_link_credentials", CallingConvention = CallingConvention.Cdecl)] + public static extern void link_credentials(SyncUserHandle handle, AppHandle app, Native.Credentials credentials, IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_serialized_identities", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr get_identities(SyncUserHandle handle, IntPtr buffer, IntPtr bufsize, out NativeException ex); + + #region Push + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_push_register", CallingConvention = CallingConvention.Cdecl)] + public static extern void push_register(SyncUserHandle handle, AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string service, IntPtr service_len, + [MarshalAs(UnmanagedType.LPWStr)] string token, IntPtr token_len, + IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_push_deregister", CallingConvention = CallingConvention.Cdecl)] + public static extern void push_deregister(SyncUserHandle handle, AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string service, IntPtr service_len, + IntPtr tcs_ptr, out NativeException ex); + + #endregion - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_session", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_session(SyncUserHandle user, [MarshalAs(UnmanagedType.LPWStr)] string path, IntPtr path_len, out NativeException ex); + #region Api Keys + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_api_key_create", CallingConvention = CallingConvention.Cdecl)] + public static extern void create_api_key(SyncUserHandle handle, AppHandle app, + [MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr name_len, + IntPtr tcs_ptr, out NativeException ex); + + // id is IntPtr rather than PrimitiveValue due to a bug in .NET Core on Linux and Mac + // that causes incorrect marshalling of the struct. + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_api_key_fetch", CallingConvention = CallingConvention.Cdecl)] + public static extern void fetch_api_key(SyncUserHandle handle, AppHandle app, IntPtr id, IntPtr tcs_ptr, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_api_key_fetch_all", CallingConvention = CallingConvention.Cdecl)] + public static extern void fetch_api_keys(SyncUserHandle handle, AppHandle app, IntPtr tcs_ptr, out NativeException ex); + + // id is IntPtr rather than PrimitiveValue due to a bug in .NET Core on Linux and Mac + // that causes incorrect marshalling of the struct. + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_api_key_delete", CallingConvention = CallingConvention.Cdecl)] + public static extern void delete_api_key(SyncUserHandle handle, AppHandle app, IntPtr id, IntPtr tcs_ptr, out NativeException ex); + + // id is IntPtr rather than PrimitiveValue due to a bug in .NET Core on Linux and Mac + // that causes incorrect marshalling of the struct. + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_api_key_disable", CallingConvention = CallingConvention.Cdecl)] + public static extern void disable_api_key(SyncUserHandle handle, AppHandle app, IntPtr id, IntPtr tcs_ptr, out NativeException ex); + + // id is IntPtr rather than PrimitiveValue due to a bug in .NET Core on Linux and Mac + // that causes incorrect marshalling of the struct. + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_api_key_enable", CallingConvention = CallingConvention.Cdecl)] + public static extern void enable_api_key(SyncUserHandle handle, AppHandle app, IntPtr id, IntPtr tcs_ptr, out NativeException ex); + + #endregion + +#pragma warning restore IDE1006 // Naming Styles +#pragma warning restore SA1121 // Use built-in type alias } - static SyncUserHandle() + static unsafe SyncUserHandle() { NativeCommon.Initialize(); + + NativeMethods.ApiKeysCallback apiKeysCallback = HandleApiKeysCallback; + + GCHandle.Alloc(apiKeysCallback); + + NativeMethods.initialize(apiKeysCallback); } [Preserve] @@ -88,12 +157,12 @@ public SyncUserHandle(IntPtr handle) : base(null, handle) { } - public string GetIdentity() + public string GetUserId() { return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => { isNull = false; - return NativeMethods.get_identity(this, buffer, length, out ex); + return NativeMethods.get_user_id(this, buffer, length, out ex); }); } @@ -106,18 +175,21 @@ public string GetRefreshToken() }); } - public void SetRefreshToken(string token) + public string GetAccessToken() { - NativeMethods.set_refresh_token(this, token, (IntPtr)token.Length, out var ex); - ex.ThrowIfNecessary(); + return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => + { + isNull = false; + return NativeMethods.get_access_token(this, buffer, length, out ex); + }); } - public string GetServerUrl() + public string GetDeviceId() { return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => { isNull = false; - return NativeMethods.get_server_url(this, buffer, length, out ex); + return NativeMethods.get_device_id(this, buffer, length, out ex); }); } @@ -128,23 +200,25 @@ public UserState GetState() return result; } - public bool GetIsAdmin() + public Credentials.AuthProvider GetProvider() { - return NativeMethods.get_is_admin(this); + var result = NativeMethods.get_auth_provider(this, out var ex); + ex.ThrowIfNecessary(); + return result; } - public bool TryGetSession(string path, out SessionHandle handle) + public bool TryGetApp(out AppHandle appHandle) { - var result = NativeMethods.get_session(this, path, (IntPtr)path.Length, out var ex); + var result = NativeMethods.get_app(this, out var ex); ex.ThrowIfNecessary(); if (result == IntPtr.Zero) { - handle = null; + appHandle = null; return false; } - handle = new SessionHandle(result); + appHandle = new AppHandle(result); return true; } @@ -154,68 +228,158 @@ public void LogOut() ex.ThrowIfNecessary(); } - public static SyncUserHandle GetSyncUser(string identity, string authServerUrl, string refreshToken, bool isAdmin) + public void RefreshCustomData(TaskCompletionSource tcs) { - var userPtr = NativeMethods.get_sync_user(identity, (IntPtr)identity.Length, - authServerUrl, (IntPtr)authServerUrl.Length, - refreshToken, (IntPtr)refreshToken.Length, - isAdmin, out var ex); - ex.ThrowIfNecessary(); + var tcsHandle = GCHandle.Alloc(tcs); + NativeMethods.refresh_custom_data(this, GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(tcsHandle); + } - return new SyncUserHandle(userPtr); + public string GetProfileData(UserProfileField field) + { + return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => + { + return NativeMethods.get_profile_data(this, field, buffer, length, out isNull, out ex); + }); + } + + public string GetCustomData() + { + return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => + { + return NativeMethods.get_custom_data(this, buffer, length, out isNull, out ex); + }); } - public static SyncUserHandle GetAdminTokenUser(string authServerUrl, string token) + public void CallFunction(AppHandle app, string name, string args, TaskCompletionSource tcs) { - var userPtr = NativeMethods.get_admintoken_user(authServerUrl, (IntPtr)authServerUrl.Length, - token, (IntPtr)token.Length, - out var ex); + var tcsHandle = GCHandle.Alloc(tcs); + + NativeMethods.call_function(this, app, name, (IntPtr)name.Length, args, (IntPtr)args.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); ex.ThrowIfNecessary(); + } - return new SyncUserHandle(userPtr); + public void LinkCredentials(AppHandle app, Native.Credentials credentials, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); + NativeMethods.link_credentials(this, app, credentials, GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(tcsHandle); } - public static bool TryGetCurrentUser(out SyncUserHandle handle) + public string GetIdentities() { - var userPtr = NativeMethods.get_current_user(out var ex); + return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => + { + isNull = false; + return NativeMethods.get_identities(this, buffer, length, out ex); + }); + } + + #region Push + + public void RegisterPushToken(AppHandle app, string service, string token, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); + + NativeMethods.push_register(this, app, service, (IntPtr)service.Length, token, (IntPtr)token.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); ex.ThrowIfNecessary(); + } - if (userPtr == IntPtr.Zero) - { - handle = null; - return false; - } + public void DeregisterPushToken(AppHandle app, string service, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); - handle = new SyncUserHandle(userPtr); - return true; + NativeMethods.push_deregister(this, app, service, (IntPtr)service.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(); } - public static IEnumerable GetAllLoggedInUsers() + #endregion + + #region Api Keys + + public void CreateApiKey(AppHandle app, string name, TaskCompletionSource tcs) { - return MarshalHelpers.GetCollection(NativeMethods.get_logged_in_users, bufferSize: 8) - .Select(h => new SyncUserHandle(h)); + var tcsHandle = GCHandle.Alloc(tcs); + NativeMethods.create_api_key(this, app, name, (IntPtr)name.Length, GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(tcsHandle); } - public static bool TryGetLoggedInUser(string identity, string authServerUrl, out SyncUserHandle handle) + public unsafe void FetchApiKey(AppHandle app, ObjectId id, TaskCompletionSource tcs) { - var userPtr = NativeMethods.get_logged_in_user(identity, (IntPtr)identity.Length, - authServerUrl, (IntPtr)authServerUrl.Length, - out var ex); - ex.ThrowIfNecessary(); + var tcsHandle = GCHandle.Alloc(tcs); + var primitiveId = PrimitiveValue.ObjectId(id); + PrimitiveValue* idPtr = &primitiveId; + NativeMethods.fetch_api_key(this, app, new IntPtr(idPtr), GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(tcsHandle); + } - if (userPtr == IntPtr.Zero) - { - handle = null; - return false; - } + public void FetchAllApiKeys(AppHandle app, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); + NativeMethods.fetch_api_keys(this, app, GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(tcsHandle); + } - handle = new SyncUserHandle(userPtr); - return true; + public unsafe void DeleteApiKey(AppHandle app, ObjectId id, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); + var primitiveId = PrimitiveValue.ObjectId(id); + PrimitiveValue* idPtr = &primitiveId; + NativeMethods.delete_api_key(this, app, new IntPtr(idPtr), GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(tcsHandle); } + public unsafe void DisableApiKey(AppHandle app, ObjectId id, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); + var primitiveId = PrimitiveValue.ObjectId(id); + PrimitiveValue* idPtr = &primitiveId; + NativeMethods.disable_api_key(this, app, new IntPtr(idPtr), GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(tcsHandle); + } + + public unsafe void EnableApiKey(AppHandle app, ObjectId id, TaskCompletionSource tcs) + { + var tcsHandle = GCHandle.Alloc(tcs); + var primitiveId = PrimitiveValue.ObjectId(id); + PrimitiveValue* idPtr = &primitiveId; + NativeMethods.enable_api_key(this, app, new IntPtr(idPtr), GCHandle.ToIntPtr(tcsHandle), out var ex); + ex.ThrowIfNecessary(tcsHandle); + } + + #endregion + protected override void Unbind() { NativeMethods.destroy(handle); } + + [MonoPInvokeCallback(typeof(NativeMethods.ApiKeysCallback))] + private static unsafe void HandleApiKeysCallback(IntPtr tcs_ptr, IntPtr api_keys, int api_keys_len, AppError error) + { + var tcsHandle = GCHandle.FromIntPtr(tcs_ptr); + try + { + var tcs = (TaskCompletionSource)tcsHandle.Target; + if (error.is_null) + { + var result = new UserApiKey[api_keys_len]; + for (var i = 0; i < api_keys_len; i++) + { + result[i] = Marshal.PtrToStructure(IntPtr.Add(api_keys, i * UserApiKey.Size)); + } + + tcs.TrySetResult(result); + } + else + { + tcs.TrySetException(new AppException(error)); + } + } + finally + { + tcsHandle.Free(); + } + } } } diff --git a/Realm/Realm/Handles/TableHandle.cs b/Realm/Realm/Handles/TableHandle.cs index 996405c4f5..ca9f1b43d3 100644 --- a/Realm/Realm/Handles/TableHandle.cs +++ b/Realm/Realm/Handles/TableHandle.cs @@ -35,12 +35,6 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "table_create_results", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr create_results(TableHandle handle, SharedRealmHandle sharedRealm, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "table_get_name", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_name(TableHandle handle, IntPtr buffer, IntPtr buffer_length, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "table_get_column_name", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_column_name(TableHandle table, ColumnKey column_key, IntPtr buffer, IntPtr buffer_length, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "table_get_object", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_object(TableHandle table, SharedRealmHandle realm, ObjectKey objectKey, out NativeException ex); @@ -48,11 +42,10 @@ private static class NativeMethods public static extern IntPtr get_object_for_string_primarykey(TableHandle handle, SharedRealmHandle realmHandle, [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "table_get_object_for_int_primarykey", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_object_for_int_primarykey(TableHandle handle, SharedRealmHandle realmHandle, Int64 value, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "table_get_object_for_null_primarykey", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr get_object_for_null_primarykey(TableHandle handle, SharedRealmHandle realmHandle, out NativeException ex); + // value is IntPtr rather than PrimitiveValue due to a bug in .NET Core on Linux and Mac + // that causes incorrect marshalling of the struct. + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "table_get_object_for_primitive_primarykey", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr get_object_for_primitive_primarykey(TableHandle handle, SharedRealmHandle realmHandle, IntPtr value, out NativeException ex); #pragma warning restore IDE1006 // Naming Styles #pragma warning restore SA1121 // Use built-in type alias @@ -89,69 +82,29 @@ public ObjectHandle Get(SharedRealmHandle realmHandle, ObjectKey objectKey) return new ObjectHandle(realmHandle, result); } - public string GetName() + public bool TryFind(SharedRealmHandle realmHandle, string id, out ObjectHandle objectHandle) { - return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => - { - isNull = false; - return NativeMethods.get_name(this, buffer, length, out ex); - }); + var result = NativeMethods.get_object_for_string_primarykey(this, realmHandle, id, (IntPtr)(id?.Length ?? 0), out var ex); + return TryFindCore(realmHandle, result, ex, out objectHandle); } - public string GetColumnName(ColumnKey columnKey) + public unsafe bool TryFind(SharedRealmHandle realmHandle, PrimitiveValue id, out ObjectHandle objectHandle) { - return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) => - { - isNull = false; - return NativeMethods.get_column_name(this, columnKey, buffer, length, out ex); - }); + PrimitiveValue* valuePtr = &id; + var result = NativeMethods.get_object_for_primitive_primarykey(this, realmHandle, new IntPtr(valuePtr), out var ex); + return TryFindCore(realmHandle, result, ex, out objectHandle); } - public bool TryFind(SharedRealmHandle realmHandle, string id, out ObjectHandle objectHandle) + private static bool TryFindCore(SharedRealmHandle realmHandle, IntPtr objectPtr, NativeException nativeException, out ObjectHandle objectHandle) { - NativeException nativeException; - IntPtr result; - if (id == null) - { - result = NativeMethods.get_object_for_null_primarykey(this, realmHandle, out nativeException); - } - else - { - result = NativeMethods.get_object_for_string_primarykey(this, realmHandle, id, (IntPtr)id.Length, out nativeException); - } - nativeException.ThrowIfNecessary(); - if (result == IntPtr.Zero) - { - objectHandle = null; - return false; - } - - objectHandle = new ObjectHandle(realmHandle, result); - return true; - } - - public bool TryFind(SharedRealmHandle realmHandle, long? id, out ObjectHandle objectHandle) - { - NativeException nativeException; - IntPtr result; - if (id.HasValue) - { - result = NativeMethods.get_object_for_int_primarykey(this, realmHandle, id.Value, out nativeException); - } - else - { - result = NativeMethods.get_object_for_null_primarykey(this, realmHandle, out nativeException); - } - - nativeException.ThrowIfNecessary(); - if (result == IntPtr.Zero) + if (objectPtr == IntPtr.Zero) { objectHandle = null; return false; } - objectHandle = new ObjectHandle(realmHandle, result); + objectHandle = new ObjectHandle(realmHandle, objectPtr); return true; } } diff --git a/Realm/Realm/Helpers/AuthenticationHelper.cs b/Realm/Realm/Helpers/AuthenticationHelper.cs deleted file mode 100644 index 76ce19b8a3..0000000000 --- a/Realm/Realm/Helpers/AuthenticationHelper.cs +++ /dev/null @@ -1,341 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Realms.Sync.Exceptions; - -namespace Realms.Sync -{ - internal static class AuthenticationHelper - { - private static readonly string AppId = string.Empty; // FIXME - private static readonly Lazy _client = new Lazy(() => new HttpClient { Timeout = TimeSpan.FromSeconds(30) }); - - private static readonly ConcurrentDictionary _tokenRefreshTimers = new ConcurrentDictionary(); - private static readonly DateTimeOffset _date_1970 = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); - private static readonly MediaTypeHeaderValue _applicationJsonUtf8MediaType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); - private static readonly MediaTypeHeaderValue _applicationProblemJsonUtf8MediaType = MediaTypeHeaderValue.Parse("application/problem+json; charset=utf-8"); - - private static readonly HashSet _connectivityStatusCodes = new HashSet - { - HttpStatusCode.NotFound, - HttpStatusCode.BadGateway, - HttpStatusCode.ServiceUnavailable, - HttpStatusCode.GatewayTimeout, - HttpStatusCode.RequestTimeout, - }; - - public static async Task RefreshAccessTokenAsync(Session session, bool reportErrors = true) - { - var user = session.User; - if (user == null) - { - return; - } - - try - { - var json = new Dictionary - { - ["data"] = user.RefreshToken, - ["path"] = session.ServerUri.AbsolutePath, - ["provider"] = "realm", - ["app_id"] = AppId - }; - - var result = await MakeAuthRequestAsync(HttpMethod.Post, new Uri(user.ServerUri, "auth"), json) - .ConfigureAwait(continueOnCapturedContext: false); - - var syncWorker = result["sync_worker"]; - if (syncWorker != null) - { - session.Handle.SetUrlPrefix(syncWorker["path"].Value()); - } - - var accessToken = result["access_token"]; - var token_data = accessToken["token_data"]; - - session.Handle.SetMultiplexIdentifier(token_data["sync_label"].Value()); - - session.Handle.RefreshAccessToken(accessToken["token"].Value(), token_data["path"].Value()); - ScheduleTokenRefresh(user.Identity, user.ServerUri, session.Path, _date_1970.AddSeconds(accessToken["token_data"]["expires"].Value())); - } - catch (HttpException ex) when (_connectivityStatusCodes.Contains(ex.StatusCode)) - { - // 30 seconds is an arbitrarily chosen value, consider rationalizing it. - ScheduleTokenRefresh(user.Identity, user.ServerUri, session.Path, DateTimeOffset.UtcNow.AddSeconds(30)); - } - catch (Exception ex) - { - if (reportErrors) - { - var sessionException = new SessionException("An error has occurred while refreshing the access token.", - ErrorCode.BadUserAuthentication, - ex); - - Session.RaiseError(session, sessionException); - } - } - finally - { - // session.User creates a new user each time, so it's safe to dispose the handle here. - // It won't actually corrupt the state of the session. - user.Handle.Dispose(); - } - } - - // Returns a Tuple - public static async Task LoginAsync(Credentials credentials, Uri serverUrl) - { - var body = credentials.ToDictionary(); - body["app_id"] = AppId; - var result = await MakeAuthRequestAsync(HttpMethod.Post, new Uri(serverUrl, "auth"), body) - .ConfigureAwait(continueOnCapturedContext: false); - var refreshToken = result["refresh_token"]; - return new UserLoginData - { - RefreshToken = refreshToken["token"].Value(), - UserId = refreshToken["token_data"]["identity"].Value(), - IsAdmin = refreshToken["token_data"]["is_admin"].Value() - }; - } - - public static Task ChangePasswordAsync(User user, string password, string otherUserId = null) - { - var json = new Dictionary - { - ["data"] = new Dictionary - { - ["new_password"] = password - } - }; - - if (otherUserId != null) - { - json["user_id"] = otherUserId; - } - - return MakeAuthRequestAsync(HttpMethod.Put, new Uri(user.ServerUri, "auth/password"), json, user.RefreshToken); - } - - public static async Task RetrieveInfoForUserAsync(User user, string provider, string providerId) - { - var uri = new Uri(user.ServerUri, $"/auth/users/{provider}/{providerId}"); - try - { - var response = await MakeAuthRequestAsync(HttpMethod.Get, uri, authHeader: user.RefreshToken) - .ConfigureAwait(continueOnCapturedContext: false); - - var accounts = response["accounts"].Children() - .Select(j => new AccountInfo - { - Provider = j["provider"].Value(), - ProviderUserIdentity = j["provider_id"].Value() - }) - .ToArray(); - - var metadata = response["metadata"].Children() - .ToDictionary(j => j["key"].Value(), j => j["value"].Value()); - - return new UserInfo - { - Identity = response["user_id"].Value(), - IsAdmin = response["is_admin"].Value(), - Accounts = accounts, - Metadata = metadata - }; - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) - { - return null; - } - } - - public static async Task LogOutAsync(Uri serverUri, string refreshToken) - { - var uri = new Uri(serverUri, "/auth/revoke"); - var body = new Dictionary - { - ["token"] = refreshToken - }; - - await MakeAuthRequestAsync(HttpMethod.Post, uri, body, refreshToken).ConfigureAwait(continueOnCapturedContext: false); - } - - public static Task UpdateAccountAsync(Uri serverUri, string action, string email = null, IDictionary data = null) - { - data = data ?? new Dictionary(); - data["action"] = action; - - var body = new Dictionary - { - ["data"] = data - }; - - if (!string.IsNullOrEmpty(email)) - { - body["provider_id"] = email; - } - - var updateUri = new Uri(serverUri, "auth/password/updateAccount"); - - return MakeAuthRequestAsync(HttpMethod.Post, updateUri, body); - } - - private static void ScheduleTokenRefresh(string userId, Uri authServerUrl, string path, DateTimeOffset expireDate) - { - var dueTime = expireDate.AddSeconds(-10) - DateTimeOffset.UtcNow; - var timerState = new TokenRefreshData - { - RealmPath = path, - UserId = userId, - ServerUrl = authServerUrl - }; - - if (dueTime < TimeSpan.Zero) - { - OnTimerCallback(timerState); - } - - _tokenRefreshTimers.AddOrUpdate( - path, - p => new Timer(OnTimerCallback, timerState, dueTime, TimeSpan.FromMilliseconds(-1)), - (p, old) => - { - old.Dispose(); - return new Timer(OnTimerCallback, timerState, dueTime, TimeSpan.FromMilliseconds(-1)); - }); - } - - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The Session handle is disposed after the session is refreshed.")] - private static void OnTimerCallback(object state) - { - var data = (TokenRefreshData)state; - - try - { - var user = User.GetLoggedInUser(data.UserId, data.ServerUrl); - if (user != null && - user.Handle.TryGetSession(data.RealmPath, out var sessionHandle)) - { - var session = new Session(sessionHandle); - RefreshAccessTokenAsync(session, reportErrors: false).ContinueWith(_ => - { - user.Handle.Close(); - session.CloseHandle(); - }); - } - } - catch - { - } - finally - { - if (_tokenRefreshTimers.TryRemove(data.RealmPath, out var timer)) - { - timer.Dispose(); - } - } - } - - // Due to https://bugzilla.xamarin.com/show_bug.cgi?id=20082 we can't use dynamic deserialization. - public static async Task MakeAuthRequestAsync(HttpMethod method, Uri uri, IDictionary body = null, string authHeader = null) - { - HttpResponseMessage response; - using (var request = new HttpRequestMessage(method, uri)) - { - if (body != null) - { - request.Content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"); - } - - request.Headers.Accept.ParseAdd(_applicationJsonUtf8MediaType.MediaType); - request.Headers.Accept.ParseAdd(_applicationProblemJsonUtf8MediaType.MediaType); - - if (!string.IsNullOrEmpty(authHeader)) - { - request.Headers.TryAddWithoutValidation("Authorization", authHeader); - } - - response = await _client.Value.SendAsync(request).ConfigureAwait(continueOnCapturedContext: false); - } - - if (response.IsSuccessStatusCode && response.Content.Headers.ContentType.Equals(_applicationJsonUtf8MediaType)) - { - var json = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); - return JObject.Parse(json); - } - - var errorJson = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); - - Exception ex; - string helpLink = null; - string errorMessage; - ErrorCode errorCode; - try - { - var problem = JObject.Parse(errorJson); - errorCode = ErrorCodeHelper.GetErrorCode(problem["code"].Value()) ?? ErrorCode.Unknown; - errorMessage = problem["title"].Value(); - helpLink = problem["type"].Value(); - } - catch - { - errorCode = ErrorCode.Unknown; - errorMessage = "An HTTP exception has occurred."; - } - - ex = new HttpException(response.StatusCode, response.ReasonPhrase, errorJson, errorMessage, errorCode) - { - HelpLink = helpLink - }; - - throw ex; - } - - private class TokenRefreshData - { - public string UserId { get; set; } - - public string RealmPath { get; set; } - - public Uri ServerUrl { get; set; } - } - - public class UserLoginData - { - public string UserId { get; set; } - - public string RefreshToken { get; set; } - - public bool IsAdmin { get; set; } - } - } -} \ No newline at end of file diff --git a/Realm/Realm/NotificationsHelper.cs b/Realm/Realm/Helpers/NotificationsHelper.cs similarity index 98% rename from Realm/Realm/NotificationsHelper.cs rename to Realm/Realm/Helpers/NotificationsHelper.cs index f0f5432cb4..99ff149938 100644 --- a/Realm/Realm/NotificationsHelper.cs +++ b/Realm/Realm/Helpers/NotificationsHelper.cs @@ -25,7 +25,7 @@ namespace Realms internal static class NotificationsHelper { /// - /// INotifiable represents a reactive object (e.g. RealmObject/Collection). + /// INotifiable represents a reactive object (e.g. RealmObjectBase/Collection). /// internal interface INotifiable { diff --git a/Realm/Realm/Helpers/Operator.cs b/Realm/Realm/Helpers/Operator.cs index 3beb936e88..ef2d84dc55 100644 --- a/Realm/Realm/Helpers/Operator.cs +++ b/Realm/Realm/Helpers/Operator.cs @@ -19,12 +19,19 @@ using System; using System.Linq; using System.Linq.Expressions; +using MongoDB.Bson; namespace Realms.Helpers { // Heavily based on http://www.yoda.arachsys.com/csharp/miscutil/index.html internal static class Operator { + [Preserve] + static Operator() + { + _ = (decimal)new Decimal128(123); + } + public static T Add(T first, T second) { return GenericOperator.Add(first, second); diff --git a/Realm/Realm/Helpers/SerializationHelper.cs b/Realm/Realm/Helpers/SerializationHelper.cs new file mode 100644 index 0000000000..6ea98d4ee7 --- /dev/null +++ b/Realm/Realm/Helpers/SerializationHelper.cs @@ -0,0 +1,65 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System.Linq; +using MongoDB.Bson; +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Options; +using MongoDB.Bson.Serialization.Serializers; + +namespace Realms.Helpers +{ + internal static class SerializationHelper + { + private static readonly JsonWriterSettings _jsonSettings = new JsonWriterSettings + { + OutputMode = JsonOutputMode.CanonicalExtendedJson, + }; + + static SerializationHelper() + { + var decimalSerializer = new DecimalSerializer(BsonType.Decimal128, new RepresentationConverter(allowOverflow: false, allowTruncation: false)); + BsonSerializer.RegisterSerializer(decimalSerializer); + } + + public static string ToNativeJson(this T value, bool tryDynamic = true) + { + if (tryDynamic && !(value is null)) + { + if (typeof(T) == typeof(object)) + { + return ToNativeJson((dynamic)value, tryDynamic: false); + } + + if (typeof(T) == typeof(object[])) + { + var elements = (value as object[]).Select(o => o is null ? ToNativeJson(o, tryDynamic: false) : ToNativeJson((dynamic)o, tryDynamic: false)); + return $"[{string.Join(",", elements)}]"; + } + } + + if (tryDynamic && (typeof(T) == typeof(object)) && !(value is null)) + { + return ToNativeJson((dynamic)value, tryDynamic: false); + } + + return value.ToJson(_jsonSettings); + } + } +} diff --git a/Realm/Realm/ISchemaSource.cs b/Realm/Realm/ISchemaSource.cs deleted file mode 100644 index b55ae965ff..0000000000 --- a/Realm/Realm/ISchemaSource.cs +++ /dev/null @@ -1,36 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using Realms.Schema; - -namespace Realms -{ - /// - /// An object describing its properties in terms of a . - /// - public interface ISchemaSource - { - /// - /// Gets the , describing the persisted properties of the object. If the object is a - /// single , the schema will describe the object itself. If it is a collection, it will - /// describe the contained objects. - /// - /// The ObjectSchema of the object or contained objects. - ObjectSchema ObjectSchema { get; } - } -} diff --git a/Realm/Realm/InteropConfig.cs b/Realm/Realm/InteropConfig.cs index edb301adb5..04635796c3 100644 --- a/Realm/Realm/InteropConfig.cs +++ b/Realm/Realm/InteropConfig.cs @@ -53,16 +53,7 @@ internal static class InteropConfig try { - var specialFolderType = typeof(Environment).GetNestedType("SpecialFolder", BindingFlags.Public); - if (specialFolderType != null) - { - var getFolderPath = typeof(Environment).GetMethod("GetFolderPath", new[] { specialFolderType }); - if (getFolderPath != null) - { - var personalField = specialFolderType.GetField("Personal"); - return (string)getFolderPath.Invoke(null, new[] { personalField.GetValue(null) }); - } - } + return Environment.GetFolderPath(Environment.SpecialFolder.Personal); } catch { @@ -78,7 +69,7 @@ internal static class InteropConfig Directory.CreateDirectory(folder); return folder; - bool IsDirectoryWritable(string path) + static bool IsDirectoryWritable(string path) { if (!Directory.Exists(path)) { diff --git a/Realm/Realm/Linq/IRealmCollection.cs b/Realm/Realm/Linq/IRealmCollection.cs index e4ba12dd44..475d122698 100644 --- a/Realm/Realm/Linq/IRealmCollection.cs +++ b/Realm/Realm/Linq/IRealmCollection.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using Realms.Schema; namespace Realms { @@ -31,13 +32,13 @@ namespace Realms /// or null if an has error occurred. /// An exception that might have occurred while asynchronously monitoring a /// for changes, or null if no errors have occurred. - /// Type of the which is being returned. + /// Type of the , , or primitive which is being returned. public delegate void NotificationCallbackDelegate(IRealmCollection sender, ChangeSet changes, Exception error); /// - /// Iterable, sortable collection of one kind of RealmObject resulting from or from a LINQ query expression. + /// Iterable, sortable collection of one kind of RealmObjectBase resulting from or from a LINQ query expression. /// - /// Type of the which is being returned. + /// Type of the , , or primitive which is being returned. public interface IRealmCollection : IReadOnlyList, INotifyCollectionChanged, INotifyPropertyChanged { /// @@ -75,6 +76,14 @@ public interface IRealmCollection : IReadOnlyList, INotifyCollectionCh /// The instance this collection belongs to. Realm Realm { get; } + /// + /// Gets the , describing the persisted properties of the + /// s or s contained in the collection. If the collection contains + /// primitive values, ObjectSchema will be null. + /// + /// The ObjectSchema of the object or contained objects. + ObjectSchema ObjectSchema { get; } + /// /// Gets a value indicating whether this collection is frozen. Frozen collections are immutable and can be accessed /// from any thread. The objects read from a frozen collection will also be frozen. diff --git a/Realm/Realm/Linq/RealmResults.cs b/Realm/Realm/Linq/RealmResults.cs index 52faa28561..e87cba08c0 100644 --- a/Realm/Realm/Linq/RealmResults.cs +++ b/Realm/Realm/Linq/RealmResults.cs @@ -36,13 +36,13 @@ internal class RealmResults : RealmCollectionBase, IOrderedQueryable, I public IQueryProvider Provider { get; } - internal RealmResults(Realm realm, RealmObject.Metadata metadata, RealmResultsProvider realmResultsProvider, Expression expression) : base(realm, metadata) + internal RealmResults(Realm realm, RealmObjectBase.Metadata metadata, RealmResultsProvider realmResultsProvider, Expression expression) : base(realm, metadata) { Provider = realmResultsProvider; Expression = expression ?? Expression.Constant(this); } - internal RealmResults(Realm realm, RealmObject.Metadata metadata, ResultsHandle handle = null) + internal RealmResults(Realm realm, RealmObjectBase.Metadata metadata, ResultsHandle handle = null) : this(realm, metadata, new RealmResultsProvider(realm, metadata), null) { _handle = handle ?? metadata.Table.CreateResults(realm.SharedRealmHandle); @@ -88,7 +88,7 @@ public override int IndexOf(T value) throw new NotSupportedException("IndexOf on non-object results is not supported."); } - var obj = Operator.Convert(value); + var obj = Operator.Convert(value); if (!obj.IsManaged) { throw new ArgumentException("Value does not belong to a realm", nameof(value)); diff --git a/Realm/Realm/Linq/RealmResultsProvider.cs b/Realm/Realm/Linq/RealmResultsProvider.cs index 4e97ebf3b8..059364cd76 100644 --- a/Realm/Realm/Linq/RealmResultsProvider.cs +++ b/Realm/Realm/Linq/RealmResultsProvider.cs @@ -27,9 +27,9 @@ namespace Realms internal class RealmResultsProvider : IQueryProvider { private readonly Realm _realm; - private readonly RealmObject.Metadata _metadata; + private readonly RealmObjectBase.Metadata _metadata; - internal RealmResultsProvider(Realm realm, RealmObject.Metadata metadata) + internal RealmResultsProvider(Realm realm, RealmObjectBase.Metadata metadata) { _realm = realm; _metadata = metadata; diff --git a/Realm/Realm/Linq/RealmResultsVisitor.cs b/Realm/Realm/Linq/RealmResultsVisitor.cs index fbb18787db..2258d0346e 100644 --- a/Realm/Realm/Linq/RealmResultsVisitor.cs +++ b/Realm/Realm/Linq/RealmResultsVisitor.cs @@ -23,6 +23,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using MongoDB.Bson; using Realms.Native; using Realms.Schema; using LazyMethod = System.Lazy; @@ -32,7 +33,7 @@ namespace Realms internal class RealmResultsVisitor : ExpressionVisitor { private readonly Realm _realm; - private readonly RealmObject.Metadata _metadata; + private readonly RealmObjectBase.Metadata _metadata; private QueryHandle _coreQueryHandle; // set when recurse down to VisitConstant private SortDescriptorHandle _sortDescriptor; @@ -77,7 +78,7 @@ internal static class String } } - internal RealmResultsVisitor(Realm realm, RealmObject.Metadata metadata) + internal RealmResultsVisitor(Realm realm, RealmObjectBase.Metadata metadata) { _realm = realm; _metadata = metadata; @@ -150,7 +151,7 @@ private IntPtr[] TraverseSort(MemberExpression expression) return chain.ToArray(); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObject instance will own its handle.")] + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObjectBase instance will own its handle.")] protected override Expression VisitMethodCall(MethodCallExpression node) { if (node.Method.DeclaringType == typeof(Queryable)) @@ -329,55 +330,55 @@ protected override Expression VisitMethodCall(MethodCallExpression node) if (AreMethodsSame(node.Method, Methods.String.Contains.Value)) { - queryMethod = (q, c, v) => q.StringContains(c, v, caseSensitive: true); + queryMethod = (q, r, p, v) => q.StringContains(r, p, v, caseSensitive: true); } else if (IsStringContainsWithComparison(node.Method, out var index)) { member = node.Arguments[0] as MemberExpression; stringArgumentIndex = index; - queryMethod = (q, c, v) => q.StringContains(c, v, GetComparisonCaseSensitive(node)); + queryMethod = (q, r, p, v) => q.StringContains(r, p, v, GetComparisonCaseSensitive(node)); } else if (AreMethodsSame(node.Method, Methods.String.StartsWith.Value)) { - queryMethod = (q, c, v) => q.StringStartsWith(c, v, caseSensitive: true); + queryMethod = (q, r, p, v) => q.StringStartsWith(r, p, v, caseSensitive: true); } else if (AreMethodsSame(node.Method, Methods.String.StartsWithStringComparison.Value)) { - queryMethod = (q, c, v) => q.StringStartsWith(c, v, GetComparisonCaseSensitive(node)); + queryMethod = (q, r, p, v) => q.StringStartsWith(r, p, v, GetComparisonCaseSensitive(node)); } else if (AreMethodsSame(node.Method, Methods.String.EndsWith.Value)) { - queryMethod = (q, c, v) => q.StringEndsWith(c, v, caseSensitive: true); + queryMethod = (q, r, p, v) => q.StringEndsWith(r, p, v, caseSensitive: true); } else if (AreMethodsSame(node.Method, Methods.String.EndsWithStringComparison.Value)) { - queryMethod = (q, c, v) => q.StringEndsWith(c, v, GetComparisonCaseSensitive(node)); + queryMethod = (q, r, p, v) => q.StringEndsWith(r, p, v, GetComparisonCaseSensitive(node)); } else if (AreMethodsSame(node.Method, Methods.String.IsNullOrEmpty.Value)) { member = node.Arguments.SingleOrDefault() as MemberExpression; if (member == null) { - throw new NotSupportedException($"The method '{node.Method}' has to be invoked with a RealmObject member"); + throw new NotSupportedException($"The method '{node.Method}' has to be invoked with a RealmObjectBase member"); } var columnName = GetColumnName(member, node.NodeType); - var columnKey = _coreQueryHandle.GetColumnKey(columnName); + var propertyIndex = _metadata.PropertyIndices[columnName]; _coreQueryHandle.GroupBegin(); - _coreQueryHandle.NullEqual(columnKey); + _coreQueryHandle.NullEqual(_realm.SharedRealmHandle, propertyIndex); _coreQueryHandle.Or(); - _coreQueryHandle.StringEqual(columnKey, string.Empty, caseSensitive: true); + _coreQueryHandle.StringEqual(_realm.SharedRealmHandle, propertyIndex, string.Empty, caseSensitive: true); _coreQueryHandle.GroupEnd(); return node; } else if (AreMethodsSame(node.Method, Methods.String.EqualsMethod.Value)) { - queryMethod = (q, c, v) => q.StringEqual(c, v, caseSensitive: true); + queryMethod = (q, r, p, v) => q.StringEqual(r, p, v, caseSensitive: true); } else if (AreMethodsSame(node.Method, Methods.String.EqualsStringComparison.Value)) { - queryMethod = (q, c, v) => q.StringEqual(c, v, GetComparisonCaseSensitive(node)); + queryMethod = (q, r, p, v) => q.StringEqual(r, p, v, GetComparisonCaseSensitive(node)); } else if (AreMethodsSame(node.Method, Methods.String.Like.Value)) { @@ -388,7 +389,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) throw new NotSupportedException($"The method '{node.Method}' has to be invoked with a string and boolean constant arguments."); } - queryMethod = (q, c, v) => q.StringLike(c, v, (bool)caseSensitive); + queryMethod = (q, r, p, v) => q.StringLike(r, p, v, (bool)caseSensitive); } if (queryMethod != null) @@ -397,11 +398,11 @@ protected override Expression VisitMethodCall(MethodCallExpression node) if (member == null) { - throw new NotSupportedException($"The method '{node.Method}' has to be invoked on a RealmObject member"); + throw new NotSupportedException($"The method '{node.Method}' has to be invoked on a RealmObjectBase member"); } var columnName = GetColumnName(member, node.NodeType); - var columnKey = _coreQueryHandle.GetColumnKey(columnName); + var propertyIndex = _metadata.PropertyIndices[columnName]; if (!TryExtractConstantValue(node.Arguments[stringArgumentIndex], out object argument) || (argument != null && argument.GetType() != typeof(string))) @@ -409,7 +410,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) throw new NotSupportedException($"The method '{node.Method}' has to be invoked with a single string constant argument or closure variable"); } - queryMethod(_coreQueryHandle, columnKey, (string)argument); + queryMethod(_coreQueryHandle, _realm.SharedRealmHandle, propertyIndex, (string)argument); return node; } } @@ -586,9 +587,9 @@ protected override Expression VisitBinary(BinaryExpression node) throw new NotSupportedException($"The rhs of the binary operator '{rightExpression.NodeType}' should be a constant or closure variable expression. \nUnable to process '{node.Right}'."); } - if (rightValue is RealmObject obj && (!obj.IsManaged || !obj.IsValid)) + if (rightValue is RealmObjectBase obj && (!obj.IsManaged || !obj.IsValid)) { - throw new NotSupportedException($"The rhs of the binary operator '{rightExpression.NodeType}' should be a managed RealmObject. \nUnable to process '{node.Right}'."); + throw new NotSupportedException($"The rhs of the binary operator '{rightExpression.NodeType}' should be a managed RealmObjectBase. \nUnable to process '{node.Right}'."); } switch (node.NodeType) @@ -619,29 +620,29 @@ protected override Expression VisitBinary(BinaryExpression node) return node; } - private static void AddQueryEqual(QueryHandle queryHandle, string columnName, object value, Type columnType) + private void AddQueryEqual(QueryHandle queryHandle, string columnName, object value, Type columnType) { - var columnKey = queryHandle.GetColumnKey(columnName); + var propertyIndex = _metadata.PropertyIndices[columnName]; switch (value) { case null: - queryHandle.NullEqual(columnKey); + queryHandle.NullEqual(_realm.SharedRealmHandle, propertyIndex); break; case string stringValue: - queryHandle.StringEqual(columnKey, stringValue, caseSensitive: true); + queryHandle.StringEqual(_realm.SharedRealmHandle, propertyIndex, stringValue, caseSensitive: true); break; case bool boolValue: - queryHandle.BoolEqual(columnKey, boolValue); + queryHandle.PrimitiveEqual(_realm.SharedRealmHandle, propertyIndex, PrimitiveValue.Bool(boolValue)); break; case DateTimeOffset dateValue: - queryHandle.TimestampTicksEqual(columnKey, dateValue); + queryHandle.PrimitiveEqual(_realm.SharedRealmHandle, propertyIndex, PrimitiveValue.Date(dateValue)); break; case byte[] buffer: if (buffer.Length == 0) { - // see RealmObject.SetByteArrayValue - queryHandle.BinaryEqual(columnKey, (IntPtr)0x1, IntPtr.Zero); + // see RealmObjectBase.SetByteArrayValue + queryHandle.BinaryEqual(_realm.SharedRealmHandle, propertyIndex, (IntPtr)0x1, IntPtr.Zero); return; } @@ -649,43 +650,43 @@ private static void AddQueryEqual(QueryHandle queryHandle, string columnName, ob { fixed (byte* bufferPtr = (byte[])value) { - queryHandle.BinaryEqual(columnKey, (IntPtr)bufferPtr, (IntPtr)buffer.Length); + queryHandle.BinaryEqual(_realm.SharedRealmHandle, propertyIndex, (IntPtr)bufferPtr, (IntPtr)buffer.Length); } } break; - case RealmObject obj: - queryHandle.ObjectEqual(columnKey, obj.ObjectHandle); + case RealmObjectBase obj: + queryHandle.ObjectEqual(_realm.SharedRealmHandle, propertyIndex, obj.ObjectHandle); break; default: // The other types aren't handled by the switch because of potential compiler applied conversions - AddQueryForConvertibleTypes(columnKey, value, columnType, queryHandle.NumericEqualMethods); + AddQueryForConvertibleTypes(_realm.SharedRealmHandle, propertyIndex, value, columnType, queryHandle.PrimitiveEqual); break; } } - private static void AddQueryNotEqual(QueryHandle queryHandle, string columnName, object value, Type columnType) + private void AddQueryNotEqual(QueryHandle queryHandle, string columnName, object value, Type columnType) { - var columnKey = queryHandle.GetColumnKey(columnName); + var propertyIndex = _metadata.PropertyIndices[columnName]; switch (value) { case null: - queryHandle.NullNotEqual(columnKey); + queryHandle.NullNotEqual(_realm.SharedRealmHandle, propertyIndex); break; case string stringValue: - queryHandle.StringNotEqual(columnKey, stringValue, caseSensitive: true); + queryHandle.StringNotEqual(_realm.SharedRealmHandle, propertyIndex, stringValue, caseSensitive: true); break; case bool boolValue: - queryHandle.BoolNotEqual(columnKey, boolValue); + queryHandle.PrimitiveNotEqual(_realm.SharedRealmHandle, propertyIndex, PrimitiveValue.Bool(boolValue)); break; case DateTimeOffset date: - queryHandle.TimestampTicksNotEqual(columnKey, date); + queryHandle.PrimitiveNotEqual(_realm.SharedRealmHandle, propertyIndex, PrimitiveValue.Date(date)); break; case byte[] buffer: if (buffer.Length == 0) { - // see RealmObject.SetByteArrayValue - queryHandle.BinaryNotEqual(columnKey, (IntPtr)0x1, IntPtr.Zero); + // see RealmObjectBase.SetByteArrayValue + queryHandle.BinaryNotEqual(_realm.SharedRealmHandle, propertyIndex, (IntPtr)0x1, IntPtr.Zero); return; } @@ -693,95 +694,95 @@ private static void AddQueryNotEqual(QueryHandle queryHandle, string columnName, { fixed (byte* bufferPtr = (byte[])value) { - queryHandle.BinaryNotEqual(columnKey, (IntPtr)bufferPtr, (IntPtr)buffer.Length); + queryHandle.BinaryNotEqual(_realm.SharedRealmHandle, propertyIndex, (IntPtr)bufferPtr, (IntPtr)buffer.Length); } } break; - case RealmObject obj: + case RealmObjectBase obj: queryHandle.Not(); - queryHandle.ObjectEqual(columnKey, obj.ObjectHandle); + queryHandle.ObjectEqual(_realm.SharedRealmHandle, propertyIndex, obj.ObjectHandle); break; default: // The other types aren't handled by the switch because of potential compiler applied conversions - AddQueryForConvertibleTypes(columnKey, value, columnType, queryHandle.NumericNotEqualMethods); + AddQueryForConvertibleTypes(_realm.SharedRealmHandle, propertyIndex, value, columnType, queryHandle.PrimitiveNotEqual); break; } } - private static void AddQueryLessThan(QueryHandle queryHandle, string columnName, object value, Type columnType) + private void AddQueryLessThan(QueryHandle queryHandle, string columnName, object value, Type columnType) { - var columnKey = queryHandle.GetColumnKey(columnName); + var propertyIndex = _metadata.PropertyIndices[columnName]; switch (value) { case DateTimeOffset date: - queryHandle.TimestampTicksLess(columnKey, date); + queryHandle.PrimitiveLess(_realm.SharedRealmHandle, propertyIndex, PrimitiveValue.Date(date)); break; case string _: case bool _: throw new Exception($"Unsupported type {value.GetType().Name}"); default: // The other types aren't handled by the switch because of potential compiler applied conversions - AddQueryForConvertibleTypes(columnKey, value, columnType, queryHandle.NumericLessMethods); + AddQueryForConvertibleTypes(_realm.SharedRealmHandle, propertyIndex, value, columnType, queryHandle.PrimitiveLess); break; } } - private static void AddQueryLessThanOrEqual(QueryHandle queryHandle, string columnName, object value, Type columnType) + private void AddQueryLessThanOrEqual(QueryHandle queryHandle, string columnName, object value, Type columnType) { - var columnKey = queryHandle.GetColumnKey(columnName); + var propertyIndex = _metadata.PropertyIndices[columnName]; switch (value) { case DateTimeOffset date: - queryHandle.TimestampTicksLessEqual(columnKey, date); + queryHandle.PrimitiveLessEqual(_realm.SharedRealmHandle, propertyIndex, PrimitiveValue.Date(date)); break; case string _: case bool _: throw new Exception($"Unsupported type {value.GetType().Name}"); default: // The other types aren't handled by the switch because of potential compiler applied conversions - AddQueryForConvertibleTypes(columnKey, value, columnType, queryHandle.NumericLessEqualMethods); + AddQueryForConvertibleTypes(_realm.SharedRealmHandle, propertyIndex, value, columnType, queryHandle.PrimitiveLessEqual); break; } } - private static void AddQueryGreaterThan(QueryHandle queryHandle, string columnName, object value, Type columnType) + private void AddQueryGreaterThan(QueryHandle queryHandle, string columnName, object value, Type columnType) { - var columnKey = queryHandle.GetColumnKey(columnName); + var propertyIndex = _metadata.PropertyIndices[columnName]; switch (value) { case DateTimeOffset date: - queryHandle.TimestampTicksGreater(columnKey, date); + queryHandle.PrimitiveGreater(_realm.SharedRealmHandle, propertyIndex, PrimitiveValue.Date(date)); break; case string _: case bool _: throw new Exception($"Unsupported type {value.GetType().Name}"); default: // The other types aren't handled by the switch because of potential compiler applied conversions - AddQueryForConvertibleTypes(columnKey, value, columnType, queryHandle.NumericGreaterMethods); + AddQueryForConvertibleTypes(_realm.SharedRealmHandle, propertyIndex, value, columnType, queryHandle.PrimitiveGreater); break; } } - private static void AddQueryGreaterThanOrEqual(QueryHandle queryHandle, string columnName, object value, Type columnType) + private void AddQueryGreaterThanOrEqual(QueryHandle queryHandle, string columnName, object value, Type columnType) { - var columnKey = queryHandle.GetColumnKey(columnName); + var propertyIndex = _metadata.PropertyIndices[columnName]; switch (value) { case DateTimeOffset date: - queryHandle.TimestampTicksGreaterEqual(columnKey, date); + queryHandle.PrimitiveGreaterEqual(_realm.SharedRealmHandle, propertyIndex, PrimitiveValue.Date(date)); break; case string _: case bool _: throw new Exception($"Unsupported type {value.GetType().Name}"); default: // The other types aren't handled by the switch because of potential compiler applied conversions - AddQueryForConvertibleTypes(columnKey, value, columnType, queryHandle.NumericGreaterEqualMethods); + AddQueryForConvertibleTypes(_realm.SharedRealmHandle, propertyIndex, value, columnType, queryHandle.PrimitiveGreaterEqual); break; } } - private static void AddQueryForConvertibleTypes(ColumnKey columnKey, object value, Type columnType, QueryHandle.NumericQueryMethods queryMethods) + private static void AddQueryForConvertibleTypes(SharedRealmHandle realm, IntPtr propertyIndex, object value, Type columnType, Action action) { if (columnType.IsConstructedGenericType && columnType.GetGenericTypeDefinition() == typeof(Nullable<>)) { @@ -792,24 +793,39 @@ private static void AddQueryForConvertibleTypes(ColumnKey columnKey, object valu columnType == typeof(short) || columnType == typeof(char) || columnType == typeof(int) || + columnType == typeof(long) || columnType == typeof(RealmInteger) || columnType == typeof(RealmInteger) || - columnType == typeof(RealmInteger)) + columnType == typeof(RealmInteger) || + columnType == typeof(RealmInteger)) { - queryMethods.Int(columnKey, (int)Convert.ChangeType(value, typeof(int))); - } - else if (columnType == typeof(long) || - columnType == typeof(RealmInteger)) - { - queryMethods.Long(columnKey, (long)Convert.ChangeType(value, typeof(long))); + action(realm, propertyIndex, PrimitiveValue.Int((long)Convert.ChangeType(value, typeof(long)))); } else if (columnType == typeof(float)) { - queryMethods.Float(columnKey, (float)Convert.ChangeType(value, typeof(float))); + action(realm, propertyIndex, PrimitiveValue.Float((float)Convert.ChangeType(value, typeof(float)))); } else if (columnType == typeof(double)) { - queryMethods.Double(columnKey, (double)Convert.ChangeType(value, typeof(double))); + action(realm, propertyIndex, PrimitiveValue.Double((double)Convert.ChangeType(value, typeof(double)))); + } + else if (columnType == typeof(Decimal128)) + { + // This is needed, because Convert.ChangeType will throw if value is Decimal128 + if (!(value is Decimal128 decimalValue)) + { + decimalValue = (Decimal128)Convert.ChangeType(value, typeof(Decimal128)); + } + + action(realm, propertyIndex, PrimitiveValue.Decimal(decimalValue)); + } + else if (columnType == typeof(decimal)) + { + action(realm, propertyIndex, PrimitiveValue.Decimal((decimal)Convert.ChangeType(value, typeof(decimal)))); + } + else if (columnType == typeof(ObjectId)) + { + action(realm, propertyIndex, PrimitiveValue.ObjectId((ObjectId)Convert.ChangeType(value, typeof(ObjectId)))); } else { @@ -825,20 +841,12 @@ private static bool GetComparisonCaseSensitive(MethodCallExpression m) } var comparison = (StringComparison)argument; - bool caseSensitive; - switch (comparison) + return comparison switch { - case StringComparison.Ordinal: - caseSensitive = true; - break; - case StringComparison.OrdinalIgnoreCase: - caseSensitive = false; - break; - default: - throw new NotSupportedException($"The comparison {comparison} is not yet supported. Use {StringComparison.Ordinal} or {StringComparison.OrdinalIgnoreCase}."); - } - - return caseSensitive; + StringComparison.Ordinal => true, + StringComparison.OrdinalIgnoreCase => false, + _ => throw new NotSupportedException($"The comparison {comparison} is not yet supported. Use {StringComparison.Ordinal} or {StringComparison.OrdinalIgnoreCase}."), + }; } private string GetColumnName(MemberExpression memberExpression, ExpressionType? parentType = null) diff --git a/Realm/Realm/MarshalHelpers.cs b/Realm/Realm/MarshalHelpers.cs index cf67a9b13c..d08309235c 100644 --- a/Realm/Realm/MarshalHelpers.cs +++ b/Realm/Realm/MarshalHelpers.cs @@ -25,16 +25,6 @@ namespace Realms { internal class MarshalHelpers { - public static IntPtr BoolToIntPtr(bool value) - { - return value ? (IntPtr)1 : (IntPtr)0; - } - - public static bool IntPtrToBool(IntPtr value) - { - return (IntPtr)1 == value; - } - public delegate IntPtr NativeCollectionGetter(IntPtr buffer, IntPtr bufferLength, out bool isNull, out NativeException ex); public delegate void NativeCollectionSetter(IntPtr buffer, IntPtr bufferLength, bool hasValue, out NativeException ex); diff --git a/Realm/Realm/Native/AppConfiguration.cs b/Realm/Realm/Native/AppConfiguration.cs new file mode 100644 index 0000000000..62de793f32 --- /dev/null +++ b/Realm/Realm/Native/AppConfiguration.cs @@ -0,0 +1,112 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Runtime.InteropServices; + +namespace Realms.Sync.Native +{ + [StructLayout(LayoutKind.Sequential)] + internal struct AppConfiguration + { + [MarshalAs(UnmanagedType.LPWStr)] + private string app_id; + private IntPtr app_id_len; + + internal string AppId + { + set + { + app_id = value; + app_id_len = (IntPtr)value.Length; + } + } + + [MarshalAs(UnmanagedType.LPWStr)] + private string base_file_path; + private IntPtr base_file_path_len; + + internal string BaseFilePath + { + set + { + base_file_path = value; + base_file_path_len = (IntPtr)value.Length; + } + } + + [MarshalAs(UnmanagedType.LPWStr)] + private string base_url; + private IntPtr base_url_len; + + internal string BaseUrl + { + set + { + base_url = value; + base_url_len = (IntPtr)(value?.Length ?? 0); + } + } + + [MarshalAs(UnmanagedType.LPWStr)] + private string local_app_name; + private IntPtr local_app_name_len; + + internal string LocalAppName + { + set + { + local_app_name = value; + local_app_name_len = (IntPtr)(value?.Length ?? 0); + } + } + + [MarshalAs(UnmanagedType.LPWStr)] + private string local_app_version; + private IntPtr local_app_version_len; + + internal string LocalAppVersion + { + set + { + local_app_version = value; + local_app_version_len = (IntPtr)(value?.Length ?? 0); + } + } + + internal UInt64 default_request_timeout_ms; + + private MetadataPersistenceMode metadata_persistence; + + [MarshalAs(UnmanagedType.U1)] + private bool metadata_persistence_has_value; + + internal MetadataPersistenceMode? MetadataPersistence + { + set + { + metadata_persistence = value.HasValue ? value.Value : default; + metadata_persistence_has_value = value.HasValue; + } + } + + internal LogLevel log_level; + + internal IntPtr managed_log_callback; + } +} diff --git a/Realm/Realm/Native/StringValue.cs b/Realm/Realm/Native/AppError.cs similarity index 50% rename from Realm/Realm/Native/StringValue.cs rename to Realm/Realm/Native/AppError.cs index 886fe59475..ae107154b7 100644 --- a/Realm/Realm/Native/StringValue.cs +++ b/Realm/Realm/Native/AppError.cs @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2016 Realm Inc. +// 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. @@ -16,16 +16,33 @@ // //////////////////////////////////////////////////////////////////////////// +using System; using System.Runtime.InteropServices; +using System.Text; namespace Realms.Sync.Native { [StructLayout(LayoutKind.Sequential)] - internal struct StringValue + internal unsafe struct AppError { - internal static readonly int Size = Marshal.SizeOf(); + [MarshalAs(UnmanagedType.U1)] + public bool is_null; - [MarshalAs(UnmanagedType.LPStr)] - public string Value; + private byte* message_buf; + private IntPtr message_len; + + private byte* error_category_buf; + private IntPtr error_category_len; + + private byte* logs_link_buf; + private IntPtr logs_link_len; + + public int http_status_code; + + public string Message => message_buf == null ? null : Encoding.UTF8.GetString(message_buf, (int)message_len); + + public string ErrorCategory => error_category_buf == null ? null : Encoding.UTF8.GetString(error_category_buf, (int)error_category_len); + + public string LogsLink => logs_link_buf == null ? null : Encoding.UTF8.GetString(logs_link_buf, (int)logs_link_len); } -} \ No newline at end of file +} diff --git a/Realm/Realm/Native/MarshaledString.cs b/Realm/Realm/Native/BsonPayload.cs similarity index 69% rename from Realm/Realm/Native/MarshaledString.cs rename to Realm/Realm/Native/BsonPayload.cs index 47cef0c5a6..a2077d7c83 100644 --- a/Realm/Realm/Native/MarshaledString.cs +++ b/Realm/Realm/Native/BsonPayload.cs @@ -17,22 +17,21 @@ //////////////////////////////////////////////////////////////////////////// using System; +using System.Runtime.InteropServices; using System.Text; +using MongoDB.Bson.Serialization; namespace Realms.Native { - internal unsafe struct MarshaledString + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct BsonPayload { -#pragma warning disable CS0649 + private byte* serialized; + private IntPtr serialized_len; - private byte* _buffer; - private IntPtr _length; - -#pragma warning restore CS0649 - - public override string ToString() + public T GetValue() { - return Encoding.UTF8.GetString(_buffer, (int)_length); + return BsonSerializer.Deserialize(Encoding.UTF8.GetString(serialized, (int)serialized_len)); } } } diff --git a/Realm/Realm/Native/Configuration.cs b/Realm/Realm/Native/Configuration.cs index dc5c2d539b..0e64138bcc 100644 --- a/Realm/Realm/Native/Configuration.cs +++ b/Realm/Realm/Native/Configuration.cs @@ -21,11 +21,11 @@ namespace Realms.Native { - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate bool MigrationCallback(IntPtr oldRealm, IntPtr newRealm, Schema oldSchema, ulong schemaVersion, IntPtr managedMigrationHandle); - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate bool ShouldCompactCallback(IntPtr config, ulong totalSize, ulong dataSize); @@ -45,12 +45,12 @@ internal string Path } } - [MarshalAs(UnmanagedType.I1)] + [MarshalAs(UnmanagedType.U1)] internal bool read_only; - [MarshalAs(UnmanagedType.I1)] + [MarshalAs(UnmanagedType.U1)] internal bool in_memory; - [MarshalAs(UnmanagedType.I1)] + [MarshalAs(UnmanagedType.U1)] internal bool delete_if_migration_needed; internal ulong schema_version; @@ -61,7 +61,7 @@ internal string Path internal ShouldCompactCallback should_compact_callback; internal IntPtr managed_should_compact_delegate; - [MarshalAs(UnmanagedType.I1)] + [MarshalAs(UnmanagedType.U1)] internal bool enable_cache; internal ulong max_number_of_active_versions; diff --git a/Realm/Realm/Sync/Permissions/KeyValueCondition.cs b/Realm/Realm/Native/Credentials.cs similarity index 52% rename from Realm/Realm/Sync/Permissions/KeyValueCondition.cs rename to Realm/Realm/Native/Credentials.cs index 471394bf5d..66e3fd950d 100644 --- a/Realm/Realm/Sync/Permissions/KeyValueCondition.cs +++ b/Realm/Realm/Native/Credentials.cs @@ -16,27 +16,40 @@ // //////////////////////////////////////////////////////////////////////////// -namespace Realms.Sync +using System; +using System.Runtime.InteropServices; +using static Realms.Sync.Credentials; + +namespace Realms.Sync.Native { - internal class KeyValueCondition : PermissionCondition + internal struct Credentials { - public string Key { get; } + internal AuthProvider provider; - public string Value { get; } + [MarshalAs(UnmanagedType.LPWStr)] + private string token; + private IntPtr token_len; - public KeyValueCondition(string key, string value) + internal string Token { - Key = key; - Value = value; + set + { + token = value; + token_len = (IntPtr)(value?.Length ?? 0); + } } - internal override object ToJsonObject() + [MarshalAs(UnmanagedType.LPWStr)] + private string password; + private IntPtr password_len; + + internal string Password { - return new + set { - metadataKey = Key, - metadataValue = Value, - }; + password = value; + password_len = (IntPtr)(value?.Length ?? 0); + } } } -} \ No newline at end of file +} diff --git a/Realm/Realm/Native/FindAndModifyOptions.cs b/Realm/Realm/Native/FindAndModifyOptions.cs new file mode 100644 index 0000000000..447a0088fa --- /dev/null +++ b/Realm/Realm/Native/FindAndModifyOptions.cs @@ -0,0 +1,94 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Runtime.InteropServices; +using Realms.Helpers; + +namespace Realms.Native +{ + [StructLayout(LayoutKind.Sequential)] + internal struct FindAndModifyOptions + { + [MarshalAs(UnmanagedType.LPWStr)] + private string projection_buf; + private IntPtr projection_len; + + internal string Projection + { + set + { + projection_buf = value; + projection_len = (IntPtr)(value?.Length ?? 0); + } + } + + [MarshalAs(UnmanagedType.LPWStr)] + private string sort_buf; + private IntPtr sort_len; + + internal string Sort + { + set + { + sort_buf = value; + sort_len = (IntPtr)(value?.Length ?? 0); + } + } + + [MarshalAs(UnmanagedType.U1)] + private bool upsert; + + [MarshalAs(UnmanagedType.U1)] + private bool return_new_document; + + private Int64 limit; + + public static FindAndModifyOptions Find(object projection, object sort, Int64? limit = null) + { + var result = new FindAndModifyOptions(projection, sort); + if (limit.HasValue) + { + result.limit = limit.Value; + } + + return result; + } + + public static FindAndModifyOptions FindAndModify(object projection, object sort, bool upsert = false, bool returnNewDocument = false) + { + var result = new FindAndModifyOptions(projection, sort); + result.upsert = upsert; + result.return_new_document = returnNewDocument; + return result; + } + + private FindAndModifyOptions(object projection, object sort) + { + projection_buf = projection?.ToNativeJson(); + projection_len = projection_buf.IntPtrLength(); + + sort_buf = sort?.ToNativeJson(); + sort_len = sort_buf.IntPtrLength(); + + upsert = false; + return_new_document = false; + limit = 0; + } + } +} diff --git a/Realm/Realm/Native/HttpClientTransport.cs b/Realm/Realm/Native/HttpClientTransport.cs new file mode 100644 index 0000000000..68b0266e38 --- /dev/null +++ b/Realm/Realm/Native/HttpClientTransport.cs @@ -0,0 +1,232 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net.Http; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Realms.Sync.Native; + +namespace Realms.Native +{ + internal static class HttpClientTransport + { + private enum CustomErrorCode + { + NoError = 0, + UnknownHttp = 998, + Unknown = 999, + Timeout = 1000, + } + +#pragma warning disable SA1300 // Element should begin with upper-case letter +#pragma warning disable IDE1006 // Naming Styles + + private enum NativeHttpMethod + { + get, + post, + patch, + put, + del + } + + [StructLayout(LayoutKind.Sequential)] + private unsafe struct HttpClientRequest + { + public NativeHttpMethod method; + + private byte* url_buf; + private IntPtr url_len; + + public UInt64 timeout_ms; + + public IntPtr /* StringStringPair[] */ headers; + public int headers_len; + + private byte* body_buf; + private IntPtr body_len; + + public string Url => Encoding.UTF8.GetString(url_buf, (int)url_len); + + public string Body => Encoding.UTF8.GetString(body_buf, (int)body_len); + } + + [StructLayout(LayoutKind.Sequential)] + private struct HttpClientResponse + { + public Int32 http_status_code; + + public CustomErrorCode custom_status_code; + + [MarshalAs(UnmanagedType.LPWStr)] + private string body; + private IntPtr body_len; + + public string Body + { + set + { + body = value; + body_len = (IntPtr)value.Length; + } + } + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void execute_request(HttpClientRequest request, IntPtr callback_ptr); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_http_transport_install_callbacks", CallingConvention = CallingConvention.Cdecl)] + private static extern void install_callbacks(execute_request execute); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_http_transport_respond", CallingConvention = CallingConvention.Cdecl)] + private static extern void respond( + HttpClientResponse response, + [MarshalAs(UnmanagedType.LPArray), In] StringStringPair[] headers, int headers_len, + IntPtr callback_ptr); + +#pragma warning restore IDE1006 // Naming Styles +#pragma warning restore SA1300 // Element should begin with upper-case letter + + private static readonly HttpClient _httpClient = new HttpClient(); + + internal static void Install() + { + execute_request execute = ExecuteRequest; + + GCHandle.Alloc(execute); + + install_callbacks(execute); + } + + [MonoPInvokeCallback(typeof(execute_request))] + private static async void ExecuteRequest(HttpClientRequest request, IntPtr callback) + { + try + { + try + { + using var message = new HttpRequestMessage(request.method.ToHttpMethod(), request.Url); + foreach (var header in StringStringPair.UnmarshalDictionary(request.headers, request.headers_len)) + { + message.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + if (request.method != NativeHttpMethod.get) + { + message.Content = new StringContent(request.Body, Encoding.UTF8, "application/json"); + } + + using var cts = new CancellationTokenSource(); + cts.CancelAfter((int)request.timeout_ms); + + var response = await _httpClient.SendAsync(message, cts.Token); + var headers = new List(response.Headers.Count()); + foreach (var header in response.Headers) + { + headers.Add(new StringStringPair + { + Key = header.Key, + Value = header.Value.FirstOrDefault() + }); + } + + foreach (var header in response.Content.Headers) + { + headers.Add(new StringStringPair + { + Key = header.Key, + Value = header.Value.FirstOrDefault() + }); + } + + var nativeResponse = new HttpClientResponse + { + http_status_code = (int)response.StatusCode, + Body = await response.Content.ReadAsStringAsync(), + }; + + respond(nativeResponse, headers.ToArray(), headers.Count, callback); + } + catch (HttpRequestException rex) + { + var sb = new StringBuilder("An unexpected error occurred while sending the request"); + + // We're doing this because the message for the top-level exception is usually pretty useless. + // If there's inner exception, we want to skip it and directly go for the more specific messages. + var innerEx = rex.InnerException ?? rex; + while (innerEx != null) + { + sb.Append($": {innerEx.Message}"); + innerEx = innerEx.InnerException; + } + + var nativeResponse = new HttpClientResponse + { + custom_status_code = CustomErrorCode.UnknownHttp, + Body = sb.ToString(), + }; + + respond(nativeResponse, null, 0, callback); + } + catch (TaskCanceledException) + { + var nativeResponse = new HttpClientResponse + { + custom_status_code = CustomErrorCode.Timeout, + Body = $"Operation failed to complete within {request.timeout_ms} ms.", + }; + + respond(nativeResponse, null, 0, callback); + } + catch (Exception ex) + { + var nativeResponse = new HttpClientResponse + { + custom_status_code = CustomErrorCode.Unknown, + Body = ex.Message, + }; + + respond(nativeResponse, null, 0, callback); + } + } + catch (Exception outerEx) + { + Debug.WriteLine($"Unexpected error occurred while trying to respond to a request: {outerEx}"); + } + } + + private static HttpMethod ToHttpMethod(this NativeHttpMethod nativeMethod) + { + return nativeMethod switch + { + NativeHttpMethod.get => HttpMethod.Get, + NativeHttpMethod.post => HttpMethod.Post, + NativeHttpMethod.patch => new HttpMethod("PATCH"), + NativeHttpMethod.put => HttpMethod.Put, + NativeHttpMethod.del => HttpMethod.Delete, + _ => throw new NotSupportedException($"Unsupported HTTP method: {nativeMethod}") + }; + } + } +} diff --git a/Realm/Realm/Native/NativeChangeSet.cs b/Realm/Realm/Native/NativeChangeSet.cs deleted file mode 100644 index 8c97574e84..0000000000 --- a/Realm/Realm/Native/NativeChangeSet.cs +++ /dev/null @@ -1,43 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Runtime.InteropServices; -using Realms.Native; - -namespace Realms.Server.Native -{ - [StructLayout(LayoutKind.Sequential)] - internal struct NativeChangeSet - { - public MarshaledString class_name; - - public MarshaledVector deletions; - public MarshaledVector insertions; - public MarshaledVector modifications; - - public string ClassName => class_name.ToString(); - } - - [StructLayout(LayoutKind.Sequential)] - internal struct NativeModificationDetails - { - public ObjectKey obj; - - public MarshaledVector changed_columns; - } -} diff --git a/Realm/Realm/Native/NativeCommon.cs b/Realm/Realm/Native/NativeCommon.cs index 80ff027f0e..1fab54a5be 100644 --- a/Realm/Realm/Native/NativeCommon.cs +++ b/Realm/Realm/Native/NativeCommon.cs @@ -61,21 +61,14 @@ internal static unsafe void Initialize() { try { - var osVersionPI = typeof(Environment).GetProperty("OSVersion"); - var platformPI = osVersionPI?.PropertyType.GetProperty("Platform"); - var assemblyLocationPI = typeof(Assembly).GetProperty("Location", BindingFlags.Public | BindingFlags.Instance); - if (osVersionPI != null && osVersionPI != null && assemblyLocationPI != null) - { - var osVersion = osVersionPI.GetValue(null); - var platform = platformPI.GetValue(osVersion); + var platform = Environment.OSVersion.Platform; - if (platform.ToString() == "Win32NT") - { - var assemblyLocation = Path.GetDirectoryName((string)assemblyLocationPI.GetValue(typeof(NativeCommon).GetTypeInfo().Assembly)); - var architecture = Environment.Is64BitProcess ? "x64" : "x86"; - var path = Path.Combine(assemblyLocation, "lib", "win32", architecture) + Path.PathSeparator + Environment.GetEnvironmentVariable("PATH"); - Environment.SetEnvironmentVariable("PATH", path); - } + if (platform == PlatformID.Win32NT) + { + var assemblyLocation = Path.GetDirectoryName(typeof(NativeCommon).GetTypeInfo().Assembly.Location); + var architecture = Environment.Is64BitProcess ? "x64" : "x86"; + var path = Path.Combine(assemblyLocation, "lib", "win32", architecture) + Path.PathSeparator + Environment.GetEnvironmentVariable("PATH"); + Environment.SetEnvironmentVariable("PATH", path); } } catch diff --git a/Realm/Realm/Native/NativeException.cs b/Realm/Realm/Native/NativeException.cs index d45f708987..b8c497d65b 100644 --- a/Realm/Realm/Native/NativeException.cs +++ b/Realm/Realm/Native/NativeException.cs @@ -54,5 +54,16 @@ internal void ThrowIfNecessary(Func overrider = throw Convert(overrider); } + + internal void ThrowIfNecessary(GCHandle handleToFree) + { + if (type == RealmExceptionCodes.NoError) + { + return; + } + + handleToFree.Free(); + throw Convert(); + } } } \ No newline at end of file diff --git a/Realm/Realm/Native/PrimitiveValue.cs b/Realm/Realm/Native/PrimitiveValue.cs index daa370ea1f..b727e23feb 100644 --- a/Realm/Realm/Native/PrimitiveValue.cs +++ b/Realm/Realm/Native/PrimitiveValue.cs @@ -1,4 +1,4 @@ -//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// // // Copyright 2017 Realm Inc. // @@ -18,119 +18,279 @@ using System; using System.Runtime.InteropServices; +using MongoDB.Bson; using Realms.Helpers; using Realms.Schema; namespace Realms.Native { [StructLayout(LayoutKind.Explicit)] - internal struct PrimitiveValue + internal unsafe struct PrimitiveValue { [FieldOffset(0)] [MarshalAs(UnmanagedType.U1)] - internal PropertyType type; + private bool bool_value; - [FieldOffset(1)] - [MarshalAs(UnmanagedType.I1)] - internal bool has_value; + [FieldOffset(0)] + private long int_value; - [FieldOffset(8)] - [MarshalAs(UnmanagedType.I1)] - internal bool bool_value; + [FieldOffset(0)] + private float float_value; - [FieldOffset(8)] - internal long int_value; + [FieldOffset(0)] + private double double_value; - [FieldOffset(8)] - internal float float_value; + [FieldOffset(0)] + private fixed ulong decimal_bits[2]; + + [FieldOffset(0)] + private fixed byte object_id_bytes[12]; + // Without this padding, .NET fails to marshal the decimal_bits array correctly and the second element is always 0. [FieldOffset(8)] - internal double double_value; + [Obsolete("Don't use, please!")] + private long dontuse; - public static PrimitiveValue Create(T value, PropertyType type) + [FieldOffset(16)] + [MarshalAs(UnmanagedType.U1)] + public PropertyType Type; + + [FieldOffset(17)] + [MarshalAs(UnmanagedType.U1)] + private bool has_value; + + public static PrimitiveValue Bool(bool value) + { + return new PrimitiveValue + { + Type = PropertyType.Bool, + has_value = true, + bool_value = value + }; + } + + public static PrimitiveValue NullableBool(bool? value) => new PrimitiveValue + { + Type = PropertyType.NullableBool, + has_value = value.HasValue, + bool_value = value.GetValueOrDefault() + }; + + public static PrimitiveValue Int(long value) => new PrimitiveValue + { + Type = PropertyType.Int, + has_value = true, + int_value = value + }; + + public static PrimitiveValue NullableInt(long? value) => new PrimitiveValue + { + Type = PropertyType.NullableInt, + has_value = value.HasValue, + int_value = value.GetValueOrDefault() + }; + + public static PrimitiveValue Float(float value) => new PrimitiveValue + { + Type = PropertyType.Float, + has_value = true, + float_value = value + }; + + public static PrimitiveValue NullableFloat(float? value) => new PrimitiveValue + { + Type = PropertyType.NullableFloat, + has_value = value.HasValue, + float_value = value.GetValueOrDefault() + }; + + public static PrimitiveValue Double(double value) => new PrimitiveValue + { + Type = PropertyType.Double, + has_value = true, + double_value = value + }; + + public static PrimitiveValue NullableDouble(double? value) => new PrimitiveValue + { + Type = PropertyType.NullableDouble, + has_value = value.HasValue, + double_value = value.GetValueOrDefault() + }; + + public static PrimitiveValue Date(DateTimeOffset value) => new PrimitiveValue + { + Type = PropertyType.Date, + has_value = true, + int_value = value.ToUniversalTime().Ticks + }; + + public static PrimitiveValue NullableDate(DateTimeOffset? value) => new PrimitiveValue + { + Type = PropertyType.NullableDate, + has_value = value.HasValue, + int_value = value.GetValueOrDefault().ToUniversalTime().Ticks + }; + + public static PrimitiveValue Decimal(Decimal128 value) { var result = new PrimitiveValue { - type = type, + Type = PropertyType.Decimal, has_value = true }; - switch (type) + result.decimal_bits[0] = value.GetIEEELowBits(); + result.decimal_bits[1] = value.GetIEEEHighBits(); + + return result; + } + + public static PrimitiveValue NullableDecimal(Decimal128? value) + { + var result = new PrimitiveValue + { + Type = PropertyType.NullableDecimal, + has_value = value.HasValue + }; + + if (value.HasValue) { - case PropertyType.Bool: - result.bool_value = Operator.Convert(value); - break; - case PropertyType.Bool | PropertyType.Nullable: - var boolValue = Operator.Convert(value); - result.has_value = boolValue.HasValue; - result.bool_value = boolValue.GetValueOrDefault(); - break; - case PropertyType.Int: - result.int_value = Operator.Convert(value); - break; - case PropertyType.Int | PropertyType.Nullable: - var longValue = Operator.Convert(value); - result.has_value = longValue.HasValue; - result.int_value = longValue.GetValueOrDefault(); - break; - case PropertyType.Float: - result.float_value = Operator.Convert(value); - break; - case PropertyType.Float | PropertyType.Nullable: - var floatValue = Operator.Convert(value); - result.has_value = floatValue.HasValue; - result.float_value = floatValue.GetValueOrDefault(); - break; - case PropertyType.Double: - result.double_value = Operator.Convert(value); - break; - case PropertyType.Double | PropertyType.Nullable: - var doubleValue = Operator.Convert(value); - result.has_value = doubleValue.HasValue; - result.double_value = doubleValue.GetValueOrDefault(); - break; - case PropertyType.Date: - result.int_value = Operator.Convert(value).ToUniversalTime().Ticks; - break; - case PropertyType.Date | PropertyType.Nullable: - var dateValue = Operator.Convert(value); - result.has_value = dateValue.HasValue; - result.int_value = dateValue.GetValueOrDefault().ToUniversalTime().Ticks; - break; - default: - throw new NotSupportedException($"PrimitiveType {type} is not supported."); + result.decimal_bits[0] = value.Value.GetIEEELowBits(); + result.decimal_bits[1] = value.Value.GetIEEEHighBits(); } return result; } - public T Get() + public static PrimitiveValue ObjectId(ObjectId value) + { + var result = new PrimitiveValue + { + Type = PropertyType.ObjectId, + has_value = true + }; + + var objectIdBytes = value.ToByteArray(); + for (var i = 0; i < 12; i++) + { + result.object_id_bytes[i] = objectIdBytes[i]; + } + + return result; + } + + public static PrimitiveValue NullableObjectId(ObjectId? value) + { + var result = new PrimitiveValue + { + Type = PropertyType.NullableObjectId, + has_value = value.HasValue + }; + + if (value.HasValue) + { + var objectIdBytes = value.Value.ToByteArray(); + for (var i = 0; i < 12; i++) + { + result.object_id_bytes[i] = objectIdBytes[i]; + } + } + + return result; + } + + public static PrimitiveValue Create(T value, PropertyType type) + { + return type switch + { + PropertyType.Bool => Bool(Operator.Convert(value)), + PropertyType.NullableBool => NullableBool(Operator.Convert(value)), + PropertyType.Int => Int(Operator.Convert(value)), + PropertyType.NullableInt => NullableInt(Operator.Convert(value)), + PropertyType.Float => Float(Operator.Convert(value)), + PropertyType.NullableFloat => NullableFloat(Operator.Convert(value)), + PropertyType.Double => Double(Operator.Convert(value)), + PropertyType.NullableDouble => NullableDouble(Operator.Convert(value)), + PropertyType.Date => Date(Operator.Convert(value)), + PropertyType.NullableDate => NullableDate(Operator.Convert(value)), + PropertyType.Decimal => Decimal(Operator.Convert(value)), + PropertyType.NullableDecimal => NullableDecimal(Operator.Convert(value)), + PropertyType.ObjectId => ObjectId(Operator.Convert(value)), + PropertyType.NullableObjectId => NullableObjectId(Operator.Convert(value)), + _ => throw new NotSupportedException($"PrimitiveType {type} is not supported."), + }; + } + + public bool ToBool() => bool_value; + + public bool? ToNullableBool() => has_value ? bool_value : (bool?)null; + + public long ToInt() => int_value; + + public long? ToNullableInt() => has_value ? int_value : (long?)null; + + public T ToIntegral() => Operator.Convert(int_value); + + public T ToNullableIntegral() => Operator.Convert(has_value ? int_value : (long?)null); + + public float ToFloat() => float_value; + + public float? ToNullableFloat() => has_value ? float_value : (float?)null; + + public double ToDouble() => double_value; + + public double? ToNullableDouble() => has_value ? double_value : (double?)null; + + public DateTimeOffset ToDate() => new DateTimeOffset(int_value, TimeSpan.Zero); + + public DateTimeOffset? ToNullableDate() => has_value ? new DateTimeOffset(int_value, TimeSpan.Zero) : (DateTimeOffset?)null; + + public Decimal128 ToDecimal() => Decimal128.FromIEEEBits(decimal_bits[1], decimal_bits[0]); + + public Decimal128? ToNullableDecimal() => has_value ? Decimal128.FromIEEEBits(decimal_bits[1], decimal_bits[0]) : (Decimal128?)null; + + public ObjectId ToObjectId() { - switch (type) + var bytes = new byte[12]; + for (var i = 0; i < 12; i++) { - case PropertyType.Bool: - return Operator.Convert(bool_value); - case PropertyType.Bool | PropertyType.Nullable: - return Operator.Convert(has_value ? bool_value : (bool?)null); - case PropertyType.Int: - return Operator.Convert(int_value); - case PropertyType.Int | PropertyType.Nullable: - return Operator.Convert(has_value ? int_value : (long?)null); - case PropertyType.Float: - return Operator.Convert(float_value); - case PropertyType.Float | PropertyType.Nullable: - return Operator.Convert(has_value ? float_value : (float?)null); - case PropertyType.Double: - return Operator.Convert(double_value); - case PropertyType.Double | PropertyType.Nullable: - return Operator.Convert(has_value ? double_value : (double?)null); - case PropertyType.Date: - return Operator.Convert(new DateTimeOffset(int_value, TimeSpan.Zero)); - case PropertyType.Date | PropertyType.Nullable: - return Operator.Convert(has_value ? new DateTimeOffset(int_value, TimeSpan.Zero) : (DateTimeOffset?)null); - default: - throw new NotSupportedException($"PrimitiveType {type} is not supported."); + bytes[i] = object_id_bytes[i]; } + + return new ObjectId(bytes); + } + + public ObjectId? ToNullableObjectId() + { + if (!has_value) + { + return null; + } + + return ToObjectId(); + } + + public T Get() + { + return Type switch + { + PropertyType.Bool => Operator.Convert(ToBool()), + PropertyType.NullableBool => Operator.Convert(ToNullableBool()), + PropertyType.Int => ToIntegral(), + PropertyType.NullableInt => ToNullableIntegral(), + PropertyType.Float => Operator.Convert(ToFloat()), + PropertyType.NullableFloat => Operator.Convert(ToNullableFloat()), + PropertyType.Double => Operator.Convert(ToDouble()), + PropertyType.NullableDouble => Operator.Convert(ToNullableDouble()), + PropertyType.Date => Operator.Convert(ToDate()), + PropertyType.NullableDate => Operator.Convert(ToNullableDate()), + PropertyType.Decimal => Operator.Convert(ToDecimal()), + PropertyType.NullableDecimal => Operator.Convert(ToNullableDecimal()), + PropertyType.ObjectId => Operator.Convert(ToObjectId()), + PropertyType.NullableObjectId => Operator.Convert(ToNullableObjectId()), + _ => throw new NotSupportedException($"PrimitiveType {Type} is not supported."), + }; } } } diff --git a/Realm/Realm/Native/SchemaObject.cs b/Realm/Realm/Native/SchemaObject.cs index 3d79fcfddc..d5e0c4b544 100644 --- a/Realm/Realm/Native/SchemaObject.cs +++ b/Realm/Realm/Native/SchemaObject.cs @@ -30,5 +30,8 @@ internal struct SchemaObject internal int properties_start; internal int properties_end; + + [MarshalAs(UnmanagedType.U1)] + internal bool is_embedded; } } \ No newline at end of file diff --git a/Realm/Realm/Native/SchemaProperty.cs b/Realm/Realm/Native/SchemaProperty.cs index 9e3101d3ce..09223bad0d 100644 --- a/Realm/Realm/Native/SchemaProperty.cs +++ b/Realm/Realm/Native/SchemaProperty.cs @@ -37,10 +37,10 @@ internal struct SchemaProperty [MarshalAs(UnmanagedType.LPStr)] internal string link_origin_property_name; - [MarshalAs(UnmanagedType.I1)] + [MarshalAs(UnmanagedType.U1)] internal bool is_primary; - [MarshalAs(UnmanagedType.I1)] + [MarshalAs(UnmanagedType.U1)] internal bool is_indexed; } } \ No newline at end of file diff --git a/Realm/Realm/Native/StringStringPair.cs b/Realm/Realm/Native/StringStringPair.cs index 67a998444e..63b0ea06d3 100644 --- a/Realm/Realm/Native/StringStringPair.cs +++ b/Realm/Realm/Native/StringStringPair.cs @@ -16,6 +16,9 @@ // //////////////////////////////////////////////////////////////////////////// +using System; +using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; namespace Realms.Sync.Native @@ -30,5 +33,12 @@ internal struct StringStringPair [MarshalAs(UnmanagedType.LPStr)] public string Value; + + public static Dictionary UnmarshalDictionary(IntPtr pairs, int pairsLength) + { + return Enumerable.Range(0, pairsLength) + .Select(i => Marshal.PtrToStructure(IntPtr.Add(pairs, i * StringStringPair.Size))) + .ToDictionary(pair => pair.Key, pair => pair.Value); + } } } \ No newline at end of file diff --git a/Realm/Realm/Native/SyncConfiguration.cs b/Realm/Realm/Native/SyncConfiguration.cs index 377f39334c..2a08accce2 100644 --- a/Realm/Realm/Native/SyncConfiguration.cs +++ b/Realm/Realm/Native/SyncConfiguration.cs @@ -35,54 +35,19 @@ internal SyncUserHandle SyncUserHandle } [MarshalAs(UnmanagedType.LPWStr)] - private string url; + private string partition; - private IntPtr url_len; + private IntPtr partition_len; - internal string Url + internal string Partition { set { - url = value; - url_len = (IntPtr)value.Length; + partition = value; + partition_len = (IntPtr)value.Length; } } - [MarshalAs(UnmanagedType.I1)] - internal bool client_validate_ssl; - - [MarshalAs(UnmanagedType.LPWStr)] - private string trusted_ca_path; - - private IntPtr trusted_ca_path_len; - - internal string TrustedCAPath - { - set - { - trusted_ca_path = value; - trusted_ca_path_len = (IntPtr)(value?.Length ?? 0); - } - } - - [MarshalAs(UnmanagedType.I1)] - internal bool is_partial; - - [MarshalAs(UnmanagedType.LPWStr)] - private string partial_sync_identifier; - - private IntPtr partial_sync_identifier_len; - - internal string PartialSyncIdentifier - { - set - { - partial_sync_identifier = value; - partial_sync_identifier_len = (IntPtr)(value?.Length ?? 0); - } - } - - [MarshalAs(UnmanagedType.U1)] - internal ClientResyncMode client_resync_mode; + internal SessionStopPolicy session_stop_policy; } } diff --git a/Realm/Realm/Native/SynchronizationContextScheduler.cs b/Realm/Realm/Native/SynchronizationContextScheduler.cs index 8a00b918ea..8a21dce557 100644 --- a/Realm/Realm/Native/SynchronizationContextScheduler.cs +++ b/Realm/Realm/Native/SynchronizationContextScheduler.cs @@ -38,7 +38,7 @@ internal static class SynchronizationContextScheduler private delegate void release_context(IntPtr context); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] + [return: MarshalAs(UnmanagedType.U1)] private delegate bool is_on_context(IntPtr context, IntPtr targetContext); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_scheduler_invoke_function", CallingConvention = CallingConvention.Cdecl)] diff --git a/Realm/Realm/Native/NativeChangeDetails.cs b/Realm/Realm/Native/UserApiKey.cs similarity index 54% rename from Realm/Realm/Native/NativeChangeDetails.cs rename to Realm/Realm/Native/UserApiKey.cs index 481f6844dd..b399761eae 100644 --- a/Realm/Realm/Native/NativeChangeDetails.cs +++ b/Realm/Realm/Native/UserApiKey.cs @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2019 Realm Inc. +// 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. @@ -18,25 +18,31 @@ using System; using System.Runtime.InteropServices; -using Realms.Native; +using System.Text; +using MongoDB.Bson; -namespace Realms.Server.Native +namespace Realms.Native { [StructLayout(LayoutKind.Sequential)] - internal struct NativeChangeDetails + internal unsafe struct UserApiKey { - public MarshaledString path; + internal static readonly int Size = Marshal.SizeOf(); - public MarshaledString path_on_disk; + private PrimitiveValue id; - public IntPtr previous_realm; + private byte* key_buf; + private IntPtr key_len; - public IntPtr current_realm; + private byte* name_buf; + private IntPtr name_len; - public MarshaledVector change_sets; + [MarshalAs(UnmanagedType.U1)] + public bool disabled; - public string Path => path.ToString(); + public ObjectId Id => id.ToObjectId(); - public string PathOnDisk => path_on_disk.ToString(); + public string Key => key_buf == null ? null : Encoding.UTF8.GetString(key_buf, (int)key_len); + + public string Name => name_buf == null ? null : Encoding.UTF8.GetString(name_buf, (int)name_len); } } diff --git a/Realm/Realm/Native/ColumnKey.cs b/Realm/Realm/Native/UserProfileField.cs similarity index 80% rename from Realm/Realm/Native/ColumnKey.cs rename to Realm/Realm/Native/UserProfileField.cs index ce43a62283..14d2f7f5f0 100644 --- a/Realm/Realm/Native/ColumnKey.cs +++ b/Realm/Realm/Native/UserProfileField.cs @@ -16,14 +16,18 @@ // //////////////////////////////////////////////////////////////////////////// -using System; -using System.Runtime.InteropServices; - namespace Realms.Native { - [StructLayout(LayoutKind.Sequential, Pack = 8)] - internal struct ColumnKey + internal enum UserProfileField : byte { - private Int64 value; + Name, + Email, + PictureUrl, + FirstName, + LastName, + Gender, + Birthday, + MinAge, + MaxAge, } } diff --git a/Realm/Realm/Realm.cs b/Realm/Realm/Realm.cs index 795252ba62..d1ba8b54d1 100644 --- a/Realm/Realm/Realm.cs +++ b/Realm/Realm/Realm.cs @@ -26,10 +26,13 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using MongoDB.Bson; +using Realms.Dynamic; using Realms.Exceptions; using Realms.Helpers; using Realms.Native; using Realms.Schema; +using Realms.Sync; namespace Realms { @@ -86,13 +89,14 @@ public static Realm GetInstance(RealmConfigurationBase config = null) /// Factory for asynchronously obtaining a instance. /// /// - /// If the configuration points to a remote realm belonging to a Realm Object Server - /// the realm will be downloaded and fully synchronized with the server prior to the completion - /// of the returned Task object. - /// Otherwise this method behaves identically to - /// and immediately returns a completed Task. + /// If the configuration is , the realm will be downloaded and fully + /// synchronized with the server prior to the completion of the returned Task object. + /// Otherwise this method will perform any migrations on a background thread before returning an + /// opened instance to the calling thread. /// - /// A that is completed once the remote realm is fully synchronized or immediately if it's a local realm. + /// + /// A that is completed once the remote realm is fully synchronized or after migrations are executed if it's a local realm. + /// /// A configuration object that describes the realm. /// An optional cancellation token that can be used to cancel the work. public static Task GetInstanceAsync(RealmConfigurationBase config = null, CancellationToken cancellationToken = default) @@ -151,10 +155,8 @@ internal static Realm GetInstance(RealmConfigurationBase config, RealmSchema sch /// true if successful, false if any file operation failed. public static bool Compact(RealmConfigurationBase config = null) { - using (var realm = GetInstance(config)) - { - return realm.SharedRealmHandle.Compact(); - } + using var realm = GetInstance(config); + return realm.SharedRealmHandle.Compact(); } /// @@ -171,12 +173,14 @@ public static void DeleteRealm(RealmConfigurationBase configuration) throw new RealmPermissionDeniedException("Unable to delete Realm because it is still open."); } - File.Delete(fullpath); - File.Delete(fullpath + ".log_a"); // eg: name at end of path is EnterTheMagic.realm.log_a - File.Delete(fullpath + ".log_b"); - File.Delete(fullpath + ".log"); - File.Delete(fullpath + ".lock"); - File.Delete(fullpath + ".note"); + var filesToDelete = new[] { string.Empty, ".log_a", ".log_b", ".log", ".lock", ".note" } + .Select(ext => fullpath + ext) + .Where(File.Exists); + + foreach (var file in filesToDelete) + { + File.Delete(file); + } if (Directory.Exists($"{fullpath}.management")) { @@ -196,7 +200,13 @@ private static bool IsRealmOpen(string path) private State _state; internal readonly SharedRealmHandle SharedRealmHandle; - internal readonly Dictionary Metadata; + internal readonly Dictionary Metadata; + + /// + /// Gets an object encompassing the dynamic API for this Realm instance. + /// + [Preserve] + public Dynamic DynamicApi { get; } /// /// Gets a value indicating whether there is an active is in transaction. @@ -266,11 +276,12 @@ internal Realm(SharedRealmHandle sharedRealmHandle, RealmConfigurationBase confi Metadata = schema.ToDictionary(t => t.Name, CreateRealmObjectMetadata); Schema = schema; IsFrozen = SharedRealmHandle.IsFrozen; + DynamicApi = new Dynamic(this); } - private RealmObject.Metadata CreateRealmObjectMetadata(ObjectSchema schema) + private RealmObjectBase.Metadata CreateRealmObjectMetadata(ObjectSchema schema) { - var table = SharedRealmHandle.GetTable(schema.Name); + var tableHandle = SharedRealmHandle.GetTable(schema.Name); Weaving.IRealmObjectHelper helper; if (schema.Type != null && !Config.IsDynamic) @@ -278,32 +289,29 @@ private RealmObject.Metadata CreateRealmObjectMetadata(ObjectSchema schema) var wovenAtt = schema.Type.GetCustomAttribute(); if (wovenAtt == null) { - throw new RealmException($"Fody not properly installed. {schema.Type.FullName} is a RealmObject but has not been woven."); + throw new RealmException($"Fody not properly installed. {schema.Type.FullName} is a RealmObjectBase but has not been woven."); } helper = (Weaving.IRealmObjectHelper)Activator.CreateInstance(wovenAtt.HelperType); } else { - helper = Dynamic.DynamicRealmObjectHelper.Instance; + helper = DynamicRealmObjectHelper.Instance(schema.IsEmbedded); } var initPropertyMap = new Dictionary(schema.Count); var persistedProperties = -1; var computedProperties = -1; + + // We're taking advantage of the fact OS keeps property indices aligned + // with the property indices in ObjectSchema foreach (var prop in schema) { var index = prop.Type.IsComputed() ? ++computedProperties : ++persistedProperties; initPropertyMap[prop.Name] = (IntPtr)index; } - return new RealmObject.Metadata - { - Table = table, - Helper = helper, - PropertyIndices = initPropertyMap, - Schema = schema - }; + return new RealmObjectBase.Metadata(tableHandle, helper, initPropertyMap, schema); } /// @@ -466,66 +474,7 @@ public override int GetHashCode() return (int)((long)SharedRealmHandle.DangerousGetHandle() % int.MaxValue); } - /// - /// Factory for a managed object in a realm. Only valid within a write . - /// - /// A dynamically-accessed Realm object. - /// The type of object to create as defined in the schema. - /// - /// The primary key of object to be created. If the object doesn't have primary key defined, this argument - /// is ignored. - /// - /// - /// If you invoke this when there is no write active on the . - /// - /// - /// If you pass null for an object with string primary key. - /// - /// - /// If you pass primaryKey with type that is different from the type, defined in the schema. - /// - /// - /// - /// WARNING: if the dynamic object has a PrimaryKey then that must be the first property set - /// otherwise other property changes may be lost. - /// - /// - /// If the realm instance has been created from an un-typed schema (such as when migrating from an older version - /// of a realm) the returned object will be purely dynamic. If the realm has been created from a typed schema as - /// is the default case when calling the returned - /// object will be an instance of a user-defined class. - /// - /// - public dynamic CreateObject(string className, object primaryKey) - { - ThrowIfDisposed(); - - return CreateObject(className, primaryKey, out var _); - } - - private RealmObject CreateObject(string className, object primaryKey, out RealmObject.Metadata metadata) - { - Argument.Ensure(Metadata.TryGetValue(className, out metadata), $"The class {className} is not in the limited set of classes for this realm", nameof(className)); - - var result = metadata.Helper.CreateInstance(); - - ObjectHandle objectHandle; - var pkProperty = metadata.Schema.PrimaryKeyProperty; - if (pkProperty.HasValue) - { - objectHandle = SharedRealmHandle.CreateObjectWithPrimaryKey(pkProperty.Value, primaryKey, metadata.Table, className, update: false, isNew: out var _); - } - else - { - objectHandle = SharedRealmHandle.CreateObject(metadata.Table); - } - - result.SetOwner(this, objectHandle, metadata); - result.OnManaged(); - return result; - } - - internal RealmObject MakeObject(RealmObject.Metadata metadata, ObjectHandle objectHandle) + internal RealmObjectBase MakeObject(RealmObjectBase.Metadata metadata, ObjectHandle objectHandle) { var ret = metadata.Helper.CreateInstance(); ret.SetOwner(this, objectHandle, metadata); @@ -533,7 +482,7 @@ internal RealmObject MakeObject(RealmObject.Metadata metadata, ObjectHandle obje return ret; } - internal RealmObject MakeObject(RealmObject.Metadata metadata, ObjectKey objectKey) + internal RealmObjectBase MakeObject(RealmObjectBase.Metadata metadata, ObjectKey objectKey) { var objectHandle = metadata.Table.Get(SharedRealmHandle, objectKey); if (objectHandle != null) @@ -604,20 +553,25 @@ public RealmObject Add(RealmObject obj, bool update = false) return obj; } + internal void ManageEmbedded(EmbeddedObject obj, ObjectHandle handle) + { + var objectType = obj.GetType(); + var objectName = objectType.GetTypeInfo().GetMappedOrOriginalName(); + Argument.Ensure(Metadata.TryGetValue(objectName, out var metadata), $"The class {objectType.Name} is not in the limited set of classes for this realm", nameof(obj)); + + obj.SetOwner(this, handle, metadata); + + // If an object is newly created, we don't need to invoke setters of properties with default values. + metadata.Helper.CopyToRealm(obj, update: false, skipDefaults: true); + obj.OnManaged(); + } + private void AddInternal(RealmObject obj, Type objectType, bool update) { - Argument.NotNull(obj, nameof(obj)); Argument.NotNull(objectType, nameof(objectType)); - - if (obj.IsManaged) + if (!ShouldAddNewObject(obj)) { - if (IsSameInstance(obj.Realm)) - { - // Already managed by this realm, so nothing to do. - return; - } - - throw new RealmObjectManagedByAnotherRealmException("Cannot start to manage an object with a realm when it's already managed by another realm"); + return; } var objectName = objectType.GetTypeInfo().GetMappedOrOriginalName(); @@ -643,6 +597,24 @@ private void AddInternal(RealmObject obj, Type objectType, bool update) obj.OnManaged(); } + private bool ShouldAddNewObject(RealmObjectBase obj) + { + Argument.NotNull(obj, nameof(obj)); + + if (obj.IsManaged) + { + if (IsSameInstance(obj.Realm)) + { + // Already managed by this realm, so nothing to do. + return false; + } + + throw new RealmObjectManagedByAnotherRealmException("Cannot start to manage an object with a realm when it's already managed by another realm"); + } + + return true; + } + /// /// Factory for a write . Essential object to create scope for updates. /// @@ -710,7 +682,7 @@ public void Write(Action action) /// /// /// Opens a new instance of this Realm on a worker thread and executes action inside a write . - /// s and s are thread-affine, so capturing any such objects in + /// s and s/s are thread-affine, so capturing any such objects in /// the action delegate will lead to errors if they're used on the worker thread. Note that it checks the /// to determine if Current is null, as a test to see if you are on the UI thread /// and will otherwise just call Write without starting a new thread. So if you know you are invoking from a worker thread, just call Write instead. @@ -841,19 +813,18 @@ public IQueryable All() return new RealmResults(this, metadata); } - /// - /// Get a view of all the objects of a particular type. - /// - /// The type of the objects as defined in the schema. - /// Because the objects inside the view are accessed dynamically, the view cannot be queried into using LINQ or other expression predicates. - /// A queryable collection that without further filtering, allows iterating all objects of className, in this realm. - public IQueryable All(string className) + // This should only be used for tests + internal IQueryable AllEmbedded() + where T : EmbeddedObject { ThrowIfDisposed(); - Argument.Ensure(Metadata.TryGetValue(className, out var metadata), $"The class {className} is not in the limited set of classes for this realm", nameof(className)); + var type = typeof(T); + Argument.Ensure( + Metadata.TryGetValue(type.GetTypeInfo().GetMappedOrOriginalName(), out var metadata) && metadata.Schema.Type.AsType() == type, + $"The class {type.Name} is not in the limited set of classes for this realm", nameof(T)); - return new RealmResults(this, metadata); + return new RealmResults(this, metadata); } #region Quick Find using primary key @@ -870,14 +841,14 @@ public IQueryable All(string className) /// /// If the class T lacks . /// - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObject instance will own its handle.")] + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObjectBase instance will own its handle.")] public T Find(long? primaryKey) where T : RealmObject { ThrowIfDisposed(); var metadata = Metadata[typeof(T).GetTypeInfo().GetMappedOrOriginalName()]; - if (metadata.Table.TryFind(SharedRealmHandle, primaryKey, out var objectHandle)) + if (metadata.Table.TryFind(SharedRealmHandle, PrimitiveValue.NullableInt(primaryKey), out var objectHandle)) { return (T)MakeObject(metadata, objectHandle); } @@ -894,7 +865,7 @@ public T Find(long? primaryKey) /// /// If the class T lacks . /// - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObject instance will own its handle.")] + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObjectBase instance will own its handle.")] public T Find(string primaryKey) where T : RealmObject { @@ -910,49 +881,24 @@ public T Find(string primaryKey) } /// - /// Fast lookup of an object for dynamic use, from a class which has a PrimaryKey property. - /// - /// Name of class in dynamic situation. - /// - /// Primary key to be matched exactly, same as an == search. - /// An argument of type long? works for all integer properties, supported as PrimaryKey. - /// - /// null or an object matching the primary key. - /// - /// If the class T lacks . - /// - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObject instance will own its handle.")] - public RealmObject Find(string className, long? primaryKey) - { - ThrowIfDisposed(); - - var metadata = Metadata[className]; - if (metadata.Table.TryFind(SharedRealmHandle, primaryKey, out var objectHandle)) - { - return MakeObject(metadata, objectHandle); - } - - return null; - } - - /// - /// Fast lookup of an object for dynamic use, from a class which has a PrimaryKey property. + /// Fast lookup of an object from a class which has a PrimaryKey property. /// - /// Name of class in dynamic situation. + /// The Type T must be a . /// Primary key to be matched exactly, same as an == search. /// null or an object matching the primary key. /// /// If the class T lacks . /// - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObject instance will own its handle.")] - public RealmObject Find(string className, string primaryKey) + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObjectBase instance will own its handle.")] + public T Find(ObjectId? primaryKey) + where T : RealmObject { ThrowIfDisposed(); - var metadata = Metadata[className]; - if (metadata.Table.TryFind(SharedRealmHandle, primaryKey, out var objectHandle)) + var metadata = Metadata[typeof(T).GetTypeInfo().GetMappedOrOriginalName()]; + if (metadata.Table.TryFind(SharedRealmHandle, PrimitiveValue.NullableObjectId(primaryKey), out var objectHandle)) { - return MakeObject(metadata, objectHandle); + return (T)MakeObject(metadata, objectHandle); } return null; @@ -966,15 +912,15 @@ public RealmObject Find(string className, string primaryKey) /// Returns the same object as the one referenced when the was first created, /// but resolved for the current Realm for this thread. /// - /// The thread-safe reference to the thread-confined to resolve in this . + /// The thread-safe reference to the thread-confined / to resolve in this . /// The type of the object, contained in the reference. /// - /// A thread-confined instance of the original resolved for the current thread or null + /// A thread-confined instance of the original / resolved for the current thread or null /// if the object has been deleted after the reference was created. /// - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObject instance will own its handle.")] + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObjectBase instance will own its handle.")] public T ResolveReference(ThreadSafeReference.Object reference) - where T : RealmObject + where T : RealmObjectBase { Argument.NotNull(reference, nameof(reference)); @@ -1023,7 +969,7 @@ public IList ResolveReference(ThreadSafeReference.List reference) /// A thread-confined instance of the original resolved for the current thread. [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The Query instance will own its handle.")] public IQueryable ResolveReference(ThreadSafeReference.Query reference) - where T : RealmObject + where T : RealmObjectBase { Argument.NotNull(reference, nameof(reference)); @@ -1043,7 +989,7 @@ public IQueryable ResolveReference(ThreadSafeReference.Query reference) /// /// If obj is null. /// If you pass a standalone object. - public void Remove(RealmObject obj) + public void Remove(RealmObjectBase obj) { ThrowIfDisposed(); @@ -1066,7 +1012,7 @@ public void Remove(RealmObject obj) /// /// If range is null. public void RemoveRange(IQueryable range) - where T : RealmObject + where T : RealmObjectBase { ThrowIfDisposed(); @@ -1095,24 +1041,6 @@ public void RemoveAll() RemoveRange(All()); } - /// - /// Remove all objects of a type from the Realm. - /// - /// Type of the objects to remove as defined in the schema. - /// - /// If you invoke this when there is no write active on the . - /// - /// - /// If you pass className that does not belong to this Realm's schema. - /// - public void RemoveAll(string className) - { - ThrowIfDisposed(); - - var query = (RealmResults)All(className); - query.ResultsHandle.Clear(SharedRealmHandle); - } - /// /// Remove all objects of all types managed by this Realm. /// @@ -1263,5 +1191,293 @@ internal void DrainTransactionQueue() } } } + + /// + /// A class that exposes the dynamic API for a instance. + /// + [Preserve(AllMembers = true)] + public class Dynamic + { + private readonly Realm _realm; + + internal Dynamic(Realm realm) + { + _realm = realm; + } + + /// + /// Factory for a managed object in a realm. Only valid within a write . + /// + /// A dynamically-accessed Realm object. + /// The type of object to create as defined in the schema. + /// + /// The primary key of object to be created. If the object doesn't have primary key defined, this argument + /// is ignored. + /// + /// + /// If you invoke this when there is no write active on the . + /// + /// + /// If you pass null for an object with string primary key. + /// + /// + /// If you pass primaryKey with type that is different from the type, defined in the schema. + /// + /// + /// If the realm instance has been created from an un-typed schema (such as when migrating from an older version + /// of a realm) the returned object will be purely dynamic. If the realm has been created from a typed schema as + /// is the default case when calling the returned + /// object will be an instance of a user-defined class. + /// + public dynamic CreateObject(string className, object primaryKey) + { + _realm.ThrowIfDisposed(); + + Argument.Ensure(_realm.Metadata.TryGetValue(className, out var metadata), $"The class {className} is not in the limited set of classes for this realm", nameof(className)); + + var result = metadata.Helper.CreateInstance(); + + ObjectHandle objectHandle; + var pkProperty = metadata.Schema.PrimaryKeyProperty; + if (pkProperty.HasValue) + { + objectHandle = _realm.SharedRealmHandle.CreateObjectWithPrimaryKey(pkProperty.Value, primaryKey, metadata.Table, className, update: false, isNew: out var _); + } + else + { + objectHandle = _realm.SharedRealmHandle.CreateObject(metadata.Table); + } + + result.SetOwner(_realm, objectHandle, metadata); + result.OnManaged(); + return result; + } + + /// + /// Factory for a managed embedded object in a realm. Only valid within a write . + /// Embedded objects need to be owned immediately which is why they can only be created for a specific property. + /// + /// + /// The parent or that will own the newly created + /// embedded object. + /// + /// The property to which the newly created embedded object will be assigned. + /// A dynamically-accessed embedded object. + public dynamic CreateEmbeddedObjectForProperty(RealmObjectBase parent, string propertyName) + { + _realm.ThrowIfDisposed(); + + Argument.NotNull(parent, nameof(parent)); + Argument.Ensure(parent.IsManaged && parent.IsValid, "The object passed as parent must be managed and valid to create an embedded object.", nameof(parent)); + Argument.Ensure(parent.Realm.IsSameInstance(_realm), "The object passed as parent is managed by a different Realm", nameof(parent)); + Argument.Ensure(parent.ObjectMetadata.Schema.TryFindProperty(propertyName, out var property), $"The schema for class {parent.GetType().Name} does not contain a property {propertyName}.", nameof(propertyName)); + Argument.Ensure(_realm.Metadata.TryGetValue(property.ObjectType, out var metadata), $"The class {property.ObjectType} linked to by {parent.GetType().Name}.{propertyName} is not in the limited set of classes for this realm", nameof(propertyName)); + Argument.Ensure(metadata.Schema.IsEmbedded, $"The class {property.ObjectType} linked to by {parent.GetType().Name}.{propertyName} is not embedded", nameof(propertyName)); + + var obj = metadata.Helper.CreateInstance(); + var handle = parent.ObjectHandle.CreateEmbeddedObjectForProperty(parent.ObjectMetadata.PropertyIndices[propertyName]); + + obj.SetOwner(_realm, handle, metadata); + obj.OnManaged(); + + return obj; + } + + /// + /// Creates an embedded object and adds it to the specified list. This also assigns correct ownership of the newly created embedded object. + /// + /// The list to which the object will be added. + /// The newly created object, after it has been added to the back of the list. + /// + /// Lists of embedded objects cannot directly add objects as that would require constructing an unowned embedded object, which is not possible. This is why + /// , , and have to be used instead of + /// , , and . + /// + /// + /// + [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Argument is validated in PerformEmbeddedListOperation.")] + public dynamic AddEmbeddedObjectToList(IRealmCollection list) + { + return PerformEmbeddedListOperation(list, listHandle => listHandle.AddEmbedded()); + } + + /// + /// Creates an embedded object and inserts it in the specified list at the specified index. This also assigns correct ownership of the newly created embedded object. + /// + /// The list in which the object will be inserted. + /// The index at which the object will be inserted. + /// The newly created object, after it has been inserted in the list. + /// + /// Lists of embedded objects cannot directly add objects as that would require constructing an unowned embedded object, which is not possible. This is why + /// , , and have to be used instead of + /// , , and . + /// + /// + /// + [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Argument is validated in PerformEmbeddedListOperation.")] + public dynamic InsertEmbeddedObjectInList(IRealmCollection list, int index) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return PerformEmbeddedListOperation(list, listHandle => listHandle.InsertEmbedded(index)); + } + + /// + /// Creates an embedded object and sets it in the specified list at the specified index. This also assigns correct ownership of the newly created embedded object. + /// + /// The list in which the object will be set. + /// The index at which the object will be set. + /// The newly created object, after it has been set to the specified index in the list. + /// + /// Lists of embedded objects cannot directly add objects as that would require constructing an unowned embedded object, which is not possible. This is why + /// , , and have to be used instead of + /// , , and . + /// + /// Setting an object at an index will remove the existing object from the list and unown it. Since unowned embedded objects are automatically deleted, + /// the old object that the list contained at will get deleted when the transaction is committed. + /// + /// + /// + [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Argument is validated in PerformEmbeddedListOperation.")] + public dynamic SetEmbeddedObjectInList(IRealmCollection list, int index) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return PerformEmbeddedListOperation(list, listHandle => listHandle.SetEmbedded(index)); + } + + /// + /// Get a view of all the objects of a particular type. + /// + /// The type of the objects as defined in the schema. + /// Because the objects inside the view are accessed dynamically, the view cannot be queried into using LINQ or other expression predicates. + /// A queryable collection that without further filtering, allows iterating all objects of className, in this realm. + public IQueryable All(string className) + { + _realm.ThrowIfDisposed(); + + Argument.Ensure(_realm.Metadata.TryGetValue(className, out var metadata), $"The class {className} is not in the limited set of classes for this realm", nameof(className)); + Argument.Ensure(!metadata.Schema.IsEmbedded, $"The class {className} represents an embedded object and thus cannot be queried directly.", nameof(className)); + + return new RealmResults(_realm, metadata); + } + + /// + /// Remove all objects of a type from the Realm. + /// + /// Type of the objects to remove as defined in the schema. + /// + /// If you invoke this when there is no write active on the . + /// + /// + /// If you pass className that does not belong to this Realm's schema. + /// + public void RemoveAll(string className) + { + _realm.ThrowIfDisposed(); + + var query = (RealmResults)All(className); + query.ResultsHandle.Clear(_realm.SharedRealmHandle); + } + + /// + /// Fast lookup of an object for dynamic use, from a class which has a PrimaryKey property. + /// + /// Name of class in dynamic situation. + /// + /// Primary key to be matched exactly, same as an == search. + /// An argument of type long? works for all integer properties, supported as PrimaryKey. + /// + /// null or an object matching the primary key. + /// + /// If the class T lacks . + /// + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObjectBase instance will own its handle.")] + public RealmObject Find(string className, long? primaryKey) + { + _realm.ThrowIfDisposed(); + + var metadata = _realm.Metadata[className]; + if (metadata.Table.TryFind(_realm.SharedRealmHandle, PrimitiveValue.NullableInt(primaryKey), out var objectHandle)) + { + return (RealmObject)_realm.MakeObject(metadata, objectHandle); + } + + return null; + } + + /// + /// Fast lookup of an object for dynamic use, from a class which has a PrimaryKey property. + /// + /// Name of class in dynamic situation. + /// Primary key to be matched exactly, same as an == search. + /// null or an object matching the primary key. + /// + /// If the class T lacks . + /// + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObjectBase instance will own its handle.")] + public dynamic Find(string className, string primaryKey) + { + _realm.ThrowIfDisposed(); + + var metadata = _realm.Metadata[className]; + if (metadata.Table.TryFind(_realm.SharedRealmHandle, primaryKey, out var objectHandle)) + { + return _realm.MakeObject(metadata, objectHandle); + } + + return null; + } + + /// + /// Fast lookup of an object for dynamic use, from a class which has a PrimaryKey property. + /// + /// Name of class in dynamic situation. + /// + /// Primary key to be matched exactly, same as an == search. + /// + /// null or an object matching the primary key. + /// + /// If the class T lacks . + /// + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObjectBase instance will own its handle.")] + public RealmObject Find(string className, ObjectId? primaryKey) + { + _realm.ThrowIfDisposed(); + + var metadata = _realm.Metadata[className]; + if (metadata.Table.TryFind(_realm.SharedRealmHandle, PrimitiveValue.NullableObjectId(primaryKey), out var objectHandle)) + { + return (RealmObject)_realm.MakeObject(metadata, objectHandle); + } + + return null; + } + + private dynamic PerformEmbeddedListOperation(IRealmCollection list, Func getHandle) + { + _realm.ThrowIfDisposed(); + + Argument.NotNull(list, nameof(list)); + + if (!(list is IRealmList realmList)) + { + throw new ArgumentException($"Expected list to be IList but was ${list.GetType().FullName} instead.", nameof(list)); + } + + var obj = realmList.Metadata.Helper.CreateInstance(); + + obj.SetOwner(_realm, getHandle(realmList.NativeHandle), realmList.Metadata); + obj.OnManaged(); + + return obj; + } + } } } diff --git a/Realm/Realm/Realm.csproj b/Realm/Realm/Realm.csproj index 65365344f0..0e48175922 100644 --- a/Realm/Realm/Realm.csproj +++ b/Realm/Realm/Realm.csproj @@ -4,19 +4,21 @@ Realms true true + 8.0 true + 8.0 Realm https://realm.io/docs/dotnet/latest/api/CHANGELOG.html $(ProjectDir)..\..\global.ruleset + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Realm/Realm/RealmCollectionBase.cs b/Realm/Realm/RealmCollectionBase.cs index d11f578e38..7e0da2b553 100644 --- a/Realm/Realm/RealmCollectionBase.cs +++ b/Realm/Realm/RealmCollectionBase.cs @@ -35,14 +35,14 @@ namespace Realms public abstract class RealmCollectionBase : NotificationsHelper.INotifiable, IRealmCollection, - ISchemaSource, IThreadConfined { protected static readonly PropertyType _argumentType = typeof(T).ToPropertyType(out _); + protected static readonly bool _isEmbedded = typeof(T).IsEmbeddedObject(); private readonly List> _callbacks = new List>(); - internal readonly RealmObject.Metadata Metadata; + internal readonly RealmObjectBase.Metadata Metadata; private NotificationTokenHandle _notificationToken; private bool _deliveredInitialNotification; @@ -102,7 +102,7 @@ public int Count public ObjectSchema ObjectSchema => Metadata?.Schema; - RealmObject.Metadata IThreadConfined.Metadata => Metadata; + RealmObjectBase.Metadata IThreadConfined.Metadata => Metadata; public bool IsManaged => Realm != null; @@ -118,7 +118,7 @@ public int Count internal readonly Lazy Handle; - internal RealmCollectionBase(Realm realm, RealmObject.Metadata metadata) + internal RealmCollectionBase(Realm realm, RealmObjectBase.Metadata metadata) { Realm = realm; Handle = new Lazy(CreateHandle); @@ -149,7 +149,13 @@ internal RealmCollectionBase(Realm realm, RealmObject.Metadata metadata) throw new ArgumentOutOfRangeException(nameof(index)); } - return Operator.Convert(Realm.MakeObject(Metadata, objectHandle)); + var result = Realm.MakeObject(Metadata, objectHandle); + if (_isEmbedded) + { + return Operator.Convert((EmbeddedObject)result); + } + + return Operator.Convert((RealmObject)result); case PropertyType.String: case PropertyType.String | PropertyType.Nullable: diff --git a/Realm/Realm/RealmInteger.cs b/Realm/Realm/RealmInteger.cs index d268e0ac7d..7395db9584 100644 --- a/Realm/Realm/RealmInteger.cs +++ b/Realm/Realm/RealmInteger.cs @@ -29,8 +29,8 @@ namespace Realms /// /// is implicitly convertible to and from T/>. ///
- /// Calling on a managed 's property must be done in a write - /// transaction. When calling on a property, it will increment + /// Calling on a managed /'s property must be done in a write + /// transaction. When calling on a / property, it will increment /// the property's value in the database, so the change will be reflected the next time this property is accessed. /// If the object is unmanaged, its property value will not be affected. ///
@@ -38,7 +38,6 @@ namespace Realms /// The integer type, represented by this . Supported types are , /// , , and . /// - /// [Preserve(AllMembers = true, Conditional = false)] [SuppressMessage("Design", "CA1066:Implement IEquatable when overriding Object.Equals", Justification = "We already implement IEquatable and RealmInteger implicitly converts to T.")] public struct RealmInteger : @@ -59,7 +58,7 @@ internal RealmInteger(T value) { _value = value; _objectHandle = null; - _propertyIndex = IntPtr.Zero; + _propertyIndex = default; } internal RealmInteger(T value, ObjectHandle objectHandle, IntPtr propertyIndex) @@ -97,7 +96,7 @@ public RealmInteger Increment(T value) if (IsManaged) { _objectHandle.AddInt64(_propertyIndex, value.ToLong()); - var result = Operator.Convert(_objectHandle.GetInt64(_propertyIndex)); + var result = _objectHandle.GetPrimitive(_propertyIndex, Schema.PropertyType.Int).ToIntegral(); return new RealmInteger(result, _objectHandle, _propertyIndex); } @@ -324,7 +323,7 @@ public int CompareTo(T other) /// /// /// The provider to use to format the value. -or- A null reference to obtain the numeric format - /// information from the current locale setting of the operating system + /// information from the current locale setting of the operating system. /// The value of the current instance in the specified format. public string ToString(string format, IFormatProvider formatProvider) => _value.ToString(format, formatProvider); diff --git a/Realm/Realm/RealmList.cs b/Realm/Realm/RealmList.cs index 9a247ae44f..e02fa4fb2a 100644 --- a/Realm/Realm/RealmList.cs +++ b/Realm/Realm/RealmList.cs @@ -25,6 +25,7 @@ using System.Linq.Expressions; using System.Runtime.CompilerServices; using Realms.Dynamic; +using Realms.Exceptions; using Realms.Helpers; using Realms.Native; using Realms.Schema; @@ -37,21 +38,18 @@ namespace Realms /// Relationships are ordered and preserve their order, hence the ability to use ordinal /// indexes in calls such as Insert and RemoveAt. /// - /// Although originally used in declarations, whilst that still compiles, - /// it is not recommended as the IList approach both supports standalone objects and is - /// implemented with a faster binding. - /// - /// Type of the RealmObject which is the target of the relationship. + /// Type of the , , or primitive which is contained by the list. [Preserve(AllMembers = true)] [EditorBrowsable(EditorBrowsableState.Never)] [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "This should not be directly accessed by users.")] [DebuggerDisplay("Count = {Count}")] - public class RealmList : RealmCollectionBase, IList, IDynamicMetaObjectProvider + public class RealmList : RealmCollectionBase, IList, IDynamicMetaObjectProvider, IRealmList { private readonly Realm _realm; + private readonly ListHandle _listHandle; - internal RealmList(Realm realm, ListHandle adoptedList, RealmObject.Metadata metadata) : base(realm, metadata) + internal RealmList(Realm realm, ListHandle adoptedList, RealmObjectBase.Metadata metadata) : base(realm, metadata) { _realm = realm; _listHandle = adoptedList; @@ -62,6 +60,10 @@ internal override CollectionHandleBase CreateHandle() return _listHandle; } + ListHandle IRealmList.NativeHandle => _listHandle; + + RealmObjectBase.Metadata IRealmList.Metadata => Metadata; + #region implementing IList properties public override bool IsReadOnly => (_realm?.Config as RealmConfiguration)?.IsReadOnly == true; @@ -81,14 +83,12 @@ internal override CollectionHandleBase CreateHandle() throw new ArgumentOutOfRangeException(nameof(value)); } - Execute(value, obj => - { - AddObjectToRealmIfNeeded(obj); - _listHandle.Set(index, obj.ObjectHandle); - }, - v => _listHandle.Set(index, v), - v => _listHandle.Set(index, v), - v => _listHandle.Set(index, v)); + Execute(value, + obj => _listHandle.Set(index, obj.ObjectHandle), + () => _listHandle.SetEmbedded(index), + v => _listHandle.Set(index, v), + v => _listHandle.Set(index, v), + v => _listHandle.Set(index, v)); } } @@ -98,14 +98,12 @@ internal override CollectionHandleBase CreateHandle() public void Add(T item) { - Execute(item, obj => - { - AddObjectToRealmIfNeeded(obj); - _listHandle.Add(obj.ObjectHandle); - }, - _listHandle.Add, - _listHandle.Add, - _listHandle.Add); + Execute(item, + obj => _listHandle.Add(obj.ObjectHandle), + () => _listHandle.AddEmbedded(), + _listHandle.Add, + _listHandle.Add, + _listHandle.Add); } public override int Add(object value) @@ -148,7 +146,7 @@ public override int IndexOf(T value) case PropertyType.Object | PropertyType.Nullable: Argument.NotNull(value, nameof(value)); - var obj = Operator.Convert(value); + var obj = Operator.Convert(value); if (!obj.IsManaged) { throw new ArgumentException("Value does not belong to a realm", nameof(value)); @@ -173,14 +171,12 @@ public void Insert(int index, T item) throw new ArgumentOutOfRangeException(nameof(index)); } - Execute(item, obj => - { - AddObjectToRealmIfNeeded(obj); - _listHandle.Insert(index, obj.ObjectHandle); - }, - value => _listHandle.Insert(index, value), - value => _listHandle.Insert(index, value), - value => _listHandle.Insert(index, value)); + Execute(item, + obj => _listHandle.Insert(index, obj.ObjectHandle), + () => _listHandle.InsertEmbedded(index), + value => _listHandle.Insert(index, value), + value => _listHandle.Insert(index, value), + value => _listHandle.Insert(index, value)); } public override void Insert(int index, object value) => Insert(index, (T)value); @@ -209,14 +205,6 @@ public override void RemoveAt(int index) _listHandle.Erase((IntPtr)index); } - private void AddObjectToRealmIfNeeded(RealmObject obj) - { - if (!obj.IsManaged) - { - _realm.Add(obj); - } - } - #endregion public void Move(int sourceIndex, int targetIndex) @@ -248,8 +236,9 @@ public override IRealmCollection Freeze() DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression expression) => new MetaRealmList(expression, this); - private static void Execute(T item, + private void Execute(T item, Action objectHandler, + Func embeddedHandler, Action primitiveHandler, Action stringHandler, Action binaryHandler) @@ -257,7 +246,31 @@ public override IRealmCollection Freeze() switch (_argumentType) { case PropertyType.Object | PropertyType.Nullable: - objectHandler(Operator.Convert(item)); + switch (item) + { + case null: + throw new NotSupportedException("Adding, setting, or inserting in a list of objects is not supported."); + case RealmObject realmObj: + if (!realmObj.IsManaged) + { + _realm.Add(realmObj); + } + + objectHandler(realmObj); + break; + case EmbeddedObject embeddedObj: + if (embeddedObj.IsManaged) + { + throw new RealmException("Can't add, set, or insert an embedded object that is already managed."); + } + + var handle = embeddedHandler(); + Realm.ManageEmbedded(embeddedObj, handle); + break; + default: + throw new NotSupportedException($"Adding, setting, or inserting {item.GetType()} in a list of objects is not supported, because it doesn't inherit from RealmObject or EmbeddedObject."); + } + break; case PropertyType.String: case PropertyType.String | PropertyType.Nullable: @@ -273,4 +286,20 @@ public override IRealmCollection Freeze() } } } + + /// + /// IRealmList is only implemented by RealmList and serves to expose the ListHandle without knowing the generic param. + /// + internal interface IRealmList + { + /// + /// Gets the native handle for that list. + /// + ListHandle NativeHandle { get; } + + /// + /// Gets the metadata for the objects contained in the list. + /// + RealmObjectBase.Metadata Metadata { get; } + } } \ No newline at end of file diff --git a/Realm/Realm/RealmObject.cs b/Realm/Realm/RealmObject.cs index a49fc0a3dc..7565d87c7f 100644 --- a/Realm/Realm/RealmObject.cs +++ b/Realm/Realm/RealmObject.cs @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2016 Realm Inc. +// 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. @@ -17,696 +17,14 @@ //////////////////////////////////////////////////////////////////////////// using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Xml.Serialization; -using Realms.DataBinding; -using Realms.Exceptions; -using Realms.Helpers; -using Realms.Schema; namespace Realms { /// /// Base for any object that can be persisted in a . /// - [Preserve(AllMembers = true, Conditional = false)] [Serializable] - public class RealmObject : INotifyPropertyChanged, ISchemaSource, IThreadConfined, NotificationsHelper.INotifiable, IReflectableType + public class RealmObject : RealmObjectBase { - [NonSerialized, XmlIgnore] - private Realm _realm; - - [NonSerialized, XmlIgnore] - private ObjectHandle _objectHandle; - - [NonSerialized, XmlIgnore] - private Metadata _metadata; - - [NonSerialized, XmlIgnore] - private NotificationTokenHandle _notificationToken; - - [field: NonSerialized, XmlIgnore] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "This is the private event - the public is uppercased.")] - private event PropertyChangedEventHandler _propertyChanged; - - /// - /// Occurs when a property value changes. - /// - public event PropertyChangedEventHandler PropertyChanged - { - add - { - if (IsManaged && _propertyChanged == null) - { - SubscribeForNotifications(); - } - - _propertyChanged += value; - } - - remove - { - _propertyChanged -= value; - - if (IsManaged && - _propertyChanged == null) - { - UnsubscribeFromNotifications(); - } - } - } - - internal ObjectHandle ObjectHandle => _objectHandle; - - internal Metadata ObjectMetadata => _metadata; - - /// - /// Gets a value indicating whether the object has been associated with a Realm, either at creation or via - /// . - /// - /// true if object belongs to a Realm; false if standalone. - public bool IsManaged => _realm != null; - - /// - /// Gets a value indicating whether this object is managed and represents a row in the database. - /// If a managed object has been removed from the Realm, it is no longer valid and accessing properties on it - /// will throw an exception. - /// Unmanaged objects are always considered valid. - /// - /// true if managed and part of the Realm or unmanaged; false if managed but deleted. - public bool IsValid => _objectHandle?.IsValid != false; - - /// - /// Gets a value indicating whether this object is frozen. Frozen objects are immutable - /// and will not update when writes are made to the Realm. Unlike live objects, frozen - /// objects can be used across threads. - /// - /// - public bool IsFrozen => _objectHandle?.IsFrozen == true; - - /// - /// Gets the instance this object belongs to, or null if it is unmanaged. - /// - /// The instance this object belongs to. - public Realm Realm => _realm; - - /// - /// Gets the instance that describes how the this object belongs to sees it. - /// - /// A collection of properties describing the underlying schema of this object. - public ObjectSchema ObjectSchema => _metadata?.Schema; - - /// - /// Gets the number of objects referring to this one via either a to-one or to-many relationship. - /// - /// - /// This property is not observable so the event will not fire when its value changes. - /// - public int BacklinksCount => _objectHandle?.GetBacklinkCount() ?? 0; - - /// - /// Freezes this object in place. The frozen object can be accessed from any thread. - /// - /// Freezing a RealmObject also creates a frozen Realm which has its own lifecycle, but if the live Realm that spawned the - /// original object is fully closed (i.e. all instances across all threads are closed), the frozen Realm and - /// object will be closed as well. - /// - /// Frozen objects can be queried as normal, but trying to mutate it in any way or attempting to register a listener will - /// throw a . - /// - /// Note: Keeping a large number of frozen objects with different versions alive can have a negative impact on the filesize - /// of the Realm. In order to avoid such a situation it is possible to set . - /// - /// - public void FreezeInPlace() - { - if (IsFrozen) - { - return; - } - - UnsubscribeFromNotifications(); - _propertyChanged = null; - var oldHandle = _objectHandle; - (_realm, _objectHandle) = FreezeImpl(); - oldHandle.Dispose(); - } - - internal (Realm FrozenRealm, ObjectHandle FrozenHandle) FreezeImpl() - { - if (!IsManaged) - { - throw new RealmException("Unmanaged objects cannot be frozen."); - } - - var frozenRealm = Realm.Freeze(); - var frozenHandle = _objectHandle.Freeze(frozenRealm.SharedRealmHandle); - return (frozenRealm, frozenHandle); - } - - /// - Metadata IThreadConfined.Metadata => ObjectMetadata; - - /// - IThreadConfinedHandle IThreadConfined.Handle => ObjectHandle; - - /// - /// Finalizes an instance of the class. - /// - ~RealmObject() - { - UnsubscribeFromNotifications(); - } - - internal void SetOwner(Realm realm, ObjectHandle objectHandle, Metadata metadata) - { - _realm = realm; - _objectHandle = objectHandle; - _metadata = metadata; - - if (_propertyChanged != null) - { - SubscribeForNotifications(); - } - } - -#pragma warning disable SA1600 // Elements should be documented - - #region Getters - - protected string GetStringValue(string propertyName) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - return _objectHandle.GetString(_metadata.PropertyIndices[propertyName]); - } - - protected char GetCharValue(string propertyName) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - return (char)_objectHandle.GetInt64(_metadata.PropertyIndices[propertyName]); - } - - protected char? GetNullableCharValue(string propertyName) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - return (char?)_objectHandle.GetNullableInt64(_metadata.PropertyIndices[propertyName]); - } - - protected float GetSingleValue(string propertyName) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - return _objectHandle.GetSingle(_metadata.PropertyIndices[propertyName]); - } - - protected float? GetNullableSingleValue(string propertyName) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - return _objectHandle.GetNullableSingle(_metadata.PropertyIndices[propertyName]); - } - - protected double GetDoubleValue(string propertyName) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - return _objectHandle.GetDouble(_metadata.PropertyIndices[propertyName]); - } - - protected double? GetNullableDoubleValue(string propertyName) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - return _objectHandle.GetNullableDouble(_metadata.PropertyIndices[propertyName]); - } - - protected bool GetBooleanValue(string propertyName) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - return _objectHandle.GetBoolean(_metadata.PropertyIndices[propertyName]); - } - - protected bool? GetNullableBooleanValue(string propertyName) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - return _objectHandle.GetNullableBoolean(_metadata.PropertyIndices[propertyName]); - } - - protected DateTimeOffset GetDateTimeOffsetValue(string propertyName) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - return _objectHandle.GetDateTimeOffset(_metadata.PropertyIndices[propertyName]); - } - - protected DateTimeOffset? GetNullableDateTimeOffsetValue(string propertyName) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - return _objectHandle.GetNullableDateTimeOffset(_metadata.PropertyIndices[propertyName]); - } - - protected internal IList GetListValue(string propertyName) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _metadata.Schema.TryFindProperty(propertyName, out var property); - return _objectHandle.GetList(_realm, _metadata.PropertyIndices[propertyName], property.ObjectType); - } - - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObject instance will own its handle.")] - protected T GetObjectValue(string propertyName) - where T : RealmObject - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - if (_objectHandle.TryGetLink(_metadata.PropertyIndices[propertyName], out var objectHandle)) - { - _metadata.Schema.TryFindProperty(propertyName, out var property); - return (T)_realm.MakeObject(_realm.Metadata[property.ObjectType], objectHandle); - } - - return null; - } - - protected byte[] GetByteArrayValue(string propertyName) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - return _objectHandle.GetByteArray(_metadata.PropertyIndices[propertyName]); - } - - protected IQueryable GetBacklinks(string propertyName) - where T : RealmObject - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - var resultsHandle = _objectHandle.GetBacklinks(_metadata.PropertyIndices[propertyName]); - return GetBacklinksForHandle(propertyName, resultsHandle); - } - - internal RealmResults GetBacklinksForHandle(string propertyName, ResultsHandle resultsHandle) - where T : RealmObject - { - _metadata.Schema.TryFindProperty(propertyName, out var property); - var relatedMeta = _realm.Metadata[property.ObjectType]; - - return new RealmResults(_realm, relatedMeta, resultsHandle); - } - - protected RealmInteger GetRealmIntegerValue(string propertyName) - where T : struct, IFormattable, IComparable - { - var propertyIndex = _metadata.PropertyIndices[propertyName]; - var result = Operator.Convert(_objectHandle.GetInt64(propertyIndex)); - return new RealmInteger(result, ObjectHandle, propertyIndex); - } - - protected RealmInteger? GetNullableRealmIntegerValue(string propertyName) - where T : struct, IFormattable, IComparable - { - var propertyIndex = _metadata.PropertyIndices[propertyName]; - var result = _objectHandle.GetNullableInt64(propertyIndex); - - if (result.HasValue) - { - return new RealmInteger(Operator.Convert(result.Value), ObjectHandle, propertyIndex); - } - - return null; - } - - #endregion - - #region Setters - - protected void SetStringValue(string propertyName, string value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetString(_metadata.PropertyIndices[propertyName], value); - } - - protected void SetStringValueUnique(string propertyName, string value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetStringUnique(_metadata.PropertyIndices[propertyName], value); - } - - protected void SetCharValue(string propertyName, char value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetNullableInt64(_metadata.PropertyIndices[propertyName], value); - } - - protected void SetCharValueUnique(string propertyName, char value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetInt64Unique(_metadata.PropertyIndices[propertyName], value); - } - - protected void SetNullableCharValue(string propertyName, char? value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetNullableInt64(_metadata.PropertyIndices[propertyName], value); - } - - protected void SetNullableCharValueUnique(string propertyName, char? value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetNullableInt64Unique(_metadata.PropertyIndices[propertyName], value); - } - - protected void SetSingleValue(string propertyName, float value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetSingle(_metadata.PropertyIndices[propertyName], value); - } - - protected void SetNullableSingleValue(string propertyName, float? value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetNullableSingle(_metadata.PropertyIndices[propertyName], value); - } - - protected void SetDoubleValue(string propertyName, double value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetDouble(_metadata.PropertyIndices[propertyName], value); - } - - protected void SetNullableDoubleValue(string propertyName, double? value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetNullableDouble(_metadata.PropertyIndices[propertyName], value); - } - - protected void SetBooleanValue(string propertyName, bool value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetBoolean(_metadata.PropertyIndices[propertyName], value); - } - - protected void SetNullableBooleanValue(string propertyName, bool? value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetNullableBoolean(_metadata.PropertyIndices[propertyName], value); - } - - protected void SetDateTimeOffsetValue(string propertyName, DateTimeOffset value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetDateTimeOffset(_metadata.PropertyIndices[propertyName], value); - } - - protected void SetNullableDateTimeOffsetValue(string propertyName, DateTimeOffset? value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetNullableDateTimeOffset(_metadata.PropertyIndices[propertyName], value); - } - - // Originally a generic fallback, now used only for RealmObject To-One relationship properties - // most other properties handled with woven type-specific setters above - protected void SetObjectValue(string propertyName, T value) - where T : RealmObject - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - if (value == null) - { - _objectHandle.ClearLink(_metadata.PropertyIndices[propertyName]); - } - else - { - if (!value.IsManaged) - { - _realm.Add(value); - } - - _objectHandle.SetLink(_metadata.PropertyIndices[propertyName], value.ObjectHandle); - } - } - - protected void SetByteArrayValue(string propertyName, byte[] value) - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetByteArray(_metadata.PropertyIndices[propertyName], value); - } - - protected void SetRealmIntegerValue(string propertyName, RealmInteger value) - where T : struct, IComparable, IFormattable - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetInt64(_metadata.PropertyIndices[propertyName], value.ToLong()); - } - - protected void SetNullableRealmIntegerValue(string propertyName, RealmInteger? value) - where T : struct, IComparable, IFormattable - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetNullableInt64(_metadata.PropertyIndices[propertyName], value.ToLong()); - } - - protected void SetRealmIntegerValueUnique(string propertyName, RealmInteger value) - where T : struct, IComparable, IFormattable - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetInt64Unique(_metadata.PropertyIndices[propertyName], value.ToLong()); - } - - protected void SetNullableRealmIntegerValueUnique(string propertyName, RealmInteger? value) - where T : struct, IComparable, IFormattable - { - Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - - _objectHandle.SetNullableInt64Unique(_metadata.PropertyIndices[propertyName], value.ToLong()); - } - - #endregion - -#pragma warning restore SA1600 // Elements should be documented - - /// - /// Returns all the objects that link to this object in the specified relationship. - /// - /// The type of the object that is on the other end of the relationship. - /// The property that is on the other end of the relationship. - /// A queryable collection containing all objects of objectType that link to the current object via property. - public IQueryable GetBacklinks(string objectType, string property) - { - Argument.Ensure(Realm.Metadata.TryGetValue(objectType, out var relatedMeta), $"Could not find schema for type {objectType}", nameof(objectType)); - Argument.Ensure(relatedMeta.PropertyIndices.ContainsKey(property), $"Type {objectType} does not contain property {property}", nameof(property)); - - var resultsHandle = ObjectHandle.GetBacklinksForType(relatedMeta.Table, relatedMeta.PropertyIndices[property]); - return new RealmResults(Realm, relatedMeta, resultsHandle); - } - - /// - public override bool Equals(object obj) - { - // If parameter is null, return false. - if (obj is null) - { - return false; - } - - // Optimization for a common success case. - if (ReferenceEquals(this, obj)) - { - return true; - } - - // If run-time types are not exactly the same, return false. - if (!(obj is RealmObject robj)) - { - return false; - } - - // standalone objects cannot participate in the same store check - if (!IsManaged || !robj.IsManaged) - { - return false; - } - - if (ObjectSchema.Name != robj.ObjectSchema.Name) - { - return false; - } - - // Return true if the fields match. - // Note that the base class is not invoked because it is - // System.Object, which defines Equals as reference equality. - return ObjectHandle.Equals(robj.ObjectHandle); - } - - /// - /// Allows you to raise the PropertyChanged event. - /// - /// The name of the property that has changed. If not specified, we'll use the caller name. - protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) - { - _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - OnPropertyChanged(propertyName); - } - - /// - /// Called when a property has changed on this class. - /// - /// The name of the property. - /// - /// For this method to be called, you need to have first subscribed to . - /// This can be used to react to changes to the current object, e.g. raising for computed properties. - /// - /// - /// - /// class MyClass : RealmObject - /// { - /// public int StatusCodeRaw { get; set; } - /// public StatusCodeEnum StatusCode => (StatusCodeEnum)StatusCodeRaw; - /// protected override void OnPropertyChanged(string propertyName) - /// { - /// if (propertyName == nameof(StatusCodeRaw)) - /// { - /// RaisePropertyChanged(nameof(StatusCode)); - /// } - /// } - /// } - /// - /// Here, we have a computed property that depends on a persisted one. In order to notify any - /// subscribers that StatusCode has changed, we override and - /// raise manually by calling . - /// - protected virtual void OnPropertyChanged(string propertyName) - { - } - - /// - /// Called when the object has been managed by a Realm. - /// - /// - /// This method will be called either when a managed object is materialized or when an unmanaged object has been - /// added to the Realm. It can be useful for providing some initialization logic as when the constructor is invoked, - /// it is not yet clear whether the object is managed or not. - /// - protected internal virtual void OnManaged() - { - } - - private void SubscribeForNotifications() - { - Debug.Assert(_notificationToken == null, "_notificationToken must be null before subscribing."); - - if (IsFrozen) - { - throw new RealmFrozenException("It is not possible to add a change listener to a frozen RealmObject since it never changes."); - } - - _realm.ExecuteOutsideTransaction(() => - { - if (ObjectHandle.IsValid) - { - var managedObjectHandle = GCHandle.Alloc(this, GCHandleType.Weak); - _notificationToken = ObjectHandle.AddNotificationCallback(GCHandle.ToIntPtr(managedObjectHandle), NotificationsHelper.NotificationCallback); - } - }); - } - - private void UnsubscribeFromNotifications() - { - _notificationToken?.Dispose(); - _notificationToken = null; - } - - /// - void NotificationsHelper.INotifiable.NotifyCallbacks(NotifiableObjectHandleBase.CollectionChangeSet? changes, NativeException? exception) - { - var managedException = exception?.Convert(); - - if (managedException != null) - { - Realm.NotifyError(managedException); - } - else if (changes.HasValue) - { - foreach (int propertyIndex in changes.Value.Properties.AsEnumerable()) - { - // Due to a yet another Mono compiler bug, using LINQ fails here :/ - var i = 0; - foreach (var property in ObjectSchema) - { - // Backlinks should be ignored. See Realm.CreateRealmObjectMetadata - if (property.Type.IsComputed()) - { - continue; - } - - if (i == propertyIndex) - { - RaisePropertyChanged(property.PropertyInfo?.Name ?? property.Name); - break; - } - - ++i; - } - } - - if (changes.Value.Deletions.AsEnumerable().Any()) - { - RaisePropertyChanged(nameof(IsValid)); - - if (!IsValid) - { - // We can proactively unsubscribe because the object has been deleted - UnsubscribeFromNotifications(); - } - } - } - } - - /// - public TypeInfo GetTypeInfo() - { - return TypeInfoHelper.GetInfo(this); - } - - internal class Metadata - { - internal TableHandle Table; - - internal Weaving.IRealmObjectHelper Helper; - - internal Dictionary PropertyIndices; - - internal ObjectSchema Schema; - } } -} \ No newline at end of file +} diff --git a/Realm/Realm/RealmObjectBase.cs b/Realm/Realm/RealmObjectBase.cs new file mode 100644 index 0000000000..9318696c41 --- /dev/null +++ b/Realm/Realm/RealmObjectBase.cs @@ -0,0 +1,588 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Xml.Serialization; +using Realms.DataBinding; +using Realms.Exceptions; +using Realms.Helpers; +using Realms.Native; +using Realms.Schema; +using Realms.Weaving; + +namespace Realms +{ + /// + /// Base for any object that can be persisted in a . + /// + [Preserve(AllMembers = true, Conditional = false)] + [Serializable] + public abstract class RealmObjectBase : INotifyPropertyChanged, IThreadConfined, NotificationsHelper.INotifiable, IReflectableType + { + [NonSerialized, XmlIgnore] + private Realm _realm; + + [NonSerialized, XmlIgnore] + private ObjectHandle _objectHandle; + + [NonSerialized, XmlIgnore] + private Metadata _metadata; + + [NonSerialized, XmlIgnore] + private NotificationTokenHandle _notificationToken; + + [field: NonSerialized, XmlIgnore] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "This is the private event - the public is uppercased.")] + private event PropertyChangedEventHandler _propertyChanged; + + /// + /// Occurs when a property value changes. + /// + public event PropertyChangedEventHandler PropertyChanged + { + add + { + if (IsManaged && _propertyChanged == null) + { + SubscribeForNotifications(); + } + + _propertyChanged += value; + } + + remove + { + _propertyChanged -= value; + + if (IsManaged && + _propertyChanged == null) + { + UnsubscribeFromNotifications(); + } + } + } + + internal ObjectHandle ObjectHandle => _objectHandle; + + internal Metadata ObjectMetadata => _metadata; + + /// + /// Gets a value indicating whether the object has been associated with a Realm, either at creation or via + /// . + /// + /// true if object belongs to a Realm; false if standalone. + public bool IsManaged => _realm != null; + + /// + /// Gets a value indicating whether this object is managed and represents a row in the database. + /// If a managed object has been removed from the Realm, it is no longer valid and accessing properties on it + /// will throw an exception. + /// Unmanaged objects are always considered valid. + /// + /// true if managed and part of the Realm or unmanaged; false if managed but deleted. + public bool IsValid => _objectHandle?.IsValid != false; + + /// + /// Gets a value indicating whether this object is frozen. Frozen objects are immutable + /// and will not update when writes are made to the Realm. Unlike live objects, frozen + /// objects can be used across threads. + /// + /// + public bool IsFrozen => _objectHandle?.IsFrozen == true; + + /// + /// Gets the instance this object belongs to, or null if it is unmanaged. + /// + /// The instance this object belongs to. + public Realm Realm => _realm; + + /// + /// Gets the instance that describes how the this object belongs to sees it. + /// + /// A collection of properties describing the underlying schema of this object. + public ObjectSchema ObjectSchema => _metadata?.Schema; + + /// + /// Gets the number of objects referring to this one via either a to-one or to-many relationship. + /// + /// + /// This property is not observable so the event will not fire when its value changes. + /// + public int BacklinksCount => _objectHandle?.GetBacklinkCount() ?? 0; + + /// + /// Freezes this object in place. The frozen object can be accessed from any thread. + /// + /// Freezing a RealmObjectBase also creates a frozen Realm which has its own lifecycle, but if the live Realm that spawned the + /// original object is fully closed (i.e. all instances across all threads are closed), the frozen Realm and + /// object will be closed as well. + /// + /// Frozen objects can be queried as normal, but trying to mutate it in any way or attempting to register a listener will + /// throw a . + /// + /// Note: Keeping a large number of frozen objects with different versions alive can have a negative impact on the filesize + /// of the Realm. In order to avoid such a situation it is possible to set . + /// + /// + public void FreezeInPlace() + { + if (IsFrozen) + { + return; + } + + UnsubscribeFromNotifications(); + _propertyChanged = null; + var oldHandle = _objectHandle; + (_realm, _objectHandle) = FreezeImpl(); + oldHandle.Dispose(); + } + + internal (Realm FrozenRealm, ObjectHandle FrozenHandle) FreezeImpl() + { + if (!IsManaged) + { + throw new RealmException("Unmanaged objects cannot be frozen."); + } + + var frozenRealm = Realm.Freeze(); + var frozenHandle = _objectHandle.Freeze(frozenRealm.SharedRealmHandle); + return (frozenRealm, frozenHandle); + } + + /// + Metadata IThreadConfined.Metadata => ObjectMetadata; + + /// + IThreadConfinedHandle IThreadConfined.Handle => ObjectHandle; + + internal RealmObjectBase() + { + } + + /// + /// Finalizes an instance of the class. + /// + ~RealmObjectBase() + { + UnsubscribeFromNotifications(); + } + + internal void SetOwner(Realm realm, ObjectHandle objectHandle, Metadata metadata) + { + _realm = realm; + _objectHandle = objectHandle; + _metadata = metadata; + + if (_propertyChanged != null) + { + SubscribeForNotifications(); + } + } + +#pragma warning disable SA1600 // Elements should be documented + + #region Getters + + protected string GetStringValue(string propertyName) + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + return _objectHandle.GetString(_metadata.PropertyIndices[propertyName]); + } + + protected T GetPrimitiveValue(string propertyName, PropertyType propertyType) + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + return _objectHandle.GetPrimitive(_metadata.PropertyIndices[propertyName], propertyType).Get(); + } + + protected internal IList GetListValue(string propertyName) + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + _metadata.Schema.TryFindProperty(propertyName, out var property); + return _objectHandle.GetList(_realm, _metadata.PropertyIndices[propertyName], property.ObjectType); + } + + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The RealmObjectBase instance will own its handle.")] + protected T GetObjectValue(string propertyName) + where T : RealmObjectBase + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + if (_objectHandle.TryGetLink(_metadata.PropertyIndices[propertyName], out var objectHandle)) + { + _metadata.Schema.TryFindProperty(propertyName, out var property); + return (T)_realm.MakeObject(_realm.Metadata[property.ObjectType], objectHandle); + } + + return null; + } + + protected byte[] GetByteArrayValue(string propertyName) + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + return _objectHandle.GetByteArray(_metadata.PropertyIndices[propertyName]); + } + + protected IQueryable GetBacklinks(string propertyName) + where T : RealmObjectBase + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + var resultsHandle = _objectHandle.GetBacklinks(_metadata.PropertyIndices[propertyName]); + return GetBacklinksForHandle(propertyName, resultsHandle); + } + + internal RealmResults GetBacklinksForHandle(string propertyName, ResultsHandle resultsHandle) + where T : RealmObjectBase + { + _metadata.Schema.TryFindProperty(propertyName, out var property); + var relatedMeta = _realm.Metadata[property.ObjectType]; + + return new RealmResults(_realm, relatedMeta, resultsHandle); + } + + protected RealmInteger GetRealmIntegerValue(string propertyName) + where T : struct, IFormattable, IComparable + { + var propertyIndex = _metadata.PropertyIndices[propertyName]; + var result = _objectHandle.GetPrimitive(propertyIndex, PropertyType.Int).ToIntegral(); + return new RealmInteger(result, ObjectHandle, propertyIndex); + } + + protected RealmInteger? GetNullableRealmIntegerValue(string propertyName) + where T : struct, IFormattable, IComparable + { + var propertyIndex = _metadata.PropertyIndices[propertyName]; + var result = _objectHandle.GetPrimitive(propertyIndex, PropertyType.NullableInt).ToNullableIntegral(); + + if (result.HasValue) + { + return new RealmInteger(result.Value, ObjectHandle, propertyIndex); + } + + return null; + } + + #endregion + + #region Setters + + protected void SetPrimitiveValue(string propertyName, T value, PropertyType propertyType) + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + _objectHandle.SetPrimitive(_metadata.PropertyIndices[propertyName], PrimitiveValue.Create(value, propertyType)); + } + + protected void SetPrimitiveValueUnique(string propertyName, T value, PropertyType propertyType) + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + _objectHandle.SetPrimitiveUnique(_metadata.PropertyIndices[propertyName], PrimitiveValue.Create(value, propertyType)); + } + + protected void SetStringValue(string propertyName, string value) + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + _objectHandle.SetString(_metadata.PropertyIndices[propertyName], value); + } + + protected void SetStringValueUnique(string propertyName, string value) + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + _objectHandle.SetStringUnique(_metadata.PropertyIndices[propertyName], value); + } + + // Originally a generic fallback, now used only for RealmObjectBase To-One relationship properties + // most other properties handled with woven type-specific setters above + protected void SetObjectValue(string propertyName, T value) + where T : RealmObjectBase + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + _objectHandle.SetObject(Realm, _metadata.PropertyIndices[propertyName], value); + } + + protected void SetByteArrayValue(string propertyName, byte[] value) + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + _objectHandle.SetByteArray(_metadata.PropertyIndices[propertyName], value); + } + + protected void SetRealmIntegerValue(string propertyName, RealmInteger value) + where T : struct, IComparable, IFormattable + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + _objectHandle.SetPrimitive(_metadata.PropertyIndices[propertyName], PrimitiveValue.Int(value.ToLong())); + } + + protected void SetNullableRealmIntegerValue(string propertyName, RealmInteger? value) + where T : struct, IComparable, IFormattable + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + _objectHandle.SetPrimitive(_metadata.PropertyIndices[propertyName], PrimitiveValue.NullableInt(value?.ToLong())); + } + + protected void SetRealmIntegerValueUnique(string propertyName, RealmInteger value) + where T : struct, IComparable, IFormattable + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + _objectHandle.SetPrimitiveUnique(_metadata.PropertyIndices[propertyName], PrimitiveValue.Int(value.ToLong())); + } + + protected void SetNullableRealmIntegerValueUnique(string propertyName, RealmInteger? value) + where T : struct, IComparable, IFormattable + { + Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); + + _objectHandle.SetPrimitiveUnique(_metadata.PropertyIndices[propertyName], PrimitiveValue.NullableInt(value?.ToLong())); + } + + #endregion + +#pragma warning restore SA1600 // Elements should be documented + + /// + /// Returns all the objects that link to this object in the specified relationship. + /// + /// The type of the object that is on the other end of the relationship. + /// The property that is on the other end of the relationship. + /// A queryable collection containing all objects of objectType that link to the current object via property. + public IQueryable GetBacklinks(string objectType, string property) + { + Argument.Ensure(Realm.Metadata.TryGetValue(objectType, out var relatedMeta), $"Could not find schema for type {objectType}", nameof(objectType)); + Argument.Ensure(relatedMeta.PropertyIndices.ContainsKey(property), $"Type {objectType} does not contain property {property}", nameof(property)); + + var resultsHandle = ObjectHandle.GetBacklinksForType(relatedMeta.Table, relatedMeta.PropertyIndices[property]); + if (relatedMeta.Schema.IsEmbedded) + { + return new RealmResults(Realm, relatedMeta, resultsHandle); + } + + return new RealmResults(Realm, relatedMeta, resultsHandle); + } + + /// + public override bool Equals(object obj) + { + // If parameter is null, return false. + if (obj is null) + { + return false; + } + + // Optimization for a common success case. + if (ReferenceEquals(this, obj)) + { + return true; + } + + // If run-time types are not exactly the same, return false. + if (!(obj is RealmObjectBase robj)) + { + return false; + } + + // standalone objects cannot participate in the same store check + if (!IsManaged || !robj.IsManaged) + { + return false; + } + + if (ObjectSchema.Name != robj.ObjectSchema.Name) + { + return false; + } + + // Return true if the fields match. + // Note that the base class is not invoked because it is + // System.Object, which defines Equals as reference equality. + return ObjectHandle.Equals(robj.ObjectHandle); + } + + /// + /// Allows you to raise the PropertyChanged event. + /// + /// The name of the property that has changed. If not specified, we'll use the caller name. + protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) + { + _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + OnPropertyChanged(propertyName); + } + + /// + /// Called when a property has changed on this class. + /// + /// The name of the property. + /// + /// For this method to be called, you need to have first subscribed to . + /// This can be used to react to changes to the current object, e.g. raising for computed properties. + /// + /// + /// + /// class MyClass : RealmObject + /// { + /// public int StatusCodeRaw { get; set; } + /// public StatusCodeEnum StatusCode => (StatusCodeEnum)StatusCodeRaw; + /// protected override void OnPropertyChanged(string propertyName) + /// { + /// if (propertyName == nameof(StatusCodeRaw)) + /// { + /// RaisePropertyChanged(nameof(StatusCode)); + /// } + /// } + /// } + /// + /// Here, we have a computed property that depends on a persisted one. In order to notify any + /// subscribers that StatusCode has changed, we override and + /// raise manually by calling . + /// + protected virtual void OnPropertyChanged(string propertyName) + { + } + + /// + /// Called when the object has been managed by a Realm. + /// + /// + /// This method will be called either when a managed object is materialized or when an unmanaged object has been + /// added to the Realm. It can be useful for providing some initialization logic as when the constructor is invoked, + /// it is not yet clear whether the object is managed or not. + /// + protected internal virtual void OnManaged() + { + } + + private void SubscribeForNotifications() + { + Debug.Assert(_notificationToken == null, "_notificationToken must be null before subscribing."); + + if (IsFrozen) + { + throw new RealmFrozenException("It is not possible to add a change listener to a frozen RealmObjectBase since it never changes."); + } + + _realm.ExecuteOutsideTransaction(() => + { + if (ObjectHandle.IsValid) + { + var managedObjectHandle = GCHandle.Alloc(this, GCHandleType.Weak); + _notificationToken = ObjectHandle.AddNotificationCallback(GCHandle.ToIntPtr(managedObjectHandle), NotificationsHelper.NotificationCallback); + } + }); + } + + private void UnsubscribeFromNotifications() + { + _notificationToken?.Dispose(); + _notificationToken = null; + } + + /// + void NotificationsHelper.INotifiable.NotifyCallbacks(NotifiableObjectHandleBase.CollectionChangeSet? changes, NativeException? exception) + { + var managedException = exception?.Convert(); + + if (managedException != null) + { + Realm.NotifyError(managedException); + } + else if (changes.HasValue) + { + foreach (int propertyIndex in changes.Value.Properties.AsEnumerable()) + { + // Due to a yet another Mono compiler bug, using LINQ fails here :/ + var i = 0; + foreach (var property in ObjectSchema) + { + // Backlinks should be ignored. See Realm.CreateRealmObjectMetadata + if (property.Type.IsComputed()) + { + continue; + } + + if (i == propertyIndex) + { + RaisePropertyChanged(property.PropertyInfo?.Name ?? property.Name); + break; + } + + ++i; + } + } + + if (changes.Value.Deletions.AsEnumerable().Any()) + { + RaisePropertyChanged(nameof(IsValid)); + + if (!IsValid) + { + // We can proactively unsubscribe because the object has been deleted + UnsubscribeFromNotifications(); + } + } + } + } + + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "This should not be directly accessed by users.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public TypeInfo GetTypeInfo() + { + return TypeInfoHelper.GetInfo(this); + } + + internal class Metadata + { + internal readonly TableHandle Table; + + internal readonly IRealmObjectHelper Helper; + + internal readonly IReadOnlyDictionary PropertyIndices; + + internal readonly ObjectSchema Schema; + + public Metadata(TableHandle table, IRealmObjectHelper helper, IDictionary propertyIndices, ObjectSchema schema) + { + Table = table; + Helper = helper; + PropertyIndices = new ReadOnlyDictionary(propertyIndices); + Schema = schema; + } + } + } +} \ No newline at end of file diff --git a/Realm/Realm/Schema/ObjectSchema.cs b/Realm/Realm/Schema/ObjectSchema.cs index 7149e304e5..9fb51e2737 100644 --- a/Realm/Realm/Schema/ObjectSchema.cs +++ b/Realm/Realm/Schema/ObjectSchema.cs @@ -49,7 +49,9 @@ public class ObjectSchema : IReadOnlyCollection internal Property? PrimaryKeyProperty { get; } - internal TypeInfo Type; + internal TypeInfo Type { get; private set; } + + internal bool IsEmbedded { get; private set; } internal IEnumerable PropertyNames => _properties.Keys; @@ -97,19 +99,20 @@ private ObjectSchema(string name, IDictionary properties) IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// - /// Creates a schema describing a subclass in terms of its persisted members. + /// Creates a schema describing a or subclass in terms of its persisted members. /// /// - /// Thrown if no class Type is provided or if it doesn't descend directly from . + /// Thrown if no class Type is provided or if it doesn't descend directly from /. /// /// An describing the specified Type. - /// Type of a descendant for which you want a schema. + /// Type of a / descendant for which you want a schema. public static ObjectSchema FromType(TypeInfo type) { Argument.NotNull(type, nameof(type)); - Argument.Ensure(type.BaseType == typeof(RealmObject), $"The class {type.FullName} must descend directly from RealmObject", nameof(type)); - var builder = new Builder(type.GetMappedOrOriginalName()); + Argument.Ensure(type.IsRealmObject() || type.IsEmbeddedObject(), $"The class {type.FullName} must descend directly from RealmObject", nameof(type)); + + var builder = new Builder(type.GetMappedOrOriginalName(), type.IsEmbeddedObject()); foreach (var property in type.DeclaredProperties.Where(p => !p.IsStatic() && p.HasCustomAttribute())) { var isPrimaryKey = property.HasCustomAttribute(); @@ -154,7 +157,9 @@ internal class Builder : List { public string Name { get; } - public Builder(string name) + private readonly bool _isEmbedded; + + public Builder(string name, bool isEmbedded) { if (string.IsNullOrEmpty(name)) { @@ -162,6 +167,7 @@ public Builder(string name) } Name = name; + _isEmbedded = isEmbedded; } public ObjectSchema Build() @@ -172,7 +178,10 @@ public ObjectSchema Build() $"No properties in {Name}, has linker stripped it? See https://realm.io/docs/xamarin/latest/#linker-stripped-schema"); } - return new ObjectSchema(Name, this.ToDictionary(p => p.Name)); + return new ObjectSchema(Name, this.ToDictionary(p => p.Name)) + { + IsEmbedded = _isEmbedded + }; } } } diff --git a/Realm/Realm/Schema/PropertyType.cs b/Realm/Realm/Schema/PropertyType.cs index ac93db398e..4ba8486f2a 100644 --- a/Realm/Realm/Schema/PropertyType.cs +++ b/Realm/Realm/Schema/PropertyType.cs @@ -75,6 +75,16 @@ public enum PropertyType : byte ///
LinkingObjects = 8, + /// + /// 96 bit ObjectID property. + /// + ObjectId = 10, + + /// + /// 128 bit decimal property. + /// + Decimal = 11, + /// /// A required property. Can be combined with other values. /// @@ -93,6 +103,41 @@ public enum PropertyType : byte /// /// Metadata flags. /// - Flags = Nullable | Array + Flags = Nullable | Array, + + /// + /// A shorthand for PropertyType.Int | PropertyType.Nullable. + /// + NullableInt = Int | Nullable, + + /// + /// A shorthand for PropertyType.Bool | PropertyType.Nullable. + /// + NullableBool = Bool | Nullable, + + /// + /// A shorthand for PropertyType.Float | PropertyType.Nullable. + /// + NullableFloat = Float | Nullable, + + /// + /// A shorthand for PropertyType.Double | PropertyType.Nullable. + /// + NullableDouble = Double | Nullable, + + /// + /// A shorthand for PropertyType.Date | PropertyType.Nullable. + /// + NullableDate = Date | Nullable, + + /// + /// A shorthand for PropertyType.ObjectId | PropertyType.Nullable. + /// + NullableObjectId = ObjectId | Nullable, + + /// + /// A shorthand for PropertyType.Decimal | PropertyType.Nullable. + /// + NullableDecimal = Decimal | Nullable, } } \ No newline at end of file diff --git a/Realm/Realm/Schema/PropertyTypeEx.cs b/Realm/Realm/Schema/PropertyTypeEx.cs index 8dbda2099a..0210fdaa29 100644 --- a/Realm/Realm/Schema/PropertyTypeEx.cs +++ b/Realm/Realm/Schema/PropertyTypeEx.cs @@ -19,7 +19,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; +using MongoDB.Bson; using Realms.Helpers; namespace Realms.Schema @@ -76,7 +76,13 @@ public static PropertyType ToPropertyType(this Type type, out Type objectType) case Type _ when type == typeof(double): return PropertyType.Double | nullabilityModifier; - case Type _ when type == typeof(RealmObject) || type.GetTypeInfo().BaseType == typeof(RealmObject): + case Type _ when type == typeof(decimal) || type == typeof(Decimal128): + return PropertyType.Decimal | nullabilityModifier; + + case Type _ when type == typeof(ObjectId): + return PropertyType.ObjectId | nullabilityModifier; + + case Type _ when type.IsRealmObject() || type.IsEmbeddedObject(): objectType = type; return PropertyType.Object | PropertyType.Nullable; @@ -96,7 +102,7 @@ public static PropertyType ToPropertyType(this Type type, out Type objectType) } } - public static bool IsComputed(this PropertyType propertyType) => propertyType.HasFlag(PropertyType.LinkingObjects); + public static bool IsComputed(this PropertyType propertyType) => propertyType == (PropertyType.LinkingObjects | PropertyType.Array); public static bool IsNullable(this PropertyType propertyType) => propertyType.HasFlag(PropertyType.Nullable); diff --git a/Realm/Realm/Schema/RealmSchema.cs b/Realm/Realm/Schema/RealmSchema.cs index d9e182bb6d..93231d72b0 100644 --- a/Realm/Realm/Schema/RealmSchema.cs +++ b/Realm/Realm/Schema/RealmSchema.cs @@ -32,9 +32,9 @@ namespace Realms.Schema /// dynamically, by evaluating a Realm from disk. ///
/// - /// By default this will be all the s in all your assemblies unless you restrict with - /// . Just because a given class may be stored in a - /// Realm doesn't imply much overhead. There will be a small amount of metadata but objects only start to + /// By default this will be all the s and s in all your assemblies + /// unless you restrict with . Just because a given class may + /// be stored in a Realm doesn't imply much overhead. There will be a small amount of metadata but objects only start to /// take up space once written. /// public class RealmSchema : IReadOnlyCollection @@ -156,7 +156,8 @@ internal static RealmSchema CreateFromObjectStoreSchema(Native.Schema nativeSche for (var i = 0; i < nativeSchema.objects_len; i++) { var objectSchema = Marshal.PtrToStructure(IntPtr.Add(nativeSchema.objects, i * Native.SchemaObject.Size)); - var builder = new ObjectSchema.Builder(objectSchema.name); + + var builder = new ObjectSchema.Builder(objectSchema.name, objectSchema.is_embedded); for (var n = objectSchema.properties_start; n < objectSchema.properties_end; n++) { diff --git a/Realm/Realm/Server/Dtos/ChangeDetails.cs b/Realm/Realm/Server/Dtos/ChangeDetails.cs deleted file mode 100644 index f888c85b3c..0000000000 --- a/Realm/Realm/Server/Dtos/ChangeDetails.cs +++ /dev/null @@ -1,57 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Generic; -using System.Linq; -using Realms.Server.Native; - -namespace Realms.Server -{ - internal class ChangeDetails : IChangeDetails - { - public string RealmPath { get; } - - public Realm PreviousRealm { get; } - - public Realm CurrentRealm { get; } - - public IReadOnlyDictionary Changes { get; } - - public Realm GetRealmForWriting() - { - var config = new NotifierRealmConfiguration(NotifierHandle.GetRealmForWriting(CurrentRealm.SharedRealmHandle), CurrentRealm.Config.DatabasePath); - return Realm.GetInstance(config); - } - - public ChangeDetails(string path, IEnumerable changeSets, Realm previousRealm, Realm currentRealm) - { - RealmPath = path; - - PreviousRealm = previousRealm; - CurrentRealm = currentRealm; - - Changes = changeSets.ToDictionary(x => x.ClassName, x => (IChangeSetDetails)new ChangeSetDetails( - previous: PreviousRealm, - current: CurrentRealm, - className: x.ClassName, - insertions: x.insertions.AsEnumerable(), - modifications: x.modifications.AsEnumerable(), - deletions: x.deletions.AsEnumerable())); - } - } -} diff --git a/Realm/Realm/Server/Dtos/ChangeSetDetails.cs b/Realm/Realm/Server/Dtos/ChangeSetDetails.cs deleted file mode 100644 index b52c3e5e9c..0000000000 --- a/Realm/Realm/Server/Dtos/ChangeSetDetails.cs +++ /dev/null @@ -1,58 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Linq; -using Realms.Native; -using Realms.Server.Native; - -namespace Realms.Server -{ - internal class ChangeSetDetails : IChangeSetDetails - { - public IReadOnlyList Insertions { get; } - - public IReadOnlyList Modifications { get; } - - public IReadOnlyList Deletions { get; } - - public ChangeSetDetails(Realm previous, Realm current, string className, IEnumerable insertions, IEnumerable modifications, IEnumerable deletions) - { - if (previous == null || !previous.Metadata.TryGetValue(className, out var previousMetadata)) - { - if (deletions.Any() || modifications.Any()) - { - throw new NotSupportedException($"Failed to find metadata for object of type {className} in the previous Realm."); - } - - previousMetadata = null; - } - - var currentMetadata = current.Metadata[className]; - - Insertions = insertions.Select(objKey => current.MakeObject(currentMetadata, objKey)).ToArray(); - Deletions = deletions.Select(objKey => previous.MakeObject(previousMetadata, objKey)).ToArray(); - Modifications = modifications.Select(m => new ModificationDetails(() => previous.MakeObject(previousMetadata, m.obj), - () => current.MakeObject(currentMetadata, m.obj), - m.changed_columns.AsEnumerable().ToArray(), - (colKeys) => new HashSet(colKeys.Select(currentMetadata.Table.GetColumnName)))) - .ToArray(); - } - } -} diff --git a/Realm/Realm/Server/Dtos/IChangeDetails.cs b/Realm/Realm/Server/Dtos/IChangeDetails.cs deleted file mode 100644 index 8087e63651..0000000000 --- a/Realm/Realm/Server/Dtos/IChangeDetails.cs +++ /dev/null @@ -1,70 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Generic; - -namespace Realms.Server -{ - /// - /// An object containing information about the change that occurred to a Realm. - /// - public interface IChangeDetails - { - /// - /// Gets a value representing the relative path of the Realm. - /// - /// A relative path in the form of /some-user-id/myrealm. - string RealmPath { get; } - - /// - /// Gets an instance of the Realm just before the change occurred. It can be used to obtain the deleted - /// items or to compare the properties of the changed items. This instance is readonly and may be null if - /// the Realm was just created. - /// - /// A instance. - Realm PreviousRealm { get; } - - /// - /// Gets an instance of the Realm just after the change has occurred. This instance is readonly. If you wish - /// to write some data in response to the change, you use . - /// - /// A instance. - Realm CurrentRealm { get; } - - /// - /// Gets a collection of detailed change information. The keys of the dictionary contain the names of the objects - /// that have been modified, while the values contain instances describing the - /// indexes of the changed objects. - /// - /// A of object name-change details pair. - IReadOnlyDictionary Changes { get; } - - /// - /// Gets an instance of the Realm that can be used for writing new information or updating existing - /// objects. Because changes may have occurred in the background, this Realm may contain slightly newer - /// data than . - /// - /// - /// Writing to this Realm will cause changes to be propagated to all synchronized clients, including the - /// . A change notification will then be sent to handlers so care must be taken to - /// avoid creating an endless loop. - /// - /// A writeable instance. - Realm GetRealmForWriting(); - } -} diff --git a/Realm/Realm/Server/Dtos/IChangeSetDetails.cs b/Realm/Realm/Server/Dtos/IChangeSetDetails.cs deleted file mode 100644 index 2f8a8995b0..0000000000 --- a/Realm/Realm/Server/Dtos/IChangeSetDetails.cs +++ /dev/null @@ -1,49 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Generic; - -namespace Realms.Server -{ - /// - /// An object containing information about the insertions, deletions, and modifications - /// performed on a single collection of a certain object type. - /// - public interface IChangeSetDetails - { - /// - /// Gets a collection of objects that have been inserted to the collection. - /// - /// An array of s. - IReadOnlyList Insertions { get; } - - /// - /// Gets a collection of instances, describing - /// the objects that have been modified in the collection. - /// - /// An array of modifications. - IReadOnlyList Modifications { get; } - - /// - /// Gets a collection of the objects that have been deleted from the collection, - /// at the version just prior to their deletion. - /// - /// An array of s. - IReadOnlyList Deletions { get; } - } -} diff --git a/Realm/Realm/Server/Dtos/IModificationDetails.cs b/Realm/Realm/Server/Dtos/IModificationDetails.cs deleted file mode 100644 index 8987768344..0000000000 --- a/Realm/Realm/Server/Dtos/IModificationDetails.cs +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Generic; - -namespace Realms.Server -{ - /// - /// An object that contains information about the change that occurred on a single - /// object. - /// - public interface IModificationDetails - { - /// - /// Gets the object as it was before the change. - /// - /// A instance. - dynamic PreviousObject { get; } - - /// - /// Gets the object as it is after the change. - /// - /// A instance. - dynamic CurrentObject { get; } - - /// - /// Gets the names of the properties that were changed. - /// - ISet ChangedProperties { get; } - } -} diff --git a/Realm/Realm/Server/Dtos/ModificationDetails.cs b/Realm/Realm/Server/Dtos/ModificationDetails.cs deleted file mode 100644 index a96f90b3f3..0000000000 --- a/Realm/Realm/Server/Dtos/ModificationDetails.cs +++ /dev/null @@ -1,44 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using Realms.Native; - -namespace Realms.Server -{ - internal class ModificationDetails : IModificationDetails - { - private readonly Lazy _previous; - private readonly Lazy _current; - private readonly Lazy> _changedProperties; - - public dynamic PreviousObject => _previous.Value; - - public dynamic CurrentObject => _current.Value; - - public ISet ChangedProperties => _changedProperties.Value; - - internal ModificationDetails(Func previous, Func current, ColumnKey[] changedColumns, Func> changedProperties) - { - _previous = new Lazy(previous); - _current = new Lazy(current); - _changedProperties = new Lazy>(() => changedProperties(changedColumns)); - } - } -} diff --git a/Realm/Realm/Server/NotificationHandlers/INotificationHandler.cs b/Realm/Realm/Server/NotificationHandlers/INotificationHandler.cs deleted file mode 100644 index f75776face..0000000000 --- a/Realm/Realm/Server/NotificationHandlers/INotificationHandler.cs +++ /dev/null @@ -1,75 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Threading.Tasks; - -namespace Realms.Server -{ - /// - /// An interface for all notification handlers. Implement it and pass an - /// instance of the implementation to - /// to be notified when a Realm changes. - /// - public interface INotificationHandler - { - /// - /// A method, invoked by the when a Realm has changed. If the handler returns - /// true, will then be invoked with information - /// about the change. - /// - /// - /// This method is invoked on the 's worker thread, so it should return - /// as soon as possible to avoid adversely affecting performance. - /// - /// If the handler returns false and no other handler wants to be notified about - /// the Realm at this path, then this method will no longer will be called for Realms - /// with that path. It is recommended that you always return the same value for a path - /// and perform any additional handling in the method. - /// - /// - /// The path to the Realm that has changed. It will be a path relative to the root - /// of your server. - /// - /// - /// true if the handler wants to handle the change, false otherwise. - /// - bool ShouldHandle(string path); - - /// - /// A method invoked by the when a Realm has changed and - /// has returned true. - /// - /// - /// An instance of , containing detailed information - /// about the changes that have occurred in the Realm. - /// - /// - /// An awaitable task that, upon completion, signifies that the changes have been processed. - /// - /// - /// Handlers will be invoked sequentially in the order in which they have been supplied - /// in the . - /// - /// This method will be invoked sequentially for Realms with the same path and in parallel - /// for different Realms. This means that if the processing takes a lot of time, it will - /// build up a queue of changes for that Realm path but will not affect notifications from - /// other Realms. - /// - Task HandleChangeAsync(IChangeDetails details); - } -} diff --git a/Realm/Realm/Server/NotificationHandlers/RegexNotificationHandler.cs b/Realm/Realm/Server/NotificationHandlers/RegexNotificationHandler.cs deleted file mode 100644 index 4a4b620753..0000000000 --- a/Realm/Realm/Server/NotificationHandlers/RegexNotificationHandler.cs +++ /dev/null @@ -1,85 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace Realms.Server -{ - /// - /// A implementation that handles Realm changes based - /// on a . - /// - public abstract class RegexNotificationHandler : INotificationHandler - { - private readonly Regex _pathRegex; - - /// - /// Initializes a new instance of the class. - /// - /// - /// A regular expression that will be used to match Realm paths against. - /// - protected RegexNotificationHandler(string regex) - { - _pathRegex = new Regex(regex, RegexOptions.Compiled | RegexOptions.IgnoreCase); - } - - /// - /// A method invoked by the when a Realm has changed and - /// has returned true. - /// - /// - /// An instance of , containing detailed information - /// about the changes that have occurred in the Realm. - /// - /// - /// An awaitable task that, upon completion, signifies that the changes have been processed. - /// - /// - /// Handlers will be invoked sequentially in the order in which they have been supplied - /// in the . - /// - /// This method will be invoked sequentially for Realms with the same path and in parallel - /// for different Realms. This means that if the processing takes a lot of time, it will - /// build up a queue of changes for that Realm path but will not affect notifications from - /// other Realms. - /// - public abstract Task HandleChangeAsync(IChangeDetails details); - - /// - /// A method, invoked by the when a Realm has changed. If the handler returns - /// true, will then be invoked with information - /// about the change. - /// - /// - /// If the handler returns false and no other handler wants to be notified about - /// the Realm at this path, then this method will no longer will be called for Realms - /// with that path. It is recommended that you always return the same value for a path - /// and perform any additional handling in the method. - /// - /// - /// The path to the Realm that has changed. It will be a path relative to the root - /// of your server. - /// - /// - /// true if the handler wants to handle the change, false otherwise. - /// - public bool ShouldHandle(string path) => _pathRegex.IsMatch(path); - } -} diff --git a/Realm/Realm/Server/Notifier/CalculationProcessor.cs b/Realm/Realm/Server/Notifier/CalculationProcessor.cs deleted file mode 100644 index 5010656a3d..0000000000 --- a/Realm/Realm/Server/Notifier/CalculationProcessor.cs +++ /dev/null @@ -1,99 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading.Tasks; -using Nito.AsyncEx; - -namespace Realms.Server -{ - internal class CalculationProcessor - { - private readonly ConcurrentDictionary _locks = new ConcurrentDictionary(); - private readonly IDictionary> _calculationDictionary = new Dictionary>(); - - private readonly Func _calculator; - - public CalculationProcessor(Func calculator) - { - _calculator = calculator; - } - - public void Enqueue(TKey key, TValue value) - { - var locker = _locks.GetOrAdd(key, _ => new object()); - lock (locker) - { - if (_calculationDictionary.TryGetValue(key, out var queue)) - { - queue.Enqueue(value); - } - else - { - queue = new Queue(new[] { value }); - _calculationDictionary.Add(key, queue); - ProcessQueue(key, queue); - } - } - } - - private void ProcessQueue(TKey key, Queue queue) - { - Task.Run(() => AsyncContext.Run(async () => - { - if (!_locks.TryGetValue(key, out var locker)) - { - // TODO: figure out how to propagate this - throw new Exception("Should not be possible!"); - } - - while (TryDequeue(key, queue, out var value)) - { - await _calculator(value); - } - })); - } - - private bool TryDequeue(TKey key, Queue queue, out TValue value) - { - if (!_locks.TryGetValue(key, out var locker)) - { - // TODO: figure out how to propagate this - throw new Exception("Should not be possible!"); - } - - bool success; - lock (locker) - { - if (success = queue.Count > 0) - { - value = queue.Dequeue(); - } - else - { - value = default(TValue); - _calculationDictionary.Remove(key); - } - } - - return success; - } - } -} \ No newline at end of file diff --git a/Realm/Realm/Server/Notifier/INotifier.cs b/Realm/Realm/Server/Notifier/INotifier.cs deleted file mode 100644 index 6a8ba5507f..0000000000 --- a/Realm/Realm/Server/Notifier/INotifier.cs +++ /dev/null @@ -1,35 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; - -namespace Realms.Server -{ - /// - /// An notifier instance that observes Realms and invokes handlers with change details. - /// It can be obtained by calling . - /// - public interface INotifier : IDisposable - { - /// - /// Gets the that was used to create this . - /// - /// The Notifier's configuration. - NotifierConfiguration Configuration { get; } - } -} diff --git a/Realm/Realm/Server/Notifier/Notifier.cs b/Realm/Realm/Server/Notifier/Notifier.cs deleted file mode 100644 index d585b13982..0000000000 --- a/Realm/Realm/Server/Notifier/Notifier.cs +++ /dev/null @@ -1,250 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Nito.AsyncEx; -using Realms.Helpers; -using Realms.Server.Exceptions; -using Realms.Server.Native; - -namespace Realms.Server -{ - /// - /// A factory class, used for creating instances. - /// - public static class Notifier - { - /// - /// Creates a new with the supplied . - /// - /// - /// A describing the various settings. - /// - /// - /// An awaitable task, that, upon completion, will contain the fully initialized - /// instance. - /// - public static Task StartAsync(NotifierConfiguration config) - { - Argument.NotNull(config, nameof(config)); - Argument.Ensure(config.Handlers?.Any() == true, "The list of handlers cannot be empty.", nameof(config)); - return Impl.StartAsync(config); - } - - internal static unsafe bool ShouldHandle(IntPtr notifierHandle, byte* pathBuffer, IntPtr pathLength) - { - if (GCHandle.FromIntPtr(notifierHandle).Target is Impl notifier) - { - var path = Encoding.UTF8.GetString(pathBuffer, (int)pathLength); - return notifier.ShouldHandle(path); - } - - return false; - } - - internal static unsafe void EnqueueCalculation(IntPtr notifierHandle, byte* pathBuffer, IntPtr pathLength, IntPtr calculator_ptr) - { - if (GCHandle.FromIntPtr(notifierHandle).Target is Impl notifier) - { - var path = Encoding.UTF8.GetString(pathBuffer, (int)pathLength); - notifier.CalculateChanges(path, calculator_ptr); - } - } - - internal static unsafe void OnStarted(IntPtr managedInstance, int errorCode, byte* messageBuffer, IntPtr messageLength) - { - var handle = GCHandle.FromIntPtr(managedInstance); - var notifier = (Impl)handle.Target; - if (errorCode == 0) - { - notifier.OnStarted(null); - } - else - { - var message = Encoding.UTF8.GetString(messageBuffer, (int)messageLength); - notifier.OnStarted(new NotifierStartException(errorCode, message)); - } - } - - internal static void OnCalculationCompleted(IntPtr details_ptr, IntPtr managedCallbackPtr) - { - var handle = GCHandle.FromIntPtr(managedCallbackPtr); - var callback = (Action)handle.Target; - var details = new PtrTo(details_ptr); - callback(details.Value); - handle.Free(); - } - - internal class Impl : INotifier - { - private readonly INotificationHandler[] _handlers; - private readonly AsyncContextThread _notificationsThread; - private readonly CalculationProcessor _processor; - private readonly TaskCompletionSource _start; - - private readonly NotifierHandle _notifierHandle; - private readonly GCHandle _gcHandle; - - public NotifierConfiguration Configuration { get; } - - private Impl(NotifierConfiguration config, AsyncContextThread notificationsThread) - { - _handlers = config.Handlers.ToArray(); - _notificationsThread = notificationsThread; - _gcHandle = GCHandle.Alloc(this); - Configuration = config; - _start = new TaskCompletionSource(); - try - { - _notifierHandle = NotifierHandle.CreateHandle(_gcHandle, config); - } - catch - { - _gcHandle.Free(); - throw; - } - - _processor = new CalculationProcessor(CalculateAsync); - } - - private int _isDisposed; - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Impl will own the AsyncContextThread.")] - internal static Task StartAsync(NotifierConfiguration config) - { - Directory.CreateDirectory(config.WorkingDirectory); - - var thread = new AsyncContextThread(); - return thread.Factory.Run(() => - { - var notifier = new Impl(config, thread); - return notifier._start.Task; - }); - } - - internal void OnStarted(NotifierStartException exception) - { - if (exception != null) - { - _start.SetException(exception); - } - else - { - _start.SetResult(this); - } - } - - internal bool ShouldHandle(string path) => _handlers.Any(h => h.ShouldHandle(path)); - - internal void CalculateChanges(string path, IntPtr calculation_ptr) - { - if (_isDisposed == 0) - { - _processor.Enqueue(path, calculation_ptr); - } - } - - private async Task CalculateAsync(IntPtr calculation_ptr) - { - using (var calculation = new NotifierNotificationHandle(calculation_ptr)) - { - ChangeDetails details = null; - calculation.GetChanges(nativeDetails => - { - if (nativeDetails.HasValue) - { - var pathOnDisk = nativeDetails.Value.PathOnDisk; - Realm previousRealm = null; - if (nativeDetails.Value.previous_realm != IntPtr.Zero) - { - previousRealm = Realm.GetInstance(new NotifierRealmConfiguration(nativeDetails.Value.previous_realm, pathOnDisk)); - } - - var currentRealm = Realm.GetInstance(new NotifierRealmConfiguration(nativeDetails.Value.current_realm, pathOnDisk)); - - details = new ChangeDetails(nativeDetails.Value.Path, nativeDetails.Value.change_sets.AsEnumerable(), previousRealm, currentRealm); - } - }); - - if (details == null) - { - return; - } - - try - { - foreach (var handler in _handlers) - { - try - { - if (handler.ShouldHandle(details.RealmPath)) - { - await handler.HandleChangeAsync(details); - } - } - catch - { - // Don't skip notifications because someone didn't do a good job at error handling - // TODO: figure out a way to propagate those. - } - } - } - finally - { - details.PreviousRealm?.Dispose(); - details.CurrentRealm.Dispose(); - } - } - } - - private void Dispose(bool disposing) - { - if (Interlocked.Exchange(ref _isDisposed, 1) != 0) - { - // Don't dispose twice. - return; - } - - if (disposing) - { - _notifierHandle.Dispose(); - _notificationsThread.Join(); - _notificationsThread.Dispose(); - _gcHandle.Free(); - foreach (var handler in _handlers.OfType()) - { - handler.Dispose(); - } - } - } - } - } -} \ No newline at end of file diff --git a/Realm/Realm/Server/Notifier/NotifierConfiguration.cs b/Realm/Realm/Server/Notifier/NotifierConfiguration.cs deleted file mode 100644 index 00b3e5efec..0000000000 --- a/Realm/Realm/Server/Notifier/NotifierConfiguration.cs +++ /dev/null @@ -1,141 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.IO; -using Realms.Helpers; -using Realms.Sync; - -using NativeSyncConfiguration = Realms.Sync.Native.SyncConfiguration; - -namespace Realms.Server -{ - /// - /// A Notifier configuration specifying various settings that affect the Notifier's behavior. - /// - public class NotifierConfiguration - { - /// - /// Gets the used to create this . - /// - /// The whose s will be synced. - public User User { get; } - - /// - /// Gets or sets a collection of s that will be invoked when - /// a change occurs in a Realm file. - /// - /// The that will handle Realm changes. - /// - /// The members of the collection will be called sequentially in the order that they appear. - /// - public IList Handlers { get; set; } = new List(); - - /// - /// Gets or sets the directory which the will use to store the Realms it observes. - /// - /// A folder on the filesystem, that your application has permissions to write to. - public string WorkingDirectory { get; set; } = Directory.GetCurrentDirectory(); - - private byte[] _encryptionKey; - - /// - /// Gets or sets the key, used to encrypt the Realms at rest that the observes. - /// Once set, must be specified each time a notifier is started in the same working directory. - /// - /// Full 64byte (512bit) key for AES-256 encryption. - public byte[] EncryptionKey - { - get - { - return _encryptionKey; - } - - set - { - if (value != null && value.Length != 64) - { - throw new FormatException("EncryptionKey must be 64 bytes"); - } - - _encryptionKey = value; - } - } - - /// - /// Gets or sets a value indicating whether SSL certificate validation is enabled for the connection associated - /// with this configuration value. - /// - /// true if SSL validation is enabled; otherwise, false. Default value is true. - public bool EnableSSLValidation { get; set; } = true; - - /// - /// Gets or sets the path to the trusted root certificate(s) authority (CA) in PEM format, that should - /// be used to validate the TLS connections to the Realm Object Server. - /// - /// The path to the certificate. - /// - /// The file will be copied at runtime into the internal storage. - ///
- /// It is recommended to include only the root CA you trust, and not the entire list of root CA as this file - /// will be loaded at runtime. It is your responsibility to download and verify the correct PEM for the root CA - /// you trust. - ///
- /// This property is ignored on Apple platforms - you should use the KeyChain API to install your certificate - /// instead. - ///
- /// - /// OpenSSL documentation for SSL_CTX_load_verify_locations. - /// - /// - /// Mozilla Included CA Certificate List - /// - public string TrustedCAPath { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// A valid that has administrative access. - public NotifierConfiguration(User user) - { - Argument.NotNull(user, nameof(user)); - Argument.Ensure(user.IsAdmin, "User must be an administrator", nameof(user)); - - User = user; - } - - internal NativeSyncConfiguration ToNative() - { - if (!string.IsNullOrEmpty(TrustedCAPath) && - !File.Exists(TrustedCAPath)) - { - throw new FileNotFoundException($"{nameof(TrustedCAPath)} has been specified, but the file was not found.", TrustedCAPath); - } - - return new NativeSyncConfiguration - { - SyncUserHandle = User.Handle, - Url = User.GetUriForRealm(string.Empty).ToString().TrimEnd('/'), - client_validate_ssl = EnableSSLValidation, - TrustedCAPath = TrustedCAPath, - PartialSyncIdentifier = WorkingDirectory // we hijack this field of the struct - }; - } - } -} \ No newline at end of file diff --git a/Realm/Realm/Server/Notifier/NotifierRealmConfiguration.cs b/Realm/Realm/Server/Notifier/NotifierRealmConfiguration.cs deleted file mode 100644 index 9b426c73ce..0000000000 --- a/Realm/Realm/Server/Notifier/NotifierRealmConfiguration.cs +++ /dev/null @@ -1,50 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Threading; -using System.Threading.Tasks; -using Realms.Schema; - -namespace Realms.Server -{ - internal class NotifierRealmConfiguration : RealmConfigurationBase - { - private readonly IntPtr _nativeRealmPtr; - - public NotifierRealmConfiguration(IntPtr realm_ptr, string path) - { - _nativeRealmPtr = realm_ptr; - IsDynamic = true; - EnableCache = false; - DatabasePath = path; - } - - internal override Realm CreateRealm(RealmSchema schema) - { - var handle = new SharedRealmHandle(_nativeRealmPtr); - handle.GetSchema(nativeSchema => schema = RealmSchema.CreateFromObjectStoreSchema(nativeSchema)); - return new Realm(handle, this, schema); - } - - internal override Task CreateRealmAsync(RealmSchema schema, CancellationToken cancellationToken) - { - return Task.FromResult(CreateRealm(schema)); - } - } -} diff --git a/Realm/Realm/Sync/ApiKey.cs b/Realm/Realm/Sync/ApiKey.cs new file mode 100644 index 0000000000..3fd0d03388 --- /dev/null +++ b/Realm/Realm/Sync/ApiKey.cs @@ -0,0 +1,81 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using MongoDB.Bson; +using Realms.Native; + +namespace Realms.Sync +{ + /// + /// A class representing an API key for a . It can be used to represent the user when logging in + /// instead of their regular credentials. These keys are created or fetched through . + /// + /// + /// An API key's is only available when the key is created and cannot be obtained after that. + /// This means that it's the caller's responsibility to safely store an API key's value upon creation. + /// + public class ApiKey + { + /// + /// Gets the unique identifier for this key. + /// + /// The id uniquely identifying the key. + public ObjectId Id { get; } + + /// + /// Gets the name of the key. + /// + /// The friendly name of the key, specified when calling . + public string Name { get; } + + /// + /// Gets the value for the key. This is only returned when the key is created. After that, it will always be null. + /// + /// The value of the key that needs to be provided when constructing . + public string Value { get; } + + /// + /// Gets a value indicating whether or not this key is currently enabled. + /// + /// true if the key is enabled; false otherwise. + public bool IsEnabled { get; } + + /// + public override bool Equals(object obj) => (obj is ApiKey key) && key.Id == Id; + + /// + /// Gets the hash code. + /// + /// The hash code. + public override int GetHashCode() => Id.GetHashCode(); + + /// + /// Returns a string representation of the value. + /// + /// A string representation of the value. + public override string ToString() => $"ApiKey {Name} ({Id})"; + + internal ApiKey(UserApiKey nativeKey) + { + Id = nativeKey.Id; + Name = nativeKey.Name; + Value = nativeKey.Key; + IsEnabled = !nativeKey.disabled; + } + } +} diff --git a/Realm/Realm/Sync/App.cs b/Realm/Realm/Sync/App.cs new file mode 100644 index 0000000000..6eb1adbbcc --- /dev/null +++ b/Realm/Realm/Sync/App.cs @@ -0,0 +1,414 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Realms.Helpers; + +namespace Realms.Sync +{ + /// + /// An is the main client-side entry point for interacting with a MongoDB Realm App. + /// + /// + /// The App can be used to: + ///
+ /// + /// + /// Register uses and perform various user-related operations through authentication providers (e.g. , ). + /// + /// + /// Synchronize data between the local device and a remote Realm App with Synchronized Realms (using ). + /// + /// + /// Invoke Realm App functions with Functions (using ). + /// + /// + /// Access remote data from MongoDB databases with a (using ). + /// + /// + ///
+ /// To create an app that is linked with a remote Realm App initialize Realm and configure the App as shown below: + /// + /// var appConfig = new AppConfiguration("my-realm-app-id") + /// { + /// LocalAppName = "My amazing iOS app", + /// LocalAppVersion = "1.2.3" + /// }; + /// + /// var app = new App(appConfig); + /// + /// After configuring the App you can start managing users, configure Synchronized Realms, call remote Realm Functions and access remote data through Mongo Collections. + ///
+ /// To register a new user and/or login with an existing user do as shown below: + /// + /// await app.EmailPassword.RegisterUserAsync("foo@bar.com", "password"); + /// // Login with existing user + /// var user = app.LoginAsync(Credentials.EmailPassword("foo@bar.com", "password"); + /// + /// With an authorized user you can synchronize data between the local device and the remote Realm App by opening a Realm with a as indicated below: + /// + /// var syncConfig = new SyncConfiguration("some-partition-value", user); + /// using var realm = await Realm.GetInstanceAsync(syncConfig); + /// + /// realm.Write(() => + /// { + /// realm.Add(...); + /// }); + /// + /// await realm.GetSession().WaitForUploadAsync(); + /// + /// You can call remote Realm functions as shown below: + /// + /// user.Functions.CallAsync("sum", 1, 2, 3, 4, 5); + /// + /// And access collections from the remote Realm App as shown here: + /// + /// var client = user.GetMongoClient("atlas-service"); + /// var db = client.GetDatabase("my-db"); + /// var collection = db.GetCollection("foos"); + /// var foosCount = await collection.CountAsync(); + /// + ///
+ /// + public class App + { + internal readonly AppHandle Handle; + + /// + /// Gets a instance that exposes API for interacting with the synchronization client for this . + /// + /// A instance scoped to this . + public SyncClient Sync { get; } + + /// + /// Gets a instance that exposes functionality related to users either being created or logged in using + /// the provider. + /// + /// An instance scoped to this . + public EmailPasswordClient EmailPasswordAuth { get; } + + /// + /// Gets the currently logged-in user. If none exists, null is returned. + /// + /// Valid user or null to indicate nobody logged in. + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The User instance will own its handle.")] + public User CurrentUser => Handle.TryGetCurrentUser(out var userHandle) ? new User(userHandle, this) : null; + + /// + /// Gets all currently logged in users. + /// + /// An array of valid logged in users. + public User[] AllUsers => Handle.GetAllLoggedInUsers() + .Select(handle => new User(handle, this)) + .ToArray(); + + internal App(AppHandle handle) + { + Handle = handle; + Sync = new SyncClient(this); + EmailPasswordAuth = new EmailPasswordClient(this); + } + + /// + /// A factory method for creating an app with a particular . + /// + /// The , specifying key parameters for the app behavior. + /// An instance can now be used to login users, call functions, or open synchronized Realms. + public static App Create(AppConfiguration config) + { + Argument.NotNull(config, nameof(config)); + + if (config.MetadataPersistenceMode.HasValue) + { + if (config.MetadataPersistenceMode == MetadataPersistenceMode.Encrypted && config.MetadataEncryptionKey == null) + { + throw new ArgumentException($"{nameof(AppConfiguration.MetadataEncryptionKey)} must be set when {nameof(AppConfiguration.MetadataPersistenceMode)} is set to {nameof(MetadataPersistenceMode.Encrypted)}."); + } + + if (config.MetadataPersistenceMode != MetadataPersistenceMode.Encrypted && config.MetadataEncryptionKey != null) + { + throw new ArgumentException($"{nameof(AppConfiguration.MetadataPersistenceMode)} must be set to {nameof(MetadataPersistenceMode.Encrypted)} when {nameof(AppConfiguration.MetadataEncryptionKey)} is set."); + } + } + + var nativeConfig = new Native.AppConfiguration + { + AppId = config.AppId, + BaseFilePath = config.BaseFilePath ?? InteropConfig.DefaultStorageFolder, + BaseUrl = config.BaseUri?.ToString().TrimEnd('/'), + LocalAppName = config.LocalAppName, + LocalAppVersion = config.LocalAppVersion, + MetadataPersistence = config.MetadataPersistenceMode, + default_request_timeout_ms = (ulong?)config.DefaultRequestTimeout?.TotalMilliseconds ?? 0, + log_level = config.LogLevel, + }; + + if (config.CustomLogger != null) + { + // TODO: should we free this eventually? + var logHandle = GCHandle.Alloc(config.CustomLogger); + nativeConfig.managed_log_callback = GCHandle.ToIntPtr(logHandle); + } + + var handle = AppHandle.CreateApp(nativeConfig, config.MetadataEncryptionKey); + return new App(handle); + } + + /// + /// A factory method for creating an app with a particular . + /// + /// + /// This is a convenience method that creates an with the default parameters and the provided + /// and invokes . + /// + /// The application id of the MongoDB Realm Application. + /// An instance can now be used to login users, call functions, or open synchronized Realms. + public static App Create(string appId) => Create(new AppConfiguration(appId)); + + /// + /// Logs in as a user with the given credentials associated with an authentication provider. + /// + /// + /// The last logged in user will be saved as . If there was already a current user, + /// that user is still logged in and can be found in the list returned by . It is also + /// possible to switch between which user is considered the current user by using . + /// + /// The representing the type of login. + /// + /// A that represents the asynchronous LogIn operation. + public async Task LogInAsync(Credentials credentials) + { + Argument.NotNull(credentials, nameof(credentials)); + + var tcs = new TaskCompletionSource(); + Handle.LogIn(credentials.ToNative(), tcs); + var handle = await tcs.Task; + + return new User(handle, this); + } + + /// + /// Switches the to the one specified in . + /// + /// The new current user. + public void SwitchUser(User user) + { + Argument.NotNull(user, nameof(user)); + + Handle.SwitchUser(user.Handle); + } + + /// + /// Removes a user and their local data from the device. If the user is logged in, they will be logged out in the process. + /// + /// + /// This is client operation and will not delete any data stored on the server for that user. + /// + /// The user to log out and remove. + /// + /// A that represents the asynchronous RemoveUser operation. Successful completion indicates that the user has been logged out, + /// their local data - removed, and the user's - revoked on the server. + /// + public async Task RemoveUserAsync(User user) + { + Argument.NotNull(user, nameof(user)); + + var tcs = new TaskCompletionSource(); + Handle.Remove(user.Handle, tcs); + await tcs.Task; + } + + /// + /// A sync manager, handling synchronization of local Realm with remote MongoDB Realm apps. It is always scoped to a + /// particular app and can only be accessed via . + /// + public class SyncClient + { + private readonly App _app; + + internal SyncClient(App app) + { + _app = app; + } + + /// + /// Attempt to reconnect all Sync sessions for the app. + /// + /// + /// Realm will automatically detect when a device gets connectivity after being offline and resume syncing. + /// However, some of these checks are performed using incremental backoff, which means that there are cases + /// when automatic reconnection doesn't happen immediately. In those cases, it can be beneficial to call + /// this method manually, which will force all sessions to attempt to reconnect and in the process, reset + /// any timers, that are used for incremental backoff. + /// + public void Reconnect() + { + _app.Handle.Reconnect(); + } + } + + /// + /// A class, encapsulating functionality for users, logged in with the provider. + /// It is always scoped to a particular app and can only be accessed via . + /// + public class EmailPasswordClient + { + private readonly App _app; + + internal EmailPasswordClient(App app) + { + _app = app; + } + + /// + /// Registers a new user with the given email and password. + /// + /// + /// The email to register with. This will be the user's username and, if user confirmation is enabled, this will be the address for + /// the confirmation email. + /// + /// The password to associate with the email. The password must be between 6 and 128 characters long. + /// + /// A representing the asynchronous RegisterUser operation. Successful completion indicates that the user has been + /// created on the server and can now be logged in calling with . + /// + public Task RegisterUserAsync(string email, string password) + { + Argument.NotNullOrEmpty(email, nameof(email)); + Argument.NotNullOrEmpty(password, nameof(password)); + + var tcs = new TaskCompletionSource(); + _app.Handle.EmailPassword.RegisterUser(email, password, tcs); + return tcs.Task; + } + + /// + /// Confirms a user with the given token and token id. These are typically included in the email the user received + /// after registering. + /// + /// + /// While confirmation typically happens in a web app, mobile applications that have deep linking enabled can intercept the url + /// and complete the user confirmation flow in the app itself. + /// + /// The confirmation token. + /// The id of the confirmation token. + /// + /// A representing the asynchronous ConfirmUser operation. Successful completion indicates that the user has been + /// confirmed on the server. + /// + public Task ConfirmUserAsync(string token, string tokenId) + { + Argument.NotNullOrEmpty(token, nameof(token)); + Argument.NotNullOrEmpty(tokenId, nameof(tokenId)); + + var tcs = new TaskCompletionSource(); + _app.Handle.EmailPassword.ConfirmUser(token, tokenId, tcs); + return tcs.Task; + } + + /// + /// Resends the confirmation email for a user to the given email. + /// + /// The email of the user. + /// + /// A representing the asynchronous request to the server that a confirmation email is sent. Successful + /// completion indicates that the server has accepted the request and will send a confirmation email to the specified address + /// if a user with that email exists. + /// + public Task ResendConfirmationEmailAsync(string email) + { + Argument.NotNullOrEmpty(email, nameof(email)); + + var tcs = new TaskCompletionSource(); + _app.Handle.EmailPassword.ResendConfirmationEmail(email, tcs); + return tcs.Task; + } + + /// + /// Sends a password reset email to the specified address. + /// + /// the email of the user. + /// + /// A representing the asynchronous request to the server that a reset password email is sent. Successful + /// completion indicates that the server has accepted the request and will send a password reset email to the specified + /// address if a user with that email exists. + /// + public Task SendResetPasswordEmailAsync(string email) + { + Argument.NotNullOrEmpty(email, nameof(email)); + + var tcs = new TaskCompletionSource(); + _app.Handle.EmailPassword.SendResetPasswordEmail(email, tcs); + return tcs.Task; + } + + /// + /// Completes the reset password flow by providing the desired new password. + /// + /// + /// While the reset password flow is typically completed in the web app, mobile applications that have deep linking enabled can intercept the url + /// and complete the password reset flow in the app itself. + /// + /// The new password for the user. + /// The password reset token that was sent to the user's email address. + /// The password reset token id that was sent together with the to the user's email address. + /// + /// A representing the asynchronous request that a user's password is reset. Successful completion indicates that the user's password has been + /// reset and they can now use the new password to create credentials and call to login. + /// + public Task ResetPasswordAsync(string password, string token, string tokenId) + { + Argument.NotNullOrEmpty(token, nameof(token)); + Argument.NotNullOrEmpty(tokenId, nameof(tokenId)); + Argument.NotNullOrEmpty(password, nameof(password)); + + var tcs = new TaskCompletionSource(); + _app.Handle.EmailPassword.ResetPassword(password, token, tokenId, tcs); + return tcs.Task; + } + + /// + /// Calls the reset password function, configured on the server. + /// + /// The email of the user. + /// The new password of the user. + /// + /// Any additional arguments provided to the reset function. All arguments must be able to be converted to JSON + /// compatible values. + /// + /// + /// A representing the asynchronous request to call a password reset function. Successful completion indicates + /// that the user's password has been change and they can now use the new password to create + /// credentials and call to login. + /// + public Task CallResetPasswordFunctionAsync(string email, string password, params object[] functionArgs) + { + Argument.NotNullOrEmpty(email, nameof(email)); + Argument.NotNullOrEmpty(password, nameof(password)); + + var tcs = new TaskCompletionSource(); + _app.Handle.EmailPassword.CallResetPasswordFunction(email, password, functionArgs.ToNativeJson(), tcs); + return tcs.Task; + } + } + } +} diff --git a/Realm/Realm/Sync/AppConfiguration.cs b/Realm/Realm/Sync/AppConfiguration.cs new file mode 100644 index 0000000000..e45c1d4c86 --- /dev/null +++ b/Realm/Realm/Sync/AppConfiguration.cs @@ -0,0 +1,145 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using Realms.Helpers; + +namespace Realms.Sync +{ + /// + /// A class exposing configuration options for a . + /// + /// . + public class AppConfiguration + { + private byte[] _metadataEncryptionKey; + + /// + /// Gets the unique app id that identifies the Realm application. + /// + /// The MongoDB Realm App's id. + public string AppId { get; } + + /// + /// Gets or sets the root folder relative to which all local data for this application will be stored. This data includes + /// metadata for users and synchronized Realms. + /// + /// The app's base path. + public string BaseFilePath { get; set; } + + /// + /// Gets or sets the base url for this Realm application. + /// + /// + /// This only needs to be set if for some reason your application isn't hosted on realm.mongodb.com. This can be the case if you're + /// testing locally or are using a preproduction environment. + /// + /// The app's base url. + public Uri BaseUri { get; set; } + + /// + /// Gets or sets the local app's name. + /// + /// + /// The local app name is typically used to differentiate between client applications that use the same + /// MongoDB Realm app. These can be the same conceptual app developed for different platforms, or + /// significantly different client side applications that operate on the same data - e.g. an event managing + /// service that has different clients apps for organizers and attendees. + /// + /// The friendly name identifying the current client application. + /// + public string LocalAppName { get; set; } + + /// + /// Gets or sets the local app's version. + /// + /// + /// The local app version is typically used to differentiate between versions of the same client application. + /// + /// The client application's version. + /// + public string LocalAppVersion { get; set; } + + /// + /// Gets or sets the persistence mode for user metadata on this device. + /// + /// + /// The default value is for iOS devices and + /// for all other platforms. On iOS we integrate with the system keychain to generate and store a random encryption key the first time the app + /// is launched. On other platforms, needs to be set if is + /// specified. + /// + /// The user metadata persistence mode. + public MetadataPersistenceMode? MetadataPersistenceMode { get; set; } + + /// + /// Gets or sets the encryption key for user metadata on this device. + /// + /// + /// This will not change the encryption key for individual Realms. This should still be set in + /// when opening the . + /// + /// The user metadata encryption key. + public byte[] MetadataEncryptionKey + { + get => _metadataEncryptionKey; + set + { + if (value != null && value.Length != 64) + { + throw new FormatException("EncryptionKey must be 64 bytes"); + } + + _metadataEncryptionKey = value; + } + } + + /// + /// Gets or sets a custom log function that will be invoked for each log message emitted by sync. + /// + /// + /// The first argument of the action is the log message itself, while the second one is the + /// at which the log message was emitted. + /// + /// The custom logger. + public Action CustomLogger { get; set; } + + /// + /// Gets or sets the log level for sync operations. + /// + /// The sync log level. + public LogLevel LogLevel { get; set; } = LogLevel.Info; + + /// + /// Gets or sets the default request timeout for HTTP requests to MongoDB Realm. + /// + /// The default HTTP request timeout. + public TimeSpan? DefaultRequestTimeout { get; set; } + + /// + /// Initializes a new instance of the class with the specified . + /// + /// The MongoDB Realm App id. + public AppConfiguration(string appId) + { + Argument.NotNullOrEmpty(appId, nameof(appId)); + + AppId = appId; + } + } +} diff --git a/Realm/Realm/Sync/ClientResyncMode.cs b/Realm/Realm/Sync/ClientResyncMode.cs deleted file mode 100644 index 3f23442b56..0000000000 --- a/Realm/Realm/Sync/ClientResyncMode.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Realms.Sync.Exceptions; - -namespace Realms.Sync -{ - /// - /// Enum describing what should happen in case of a Client Resync. - /// - /// - /// A Client Resync is triggered if the device and server cannot agree on a common shared history - /// for the Realm file, thus making it impossible for the device to upload or receive any changes. - /// This can happen if the server is rolled back or restored from backup. - ///
- /// IMPORTANT: Just having the device offline will not trigger a Client Resync. - ///
- public enum ClientResyncMode : byte - { - /// - /// Realm will compare the local Realm with the Realm on the server and automatically transfer - /// any changes from the local Realm that makes sense to the Realm provided by the server. - ///
- /// This is the default mode for fully synchronized Realms. It is not yet supported by - /// Query-based Realms. - ///
- RecoverLocalRealm = 0, - - /// - /// The local Realm will be discarded and replaced with the server side Realm. - /// All local changes will be lost. - ///
- /// This mode is not yet supported by Query-based Realms. - ///
- DiscardLocalRealm = 1, - - /// - /// A manual Client Resync is also known as a Client Reset. - ///
- /// A will be sent to , - /// triggering a Client Reset. Doing this provides a handle to both the old and new Realm file, enabling - /// full control over which changes to move, if any. - ///
- /// This is the only supported mode for Query-based Realms. - ///
- Manual = 2, - } -} diff --git a/Realm/Realm/Sync/Credentials.cs b/Realm/Realm/Sync/Credentials.cs index 988f18ad21..0013c3a749 100644 --- a/Realm/Realm/Sync/Credentials.cs +++ b/Realm/Realm/Sync/Credentials.cs @@ -16,9 +16,6 @@ // //////////////////////////////////////////////////////////////////////////// -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; using Realms.Helpers; namespace Realms.Sync @@ -28,263 +25,214 @@ namespace Realms.Sync /// public class Credentials { - internal static class Provider + /// + /// An enum containing the possible authentication providers. These have to manually be enabled for + /// your app before they can be used. + /// + /// + public enum AuthProvider { - public const string Debug = "debug"; - - public const string AdminToken = "adminToken"; - - public const string Facebook = "facebook"; - - public const string Google = "google"; - - public const string UsernamePassword = "password"; - - public const string AzureAD = "azuread"; - - public const string JWT = "jwt"; - - public const string Anonymous = "anonymous"; - - public const string Nickname = "nickname"; - - public const string CustomRefreshToken = "customRefreshToken"; - } - - internal static class Keys - { - internal const string CreateUser = "register"; - - internal const string Password = "password"; - - internal const string Identity = "identity"; - - internal const string IsAdmin = "is_admin"; + /// + /// Mechanism for authenticating without credentials. + /// + Anonymous = 0, + + /// + /// OAuth2-based mechanism for logging in with an existing Facebook account. + /// + Facebook = 1, + + /// + /// OAuth2-based mechanism for logging in with an existing Google account. + /// + Google = 2, + + /// + /// OAuth2-based mechanism for logging in with an Apple ID. + /// + Apple = 3, + + /// + /// Allow users to log in with JWT-based credentials generated by a service external to Realm. + /// + JWT = 4, + + /// + /// Mechanism for authenticating with an email and a password. + /// + EmailPassword = 5, + + /// + /// Allow users to log in with arbitrary credentials according to custom authentication logic that you define + /// on the server. + /// + Function = 6, + + /// + /// Mechanism for logging in with API keys generated by the client SDK. + /// + ApiKey = 7, + + /// + /// Mechanism for logging in with API keys generated in the server UI. + /// + ServerApiKey = 8, + + /// + /// A provider that is not among the well known provider types. This is most likely the result of the server + /// introducing a new provider type that this version of the SDK doesn't know about. + /// + Unknown = 999, } /// - /// Creates an instance of with a custom provider and user identifier. + /// Creates credentials representing an anonymous user. /// - /// Provider used to verify the credentials. - /// String identifying the user. Usually a username of id. - /// Data describing the user further or null if the user does not have any extra data. The data will be serialized to JSON, so all values must be mappable to a valid JSON data type. - /// An instance of that can be used in . - public static Credentials Custom(string identityProvider, string userIdentifier, - IDictionary userInfo) - { - return new Credentials - { - IdentityProvider = identityProvider, - Token = userIdentifier, - UserInfo = new ReadOnlyDictionary(userInfo) - }; - } + /// A Credentials that can be used to authenticate an anonymous user. + /// + public static Credentials Anonymous() => new Credentials(AuthProvider.Anonymous); /// - /// Creates based on a Facebook login. + /// Creates credentials representing a login using a Facebook access token. /// - /// A Facebook authentication token, obtained by logging into Facebook. - /// An instance of that can be used in . - public static Credentials Facebook(string facebookToken) + /// The OAuth 2.0 access token representing the Facebook user. + /// A Credentials that can be used to authenticate a user with Facebook. + /// + public static Credentials Facebook(string accessToken) { - Argument.NotNull(facebookToken, nameof(facebookToken)); + Argument.NotNull(accessToken, nameof(accessToken)); - return new Credentials - { - IdentityProvider = Provider.Facebook, - Token = facebookToken - }; + return new Credentials(AuthProvider.Facebook, accessToken); } /// - /// Creates based on a Google login. + /// Creates credentials representing a login using a Google access token. /// - /// A Google authentication token, obtained by logging into Google. - /// An instance of that can be used in . - public static Credentials Google(string googleToken) + /// The auth code representing the Google user. + /// A Credentials that can be used to authenticate a user with Google. + /// + public static Credentials Google(string authCode) { - Argument.NotNull(googleToken, nameof(googleToken)); + Argument.NotNull(authCode, nameof(authCode)); - return new Credentials - { - IdentityProvider = Provider.Google, - Token = googleToken - }; + return new Credentials(AuthProvider.Google, authCode); } /// - /// Creates based on a login with a username and a password. + /// Creates credentials representing a login using an Apple ID access token. /// - /// The username of the user. - /// The user's password. - /// true if the user should be created, false otherwise. It is not possible to create a user twice when logging in, so this flag should only be set to true the first time a user logs in. - /// An instance of that can be used in . - public static Credentials UsernamePassword(string username, string password, bool? createUser = null) + /// The OAuth 2.0 access token representing the user's Apple ID. + /// A Credentials that can be used to authenticate a user via an Apple ID. + /// + public static Credentials Apple(string accessToken) { - var userInfo = new Dictionary { [Keys.Password] = password }; - if (createUser != null) - { - userInfo[Keys.CreateUser] = createUser; - } + Argument.NotNull(accessToken, nameof(accessToken)); - return new Credentials - { - IdentityProvider = Provider.UsernamePassword, - Token = username, - UserInfo = userInfo, - }; + return new Credentials(AuthProvider.Apple, accessToken); } /// - /// Creates for an anonymous user. These can only be used once - using them a second - /// time will result in a different user being logged in. If you need to get a user that has already logged - /// in with the Anonymous credentials, use or . + /// Creates credentials representing a login using a JWT Token. /// - /// - /// An instance of that can be used in . - /// - public static Credentials Anonymous() + /// The custom JWT token representing the user. + /// A Credentials that can be used to authenticate a user with a custom JWT Token. + /// + public static Credentials JWT(string customToken) { - return new Credentials - { - IdentityProvider = Provider.Anonymous - }; + Argument.NotNull(customToken, nameof(customToken)); + + return new Credentials(AuthProvider.JWT, customToken); } /// - /// Creates based on a login with a nickname. If multiple users try to login - /// with the same nickname, they'll get the same underlying sync user. + /// Creates credentials representing a login using an email and password. /// - /// The nickname of the user. - /// - /// An instance of that can be used in . - /// - [Obsolete("The Nickname auth provider is insecure and will be removed in a future version. Please use UsernamePassword or Anonymous instead.")] - public static Credentials Nickname(string value) + /// The user's email. + /// The user's password. + /// A Credentials that can be used to authenticate a user with their email and password. + /// + /// A user can login with email and password only after they've registered their account and verified their + /// email. To register an email/password user via the SDK, use . + /// To verify an email from the SDK, use . The email/password + /// provider can also be configured to automatically confirm users or to run a custom confirmation function upon + /// user registration. + /// + /// + public static Credentials EmailPassword(string email, string password) { - return new Credentials - { - IdentityProvider = Provider.Nickname, - Token = value - }; + Argument.NotNullOrEmpty(email, nameof(email)); + Argument.NotNullOrEmpty(password, nameof(password)); + + return new Credentials(AuthProvider.EmailPassword, email, password); } /// - /// Creates based on an Active Directory login. + /// Creates credentials represetning a login with Realm function. /// - /// An access token, obtained by logging into Azure Active Directory. - /// An instance of that can be used in . - public static Credentials AzureAD(string adToken) + /// The payload that will be passed as an argument to the server function. + /// A Credentials that can be used to authenticate a user by invoking a server function. + /// + /// The payload object will be serialized and parsed when invoking the Realm function. This means that + /// unserializable values, such as references to functions or cyclic object graphs will not work. + /// Additionally, the names of the fields/properties must match exactly the names that your function + /// expects. + /// + /// + public static Credentials Function(object payload) { - Argument.NotNull(adToken, nameof(adToken)); - - return new Credentials - { - IdentityProvider = Provider.AzureAD, - Token = adToken - }; + return new Credentials(AuthProvider.Function, payload.ToNativeJson()); } /// - /// Creates based on a JWT access token. + /// Creates credentials representing a login using an API key generated by a client SDK. /// - /// A Json Web Token, obtained by logging into your auth service. - /// - /// The name of the jwt provider in ROS. By default, it will be jwt, unless explicitly overridden - /// by the ROS configuration. - /// - /// An instance of that can be used in . - public static Credentials JWT(string token, string providerName = Provider.JWT) + /// The API key to use for login. + /// A Credentials that can be used to authenticate user with an API key. + /// + public static Credentials ApiKey(string key) { - Argument.NotNull(token, nameof(token)); + Argument.NotNull(key, nameof(key)); - return new Credentials - { - IdentityProvider = providerName, - Token = token - }; + return new Credentials(AuthProvider.ApiKey, key); } /// - /// Creates a based on a custom Refresh token. + /// Creates credentials representing a login using an API key generated in the server UI. /// - /// A Json Web Token, obtained by a 3rd party source that will be used instead of the ROS-issued refresh tokens. - /// - /// The identity of the user. This value is used for client side validation only as the server will compute its own - /// value based on the userIdFieldName configuration. It is still important for proper functioning of the system - /// that these values match. - /// - /// - /// A value indicating whether the user is an admin. This value is used for client side validation only as the server - /// will compute its own value based on the isAdminQuery configuration. It is still important for proper - /// functioning of the system that these values match. - /// - /// Unlike other methods, users logged in via the CustomRefreshToken API will not go through the regular - /// login flow (since we already have a refresh token). Instead, the provided token will be used at any point when we need to exchange - /// the refresh token for an access token, e.g. when opening a Realm file. If the refresh token is invalid or expired, the user instance - /// will still be valid and they'll be able to create/open Realms, but those will never be synchronized with ROS. If the token is then updated - /// with a new valid one, existing changes will be synchronized with the server as usual. - /// - /// To update a refresh token, just set to the new updated value. - /// - /// ROS must be configured with refreshTokenValidators for this user to ever be able to sync with it. - /// - /// An instance of that can be used in . - public static Credentials CustomRefreshToken(string token, string userId, bool isAdmin = false) + /// The server API key to use for login. + /// A Credentials that can be used to authenticate user with an API key. + /// + public static Credentials ServerApiKey(string serverApiKey) { - Argument.NotNull(token, nameof(token)); - Argument.NotNull(userId, nameof(userId)); - - return new Credentials - { - IdentityProvider = Provider.CustomRefreshToken, - Token = token, - UserInfo = new Dictionary - { - [Keys.IsAdmin] = isAdmin, - [Keys.Identity] = userId - } - }; - } + Argument.NotNull(serverApiKey, nameof(serverApiKey)); - internal static Credentials AdminToken(string token) - { - return new Credentials - { - IdentityProvider = Provider.AdminToken, - Token = token - }; + return new Credentials(AuthProvider.ServerApiKey, serverApiKey); } /// - /// Gets the identity provider for the credentials. + /// Gets a value indicating which these Credentials are using. /// - /// The identity provider, such as Google, Facebook, etc. - public string IdentityProvider { get; private set; } + /// The these credentials use. + public AuthProvider Provider { get; } - /// - /// Gets the access token. - /// - /// The access token. - public string Token { get; private set; } + internal string Token { get; } - /// - /// Gets additional user information associated with the credentials. - /// - /// A dictionary, containing the additional information. - public IReadOnlyDictionary UserInfo { get; private set; } = new Dictionary(); + internal string Password { get; } - private Credentials() + private Credentials(AuthProvider provider, string token = null, string password = null) { + Provider = provider; + Token = token; + Password = password; } - internal IDictionary ToDictionary() + internal Native.Credentials ToNative() { - return new Dictionary + return new Native.Credentials { - ["data"] = Token, - ["provider"] = IdentityProvider, - ["user_info"] = UserInfo + provider = Provider, + Token = Token, + Password = Password, }; } } diff --git a/Realm/Realm/Sync/Dtos/AccountInfo.cs b/Realm/Realm/Sync/Dtos/AccountInfo.cs deleted file mode 100644 index 969843b27b..0000000000 --- a/Realm/Realm/Sync/Dtos/AccountInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - /// - /// An object containing information about an account associated with a user. - /// - public class AccountInfo - { - /// - /// Gets the provider that manages this user account. - /// - public string Provider { get; internal set; } - - /// - /// Gets the user account's identity in the provider's system. - /// - public string ProviderUserIdentity { get; internal set; } - } -} diff --git a/Realm/Realm/Sync/Dtos/UserInfo.cs b/Realm/Realm/Sync/Dtos/UserInfo.cs deleted file mode 100644 index 8392d66bbb..0000000000 --- a/Realm/Realm/Sync/Dtos/UserInfo.cs +++ /dev/null @@ -1,48 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Generic; - -namespace Realms.Sync -{ - /// - /// An object containing information about a user's identity in Realm's authentication system. - /// - public class UserInfo - { - /// - /// Gets the identity of the user in Realm's system. Equivalent to . - /// - public string Identity { get; internal set; } - - /// - /// Gets a value indicating whether the user is a Realm Object Server administrator user. Equivalent to . - /// - public bool IsAdmin { get; internal set; } - - /// - /// Gets a collection of all the user accounts associated with the user. - /// - public IEnumerable Accounts { get; internal set; } - - /// - /// Gets the metadata about this user stored on the Realm Object Server. - /// - public IDictionary Metadata { get; internal set; } - } -} diff --git a/Realm/Realm/Sync/UserPersistenceMode.cs b/Realm/Realm/Sync/MetadataPersistenceMode.cs similarity index 97% rename from Realm/Realm/Sync/UserPersistenceMode.cs rename to Realm/Realm/Sync/MetadataPersistenceMode.cs index cf81eae7d0..c324c6b415 100644 --- a/Realm/Realm/Sync/UserPersistenceMode.cs +++ b/Realm/Realm/Sync/MetadataPersistenceMode.cs @@ -22,7 +22,7 @@ namespace Realms.Sync /// Enumeration that specifies how and if logged-in objects are persisted /// across application launches. /// - public enum UserPersistenceMode + public enum MetadataPersistenceMode { /// /// Persist objects, but do not encrypt them. diff --git a/Realm/Realm/Sync/MongoClient.cs b/Realm/Realm/Sync/MongoClient.cs new file mode 100644 index 0000000000..fcd061eaac --- /dev/null +++ b/Realm/Realm/Sync/MongoClient.cs @@ -0,0 +1,544 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using Realms.Helpers; +using Realms.Native; + +namespace Realms.Sync +{ + /// + /// The remote MongoClient used for working with data in MongoDB remotely via Realm. + /// + public class MongoClient + { + internal User User { get; } + + /// + /// Gets the service name for this client. + /// + /// The name of the remote MongoDB service. + public string ServiceName { get; } + + internal MongoClient(User user, string serviceName) + { + User = user; + ServiceName = serviceName; + } + + /// + /// Gets a instance for the given database name. + /// + /// The name of the database to retrieve. + /// A instance that exposes an API for querying its collections. + public Database GetDatabase(string name) + { + Argument.Ensure(IsNameValid(name), "Database names must be non-empty and not contain '.' or the null character.", nameof(name)); + + return new Database(this, name); + } + + internal static bool IsNameValid(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + return false; + } + + var index = name.IndexOfAny(new[] { '\0', '.' }); + return index == -1; + } + + /// + /// An object representing a remote MongoDB database. + /// + public class Database + { + /// + /// Gets the that manages this database. + /// + /// The database's . + public MongoClient Client { get; } + + /// + /// Gets the name of the database. + /// + /// The database name. + public string Name { get; } + + internal Database(MongoClient client, string name) + { + Client = client; + Name = name; + } + + /// + /// Gets a collection from the database. + /// + /// The name of the collection. + /// A instance that exposes an API for CRUD operations on its contents. + public Collection GetCollection(string name) => GetCollection(name); + + /// + /// Gets a collection from the database. + /// + /// + /// The MongoDB Bson library is used + /// to decode the response. It will automatically handle most cases, but if you want to control the behavior + /// of the deserializer, you can use the attributes in the + /// MongoDB.Bson.Serialization.Attributes + /// namespace. + ///
+ /// If you want to modify the global conventions used when deserializing the response, such as convert + /// camelCase properties to PascalCase, you can regiseter a + /// ConventionPack. + ///
+ /// The managed type that matches the shape of the documents in the collection. + /// The name of the collection. + /// A instance that exposes an API for CRUD operations on its contents. + public Collection GetCollection(string name) + where TDocument : class + { + Argument.Ensure(IsNameValid(name), "Collection names must be non-empty and not contain '.' or the null character.", nameof(name)); + + var handle = MongoCollectionHandle.Create(Client.User.Handle, Client.ServiceName, Name, name); + return new Collection(this, name, handle); + } + } + + /// + /// An object representing a remote MongoDB collection. + /// + /// The managed type that matches the shape of the documents in the collection. + public class Collection + where TDocument : class + { + private readonly MongoCollectionHandle _handle; + + /// + /// Gets the this collection belongs to. + /// + /// The collection's . + public Database Database { get; } + + /// + /// Gets the name of the collection. + /// + /// The collection name. + public string Name { get; } + + internal Collection(Database database, string name, MongoCollectionHandle handle) + { + Database = database; + Name = name; + _handle = handle; + } + + /// + /// Inserts the provided document in the collection. + /// + /// The document to insert. + /// + /// A representing the remote insert operation. The result of the task + /// contains the _id of the inserted document. + /// + /// + public async Task InsertOneAsync(TDocument doc) + { + Argument.NotNull(doc, nameof(doc)); + + var result = await _handle.InsertOne(doc.ToNativeJson()); + + return result.GetValue(); + } + + /// + /// Inserts one or more documents in the collection. + /// + /// The documents to insert. + /// + /// A representing the remote insert many operation. The result of the task + /// contains the _ids of the inserted documents. + /// + /// + public async Task InsertManyAsync(IEnumerable docs) + { + Argument.NotNull(docs, nameof(docs)); + Argument.Ensure(docs.All(d => d != null), "Collection must not contain null elements.", nameof(docs)); + + var result = await _handle.InsertMany(docs.ToNativeJson()); + return result.GetValue(); + } + + /// + /// Updates a single document in the collection according to the specified arguments. + /// + /// + /// A document describing the selection criteria of the update. If not specified, the first document in the + /// collection will be updated. Can only contain + /// query selector expressions. + /// + /// + /// A document describing the update. Can only contain + /// update operator expressions. + /// + /// + /// A boolean controlling whether the update should insert a document if no documents match the . + /// Defaults to false. + /// + /// + /// A representing the remote update one operation. The result of the task + /// contains information about the number of matched and updated documents, as well as the _id of the + /// upserted document if was set to true and the operation resulted in an + /// upsert. + /// + /// + public async Task UpdateOneAsync(object filter, object updateDocument, bool upsert = false) + { + Argument.NotNull(updateDocument, nameof(updateDocument)); + + var result = await _handle.UpdateOne(filter?.ToNativeJson(), updateDocument?.ToNativeJson(), upsert); + return result.GetValue(); + } + + /// + /// Updates one or more documents in the collection according to the specified arguments. + /// + /// + /// A document describing the selection criteria of the update. If not specified, all documents in the + /// collection will be updated. Can only contain + /// query selector expressions. + /// + /// + /// A document describing the update. Can only contain + /// update operator expressions. + /// + /// + /// A boolean controlling whether the update should insert a document if no documents match the . + /// Defaults to false. + /// + /// + /// A representing the remote update many operation. The result of the task + /// contains information about the number of matched and updated documents, as well as the _id of the + /// upserted document if was set to true and the operation resulted in an + /// upsert. + /// + /// + public async Task UpdateManyAsync(object filter, object updateDocument, bool upsert = false) + { + Argument.NotNull(updateDocument, nameof(updateDocument)); + + var result = await _handle.UpdateMany(filter?.ToNativeJson(), updateDocument.ToNativeJson(), upsert); + return result.GetValue(); + } + + /// + /// Removes a single document from a collection. If no documents match the , the collection is not modified. + /// + /// + /// A document describing the deletion criteria using query operators. + /// If not specified, the first document in the collection will be deleted. + /// + /// + /// A representing the remote delete one operation. The result of the task contains the number + /// of deleted documents. + /// + /// + public async Task DeleteOneAsync(object filter = null) + { + var result = await _handle.DeleteOne(filter?.ToNativeJson()); + return result.GetValue(); + } + + /// + /// Removes one or more documents from a collection. If no documents match the , the collection is not modified. + /// + /// + /// A document describing the deletion criteria using query operators. + /// If not specified, all documents in the collection will be deleted. + /// + /// + /// A representing the remote delete many operation. The result of the task contains the number + /// of deleted documents. + /// + /// + public async Task DeleteManyAsync(object filter = null) + { + var result = await _handle.DeleteMany(filter?.ToNativeJson()); + return result.GetValue(); + } + + /// + /// Finds the all documents in the collection up to . + /// + /// + /// A document describing the find criteria using query operators. + /// If not specified, all documents in the collection will be returned. + /// + /// A document describing the sort criteria. If not specified, the order of the returned documents is not guaranteed. + /// + /// A document describing the fields to return for all matching documents. If not specified, all fields are returned. + /// + /// The maximum number of documents to return. If not specified all documents in the collection are returned. + /// + /// A representing the remote find operation. The result of the task is an array containing the documents that match the find criteria. + /// + /// + public async Task FindAsync(object filter = null, object sort = null, object projection = null, long? limit = null) + { + var result = await _handle.Find(filter?.ToNativeJson(), FindAndModifyOptions.Find(projection, sort, limit)); + return result.GetValue(); + } + + /// + /// Finds the first document in the collection that satisfies the query criteria. + /// + /// + /// A document describing the find criteria using query operators. + /// If not specified, all documents in the collection will match the request. + /// + /// A document describing the sort criteria. If not specified, the order of the returned documents is not guaranteed. + /// + /// A document describing the fields to return for all matching documents. If not specified, all fields are returned. + /// + /// + /// A representing the remote find one operation. The result of the task is the first document that matches the find criteria. + /// + /// + public async Task FindOneAsync(object filter = null, object sort = null, object projection = null) + { + var result = await _handle.FindOne(filter?.ToNativeJson(), FindAndModifyOptions.Find(projection, sort)); + return result.GetValue(); + } + + /// + /// Finds the first document in the collection that satisfies the query criteria. + /// + /// + /// A document describing the update. Can only contain + /// update operator expressions. + /// + /// + /// A document describing the find criteria using query operators. + /// If not specified, all documents in the collection will match the request. + /// + /// A document describing the sort criteria. If not specified, the order of the returned documents is not guaranteed. + /// + /// A document describing the fields to return for all matching documents. If not specified, all fields are returned. + /// + /// + /// A boolean controlling whether the update should insert a document if no documents match the . + /// Defaults to false. + /// + /// + /// A boolean controlling whether to return the new updated document. If set to false the original document + /// before the update is returned. Defaults to false. + /// + /// + /// A representing the remote find one operation. The result of the task is the first document that matches the find criteria. + /// + /// + public async Task FindOneAndUpdateAsync(object updateDocument, object filter = null, object sort = null, object projection = null, bool upsert = false, bool returnNewDocument = false) + { + Argument.NotNull(updateDocument, nameof(updateDocument)); + + var result = await _handle.FindOneAndUpdate(filter?.ToNativeJson(), updateDocument.ToNativeJson(), FindAndModifyOptions.FindAndModify(projection, sort, upsert, returnNewDocument)); + return result.GetValue(); + } + + /// + /// Finds the first document in the collection that satisfies the query criteria. + /// + /// + /// The replacement document. Cannot contain update operator expressions. + /// + /// + /// A document describing the find criteria using query operators. + /// If not specified, all documents in the collection will match the request. + /// + /// + /// A document describing the sort criteria. If not specified, the order of the returned documents is not guaranteed. + /// + /// + /// A document describing the fields to return for all matching documents. If not specified, all fields are returned. + /// + /// + /// A boolean controlling whether the replace should insert a document if no documents match the . + /// Defaults to false. + ///
+ /// MongoDB will add the _id field to the replacement document if it is not specified in either the filter or + /// replacement documents. If _id is present in both, the values must be equal. + /// + /// + /// A boolean controlling whether to return the replacement document. If set to false the original document + /// before the update is returned. Defaults to false. + /// + /// + /// A representing the remote find one operation. The result of the task is the first document that matches the find criteria. + /// + /// + public async Task FindOneAndReplaceAsync(TDocument replacementDoc, object filter = null, object sort = null, object projection = null, bool upsert = false, bool returnNewDocument = false) + { + Argument.NotNull(replacementDoc, nameof(replacementDoc)); + + var result = await _handle.FindOneAndReplace(filter?.ToNativeJson(), replacementDoc.ToNativeJson(), FindAndModifyOptions.FindAndModify(projection, sort, upsert, returnNewDocument)); + return result.GetValue(); + } + + /// + /// Finds the first document in the collection that satisfies the query criteria. + /// + /// + /// A document describing the find criteria using query operators. + /// If not specified, all documents in the collection will match the request. + /// + /// A document describing the sort criteria. If not specified, the order of the returned documents is not guaranteed. + /// + /// A document describing the fields to return for all matching documents. If not specified, all fields are returned. + /// + /// + /// A representing the remote find one operation. The result of the task is the first document that matches the find criteria. + /// + /// + public async Task FindOneAndDeleteAsync(object filter = null, object sort = null, object projection = null) + { + var result = await _handle.FindOneAndDelete(filter?.ToNativeJson(), FindAndModifyOptions.FindAndModify(projection, sort)); + return result.GetValue(); + } + + /// + /// Executes an aggregation pipeline on the collection and returns the results as a array. + /// + /// The managed type that matches the shape of the result of the pipeline. + /// + /// Documents describing the different pipeline stages using pipeline expressions. + /// + /// + /// A representing the remote aggregate operation. The result of the task is an array containing the documents returned + /// by executing the aggregation . + /// + /// + public async Task AggregateAsync(params object[] pipeline) + { + var result = await _handle.Aggregate(pipeline.ToNativeJson()); + return result.GetValue(); + } + + /// + /// Executes an aggregation pipeline on the collection and returns the results as a array. + /// + /// + /// Documents describing the different pipeline stages using pipeline expressions. + /// + /// + /// A representing the remote aggregate operation. The result of the task is an array containing the documents returned + /// by executing the aggregation . + /// + /// + public Task AggregateAsync(params object[] pipeline) => AggregateAsync(pipeline); + + /// + /// Counts the number of documents in the collection that match the provided . + /// + /// + /// A document describing the find criteria using query operators. + /// If not specified, all documents in the collection will be counted. + /// + /// The maximum number of documents to count. If not specified all documents in the collection are counted. + /// + /// A representing the remote count operation. The result of the task is the number of documents that match the + /// and criteria. + /// + public async Task CountAsync(object filter = null, long? limit = null) + { + var result = await _handle.Count(filter?.ToNativeJson(), limit); + return result.GetValue(); + } + } + + /// + /// The result of or operation. + /// + public class UpdateResult + { + /// + /// Gets the number of documents matched by the filter. + /// + /// The number of matched documents. + [BsonElement("matchedCount")] + public int MatchedCount { get; private set; } + + /// + /// Gets the number of documents modified by the operation. + /// + /// The number of modified documents. + [BsonElement("modifiedCount")] + public int ModifiedCount { get; private set; } + + /// + /// Gets the _id of the inserted document if the operation resulted in an insertion. + /// + /// The _id of the inserted document or null if the operation didn't result in an insertion. + [BsonElement("upsertedId")] + public object UpsertedId { get; private set; } + } + + /// + /// The result of operation. + /// + public class InsertResult + { + /// + /// Gets the _id of the inserted document. + /// + /// The _id of the inserted document. + [BsonElement("insertedId")] + public object InsertedId { get; private set; } + } + + /// + /// The result of operation. + /// + public class InsertManyResult + { + /// + /// Gets an array containing the _ids of the inserted documents. + /// + /// The _ids of the inserted documents. + [BsonElement("insertedIds")] + public object[] InsertedIds { get; private set; } + } + + /// + /// The result of or operation. + /// + public class DeleteResult + { + /// + /// Gets the number of deleted documents. + /// + /// The number of deleted documents. + [BsonElement("deletedCount")] + public int DeletedCount { get; private set; } + } + } +} diff --git a/Realm/Realm/Sync/ObjectLevelPermissions/ClassPermission.cs b/Realm/Realm/Sync/ObjectLevelPermissions/ClassPermission.cs deleted file mode 100644 index 8026b0fc7c..0000000000 --- a/Realm/Realm/Sync/ObjectLevelPermissions/ClassPermission.cs +++ /dev/null @@ -1,95 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Generic; -using System.Reflection; -using Realms.Helpers; - -namespace Realms.Sync -{ - /// - /// An object which describes class-wide permissions. - /// - /// - /// An instance of this object is automatically created in the Realm for class in your schema, - /// and should not be created manually. Call or - /// to obtain the existing instance, or query - /// as normal. - /// - [MapTo("__Class")] - [Explicit] - public class ClassPermission : RealmObject - { - /// - /// Gets the name of the class which these permissions apply to. - /// - [MapTo("name")] - [PrimaryKey] - [Required] - public string Name { get; private set; } - - /// - /// Gets the permissions for this class. - /// - [MapTo("permissions")] - public IList Permissions { get; } - - /// - /// Retrieves the for the given - /// subclass. This will return null for Realms in full synchronization mode. - /// - /// - /// The subclass whose corresponding - /// will be obtained. - /// - /// The Realm instance. - /// - /// A ClassPermission instance that allows you to manipulate the permissions - /// for this class. - /// - public static ClassPermission Get(Realm realm) - where T : RealmObject - { - return Get(realm, typeof(T).GetTypeInfo().GetMappedOrOriginalName()); - } - - /// - /// Retrieves the for the given class name. - /// This will return null for Realms in full synchronization mode. - /// - /// The Realm instance. - /// - /// The name of a subclass whose corresponding - /// will be obtained. - /// - /// - /// A ClassPermission instance that allows you to manipulate the permissions - /// for this class. - /// - public static ClassPermission Get(Realm realm, string className) - { - Argument.NotNull(realm, nameof(realm)); - - return realm.Find(className); - } - - private ClassPermission() - { - } - } -} diff --git a/Realm/Realm/Sync/ObjectLevelPermissions/ClassPrivileges.cs b/Realm/Realm/Sync/ObjectLevelPermissions/ClassPrivileges.cs deleted file mode 100644 index 7aa9210024..0000000000 --- a/Realm/Realm/Sync/ObjectLevelPermissions/ClassPrivileges.cs +++ /dev/null @@ -1,91 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; - -namespace Realms.Sync -{ - /// - /// A description of the actual privileges which apply to a Class within a Realm. - /// - /// - /// This is a combination of all of the privileges granted to all of the Roles which the - /// current User is a member of, obtained by calling - /// or . - /// - [Flags] - public enum ClassPrivileges : byte - { - /// - /// If this flag is not present, the current User is not permitted to see objects of this type, - /// and attempting to query this class will always return empty results. - /// - /// - /// Note that Read permissions are transitive, and so it may be possible to read an - /// object which the user does not directly have Read permissions for by following a - /// link to it from an object they do have Read permissions for. This does not apply - /// to any of the other permission types. - /// - Read = 1 << 0, - - /// - /// If this flag is not present, no modifications to objects of this type are permitted. Write - /// transactions modifying the objects can be performed locally, but any changes made - /// will be reverted by the server. - /// - /// - /// Deleting an object is considered a modification, and is governed by this privilege. - /// - Update = 1 << 1, - - /// - /// If this flag is not present, no modifications to the permissions property of the - /// object for this type are permitted. Write transactions can be performed locally, - /// but any changes made will be reverted by the server. - /// - /// - /// Note that if invalid privilege changes are made, - /// will return results reflecting those invalid changes until synchronization occurs. - /// - /// Even if this flag is present, note that the user will be unable to grant more - /// privileges to a Role than what they have themselves, e.g. they won't be able to grant - /// if they haven't been granted first. - /// - SetPermissions = 1 << 3, - - /// - /// If this flag is not present, the User is not permitted to create new subscriptions for this class. - /// Local queries against the objects within the Realm will work, but new - /// subscriptions will never add objects to the Realm. - /// - Subscribe = 1 << 4, - - /// - /// If this flag is not present, creating new objects of this type is not permitted. Write transactions - /// creating objects can be performed locally, but the objects will be deleted by the - /// server when synchronization occurs. - /// - /// - /// For objects with Primary Keys, it may not be locally determinable if or - /// privileges are applicable. It may appear that you are creating a new object, - /// but an object with that Primary Key may already exist and simply not be visible to - /// you, in which case it is actually an Update operation. - /// - Create = 1 << 5 - } -} diff --git a/Realm/Realm/Sync/ObjectLevelPermissions/NamedSubscription.cs b/Realm/Realm/Sync/ObjectLevelPermissions/NamedSubscription.cs deleted file mode 100644 index ca13c6554c..0000000000 --- a/Realm/Realm/Sync/ObjectLevelPermissions/NamedSubscription.cs +++ /dev/null @@ -1,125 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Linq; -using System.Text.RegularExpressions; -using Realms.Exceptions; - -namespace Realms.Sync -{ - /// - /// A managed Realm object representing a subscription. Subscriptions are used by Query-based Realms to define which - /// data should be available on the device. It is the persisted version of a created by - /// calling . - /// - [MapTo("__ResultSets")] - [Explicit] - public class NamedSubscription : RealmObject - { - private static readonly Regex _matchesRegex = new Regex("^(class_)?(?.*?)(_matches)?$", RegexOptions.Compiled); - - /// - /// Gets the name of the subscription. If no name was provided in , then - /// an automatic name will have been generated based on the query. - /// - /// The subscription name. - [MapTo("name")] - [Indexed] - [Required] - public string Name { get; private set; } - - /// - /// Gets the string representation of the query used to create the subscription. - /// - /// The subscription query. - [MapTo("query")] - [Required] - public string Query { get; private set; } - - /// - /// Gets the point in time when the subscription was created. - /// - /// The creation date of the subscription. - [MapTo("created_at")] - public DateTimeOffset CreatedAt { get; private set; } - - /// - /// Gets the point in time when the subscription was updated. - /// - /// - /// In this context, - /// "updated" means that the subscription was resubscribed to or some property - /// was updated by calling . - /// The field is NOT updated whenever the results of the query changes. - /// - /// The last updated date of the subscription. - [MapTo("updated_at")] - public DateTimeOffset UpdatedAt { get; private set; } - - /// - /// Gets the point in time when the subscription will expire and become eligible for removal. - /// - /// - /// Realm will automatically remove expired subscriptions at opportunistic times. There - /// are no guarantees as to when the subscription will be removed. - /// - /// The expiration date of the subscription. - [MapTo("expires_at")] - public DateTimeOffset? ExpiresAt { get; private set; } - - [MapTo("time_to_live")] - private long? TimeToLiveMs { get; set; } - - /// - /// Gets the time to live of the subscription. - /// - /// The subscription's time to live. - public TimeSpan? TimeToLive => TimeToLiveMs.HasValue ? TimeSpan.FromMilliseconds(TimeToLiveMs.Value) : (TimeSpan?)null; - - [MapTo("status")] - private int StateInt { get; set; } - - /// - /// Gets a value indicating the state of this subscription. - /// - /// The state of the subscription. - public SubscriptionState State => (SubscriptionState)StateInt; - - [MapTo("error_message")] - [Required] - private string ErrorMessage { get; set; } - - /// - /// Gets a value indicating what error (if any) has occurred while processing the subscription. - /// If the is not , this will be null. - /// - /// An instance of if an error has occurred; null otherwise. - public Exception Error => string.IsNullOrEmpty(ErrorMessage) ? null : new RealmException(ErrorMessage); - - [MapTo("matches_property")] - [Required] - private string Matches { get; set; } - - /// - /// Gets the type of the object that this subscription is applied to. - /// - /// The type of the object that the subscription matches. - public string ObjectType => _matchesRegex.Match(Matches).Groups["objectType"].Value; - } -} diff --git a/Realm/Realm/Sync/ObjectLevelPermissions/ObjectPrivileges.cs b/Realm/Realm/Sync/ObjectLevelPermissions/ObjectPrivileges.cs deleted file mode 100644 index 798e6861b7..0000000000 --- a/Realm/Realm/Sync/ObjectLevelPermissions/ObjectPrivileges.cs +++ /dev/null @@ -1,81 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; - -namespace Realms.Sync -{ - /// - /// A description of the actual privileges which apply to a specific Object. - /// - /// - /// This is a combination of all of the privileges granted to all of the Roles which the - /// current User is a member of, obtained by calling . - /// - [Flags] - public enum ObjectPrivileges : byte - { - /// - /// If this flag is not present, the current User is not permitted to read this object directly. - /// - /// - /// Note that Read permissions are transitive, and so it may be possible to read an - /// object which the user does not directly have Read permissions for by following a - /// link to it from an object they do have Read permissions for. This does not apply - /// to any of the other permission types. - /// - Read = 1 << 0, - - /// - /// If this flag is not present, modifying the fields of this type is not permitted. Write - /// transactions modifying the objects can be performed locally, but any changes made - /// will be reverted by the server. - /// - /// - /// Note that even if the user has permission, they may not be able to - /// modify the IList<Permission> property of the object (if it exists), as that is - /// governed by . - /// - Update = 1 << 1, - - /// - /// If this flag is not present, deleting this object is not permitted. Write transactions which - /// delete the object can be performed locally, but the server will restore it. - /// - /// - /// It is possible to have but not privileges, or vice - /// versa. For objects with primary keys, but not is ill-advised - /// as an object can be updated by deleting and recreating it. - /// - Delete = 1 << 2, - - /// - /// If this flag is not present, modifying the privileges of this specific object is not permitted. - /// - /// - /// Object-specific permissions are set by declaring an IList<Permission> - /// property on the subclass. Modifications to this property are - /// controlled by rather than . - /// - /// Even if this flag is present, note that the user will be unable to grant more - /// privileges to a Role than what they have themselves, e.g. they won't be able to grant - /// if they haven't been granted first. - /// - SetPermissions = 1 << 3 - } -} diff --git a/Realm/Realm/Sync/ObjectLevelPermissions/Permission.cs b/Realm/Realm/Sync/ObjectLevelPermissions/Permission.cs deleted file mode 100644 index 3b56fdd640..0000000000 --- a/Realm/Realm/Sync/ObjectLevelPermissions/Permission.cs +++ /dev/null @@ -1,456 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Realms.Helpers; -using Realms.Schema; - -namespace Realms.Sync -{ - /// - /// A permission which can be applied to a Realm, Class, or specific Object. - /// - /// - /// Permissions are applied by adding the permission to the singleton - /// object, the object for the desired class, or to a user-defined - /// IList<Permission> property on a specific Object instance. The meaning of each of - /// the properties of depend on what the permission is applied to, and so are - /// left undocumented here. See , , and - /// for details about what each of the properties mean when applied to - /// that type. - /// - [MapTo("__Permission")] - [Explicit] - public class Permission : RealmObject - { - /// - /// Gets the Role which this Permission applies to. All users within the Role are - /// granted the permissions specified by the fields below any - /// objects/classes/realms which use this . - /// - [MapTo("role")] - public PermissionRole Role { get; private set; } - - /// - /// Gets or sets a value indicating whether the user can read the object to which this - /// is attached. - /// - [MapTo("canRead")] - public bool CanRead { get; set; } - - /// - /// Gets or sets a value indicating whether the user can modify the object to which this is attached. - /// - [MapTo("canUpdate")] - public bool CanUpdate { get; set; } - - /// - /// Gets or sets a value indicating whether the user can delete the object to which this is attached. - /// - /// - /// This field is only applicable to Permissions attached to Objects, and not to Realms or Classes. - /// - [MapTo("canDelete")] - public bool CanDelete { get; set; } - - /// - /// Gets or sets a value indicating whether the user can add or modify Permissions for the object which this - /// is attached to. - /// - [MapTo("canSetPermissions")] - public bool CanSetPermissions { get; set; } - - /// - /// Gets or sets a value indicating whether the user can subscribe to queries for this object type. - /// - /// - /// This field is only applicable to Permissions attached to Classes, and not to Realms or Objects. - /// - [MapTo("canQuery")] - public bool CanQuery { get; set; } - - /// - /// Gets or sets a value indicating whether the user can create new objects of the type this is attached to. - /// - /// - /// This field is only applicable to Permissions attached to Classes, and not to Realms or Objects. - /// - [MapTo("canCreate")] - public bool CanCreate { get; set; } - - /// - /// Gets or sets a value indicating whether the user can modify the schema of the Realm which this - /// is attached to. - /// - /// - /// This field is only applicable to Permissions attached to Realms, and not to Realms or Objects. - /// - [MapTo("canModifySchema")] - public bool CanModifySchema { get; set; } - - /// - /// Gets or creates a instance for the named role - /// on the Realm. - /// - /// - /// This function should be used in preference to manually querying for the - /// applicable Permission as it ensures that there is exactly one Permission for - /// the given Role on the Realm, merging duplicates or creating and adding new ones - /// as needed. - /// - /// The associated with that Permission. - /// The Realm whose permissions this instance manipulates. - /// - /// A instance that can be used to inspect or modify the Realm - /// permissions of that . - /// - public static Permission Get(PermissionRole role, Realm realm) - { - return Get(role, RealmPermission.Get(realm).Permissions); - } - - /// - /// Gets or creates a instance for the named role - /// on the class. - /// - /// - /// This function should be used in preference to manually querying for the - /// applicable Permission as it ensures that there is exactly one Permission for - /// the given Role on the class, merging duplicates or creating and adding new ones - /// as needed. - /// - /// - /// The subclass whose corresponding class permissions this - /// instance manipulates. - /// - /// The associated with that Permission. - /// The Realm whose class permissions this instance manipulates. - /// - /// A instance that can be used to inspect or modify the class - /// permissions of that . - /// - public static Permission Get(PermissionRole role, Realm realm) - where T : RealmObject - { - return Get(role, ClassPermission.Get(realm).Permissions); - } - - /// - /// Gets or creates a instance for the named role - /// on the class. - /// - /// - /// This function should be used in preference to manually querying for the - /// applicable Permission as it ensures that there is exactly one Permission for - /// the given Role on the class, merging duplicates or creating and adding new ones - /// as needed. - /// - /// The associated with that Permission. - /// - /// The name of the subclass whose corresponding class permissions this - /// instance manipulates. - /// - /// The Realm whose class permissions this instance manipulates. - /// - /// A instance that can be used to inspect or modify the class - /// permissions of that . - /// - public static Permission Get(PermissionRole role, string className, Realm realm) - { - return Get(role, ClassPermission.Get(realm, className).Permissions); - } - - /// - /// Gets or creates a instance for the named role - /// on the object. - /// - /// - /// This function should be used in preference to manually querying for the - /// applicable Permission as it ensures that there is exactly one Permission for - /// the given Role on the object, merging duplicates or creating and adding new ones - /// as needed. - /// - /// The given object must have a IList<Permission> property defined on it. - /// If more than one such property exists, the first one will be used. - /// - /// The associated with that Permission. - /// - /// The inheritor whose permissions this - /// instance manipulates. - /// - /// - /// A instance that can be used to inspect or modify the object - /// permissions of that . - /// - public static Permission Get(PermissionRole role, RealmObject obj) - { - Argument.NotNull(obj, nameof(obj)); - - var permissionType = typeof(Permission).GetTypeInfo().GetMappedOrOriginalName(); - var prop = obj.ObjectSchema.FirstOrDefault(o => o.Type == (PropertyType.Array | PropertyType.Object) && o.ObjectType == permissionType); - if (prop.Name == null) - { - throw new ArgumentException("The given object doesn't have an IList property.", nameof(obj)); - } - - var permissions = obj.GetListValue(prop.Name); - return Get(role, permissions); - } - - /// - /// Gets or creates a instance for the named role - /// on the Realm. - /// - /// - /// This function should be used in preference to manually querying for the - /// applicable Permission as it ensures that there is exactly one Permission for - /// the given Role on the Realm, merging duplicates or creating and adding new ones - /// as needed. - /// - /// - /// The name of the associated with that Permission. If no such - /// Role exists, it will be created automatically. - /// - /// The Realm whose permissions this instance manipulates. - /// - /// A instance that can be used to inspect or modify the Realm - /// permissions of that . - /// - public static Permission Get(string roleName, Realm realm) - { - return Get(PermissionRole.Get(realm, roleName), realm); - } - - /// - /// Gets or creates a instance for the named role - /// on the class. - /// - /// - /// This function should be used in preference to manually querying for the - /// applicable Permission as it ensures that there is exactly one Permission for - /// the given Role on the class, merging duplicates or creating and adding new ones - /// as needed. - /// - /// - /// The subclass whose corresponding class permissions this - /// instance manipulates. - /// - /// - /// The name of the associated with that Permission. If no such - /// Role exists, it will be created automatically. - /// - /// The Realm whose class permissions this instance manipulates. - /// - /// A instance that can be used to inspect or modify the class - /// permissions of that . - /// - public static Permission Get(string roleName, Realm realm) - where T : RealmObject - { - return Get(PermissionRole.Get(realm, roleName), ClassPermission.Get(realm).Permissions); - } - - /// - /// Gets or creates a instance for the named role - /// on the class. - /// - /// - /// This function should be used in preference to manually querying for the - /// applicable Permission as it ensures that there is exactly one Permission for - /// the given Role on the class, merging duplicates or creating and adding new ones - /// as needed. - /// - /// - /// The name of the associated with that Permission. If no such - /// Role exists, it will be created automatically. - /// - /// - /// The name of the subclass whose corresponding class permissions this - /// instance manipulates. - /// - /// The Realm whose class permissions this instance manipulates. - /// - /// A instance that can be used to inspect or modify the class - /// permissions of that . - /// - public static Permission Get(string roleName, string className, Realm realm) - { - return Get(PermissionRole.Get(realm, roleName), ClassPermission.Get(realm, className).Permissions); - } - - /// - /// Gets or creates a instance for the named role - /// on the object. - /// - /// - /// This function should be used in preference to manually querying for the - /// applicable Permission as it ensures that there is exactly one Permission for - /// the given Role on the object, merging duplicates or creating and adding new ones - /// as needed. - /// - /// The given object must have a IList<Permission> property defined on it. - /// If more than one such property exists, the first one will be used. - /// - /// - /// The name of the associated with that Permission. If no such - /// Role exists, it will be created automatically. - /// - /// - /// The inheritor whose permissions this - /// instance manipulates. - /// - /// - /// A instance that can be used to inspect or modify the object - /// permissions of that . - /// - public static Permission Get(string roleName, RealmObject obj) - { - Argument.NotNull(obj, nameof(obj)); - return Get(PermissionRole.Get(obj.Realm, roleName), obj); - } - - /// - /// Gets or creates a instance for the named role - /// on the collection. - /// - /// - /// This function should be used in preference to manually querying for the - /// applicable Permission as it ensures that there is exactly one Permission for - /// the given Role on the object, merging duplicates or creating and adding new ones - /// as needed. - /// - /// - /// The name of the associated with that Permission. If no such - /// Role exists, it will be created automatically. - /// - /// - /// The collection of permissions to which the new instance will be added. - /// - /// - /// A instance that can be used to inspect or modify the object - /// permissions of that . - /// - public static Permission Get(string roleName, IList permissions) - { -#if PCL - RealmPCLHelpers.ThrowProxyShouldNeverBeUsed(); - return null; -#else - if (!(permissions is RealmList realmPermissions)) - { - throw new ArgumentException($"{nameof(Get)} may only be called on managed lists.", nameof(permissions)); - } - - var role = PermissionRole.Get(realmPermissions.Realm, roleName); - return Get(role, permissions); -#endif - } - - /// - /// Gets or creates a instance for the named role - /// on the collection. - /// - /// - /// This function should be used in preference to manually querying for the - /// applicable Permission as it ensures that there is exactly one Permission for - /// the given Role on the object, merging duplicates or creating and adding new ones - /// as needed. - /// - /// The associated with that Permission. - /// - /// The collection of permissions to which the new instance will be added. - /// - /// - /// A instance that can be used to inspect or modify the object - /// permissions of that . - /// - public static Permission Get(PermissionRole role, IList permissions) - { -#if PCL - RealmPCLHelpers.ThrowProxyShouldNeverBeUsed(); - return null; -#else - if (!(permissions is RealmList realmList)) - { - throw new ArgumentException("Permission.Get may only be called on managed objects."); - } - - if (!realmList.Realm.IsInTransaction) - { - throw new ArgumentException("Permissions may only be obtained or created in a write transaction."); - } - - Permission result = null; - var filtered = realmList.Where(p => p.Role.Equals(role)).ToArray(); - foreach (var permission in filtered) - { - if (result == null) - { - result = permission; - } - else - { - foreach (var propertyToMerge in _propertiesToMerge) - { - MergePermission(result, permission, propertyToMerge); - } - - realmList.Remove(permission); - } - } - - if (result == null) - { - result = realmList.Realm.Add(new Permission - { - Role = role - }); - realmList.Add(result); - } - - return result; -#endif - } - - private Permission() - { - } - - private static string[] _propertiesToMerge = new[] - { - nameof(CanRead), - nameof(CanUpdate), - nameof(CanDelete), - nameof(CanSetPermissions), - nameof(CanQuery), - nameof(CanCreate), - nameof(CanModifySchema), - }; - - private static void MergePermission(Permission target, Permission source, string propertyName) - { - if (!target.GetBooleanValue(propertyName) && source.GetBooleanValue(propertyName)) - { - target.SetBooleanValue(propertyName, true); - } - } - } -} diff --git a/Realm/Realm/Sync/ObjectLevelPermissions/PermissionExtensions.cs b/Realm/Realm/Sync/ObjectLevelPermissions/PermissionExtensions.cs deleted file mode 100644 index 98956bcec8..0000000000 --- a/Realm/Realm/Sync/ObjectLevelPermissions/PermissionExtensions.cs +++ /dev/null @@ -1,159 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Reflection; -using Realms.Helpers; - -namespace Realms.Sync -{ - /// - /// A set of extension methods that simplify working with object level permissions. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public static class PermissionExtensions - { - /// - /// Returns the computed privileges which the current user has for this Realm. - /// - /// This combines all privileges granted on the Realm by all Roles which the - /// current User is a member of into the final privileges which will be - /// enforced by the server. - /// - /// The privilege calculation is done locally using cached data, and inherently - /// may be stale.It is possible that this method may indicate that an - /// operation is permitted but the server will still reject it if permission is - /// revoked before the changes have been integrated on the server. - /// - /// Non-synchronized Realms always have permission to perform all operations. - /// - /// The Realm whose privileges are inspected. - /// The privileges which the current user has for the current Realm. - public static RealmPrivileges GetPrivileges(this Realm realm) - { - Argument.NotNull(realm, nameof(realm)); - return realm.SharedRealmHandle.GetPrivileges(); - } - - /// - /// Returns the computed privileges which the current user has for the given class. - /// - /// This combines all privileges granted on the class by all Roles which the - /// current User is a member of into the final privileges which will be - /// enforced by the server. - /// - /// The privilege calculation is done locally using cached data, and inherently - /// may be stale. It is possible that this method may indicate that an - /// operation is permitted but the server will still reject it if permission is - /// revoked before the changes have been integrated on the server. - /// - /// Non-synchronized Realms always have permission to perform all operations. - /// - /// The inheritor to get the privileges for. - /// The Realm whose privileges are inspected. - /// The privileges which the current user has for the given class. - public static ClassPrivileges GetPrivileges(this Realm realm) - where T : RealmObject - { - return realm.GetPrivileges(typeof(T).GetTypeInfo().GetMappedOrOriginalName()); - } - - /// - /// Returns the computed privileges which the current user has for the given class. - /// - /// This combines all privileges granted on the class by all Roles which the - /// current User is a member of into the final privileges which will be - /// enforced by the server. - /// - /// The privilege calculation is done locally using cached data, and inherently - /// may be stale. It is possible that this method may indicate that an - /// operation is permitted but the server will still reject it if permission is - /// revoked before the changes have been integrated on the server. - /// - /// Non-synchronized Realms always have permission to perform all operations. - /// - /// The Realm whose privileges are inspected. - /// The name of a inheritor to get the privileges for. - /// The privileges which the current user has for the given class. - public static ClassPrivileges GetPrivileges(this Realm realm, string className) - { - Argument.NotNull(realm, nameof(realm)); - Argument.NotNull(className, nameof(className)); - - return realm.SharedRealmHandle.GetPrivileges(className); - } - - /// - /// Returns the computed privileges which the current user has for the given object. - /// - /// - /// This combines all privileges granted on the object by all Roles which the - /// current User is a member of into the final privileges which will be - /// enforced by the server. - /// - /// The privilege calculation is done locally using cached data, and inherently - /// may be stale. It is possible that this method may indicate that an - /// operation is permitted but the server will still reject it if permission is - /// revoked before the changes have been integrated on the server. - /// - /// Non-synchronized Realms always have permission to perform all operations. - /// - /// The object must be a valid object managed by this Realm. Passing in an - /// invalidated object, an unmanaged object, or an object managed by a - /// different Realm will throw an exception. - /// - /// The Realm whose privileges are inspected. - /// A managed object to get the privileges for. - /// The privileges which the current user has for the given object. - public static ObjectPrivileges GetPrivileges(this Realm realm, RealmObject obj) - { - Argument.NotNull(realm, nameof(realm)); - Argument.NotNull(obj, nameof(obj)); - - return realm.SharedRealmHandle.GetPrivileges(obj.ObjectHandle); - } - - /// - /// A convenience method that converts a to - /// and adds it to the list of users - /// if necessary. If a with that identity - /// already belongs to the collection, this method will be a no-op. - /// - /// - /// The collection of users to which user will be added. - /// - /// The user to add. - public static void Add(this IList users, User user) - { - Argument.NotNull(user, nameof(user)); - - if (!(users is RealmList realmUsers)) - { - throw new ArgumentException($"{nameof(Add)} may only be called on managed lists.", nameof(users)); - } - - var permissionUser = PermissionUser.Get(realmUsers.Realm, user.Identity); - if (!realmUsers.Contains(permissionUser)) - { - realmUsers.Add(permissionUser); - } - } - } -} diff --git a/Realm/Realm/Sync/ObjectLevelPermissions/PermissionRole.cs b/Realm/Realm/Sync/ObjectLevelPermissions/PermissionRole.cs deleted file mode 100644 index 249f4889ee..0000000000 --- a/Realm/Realm/Sync/ObjectLevelPermissions/PermissionRole.cs +++ /dev/null @@ -1,82 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Generic; -using Realms.Helpers; - -namespace Realms.Sync -{ - /// - /// A Role within the permissions system. - /// - /// - /// A Role consists of a name for the role and a list of users which are members of the role. - /// Roles are granted privileges on Realms, Classes and Objects, and in turn grant those - /// privileges to all users which are members of the role. - /// - /// A role named "everyone" is automatically created in new Realms, and all new users which - /// connect to the Realm are automatically added to it. Any other roles you wish to use are - /// managed as normal Realm objects. - /// - [MapTo("__Role")] - [Explicit] - public class PermissionRole : RealmObject - { - /// - /// Gets the name of the Role. - /// - [MapTo("name")] - [PrimaryKey] - [Required] - public string Name { get; private set; } - - /// - /// Gets the users which belong to the role. - /// - [MapTo("members")] - public IList Users { get; } - - private PermissionRole() - { - } - - /// - /// Gets or creates a in the provided Realm. - /// - /// The Realm where the Role will be created. - /// The name of the Role. - /// - /// A instance that can be inspected or manipulated. - /// - public static PermissionRole Get(Realm realm, string roleName) - { - Argument.NotNull(realm, nameof(realm)); - - var role = realm.Find(roleName); - if (role == null) - { - role = realm.Add(new PermissionRole - { - Name = roleName - }); - } - - return role; - } - } -} diff --git a/Realm/Realm/Sync/ObjectLevelPermissions/PermissionUser.cs b/Realm/Realm/Sync/ObjectLevelPermissions/PermissionUser.cs deleted file mode 100644 index 80ccbbb1ed..0000000000 --- a/Realm/Realm/Sync/ObjectLevelPermissions/PermissionUser.cs +++ /dev/null @@ -1,84 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Linq; -using Realms.Helpers; - -namespace Realms.Sync -{ - /// - /// A representation of a sync user within the permissions system. - /// - /// - /// objects are created automatically for each sync user which connects - /// to a Realm, and can also be created manually if you wish to grant permissions to a user - /// which has not yet connected to this Realm. - /// - [MapTo("__User")] - [Explicit] - public class PermissionUser : RealmObject - { - /// - /// Gets the unique Realm Object Server user ID string identifying this user. This will have - /// the same value as . - /// - [MapTo("id")] - [PrimaryKey] - [Required] - public string Identity { get; private set; } - - /// - /// Gets the user's private role. This will be initialized to a role named for the user's - /// identity that contains this user as its only member. - /// - [MapTo("role")] - public PermissionRole Role { get; private set; } - - /// - /// Gets the Roles which this user belongs to. - /// - [MapTo("roles")] - [Backlink(nameof(PermissionRole.Users))] - public IQueryable Roles { get; } - - /// - /// Gets or creates a with the specified identity. - /// - /// The Realm instance. - /// The Realm Object Server user ID. - /// - /// A instance that can be added to one or more s. - /// - public static PermissionUser Get(Realm realm, string identity) - { - Argument.NotNull(realm, nameof(realm)); - - var user = realm.Find(identity); - if (user != null) - { - return user; - } - - return realm.Add(new PermissionUser { Identity = identity }); - } - - private PermissionUser() - { - } - } -} diff --git a/Realm/Realm/Sync/ObjectLevelPermissions/RealmPermission.cs b/Realm/Realm/Sync/ObjectLevelPermissions/RealmPermission.cs deleted file mode 100644 index 137295d8a9..0000000000 --- a/Realm/Realm/Sync/ObjectLevelPermissions/RealmPermission.cs +++ /dev/null @@ -1,68 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Realms.Helpers; - -namespace Realms.Sync -{ - /// - /// A singleton object which describes Realm-wide permissions. - /// - /// - /// An object of this type is automatically created in the Realm for you, and more objects - /// cannot be created manually. Call to obtain the - /// instance for a specific Realm. - /// - [MapTo("__Realm")] - [Explicit] - public class RealmPermission : RealmObject - { - [MapTo("id")] - [PrimaryKey] - [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Having the correct schema is required for offline access.")] - private int Id { get; set; } - - /// - /// Gets the permissions for the Realm. - /// - [MapTo("permissions")] - public IList Permissions { get; } - - /// - /// Retrieve the singleton object for the given Realm. This will return - /// null for Realms in full synchronization mode. - /// - /// The Realm instance. - /// - /// A RealmPermission instance that allows you to manipulate the permissions - /// for this Realm. - /// - public static RealmPermission Get(Realm realm) - { - Argument.NotNull(realm, nameof(realm)); - - return realm.Find(0); - } - - private RealmPermission() - { - } - } -} diff --git a/Realm/Realm/Sync/ObjectLevelPermissions/RealmPrivileges.cs b/Realm/Realm/Sync/ObjectLevelPermissions/RealmPrivileges.cs deleted file mode 100644 index a164c662d6..0000000000 --- a/Realm/Realm/Sync/ObjectLevelPermissions/RealmPrivileges.cs +++ /dev/null @@ -1,77 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; - -namespace Realms.Sync -{ - /// - /// A description of the actual privileges which apply to a Realm. - /// - /// - /// This is a combination of all of the privileges granted to all of the Roles which the - /// current User is a member of, obtained by calling on - /// the Realm. - /// - [Flags] - public enum RealmPrivileges : byte - { - /// - /// If this flag is not present, the current User is not permitted to see the Realm at all. This can - /// happen only if the Realm was created locally and has not yet been synchronized. - /// - Read = 1 << 0, - - /// - /// If this flag is not present, no modifications to the Realm are permitted. Write transactions can - /// be performed locally, but any changes made will be reverted by the server. - /// and will always be denied when this is denied. - /// - Update = 1 << 1, - - /// - /// If this flag is not present, no modifications to the permissions property of the - /// object for are permitted. Write transactions can be performed locally, but any - /// changes made will be reverted by the server. - /// - /// - /// Note that if invalid privilege changes are made, - /// will return results reflecting those invalid changes until synchronization occurs. - /// - /// Even if this flag is present, note that the user will be unable to grant more - /// privileges to a Role than what they have themselves, e.g. they won't be able to grant - /// if they haven't been granted first. - /// - /// Adding or removing Users from a Role is controlled by privileges on that - /// Role, and not by this value. - /// - SetPermissions = 1 << 3, - - /// - /// If this flag is not present, the user is not permitted to add new object types to the Realm or add - /// new properties to existing object types. - /// - /// - /// Defining new subclasses (and not - /// excluding them from the schema with will result - /// in the application crashing if the object types are not first added on the server by a more privileged - /// user. - /// - ModifySchema = 1 << 6 - } -} diff --git a/Realm/Realm/Sync/Permissions/AccessLevel.cs b/Realm/Realm/Sync/Permissions/AccessLevel.cs deleted file mode 100644 index 098c44e893..0000000000 --- a/Realm/Realm/Sync/Permissions/AccessLevel.cs +++ /dev/null @@ -1,55 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - /// - /// Access levels which can be granted to Realm Platform users for specific synchronized Realms, using the permissions APIs. - ///
- /// Note that each access level guarantees all allowed actions provided by less permissive access levels. - /// Specifically, users with write access to a Realm can always read from that Realm, and users with administrative - /// access can always read or write from the Realm. - ///
- public enum AccessLevel - { - /// - /// No access whatsoever. - /// - None, - - /// - /// User can only read the contents of the Realm. - /// - /// - /// Users who have read-only access to a Realm should open the Realm using - /// Attempting to directly open the Realm is - /// an error; in this case the Realm must be deleted and re-opened. - /// - Read, - - /// - /// User can read and write the contents of the Realm. - /// - Write, - - /// - /// User can read, write, and administer the Realm, including granting permissions to other users. - /// - Admin - } -} diff --git a/Realm/Realm/Sync/Permissions/PathPermission.cs b/Realm/Realm/Sync/Permissions/PathPermission.cs deleted file mode 100644 index 6755b646fe..0000000000 --- a/Realm/Realm/Sync/Permissions/PathPermission.cs +++ /dev/null @@ -1,94 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using Newtonsoft.Json; - -namespace Realms.Sync -{ - /// - /// This model is used to reflect permissions granted to a user. - /// It should be used in conjunction with a 's Permission Realm. - /// - public class PathPermission - { - /// - /// Gets when the object was updated the last time. - /// - /// A indicating the last time the object has been updated. - [MapTo("updatedAt")] - public DateTimeOffset UpdatedAt { get; private set; } - - /// - /// Gets the identity of the user affected by this permission. - /// - /// The user identity. - [JsonProperty("userId")] - public string UserId { get; private set; } - - /// - /// Gets the relative path to the Realm on the server. - /// - /// A relative path component. - [JsonProperty("path")] - public string Path { get; private set; } - - /// - /// Gets a value indicating the access level of the user with the specified - /// for the Realm at the specified . - /// - /// The . - [JsonProperty("accessLevel")] - public AccessLevel AccessLevel { get; private set; } - - /// - /// Gets the Id of the user who owns the Realm at . - /// - /// A User Id. - [JsonProperty("realmOwnerId")] - public string RealmOwnerId { get; private set; } - - /// - /// Gets a value indicating whether the user inspecting that permission is allowed to read the Realm at the - /// specified . - /// - /// true if reading is allowed, false otherwise. - [Obsolete("Use AccessLevel >= AccessLevel.Read instead")] - public bool MayRead => AccessLevel >= AccessLevel.Read; - - /// - /// Gets a value indicating whether the user inspecting that permission is allowed to write to the Realm at the - /// specified . - /// - /// true if writing is allowed, false otherwise. - [Obsolete("Use AccessLevel >= AccessLevel.Write instead")] - public bool MayWrite => AccessLevel >= AccessLevel.Write; - - /// - /// Gets a value indicating whether the user inspecting that permission is allowed to manage the permissions for - /// the Realm at the specified . - /// - /// true if managing is allowed, false otherwise. - [Obsolete("Use AccessLevel >= AccessLevel.Admin instead")] - public bool MayManage => AccessLevel >= AccessLevel.Admin; - - private PathPermission() - { - } - } -} diff --git a/Realm/Realm/Sync/Permissions/PermissionCondition.cs b/Realm/Realm/Sync/Permissions/PermissionCondition.cs deleted file mode 100644 index ce68a3275f..0000000000 --- a/Realm/Realm/Sync/Permissions/PermissionCondition.cs +++ /dev/null @@ -1,76 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - /// - /// A class describing the condition based on which permissions will be applied. - /// - public abstract class PermissionCondition - { - /// - /// Apply permissions based on the user's Id. - /// - /// A containing information about the user's Id. - /// The Id of the user. - public static PermissionCondition UserId(string userId) - { - return new UserIdCondition(userId); - } - - /// - /// Gets a that describes the default permissions for all users - /// who don't have explicit permissions applied. The granted alongside - /// this condition will also be used as default access level for future new users. - /// - /// - /// The default permissions are not additive with more specific permissions, even if the latter - /// are more restrictive - for example, a user who has been granted - /// access will not be write to a Realm, even if the default permissions grant - /// access. - /// - /// A describing the default permissions. - public static PermissionCondition Default => UserId("*"); - - /// - /// Apply permissions based on the user's Email when using the username/password login provider. - /// - /// A containing information about the user's email. - /// The email (username) of the user that will be affected by this condition. - public static PermissionCondition Email(string email) - { - return KeyValue("email", email); - } - - /// - /// Apply permissions based on a key/value combination in the user's metadata. - /// - /// - /// A containing information about the key/value combination that will be used - /// for matching against. - /// - /// The metadata key to look for. - /// The metadata value that must match the key. - public static PermissionCondition KeyValue(string key, string value) - { - return new KeyValueCondition(key, value); - } - - internal abstract object ToJsonObject(); - } -} \ No newline at end of file diff --git a/Realm/Realm/Sync/Permissions/PermissionOffer.cs b/Realm/Realm/Sync/Permissions/PermissionOffer.cs deleted file mode 100644 index 70c8dbb473..0000000000 --- a/Realm/Realm/Sync/Permissions/PermissionOffer.cs +++ /dev/null @@ -1,86 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using Newtonsoft.Json; -using Realms.Sync.Exceptions; - -namespace Realms.Sync -{ - /// - /// Objects of this class are used to offer permissions to owned Realms. - /// - public class PermissionOffer - { - /// - /// Gets the creation time of this object. - /// - /// A indicating the object's creation date and time. - [JsonProperty("createdAt")] - public DateTimeOffset CreatedAt { get; private set; } = DateTimeOffset.UtcNow; - - /// - /// Gets the token that can be used to offer the permissions defined in this object to another user. - /// - /// A string, set by the server, that can be used in or - /// . - [JsonProperty("token")] - public string Token { get; private set; } - - /// - /// Gets the path of the Realm to offer permissions to. - /// - [JsonProperty("realmPath")] - public string RealmPath { get; private set; } - - /// - /// Gets a value indicating whether the receiver of this offer will be able to read from the Realm. - /// - /// true to allow the receiver to read data from the . - [JsonProperty("accessLevel")] - public AccessLevel AccessLevel { get; private set; } - - /// - /// Gets the expiration date and time of the offer. - /// - /// If null, the offer will never expire. Otherwise, the offer may not be consumed past the expiration date. - [JsonProperty("expiresAt")] - public DateTimeOffset? ExpiresAt { get; private set; } - - /// - /// Gets a value indicating whether the receiver of this offer will be able to read data from the Realm. - /// - /// true to allow the receiver to read date from the . - [Obsolete("Use AccessLevel >= AccessLevel.Read instead")] - public bool MayRead => AccessLevel >= AccessLevel.Read; - - /// - /// Gets a value indicating whether the receiver of this offer will be able to write to the Realm. - /// - /// true to allow the receiver to write data to the . - [Obsolete("Use AccessLevel >= AccessLevel.Write instead")] - public bool MayWrite => AccessLevel >= AccessLevel.Write; - - /// - /// Gets a value indicating whether the receiver of this offer will be able to manage access rights for others. - /// - /// true to allow the receiver to offer others access to the . - [Obsolete("Use AccessLevel >= AccessLevel.Admin instead")] - public bool MayManage => AccessLevel >= AccessLevel.Admin; - } -} diff --git a/Realm/Realm/Sync/Session.cs b/Realm/Realm/Sync/Session.cs index 4bbade0ab9..536683d905 100644 --- a/Realm/Realm/Sync/Session.cs +++ b/Realm/Realm/Sync/Session.cs @@ -23,8 +23,9 @@ namespace Realms.Sync { /// - /// An object encapsulating a Realm Object Server session. Sessions represent the communication between the client (and a local Realm file on disk), and the server (and a remote Realm at a given URL stored on a Realm Object Server). - /// Sessions are always created by the SDK and vended out through various APIs. The lifespans of sessions associated with Realms are managed automatically. + /// An object encapsulating a synchronization session. Sessions represent the communication between the client (and a local Realm file on disk), + /// and the server (and a remote Realm at a given partition served by a MongoDB Realm Server). Sessions are always created by the SDK and vended + /// out through various APIs. The lifespans of sessions associated with Realms are managed automatically. /// public class Session { @@ -33,12 +34,6 @@ public class Session ///
public static event EventHandler Error; - /// - /// Gets the describing the remote Realm which this session connects to and synchronizes changes with. - /// - /// The where the Realm Object Server resides. - public Uri ServerUri => new Uri(Handle.GetServerUri()); - /// /// Gets the session’s current state. /// @@ -46,9 +41,9 @@ public class Session public SessionState State => Handle.GetState(); /// - /// Gets the defined by the that is used to connect to the Realm Object Server. + /// Gets the defined by the that is used to connect to MongoDB Realm. /// - /// The that was used to create the 's . + /// The that was used to create the 's . [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The User instance will own its handle.")] public User User => Handle.TryGetUser(out var userHandle) ? new User(userHandle) : null; @@ -131,7 +126,7 @@ public Task WaitForDownloadAsync() } /// - /// Stops any synchronization with the Realm Object Server until the Realm is re-opened again + /// Stops any synchronization with the server until the Realm is re-opened again /// after fully closing it. ///
/// Synchronization can be re-enabled by calling again. @@ -145,7 +140,7 @@ public void Stop() } /// - /// Attempts to resume the session and enable synchronization with the Realm Object Server. + /// Attempts to resume the session and enable synchronization with the server. /// /// /// All sessions will be active by default and calling this method only makes sense if @@ -163,24 +158,6 @@ internal Session(SessionHandle handle) Handle = handle; } - internal Session(string path) : this(SessionHandle.GetSessionForPath(path)) - { - } - - /// - /// Attempts to reconnect all sessions. - /// - /// - /// By default, the sync engine will attempt to reconnect sessions at incrementing intervals. This method is - /// useful when you are monitoring connectivity yourself, using e.g. - /// Connectivity Plugin or through the - /// native connectivity API and you wish to cancel that delay and try to reconnect immediately. - /// - public static void Reconnect() - { - SharedRealmHandleExtensions.ReconnectSessions(); - } - internal static void RaiseError(Session session, Exception error) { var args = new ErrorEventArgs(error); diff --git a/Realm/Realm/Sync/SessionState.cs b/Realm/Realm/Sync/SessionState.cs index c39628db45..f630f8946a 100644 --- a/Realm/Realm/Sync/SessionState.cs +++ b/Realm/Realm/Sync/SessionState.cs @@ -24,12 +24,12 @@ namespace Realms.Sync public enum SessionState : byte { /// - /// The session is connected to the Realm Object Server and is actively transferring data. + /// The session is connected to the MongoDB Realm server and is actively transferring data. /// Active = 0, /// - /// The session is not currently communicating with the Realm Object Server. + /// The session is not currently communicating with the server. /// Inactive } diff --git a/Realm/Realm/Sync/Permissions/UserIdCondition.cs b/Realm/Realm/Sync/SessionStopPolicy.cs similarity index 69% rename from Realm/Realm/Sync/Permissions/UserIdCondition.cs rename to Realm/Realm/Sync/SessionStopPolicy.cs index f357ba2139..c943fccea4 100644 --- a/Realm/Realm/Sync/Permissions/UserIdCondition.cs +++ b/Realm/Realm/Sync/SessionStopPolicy.cs @@ -18,21 +18,10 @@ namespace Realms.Sync { - internal class UserIdCondition : PermissionCondition + internal enum SessionStopPolicy { - public new string UserId { get; } - - public UserIdCondition(string userId) - { - UserId = userId; - } - - internal override object ToJsonObject() - { - return new - { - userId = UserId - }; - } + Immediately, // Immediately stop the session as soon as all Realms/Sessions go out of scope. + LiveIndefinitely, // Never stop the session. + AfterChangesUploaded, // Once all Realms/Sessions go out of scope, wait for uploads to complete and stop. } } \ No newline at end of file diff --git a/Realm/Realm/Sync/Subscription.cs b/Realm/Realm/Sync/Subscription.cs deleted file mode 100644 index 202feabde7..0000000000 --- a/Realm/Realm/Sync/Subscription.cs +++ /dev/null @@ -1,360 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Linq.Expressions; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Realms.Exceptions; -using Realms.Helpers; -using Realms.Native; - -namespace Realms.Sync -{ - /// - /// A subscription without the generic parameter. - /// - [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "The file contains all subscriptions.")] - internal interface ISubscription - { - /// - /// Reloads the state of the subscription. - /// - void ReloadState(); - } - - /// - /// A set of extension methods exposing query-based sync related functionality over collections. - /// - public static class Subscription - { - internal static readonly SubscriptionHandle.SubscriptionCallbackDelegate SubscriptionCallback; - - static Subscription() - { - SubscriptionCallback = SubscriptionCallbackImpl; - - // prevent the delegate from ever being garbage collected - GCHandle.Alloc(SubscriptionCallback); - } - - /// - /// For Realms using query-based synchronization, fetches and synchronizes the objects that match the query. - /// - /// The type of the objects making up the query. - /// - /// A query, obtained by calling with or without additional filtering applied. - /// - /// The name of this query that can be used to unsubscribe from. - /// - /// A instance that contains information and methods for monitoring - /// the state of the subscription. - /// - /// Thrown if query is null. - /// - /// Thrown if the query was not obtained from a query-based synchronized Realm. - /// - [Obsolete("Use Subscribe(query, options) instead.")] - public static Subscription Subscribe(this IQueryable query, string name) - { - return query.Subscribe(new SubscriptionOptions - { - Name = name - }); - } - - /// - /// For Realms using query-based synchronization, fetches and synchronizes the objects that match the query. - /// - /// The type of the objects making up the query. - /// - /// A query, obtained by calling with or without additional filtering applied. - /// - /// - /// Options that configure some metadata of the subscription, such as its name or time to live. - /// - /// - /// An array of property expressions which specifies which linkingObjects relationships should be included in - /// the subscription. Subscriptions already include link and list properties (in the forward direction) - /// automatically by default. - /// - /// - /// A instance that contains information and methods for monitoring - /// the state of the subscription. - /// - /// Thrown if query is null. - /// - /// Thrown if the query was not obtained from a query-based synchronized Realm. - /// - public static Subscription Subscribe( - this IQueryable query, - SubscriptionOptions options = null, - params Expression>[] includedBacklinks) - { - Argument.NotNull(query, nameof(query)); - - var results = query as RealmResults; - Argument.Ensure(results != null, $"{nameof(query)} must be an instance of IRealmCollection<{typeof(T).Name}>.", nameof(query)); - - options = options ?? new SubscriptionOptions(); - - var syncConfig = results.Realm.Config as SyncConfigurationBase; - Argument.Ensure(syncConfig?.IsFullSync == false, $"{nameof(query)} must be obtained from a synchronized Realm using query-based synchronization.", nameof(query)); - - var handle = SubscriptionHandle.Create( - results.ResultsHandle, - options.Name, - (long?)options.TimeToLive?.TotalMilliseconds, - options.ShouldUpdate, - includedBacklinks.ToStringPaths()); - return new Subscription(handle, results); - } - - /// - /// Returns all subscriptions registered for that Realm. - /// - /// A queryable collection of all registered subscriptions. - /// A Realm opened with a . - public static IRealmCollection GetAllSubscriptions(this Realm realm) - { - Argument.NotNull(realm, nameof(realm)); - var syncConfig = realm.Config as SyncConfigurationBase; - Argument.Ensure(syncConfig?.IsFullSync == false, $"{nameof(realm)} must be a synchronized Realm using query-based synchronization.", nameof(realm)); - - return realm.All().AsRealmCollection(); - } - - /// - /// Cancel a named subscription that was created by calling . - /// - /// Removing a subscription will delete all objects from the local Realm that were matched - /// only by that subscription and not any remaining subscriptions. The deletion is performed - /// by the server, and so has no immediate impact on the contents of the local Realm. If the - /// device is currently offline, the removal will not be processed until the device returns online. - /// - /// The Realm where this subscription was added. - /// The name of the subscription to remove. - /// An awaitable task, that indicates that the subscription has been removed locally. - public static Task UnsubscribeAsync(this Realm realm, string subscriptionName) - { - Argument.NotNull(realm, nameof(realm)); - var syncConfig = realm.Config as SyncConfigurationBase; - Argument.Ensure(syncConfig?.IsFullSync == false, $"{nameof(realm)} must be a synchronized Realm using query-based synchronization.", nameof(realm)); - - var config = realm.Config.Clone(); - config.ObjectClasses = new[] { typeof(NamedSubscription) }; - config.EnableCache = false; - - return Task.Run(() => - { - using (var backgroundRealm = Realm.GetInstance(config)) - { - var resultSets = backgroundRealm.All().Where(r => r.Name == subscriptionName); - if (!resultSets.Any()) - { - throw new RealmException($"A subscription with the name {subscriptionName} doesn't exist."); - } - - backgroundRealm.Write(() => - { - backgroundRealm.RemoveRange(resultSets); - }); - } - }); - } - - /// - /// Cancel a subscription that was created by calling . - /// - /// Removing a subscription will delete all objects from the local Realm that were matched - /// only by that subscription and not any remaining subscriptions. The deletion is performed - /// by the server, and so has no immediate impact on the contents of the local Realm. If the - /// device is currently offline, the removal will not be processed until the device returns online. - /// - /// The type of the objects that make up the subscription query. - /// The subscription to cancel. - /// An awaitable task, that indicates that the subscription has been removed locally. - public static async Task UnsubscribeAsync(this Subscription subscription) - { - Argument.NotNull(subscription, nameof(subscription)); - if (subscription.State == SubscriptionState.Invalidated) - { - return; - } - - AsyncHelper.EnsureValidContext(); - - subscription.Handle.Unsubscribe(); - - var tcs = new TaskCompletionSource(); - PropertyChangedEventHandler handler = null; - handler = new PropertyChangedEventHandler((s, e) => - { - switch (subscription.State) - { - case SubscriptionState.Invalidated: - tcs.TrySetResult(null); - break; - case SubscriptionState.Error: - tcs.TrySetException(subscription.Error); - break; - } - }); - - subscription.PropertyChanged += handler; - try - { - await tcs.Task; - } - finally - { - subscription.PropertyChanged -= handler; - subscription.SubscriptionToken.Dispose(); - } - } - - [MonoPInvokeCallback(typeof(SubscriptionHandle.SubscriptionCallbackDelegate))] - private static void SubscriptionCallbackImpl(IntPtr managedHandle) - { - if (GCHandle.FromIntPtr(managedHandle).Target is ISubscription subscription) - { - subscription.ReloadState(); - } - } - } - - /// - /// A class that represents a subscription to a set of objects in a synced Realm. - /// - /// When query-based sync is enabled for a synced Realm, the only objects that the server synchronizes to the - /// client are those that match a sync subscription registered by that client. A subscription consists of - /// of a query (represented by an IQueryable{T}) and an optional name. - /// - /// The state of the subscription can be observed by subscribing to the event handler. - /// - /// Subscriptions are created by calling . - /// - /// The type of the objects that make up the subscription query. - [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Makes sense to have all subscriptions in the same file.")] - public class Subscription : INotifyPropertyChanged, ISubscription - { - internal readonly SubscriptionHandle Handle; - internal readonly SubscriptionTokenHandle SubscriptionToken; - - private readonly TaskCompletionSource _syncTcs = new TaskCompletionSource(); - - /// - /// Occurs when a property value changes. - /// - public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Gets a value indicating the state of this subscription. - /// - /// The state of the subscription. - public SubscriptionState State { get; private set; } - - /// - /// Gets a value indicating what error (if any) has occurred while processing the subscription. - /// If the is not , this will be null. - /// - /// An instance of if an error has occurred; null otherwise. - public Exception Error { get; private set; } - - /// - /// Gets the query that this subscription is associated with. Regardless of the state of the subscription, - /// this value will reflect the results in the local Realm. This allows you to data-bind to this property - /// immediately and show the last synchronized data. If the is , - /// the values returned will not be an adequate representation of the state of the remote Realm. - /// - /// - /// A queryable collection that can be further filtered, ordered, or observed for changes. - /// - public IQueryable Results { get; } - - internal Subscription(SubscriptionHandle handle, RealmResults query) - { - Results = query; - - Handle = handle; - ((ISubscription)this).ReloadState(); - - var managedSubscriptionHandle = GCHandle.Alloc(this, GCHandleType.Weak); - SubscriptionToken = Handle.AddNotificationCallback(GCHandle.ToIntPtr(managedSubscriptionHandle), Subscription.SubscriptionCallback); - } - - /// - /// Waits for the subscription to complete synchronizing (equivalent to transitioning to the - /// state. - /// - /// - /// An awaitable task, that, upon completion, indicates that the objects matching the specified query - /// have been synchronized to the local Realm. - /// - public Task WaitForSynchronizationAsync() - { - AsyncHelper.EnsureValidContext(); - - return _syncTcs.Task; - } - - /// - void ISubscription.ReloadState() - { - if (Handle.IsClosed) - { - return; - } - - var newState = Handle.GetState(); - if (newState != State) - { - try - { - // If we encounter an unexpected value, assume it's an error. - if (!Enum.IsDefined(typeof(SubscriptionState), newState)) - { - newState = SubscriptionState.Error; - } - - State = newState; - switch (State) - { - case SubscriptionState.Error: - Error = Handle.GetError() ?? new RealmException($"An unknown error has occurred. State: {Handle.GetState()}"); - _syncTcs.TrySetException(Error); - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Error))); - break; - case SubscriptionState.Complete: - _syncTcs.TrySetResult(null); - break; - } - } - catch (Exception ex) - { - _syncTcs.TrySetException(ex); - } - - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(State))); - } - } - } -} diff --git a/Realm/Realm/Sync/SubscriptionOptions.cs b/Realm/Realm/Sync/SubscriptionOptions.cs deleted file mode 100644 index 28f5df0b39..0000000000 --- a/Realm/Realm/Sync/SubscriptionOptions.cs +++ /dev/null @@ -1,52 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Linq; - -namespace Realms.Sync -{ - /// - /// A set of options provided to - /// to control the behavior of the subscription. - /// - public class SubscriptionOptions - { - /// - /// Gets or sets the name of the subscription. - /// - /// The subscription name. - public string Name { get; set; } - - /// - /// Gets or sets the time to live of the subscription. If not set or set - /// to null, the subscription is kept indefinitely. The subscription - /// will be automatically removed after the time to live passes. - /// - /// The time to live. - public TimeSpan? TimeToLive { get; set; } - - /// - /// Gets or sets a value indicating whether the subscription should be updated - /// if one with the same name already exists. If set to false and a subscription - /// with the same name exists, an exception will be thrown. - /// - /// true if subscription should be updated; otherwise, false. - public bool ShouldUpdate { get; set; } - } -} diff --git a/Realm/Realm/Sync/SubscriptionState.cs b/Realm/Realm/Sync/SubscriptionState.cs deleted file mode 100644 index d732cd3239..0000000000 --- a/Realm/Realm/Sync/SubscriptionState.cs +++ /dev/null @@ -1,52 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace Realms.Sync -{ - /// - /// An enumeration, representing the possible state of a sync subscription. - /// - public enum SubscriptionState : sbyte - { - /// - /// An error occurred while creating the subscription or while the server was processing it. - /// - Error = -1, - - /// - /// The subscription has been created, and is waiting to be processed by the server. - /// - Pending = 0, - - /// - /// The subscription has been processed by the server, and objects matching the subscription - /// are now being synchronized to this client. - /// - Complete = 1, - - /// - /// The subscription is being created, but has not yet been written to the synced Realm. - /// - Creating = 2, - - /// - /// This subscription has been removed. - /// - Invalidated = 3 - } -} diff --git a/Realm/Realm/Sync/User.cs b/Realm/Realm/Sync/User.cs index 1ed8c55900..4b8c8f1338 100644 --- a/Realm/Realm/Sync/User.cs +++ b/Realm/Realm/Sync/User.cs @@ -18,370 +18,267 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Net.Http; +using System.Net; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; -using Realms.Exceptions; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; using Realms.Helpers; +using Realms.Native; +using Realms.Sync.Exceptions; namespace Realms.Sync { /// - /// This class represents a user on the Realm Object Server. The credentials are provided by various 3rd party providers (Facebook, Google, etc.). - /// A user can log in to the Realm Object Server, and if access is granted, it is possible to synchronize the local and the remote Realm. Moreover, synchronization is halted when the user is logged out. + /// This class represents a user in a MongoDB Realm app. The credentials are provided by various 3rd party providers (Facebook, Google, etc.). + /// A user can log in to the server and, if access is granted, it is possible to synchronize the local and the remote Realm. Moreover, synchronization is halted when the user is logged out. /// It is possible to persist a user. By retrieving a user, there is no need to log in to the 3rd party provider again. Persisting a user between sessions, the user's credentials are stored locally on the device, and should be treated as sensitive data. /// public class User : IEquatable { - #region static - /// - /// Gets the currently logged-in user. If none exists, null is returned. - /// If more than one user is currently logged in, an exception is thrown. + /// Gets this user's refresh token. This is the user's credential for accessing MongoDB Realm data and should be treated as sensitive information. /// - /// Valid user or null to indicate nobody logged in. - /// Thrown if there are more than one users logged in. - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The User instance will own its handle.")] - public static User Current + /// A unique string that can be used for refreshing the user's credentials. + public string RefreshToken { - get - { - SharedRealmHandleExtensions.DoInitialMetadataConfiguration(); - - if (SyncUserHandle.TryGetCurrentUser(out var userHandle)) - { - return new User(userHandle); - } - - return null; - } + get => Handle.GetRefreshToken(); } /// - /// Gets all currently logged in users. + /// Gets this user's access token. This is the user's credential for accessing MongoDB Realm data and should be treated as sensitive information. /// - /// An array of valid logged in users. - public static User[] AllLoggedIn + /// A unique string that can be used to represent this user before the server. + public string AccessToken { - get - { - SharedRealmHandleExtensions.DoInitialMetadataConfiguration(); - - return SyncUserHandle.GetAllLoggedInUsers() - .Select(handle => new User(handle)) - .ToArray(); - } + get => Handle.GetAccessToken(); } /// - /// Logs the user in to the Realm Object Server. + /// Gets a unique identifier for the device the user logged in to. /// - /// The credentials to use for authentication. - /// The URI of the server that the user is authenticated against. - /// An awaitable Task, that, upon completion, contains the logged in user. - public static async Task LoginAsync(Credentials credentials, Uri serverUri) + /// A unique string that identifies the current device. + public string DeviceId { - Argument.NotNull(credentials, nameof(credentials)); - Argument.NotNull(serverUri, nameof(serverUri)); - Argument.Ensure(serverUri.Scheme.StartsWith("http"), "Unexpected protocol for login url. Expected http:// or https://.", nameof(serverUri)); - - SharedRealmHandleExtensions.DoInitialMetadataConfiguration(); + get => Handle.GetDeviceId(); + } - if (credentials.IdentityProvider == Credentials.Provider.AdminToken) - { - return new User(SyncUserHandle.GetAdminTokenUser(serverUri.AbsoluteUri, credentials.Token)); - } + /// + /// Gets the Id of this user on MongoDB Realm. + /// + /// A string that uniquely identifies that user. + public string Id => Handle.GetUserId(); - if (credentials.IdentityProvider == Credentials.Provider.CustomRefreshToken) - { - var userId = (string)credentials.UserInfo[Credentials.Keys.Identity]; - var isAdmin = (bool)credentials.UserInfo[Credentials.Keys.IsAdmin]; - return new User(SyncUserHandle.GetSyncUser(userId, serverUri.AbsoluteUri, credentials.Token, isAdmin)); - } + /// + /// Gets the current state of the user. + /// + /// A value indicating whether the user is active, logged out, or an error has occurred. + public UserState State => Handle.GetState(); - var result = await AuthenticationHelper.LoginAsync(credentials, serverUri); - var handle = SyncUserHandle.GetSyncUser(result.UserId, serverUri.AbsoluteUri, result.RefreshToken, result.IsAdmin); - return new User(handle); - } + /// + /// Gets a value indicating which this user logged in with. + /// + /// The used to login the user. + public Credentials.AuthProvider Provider => Handle.GetProvider(); /// - /// Gets a logged in user with a specified identity. + /// Gets the app with which this user is associated. /// - /// A user instance if a logged in user with that id exists, null otherwise. - /// The identity of the user. - /// The URI of the server that the user is authenticated against. - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The User instance will own its handle.")] - public static User GetLoggedInUser(string identity, Uri serverUri) - { - Argument.NotNull(identity, nameof(identity)); - Argument.NotNull(serverUri, nameof(serverUri)); + /// An instance that owns this user. + public App App { get; } - SharedRealmHandleExtensions.DoInitialMetadataConfiguration(); + /// + /// Gets the profile information for that user. + /// + /// A object, containing information about the user's name, email, and so on. + public UserProfile Profile { get; } - if (SyncUserHandle.TryGetLoggedInUser(identity, serverUri.AbsoluteUri, out var userHandle)) + /// + /// Gets the custom user data associated with this user in the Realm app. + /// + /// + /// The data is only refreshed when the user's access token is refreshed or when explicitly calling . + /// + /// A document containing the user data. + /// + public BsonDocument GetCustomData() + { + var serialized = Handle.GetCustomData(); + if (string.IsNullOrEmpty(serialized) || !BsonDocument.TryParse(serialized, out var doc)) { - return new User(userHandle); + return null; } - return null; + return doc; } - #endregion static - /// - /// Gets or sets this user's refresh token. This is the user's credential for accessing the Realm Object Server and should be treated as sensitive data. - /// Setting the refresh token is only supported for users authenticated with . + /// Gets the custom user data associated with this user in the Realm app and parses it to the specified type. /// - /// A unique string that can be used for refreshing the user's credentials. - public string RefreshToken + /// The managed type that matches the shape of the custom data documents. + /// + /// The data is only refreshed when the user's access token is refreshed or when explicitly calling . + /// + /// A document containing the user data. + /// + public T GetCustomData() + where T : class { - get => Handle.GetRefreshToken(); - set + var customData = GetCustomData(); + if (customData == null) { - Argument.NotNull(value, nameof(value)); - Handle.SetRefreshToken(value); + return null; } - } - /// - /// Gets the identity of this user on the Realm Object Server. The identity is a guaranteed to be unique among all users on the Realm Object Server. - /// - /// A string that uniquely identifies that user in Realm Object Server. - public string Identity => Handle.GetIdentity(); + return BsonSerializer.Deserialize(customData); + } /// - /// Gets the server URI that was used for authentication. + /// Gets a collection of all identities associated with this user. /// - /// The used to connect to the authentication service. - public Uri ServerUri + /// The user's identities across different s. + public UserIdentity[] Identities { get { - var serverUrl = Handle.GetServerUrl(); - if (string.IsNullOrEmpty(serverUrl)) - { - return null; - } - - return new Uri(serverUrl); + var serialized = Handle.GetIdentities(); + return BsonSerializer.Deserialize(serialized); } } /// - /// Gets a value indicating whether this is a Realm Object Server administrator user. + /// Gets a instance that exposes functionality about managing user API keys. /// - /// true if the user is admin; otherwise, false. - public bool IsAdmin => Handle.GetIsAdmin(); + /// A instance scoped to this . + /// + public ApiKeyClient ApiKeys { get; } /// - /// Gets the current state of the user. + /// Gets a instance that exposes functionality about calling remote MongoDB Realm functions. /// - /// A value indicating whether the user is active, logged out, or an error has occurred. - public UserState State => Handle.GetState(); + /// A instance scoped to this . + /// + public FunctionsClient Functions { get; } internal readonly SyncUserHandle Handle; - internal User(SyncUserHandle handle) - { - Handle = handle; - } - - internal static User Create(IntPtr userPtr) + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The App instance will own its handle.")] + internal User(SyncUserHandle handle, App app = null) { - var userHandle = new SyncUserHandle(userPtr); - return new User(userHandle); - } - - internal Uri GetUriForRealm(string path) - { - if (!path.StartsWith("/")) + if (app == null && handle.TryGetApp(out var appHandle)) { - path = $"/{path}"; + app = new App(appHandle); } - return GetUriForRealm(new Uri(path, UriKind.Relative)); - } - - internal Uri GetUriForRealm(Uri uri) - { - Argument.Ensure(!uri.IsAbsoluteUri, "The passed Uri must be relative", nameof(uri)); - var uriBuilder = new UriBuilder(new Uri(ServerUri, uri)); - uriBuilder.Scheme = uriBuilder.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase) ? "realms" : "realm"; - return uriBuilder.Uri; - } - - /// - /// Logs out the user from the Realm Object Server. Once the Object Server has confirmed the logout the user credentials will be deleted from this device. - /// - /// An awaitable Task, that, upon completion indicates that the user has been logged out both locally and on the server. - public async Task LogOutAsync() - { - var uri = ServerUri; - var refreshToken = RefreshToken; - Handle.LogOut(); - - try - { - await AuthenticationHelper.LogOutAsync(uri, refreshToken); - } - catch (Exception ex) - { - ErrorMessages.OutputError($"An error has occurred while logging the user out: {ex.Message}. The user is still logged out locally, but their refresh token may not have been revoked yet."); - } + App = app; + Handle = handle; + Profile = new UserProfile(this); + ApiKeys = new ApiKeyClient(this); + Functions = new FunctionsClient(this); } /// - /// Changes the user's password. + /// Removes the user's local credentials and attempts to invalidate their refresh token from the server. /// - /// The user's new password. - /// - /// Changing a user's password using an authentication server that doesn't - /// use HTTPS is a major security flaw, and should only be done while testing. - /// - /// An awaitable task that, when successful, indicates that the password has changed. - public Task ChangePasswordAsync(string newPassword) - { - Argument.Ensure(State == UserState.Active, "Password may be changed only by active users."); - Argument.NotNullOrEmpty(newPassword, nameof(newPassword)); - - return AuthenticationHelper.ChangePasswordAsync(this, newPassword); - } + /// A that represents the remote logout operation. + public Task LogOutAsync() => App.RemoveUserAsync(this); /// - /// Changes another user's password. + /// Re-fetch the user's custom data from the server. /// - /// The of the user we want to change the password for. - /// The user's new password. - /// - /// This user needs admin privilege in order to change someone else's password. - ///
- /// Changing a user's password using an authentication server that doesn't - /// use HTTPS is a major security flaw, and should only be done while testing. - ///
- /// An awaitable task that, when successful, indicates that the password has changed. - public Task ChangePasswordAsync(string userId, string newPassword) + /// + /// A that represents the remote refresh operation. The result is a + /// containing the updated custom user data. The value returned by will also be updated with the new information. + /// + public async Task RefreshCustomDataAsync() { - Argument.Ensure(State == UserState.Active, "Password may be changed only by active users."); - Argument.Ensure(IsAdmin, "Other users' passwords may be changed only by admin users."); - Argument.NotNullOrEmpty(userId, nameof(userId)); - Argument.NotNullOrEmpty(newPassword, nameof(newPassword)); + var tcs = new TaskCompletionSource(); + Handle.RefreshCustomData(tcs); + await tcs.Task; - return AuthenticationHelper.ChangePasswordAsync(this, newPassword, userId); + return GetCustomData(); } /// - /// Looks up user's information by provider id. This is useful when you know the id of a user in a provider's system, - /// e.g. on Facebook and want to find the associated Realm user's Id. + /// Re-fetch the user's custom data from the server. /// - /// The provider that the user has signed up with. - /// The id of the user in the provider's system. - /// - /// This user needs admin privilege in order to look up other users by provider id. - ///
- /// The exact names of built-in providers can be found in . - ///
+ /// The managed type that matches the shape of the custom data documents. /// - /// A , containing information about the User's Identity in Realm's authentication system, - /// or null if a user has not been found. + /// A that represents the remote refresh operation. The result is an object + /// containing the updated custom user data. The value returned by will also be updated with the new information. /// - public Task RetrieveInfoForUserAsync(string provider, string providerUserIdentity) + public async Task RefreshCustomDataAsync() + where T : class { - Argument.Ensure(State == UserState.Active, "Users may be looked up only by active users."); - Argument.Ensure(IsAdmin, "Users may be looked up only by admin users."); - Argument.NotNullOrEmpty(provider, nameof(provider)); - Argument.NotNullOrEmpty(providerUserIdentity, nameof(providerUserIdentity)); + var result = await RefreshCustomDataAsync(); + if (result == null) + { + return null; + } - return AuthenticationHelper.RetrieveInfoForUserAsync(this, provider, providerUserIdentity); + return BsonSerializer.Deserialize(result); } /// - /// Request a password reset email to be sent to a user's email. This method requires internet connection - /// and will not throw an exception, even if the email doesn't belong to a Realm Object Server user. + /// Gets a instance for accessing documents in a MongoDB database. /// - /// - /// This can only be used for users who authenticated with - /// and passed a valid email address as a username. - /// - /// The URI of the server that the user is authenticated against. - /// The email that corresponds to the user's username. - /// An awaitable task that, upon completion, indicates that the request has been sent. - public static Task RequestPasswordResetAsync(Uri serverUri, string email) - { - Argument.NotNull(serverUri, nameof(serverUri)); - Argument.NotNullOrEmpty(email, nameof(email)); - - return AuthenticationHelper.UpdateAccountAsync(serverUri, "reset_password", email); - } + /// The name of the service as configured on the server. + /// A instance that can interact with the databases exposed in the remote service. + public MongoClient GetMongoClient(string serviceName) => new MongoClient(this, serviceName); /// - /// Complete the password reset flow by using the reset token sent to the user's email as a one-time - /// authorization token to change the password. + /// Gets a client for interacting the with Firebase Cloud Messaging service exposed in MongoDB Realm. /// /// - /// By default, the link that will be sent to the user's email will redirect to a webpage where - /// they can enter their new password. If you wish to provide a native UX, you may wish to modify - /// the url to use deep linking to open the app, extract the token, and navigate to a view that - /// allows them to change their password within the app. + /// The FCM service needs to be configured and enabled in the MongodB Realm UI before devices can register + /// and receive push notifications. /// - /// The URI of the server that the user is authenticated against. - /// The token that was sent to the user's email address. - /// The user's new password. - /// An awaitable task that, when successful, indicates that the password has changed. - public static Task CompletePasswordResetAsync(Uri serverUri, string token, string newPassword) - { - Argument.NotNull(serverUri, nameof(serverUri)); - Argument.NotNullOrEmpty(token, nameof(token)); - Argument.NotNullOrEmpty(newPassword, nameof(newPassword)); - - var data = new Dictionary - { - ["token"] = token, - ["new_password"] = newPassword - }; - return AuthenticationHelper.UpdateAccountAsync(serverUri, "complete_reset", data: data); - } + /// The name of the service as configured in the MongoDB Realm UI. + /// A client that exposes API to register/deregister push notification tokens. + /// + public PushClient GetPushClient(string serviceName) => new PushClient(this, serviceName); /// - /// Request an email confirmation email to be sent to a user's email. This method requires internet connection - /// and will not throw an exception, even if the email doesn't belong to a Realm Object Server user. - /// - /// The URI of the server that the user is authenticated against. - /// The email that corresponds to the user's username. - /// An awaitable task that, upon completion, indicates that the request has been sent. - public static Task RequestEmailConfirmationAsync(Uri serverUri, string email) - { - Argument.NotNull(serverUri, nameof(serverUri)); - Argument.NotNullOrEmpty(email, nameof(email)); - - return AuthenticationHelper.UpdateAccountAsync(serverUri, "request_email_confirmation", email); - } - - /// - /// Complete the password reset flow by using the confirmation token sent to the user's email as a one-time - /// authorization token to confirm their email. + /// Links the current user with a new user identity represented by the given credentials. /// /// - /// By default, the link that will be sent to the user's email will redirect to a webpage where - /// they'll see a generic "Thank you for confirming" text. If you wish to provide a native UX, you - /// may wish to modify the url to use deep linking to open the app, extract the token, and inform them - /// that their email has been confirmed. + /// Linking a user with more credentials, mean the user can login either of these credentials. It also + /// makes it possible to "upgrade" an anonymous user by linking it with e.g. Email/Password credentials. + ///
+ /// Note: It is not possible to link two existing users of MongoDB Realm. The provided credentials must not have been used by another user. + ///
+ /// Note for email/password auth: To link a user with a new set of credentials, you will need to first + /// register these credentials by calling . ///
- /// The URI of the server that the user is authenticated against. - /// The token that was sent to the user's email address. - /// An awaitable task that, when successful, indicates that the email has been confirmed. - public static Task ConfirmEmailAsync(Uri serverUri, string token) + /// + /// The following snippet shows how to associate an email and password with an anonymous user + /// allowing them to login on a different device. + /// + /// var app = App.Create("app-id") + /// var user = await app.LogInAsync(Credentials.Anonymous()); + /// + /// // This step is only needed for email password auth - a password record must exist + /// // before you can link a user to it. + /// await app.EmailPasswordAuth.RegisterUserAsync("email", "password"); + /// await user.LinkCredentialsAsync(Credentials.EmailPassword("email", "password")); + /// + /// + /// The credentials to link with the current user. + /// + /// A representing the remote link credentials operation. Upon successful completion, the task result + /// will contain the user to which the credentials were linked. + /// + public async Task LinkCredentialsAsync(Credentials credentials) { - Argument.NotNull(serverUri, nameof(serverUri)); - Argument.NotNullOrEmpty(token, nameof(token)); + Argument.NotNull(credentials, nameof(credentials)); - var data = new Dictionary - { - ["token"] = token - }; + var tcs = new TaskCompletionSource(); + Handle.LinkCredentials(App.Handle, credentials.ToNative(), tcs); + var handle = await tcs.Task; - return AuthenticationHelper.UpdateAccountAsync(serverUri, "confirm_email", data: data); + return new User(handle, App); } /// @@ -397,162 +294,252 @@ public override bool Equals(object obj) /// true if the two instances are equal; false otherwise. public bool Equals(User other) { - return Identity.Equals(other?.Identity); + return Id.Equals(other?.Id); } /// public override int GetHashCode() { - return Identity.GetHashCode(); + return Id.GetHashCode(); } - #region Permissions - /// - /// Asynchronously retrieve all permissions associated with the user calling this method. + /// A class exposing functionality for users to manage API keys from the client. It is always scoped + /// to a particular and can only be accessed via . /// - /// - /// A collection of objects that provide detailed information - /// regarding the granted access. - /// - /// The optional recipient of the permission. - public async Task> GetGrantedPermissionsAsync(Recipient recipient = Recipient.Any) + public class ApiKeyClient { - var result = await MakePermissionRequestAsync(HttpMethod.Get, $"permissions?recipient={recipient}"); - return result["permissions"].ToObject>(); - } + private readonly User _user; - /// - /// Changes the permissions of a Realm. - /// - /// - /// An awaitable task, that, upon completion, indicates that the permissions have been successfully applied by the server. - /// - /// A that will be used to match existing users against. - /// The Realm path whose permissions settings should be changed. Use * to change the permissions of all Realms managed by this . - /// - /// The access level to grant matching users. Note that the access level setting is absolute, i.e. it may revoke permissions for users that - /// previously had a higher access level. To revoke all permissions, use . - /// - public async Task ApplyPermissionsAsync(PermissionCondition condition, string realmPath, AccessLevel accessLevel) - { - Argument.NotNull(condition, nameof(condition)); + internal ApiKeyClient(User user) + { + _user = user; + } - if (string.IsNullOrEmpty(realmPath)) + /// + /// Creates an API key that can be used to authenticate as the user. + /// + /// + /// The value of the returned API key must be persisted at this time as this is the only + /// time it is visible. The key is enabled when created. It can be disabled by calling + /// . + /// + /// The friendly name of the key. + /// + /// A representing the asynchronous operation. Successful completion indicates + /// that the has been created on the server and its can + /// be used to create . + /// + public async Task CreateAsync(string name) { - throw new ArgumentNullException(nameof(realmPath)); + Argument.NotNullOrEmpty(name, nameof(name)); + + var tcs = new TaskCompletionSource(); + _user.Handle.CreateApiKey(_user.App.Handle, name, tcs); + var apiKeys = await tcs.Task; + + Debug.Assert(apiKeys.Length == 1, "The result of Create should be exactly 1 ApiKey."); + + return new ApiKey(apiKeys.Single()); } - var payload = new Dictionary + /// + /// Fetches a specific user API key by id. + /// + /// The id of the key to fetch. + /// + /// A representing the asynchronous lookup operation. + /// + public async Task FetchAsync(ObjectId id) { - ["condition"] = condition.ToJsonObject(), - ["realmPath"] = realmPath, - ["accessLevel"] = accessLevel.ToString().ToLower() - }; - await MakePermissionRequestAsync(HttpMethod.Post, "permissions/apply", payload); - } + var tcs = new TaskCompletionSource(); + _user.Handle.FetchApiKey(_user.App.Handle, id, tcs); + var apiKeys = await Handle404(tcs); - /// - /// Generates a token that can be used for sharing a Realm. - /// - /// - /// A token that can be shared with another user, e.g. via email or message and then consumed by - /// to obtain permissions to a Realm. - /// The Realm URL whose permissions settings should be changed. Use * to change the permissions of all Realms managed by this . - /// - /// The access level to grant matching users. Note that the access level setting is absolute, i.e. it may revoke permissions for users that - /// previously had a higher access level. To revoke all permissions, use . - /// - /// Optional expiration date of the offer. If set to null, the offer doesn't expire. - public async Task OfferPermissionsAsync(string realmPath, AccessLevel accessLevel, DateTimeOffset? expiresAt = null) - { - if (string.IsNullOrEmpty(realmPath)) + Debug.Assert(apiKeys == null || apiKeys.Length <= 1, "The result of the fetch operation should be either null, or an array of 0 or 1 elements."); + + return apiKeys == null || apiKeys.Length == 0 ? null : new ApiKey(apiKeys.Single()); + } + + /// + /// Fetches all API keys associated with the user. + /// + /// + /// An awaitable task representing the asynchronous lookup operation. Upon completion, the result contains + /// a collection of all API keys for that user. + /// + public async Task> FetchAllAsync() { - throw new ArgumentNullException(nameof(realmPath)); + var tcs = new TaskCompletionSource(); + _user.Handle.FetchAllApiKeys(_user.App.Handle, tcs); + var apiKeys = await tcs.Task; + + return apiKeys.Select(k => new ApiKey(k)).ToArray(); } - if (expiresAt < DateTimeOffset.UtcNow) + /// + /// Deletes an API key by id. + /// + /// The id of the key to delete. + /// A representing the asynchronous delete operation. + public Task DeleteAsync(ObjectId id) { - throw new ArgumentException("The expiration date may not be in the past", nameof(expiresAt)); + var tcs = new TaskCompletionSource(); + _user.Handle.DeleteApiKey(_user.App.Handle, id, tcs); + + return Handle404(tcs); } - if (accessLevel == AccessLevel.None) + /// + /// Disables an API key by id. + /// + /// The id of the key to disable. + /// A representing the asynchronous disable operation. + /// + public Task DisableAsync(ObjectId id) { - throw new ArgumentException("The access level may not be None", nameof(accessLevel)); + var tcs = new TaskCompletionSource(); + _user.Handle.DisableApiKey(_user.App.Handle, id, tcs); + + return Handle404(tcs, id, shouldThrow: true); } - var payload = new Dictionary + /// + /// Enables an API key by id. + /// + /// The id of the key to enable. + /// A representing the asynchrounous enable operation. + /// + public Task EnableAsync(ObjectId id) { - ["expiresAt"] = expiresAt?.ToString("O"), - ["realmPath"] = realmPath, - ["accessLevel"] = accessLevel.ToString().ToLower() - }; + var tcs = new TaskCompletionSource(); + _user.Handle.EnableApiKey(_user.App.Handle, id, tcs); + + return Handle404(tcs, id, shouldThrow: true); + } - var result = await MakePermissionRequestAsync(HttpMethod.Post, "permissions/offers", payload); - return result.ToObject().Token; + private static async Task Handle404(TaskCompletionSource tcs, ObjectId? id = null, bool shouldThrow = false) + { + try + { + return await tcs.Task; + } + catch (AppException ex) when (ex.StatusCode == HttpStatusCode.NotFound) + { + if (shouldThrow) + { + throw new AppException($"Failed to execute operation because ApiKey with Id: {id} doesn't exist.", ex.HelpLink, 404); + } + + return default; + } + } } /// - /// Consumes a token generated by to obtain permissions to a shared Realm. + /// A class exposing functionality for calling remote MongoDB Realm functions. /// - /// The relative url of the Realm that the token has granted permissions to. - /// The token, generated by . - public async Task AcceptPermissionOfferAsync(string offerToken) + /// + public class FunctionsClient { - if (string.IsNullOrEmpty(offerToken)) + private readonly User _user; + + internal FunctionsClient(User user) { - throw new ArgumentNullException(nameof(offerToken)); + _user = user; } - var result = await MakePermissionRequestAsync(HttpMethod.Post, $"permissions/offers/{offerToken}/accept"); - return result["path"].Value(); + /// + /// Calls a remote function with the supplied arguments. + /// + /// Name of the Realm function to call. + /// Arguments that will be sent to the Realm function. They have to be json serializable values. + /// + /// A wrapping the asynchronous call function operation. The result of the task is + /// the value returned by the function. + /// + public Task CallAsync(string name, params object[] args) => CallAsync(name, args); + + /// + /// Calls a remote function with the supplied arguments. + /// + /// + /// The MongoDB Bson library is used + /// to decode the response. It will automatically handle most cases, but if you want to control the behavior + /// of the deserializer, you can use the attributes in the + /// MongoDB.Bson.Serialization.Attributes + /// namespace. + ///
+ /// If you want to modify the global conventions used when deserializing the response, such as convert + /// camelCase properties to PascalCase, you can regiseter a + /// ConventionPack. + ///
+ /// The type that the response will be decoded to. + /// Name of the Realm function to call. + /// Arguments that will be sent to the Realm function. They have to be json serializable values. + /// + /// A wrapping the asynchronous call function operation. The result of the task is + /// the value returned by the function decoded as . + /// + public async Task CallAsync(string name, params object[] args) + { + Argument.NotNullOrEmpty(name, nameof(name)); + + var tcs = new TaskCompletionSource(); + + _user.Handle.CallFunction(_user.App.Handle, name, args.ToNativeJson(), tcs); + + var response = await tcs.Task; + + return response.GetValue(); + } } /// - /// Invalidates a permission offer. + /// The Push client exposes an API to register/deregister for push notifications from a client app. /// - /// - /// Invalidating an offer prevents new users from consuming its token. It doesn't revoke any permissions that have - /// already been granted. - /// - /// - /// An awaitable task, that, upon completion, indicates that the offer has been successfully invalidated by the server. - /// - /// The offer that should be invalidated. - [Obsolete("Use InvalidateOfferAsync(string) by passing the offer.Token instead.")] - public Task InvalidateOfferAsync(PermissionOffer offer) + public class PushClient { - Argument.NotNull(offer, nameof(offer)); + private readonly User _user; + private readonly string _service; - return InvalidateOfferAsync(offer.Token); - } + internal PushClient(User user, string service) + { + _user = user; + _service = service; + } - /// - /// Invalidates a permission offer by its token. - /// - /// - /// Invalidating an offer prevents new users from consuming its token. It doesn't revoke any permissions that have - /// already been granted. - /// - /// - /// An awaitable task, that, upon completion, indicates that the offer has been successfully invalidated by the server. - /// - /// The token of the offer that should be invalidated. - public Task InvalidateOfferAsync(string offerToken) => MakePermissionRequestAsync(HttpMethod.Delete, $"permissions/offers/{offerToken}"); + /// + /// Registers the given Firebase Cloud Messaging registration token with the user's device on MongoDB Realm. + /// + /// The FCM registration token. + /// + /// A representing the remote operation. Successful completion indicates that the registration token was registered + /// by the MongoDB Realm server and this device can now receive push notifications. + /// + public Task RegisterDeviceAsync(string token) + { + Argument.NotNullOrEmpty(token, nameof(token)); + var tcs = new TaskCompletionSource(); + _user.Handle.RegisterPushToken(_user.App.Handle, _service, token, tcs); - /// - /// Asynchronously retrieve the permission offers that this user has created by invoking . - /// - /// A collection of objects. - public async Task> GetPermissionOffersAsync() - { - var result = await MakePermissionRequestAsync(HttpMethod.Get, $"permissions/offers"); - return result["offers"].ToObject>(); - } + return tcs.Task; + } - private Task MakePermissionRequestAsync(HttpMethod method, string relativeUri, IDictionary body = null) - => AuthenticationHelper.MakeAuthRequestAsync(method, new Uri(ServerUri, relativeUri), body, RefreshToken); + /// + /// Deregister the user's device from Firebase Cloud Messaging. + /// + /// + /// A representing the remote operation. Successful completion indicates that the devices registration token + /// was removed from the MongoDB Realm server and it will no longer receive push notifications. + /// + public Task DeregisterDeviceAsync() + { + var tcs = new TaskCompletionSource(); + _user.Handle.DeregisterPushToken(_user.App.Handle, _service, tcs); - #endregion Permissions + return tcs.Task; + } + } } } \ No newline at end of file diff --git a/Realm/Realm/Sync/UserIdentity.cs b/Realm/Realm/Sync/UserIdentity.cs new file mode 100644 index 0000000000..5e6b82ffdf --- /dev/null +++ b/Realm/Realm/Sync/UserIdentity.cs @@ -0,0 +1,62 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using MongoDB.Bson.Serialization.Attributes; + +namespace Realms.Sync +{ + /// + /// A class containing information about an identity associated with a user. + /// + [BsonNoId] + public class UserIdentity + { + /// + /// Gets the unique identifier for this identity. + /// + /// The identity's Id. + public string Id { get; private set; } + + /// + /// Gets the auth provider defining this identity. + /// + /// The identity's auth provider. + public Credentials.AuthProvider Provider { get; private set; } + + /// + public override bool Equals(object obj) => (obj is UserIdentity id) && id.Id == Id && id.Provider == Provider; + + /// + /// Gets the hash code. + /// + /// The hash code. + public override int GetHashCode() + { + var hashCode = -1285871140; + hashCode = (hashCode * -1521134295) + (Id?.GetHashCode() ?? 0); + hashCode = (hashCode * -1521134295) + Provider.GetHashCode(); + return hashCode; + } + + /// + /// Returns a string representation of the value. + /// + /// A string representation of the value. + public override string ToString() => $"UserIdentity: {Id} ({Provider})"; + } +} diff --git a/Realm/Realm/Sync/UserProfile.cs b/Realm/Realm/Sync/UserProfile.cs new file mode 100644 index 0000000000..0352598568 --- /dev/null +++ b/Realm/Realm/Sync/UserProfile.cs @@ -0,0 +1,97 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using Realms.Native; + +namespace Realms.Sync +{ + /// + /// A class containing profile information about . + /// + public class UserProfile + { + private readonly User _user; + + /// + /// Gets the name of the user. + /// + /// A string representing the user's name or null if not available. + public string Name => _user.Handle.GetProfileData(UserProfileField.Name); + + /// + /// Gets the email of the user. + /// + /// A string representing the user's email or null if not available. + public string Email => _user.Handle.GetProfileData(UserProfileField.Email); + + /// + /// Gets the url for the user's profile picture. + /// + /// A string representing the user's profile picture url or null if not available. + public Uri PictureUrl + { + get + { + var url = _user.Handle.GetProfileData(UserProfileField.PictureUrl); + return url != null ? new Uri(url) : null; + } + } + + /// + /// Gets the first name of the user. + /// + /// A string representing the user's first name or null if not available. + public string FirstName => _user.Handle.GetProfileData(UserProfileField.FirstName); + + /// + /// Gets the last name of the user. + /// + /// A string representing the user's last name or null if not available. + public string LastName => _user.Handle.GetProfileData(UserProfileField.LastName); + + /// + /// Gets the gender of the user. + /// + /// A string representing the user's gender or null if not available. + public string Gender => _user.Handle.GetProfileData(UserProfileField.Gender); + + /// + /// Gets the birthday of the user. + /// + /// A string representing the user's birthday or null if not available. + public string Birthday => _user.Handle.GetProfileData(UserProfileField.Birthday); + + /// + /// Gets the minimum age of the user. + /// + /// A string representing the user's minimum age or null if not available. + public string MinAge => _user.Handle.GetProfileData(UserProfileField.MinAge); + + /// + /// Gets the maximum age of the user. + /// + /// A string representing the user's maximum age or null if not available. + public string MaxAge => _user.Handle.GetProfileData(UserProfileField.MaxAge); + + internal UserProfile(User user) + { + _user = user; + } + } +} \ No newline at end of file diff --git a/Realm/Realm/Sync/UserState.cs b/Realm/Realm/Sync/UserState.cs index 07b4ae13e0..b7deb34315 100644 --- a/Realm/Realm/Sync/UserState.cs +++ b/Realm/Realm/Sync/UserState.cs @@ -24,18 +24,18 @@ namespace Realms.Sync public enum UserState { /// - /// The user is logged out. Call with valid credentials to log the user back in. + /// The user is logged out. Call with valid credentials to log the user back in. /// LoggedOut, /// - /// The user is logged in, and any Realms associated with it are synchronizing with the Realm Object Server. + /// The user is logged in, and any Realms associated with it are synchronizing with MongoDB Realm. /// - Active, + LoggedIn, /// - /// The user has encountered a fatal error state, and cannot be used. + /// The user has been logged out and their local data has been removed. /// - Error + Removed } } \ No newline at end of file diff --git a/Realm/Realm/Thread Handover/IThreadConfined.cs b/Realm/Realm/Thread Handover/IThreadConfined.cs index b352b3f666..b2e689d2f8 100644 --- a/Realm/Realm/Thread Handover/IThreadConfined.cs +++ b/Realm/Realm/Thread Handover/IThreadConfined.cs @@ -36,7 +36,7 @@ internal interface IThreadConfined /// /// Gets a value representing the object's metadata. /// - RealmObject.Metadata Metadata { get; } + RealmObjectBase.Metadata Metadata { get; } /// /// Gets a value representing the native handle for that object. diff --git a/Realm/Realm/Thread Handover/ThreadSafeReference.cs b/Realm/Realm/Thread Handover/ThreadSafeReference.cs index ce986f0c96..df02f41cdb 100644 --- a/Realm/Realm/Thread Handover/ThreadSafeReference.cs +++ b/Realm/Realm/Thread Handover/ThreadSafeReference.cs @@ -44,7 +44,7 @@ public abstract class ThreadSafeReference { internal readonly ThreadSafeReferenceHandle Handle; - internal readonly RealmObject.Metadata Metadata; + internal readonly RealmObjectBase.Metadata Metadata; internal readonly Type ReferenceType; @@ -79,10 +79,10 @@ internal ThreadSafeReference(IThreadConfined value, Type type) /// The thread-confined to create a thread-safe reference to. It must be a collection, /// obtained by calling or a subsequent LINQ query. /// - /// The type of the contained in the query. + /// The type of the or contained in the query. /// A that can be passed to Realm.ResolveReference(ThreadSafeReference.Query) on a different thread. public static Query Create(IQueryable value) - where T : RealmObject + where T : RealmObjectBase { return new Query(value); } @@ -90,11 +90,11 @@ public static Query Create(IQueryable value) /// /// Initializes a new instance of the class. /// - /// The thread-confined to create a thread-safe reference to. - /// The type of the . + /// The thread-confined or to create a thread-safe reference to. + /// The type of the /. /// A that can be passed to Realm.ResolveReference(ThreadSafeReference.Object) on a different thread. public static Object Create(T value) - where T : RealmObject + where T : RealmObjectBase { return new Object(value); } @@ -104,7 +104,7 @@ public static Object Create(T value) /// /// /// The thread-confined to create a thread-safe reference to. It must be a collection - /// representing to-many relationship as a property of a . + /// that is a managed property of a or a . /// /// The type of the objects contained in the list. /// A that can be passed to Realm.ResolveReference(ThreadSafeReference.List) on a different thread. @@ -132,9 +132,9 @@ public static List Create(IList value) /// Prefer short-lived s as the data for the version of the source Realm /// will be retained until all references have been resolved or deallocated. /// - /// The type of the contained in the query. + /// The type of the / contained in the query. public class Query : ThreadSafeReference - where T : RealmObject + where T : RealmObjectBase { internal Query(IQueryable value) : base((RealmResults)value, Type.Query) { @@ -142,7 +142,7 @@ internal Query(IQueryable value) : base((RealmResults)value, Type.Query) } /// - /// A reference to a intended to be passed between threads. + /// A reference to a or an intended to be passed between threads. /// /// To resolve a thread-safe reference on a target on a different thread, pass it to /// Realm.ResolveReference(ThreadSafeReference.Object). @@ -156,11 +156,11 @@ internal Query(IQueryable value) : base((RealmResults)value, Type.Query) /// Prefer short-lived s as the data for the version of the source Realm /// will be retained until all references have been resolved or deallocated. /// - /// The type of the . + /// The type of the /. [SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "A nested class with generic argument is unlikely to be confused with System.Object.")] [SuppressMessage("Naming", "CA1720:Identifier contains type name", Justification = "This is intentional as ThreadSafeReference.Object represents an object.")] public class Object : ThreadSafeReference - where T : RealmObject + where T : RealmObjectBase { internal Object(T value) : base(value, Type.Object) { diff --git a/Realm/Realm/Weaving/IRealmObjectHelper.cs b/Realm/Realm/Weaving/IRealmObjectHelper.cs index 8504654021..f269b37d71 100644 --- a/Realm/Realm/Weaving/IRealmObjectHelper.cs +++ b/Realm/Realm/Weaving/IRealmObjectHelper.cs @@ -24,27 +24,27 @@ namespace Realms.Weaving public interface IRealmObjectHelper { /// - /// Creates an instance of a RealmObject. + /// Creates an instance of a RealmObjectBase. /// - /// The RealmObject. - RealmObject CreateInstance(); + /// The RealmObjectBase. + RealmObjectBase CreateInstance(); /// - /// A strongly typed, optimized method to add a RealmObject to the realm. + /// A strongly typed, optimized method to add a RealmObjectBase to the realm. /// - /// The RealmObject to add. + /// The RealmObjectBase to add. /// If set to true, update the existing value (if any). Otherwise, try to add and throw if an object with the same primary key already exists. /// /// If set to true will not invoke the setters of properties that have default values. /// Generally, should be true for newly created objects and false when updating existing ones. /// - void CopyToRealm(RealmObject instance, bool update, bool skipDefaults); + void CopyToRealm(RealmObjectBase instance, bool update, bool skipDefaults); /// - /// Tries the get primary key value from a RealmObject. + /// Tries the get primary key value from a RealmObjectBase. /// /// true, if the class has primary key, false otherwise. - /// The RealmObject instance. + /// The RealmObjectBase instance. /// The value of the primary key. bool TryGetPrimaryKeyValue(RealmObject instance, out object value); } diff --git a/Tests/Realm.Tests/Database/APITests.cs b/Tests/Realm.Tests/Database/APITests.cs index da65457e50..5f5f33b640 100644 --- a/Tests/Realm.Tests/Database/APITests.cs +++ b/Tests/Realm.Tests/Database/APITests.cs @@ -22,9 +22,7 @@ using System.Reflection; #endif using System.Threading.Tasks; -using Nito.AsyncEx; using NUnit.Framework; -using Realms; namespace Realms.Tests.Database { @@ -36,7 +34,7 @@ public class APITests [TestCase(typeof(RealmList))] public void RealmCollectionContravariance(Type type) { - Assert.That(typeof(IRealmCollection).IsAssignableFrom(type)); + Assert.That(typeof(IRealmCollection).IsAssignableFrom(type)); } [Test] diff --git a/Tests/Realm.Tests/Database/AccessTests.cs b/Tests/Realm.Tests/Database/AccessTests.cs index 93d6406b4b..d62a2d52bc 100644 --- a/Tests/Realm.Tests/Database/AccessTests.cs +++ b/Tests/Realm.Tests/Database/AccessTests.cs @@ -17,6 +17,7 @@ //////////////////////////////////////////////////////////////////////////// using System; +using MongoDB.Bson; using NUnit.Framework; using Realms.Exceptions; @@ -52,7 +53,21 @@ public void SetAndGetValue(string propertyName, object propertyValue) new object[] { "ByteArrayProperty", new byte[] { 0xde, 0xad, 0xbe, 0xef } }, new object[] { "ByteArrayProperty", Array.Empty() }, new object[] { "StringProperty", "hello" }, - new object[] { "DateTimeOffsetProperty", new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero) } + new object[] { "DateTimeOffsetProperty", new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero) }, + new object[] { "DecimalProperty", 123.456M }, + new object[] { "DecimalProperty", decimal.MinValue }, + new object[] { "DecimalProperty", decimal.MaxValue }, + new object[] { "DecimalProperty", decimal.One }, + new object[] { "DecimalProperty", decimal.MinusOne }, + new object[] { "DecimalProperty", decimal.Zero }, + new object[] { "Decimal128Property", new Decimal128(564.42343424323) }, + new object[] { "Decimal128Property", new Decimal128(decimal.MinValue) }, + new object[] { "Decimal128Property", new Decimal128(decimal.MaxValue) }, + new object[] { "Decimal128Property", Decimal128.MinValue }, + new object[] { "Decimal128Property", Decimal128.MaxValue }, + new object[] { "Decimal128Property", Decimal128.Zero }, + new object[] { "ObjectIdProperty", ObjectId.Empty }, + new object[] { "ObjectIdProperty", new ObjectId("5f63e882536de46d71877979") }, }; [TestCaseSource(nameof(SetAndReplaceWithNullCases))] @@ -86,11 +101,14 @@ public void SetValueAndReplaceWithNull(string propertyName, object propertyValue new object[] { "NullableSingleProperty", 123.123f }, new object[] { "NullableDoubleProperty", 123.123 }, new object[] { "NullableBooleanProperty", true }, + new object[] { "NullableDecimalProperty", 123.456M }, + new object[] { "NullableDecimal128Property", new Decimal128(123.456) }, new object[] { "ByteArrayProperty", new byte[] { 0xde, 0xad, 0xbe, 0xef } }, new object[] { "ByteArrayProperty", Array.Empty() }, new object[] { "StringProperty", "hello" }, new object[] { "StringProperty", string.Empty }, - new object[] { "NullableDateTimeOffsetProperty", new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero) } + new object[] { "NullableDateTimeOffsetProperty", new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero) }, + new object[] { "NullableObjectIdProperty", new ObjectId("5f63e882536de46d71877979") } }; [Test] @@ -151,6 +169,9 @@ public void RealmObjectProperties_WhenNotSet_ShouldHaveDefaultValues() Assert.That(obj.Int16Property, Is.EqualTo(default(short))); Assert.That(obj.Int32Property, Is.EqualTo(default(int))); Assert.That(obj.Int64Property, Is.EqualTo(default(long))); + Assert.That(obj.DecimalProperty, Is.EqualTo(default(decimal))); + Assert.That(obj.Decimal128Property, Is.EqualTo(default(Decimal128))); + Assert.That(obj.ObjectIdProperty, Is.EqualTo(default(ObjectId))); Assert.That(obj.NullableBooleanProperty, Is.EqualTo(default(bool?))); Assert.That(obj.NullableByteProperty, Is.EqualTo(default(byte?))); Assert.That(obj.NullableCharProperty, Is.EqualTo(default(char?))); @@ -160,6 +181,9 @@ public void RealmObjectProperties_WhenNotSet_ShouldHaveDefaultValues() Assert.That(obj.NullableInt16Property, Is.EqualTo(default(short?))); Assert.That(obj.NullableInt32Property, Is.EqualTo(default(int?))); Assert.That(obj.NullableInt64Property, Is.EqualTo(default(long?))); + Assert.That(obj.NullableDecimalProperty, Is.EqualTo(default(decimal?))); + Assert.That(obj.NullableDecimal128Property, Is.EqualTo(default(Decimal128?))); + Assert.That(obj.NullableObjectIdProperty, Is.EqualTo(default(ObjectId?))); } } } diff --git a/Tests/Realm.Tests/Database/AsyncTests.cs b/Tests/Realm.Tests/Database/AsyncTests.cs index 05248dcfd3..d775f03595 100644 --- a/Tests/Realm.Tests/Database/AsyncTests.cs +++ b/Tests/Realm.Tests/Database/AsyncTests.cs @@ -104,10 +104,8 @@ public void AsyncWrite_ShouldRethrowExceptions() TestHelpers.RunAsyncTest(async () => { const string message = "this is an exception from user code"; - await TestHelpers.AssertThrows(() => _realm.WriteAsync(_ => throw new Exception(message)), ex => - { - Assert.That(ex.Message, Is.EqualTo(message)); - }); + var ex = await TestHelpers.AssertThrows(() => _realm.WriteAsync(_ => throw new Exception(message))); + Assert.That(ex.Message, Is.EqualTo(message)); }); } @@ -128,14 +126,12 @@ public void RefreshAsync_Tests() Task.Run(() => { - using (var realm = Realm.GetInstance(_realm.Config)) + using var realm = GetRealm(_realm.Config); + var bgObj = realm.ResolveReference(reference); + realm.Write(() => { - var bgObj = realm.ResolveReference(reference); - realm.Write(() => - { - bgObj.StringValue = "123"; - }); - } + bgObj.StringValue = "123"; + }); }).Wait(); // <- wait to avoid the main thread autoupdating while idle Assert.That(obj.StringValue, Is.Null); @@ -149,7 +145,7 @@ public void RefreshAsync_Tests() Assert.That(changeTiming, Is.GreaterThan(idleTiming)); - async Task MeasureTiming(Func func) + static async Task MeasureTiming(Func func) { var sw = new Stopwatch(); sw.Start(); diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index 833d7c0a85..0f5c1ec645 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -18,7 +18,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; using NUnit.Framework; @@ -160,11 +159,9 @@ public void IList_IsReadOnly_WhenRealmIsReadOnly_ShouldBeTrue() var config = _configuration.ConfigWithPath(_configuration.DatabasePath); config.IsReadOnly = true; - using (var readonlyRealm = Realm.GetInstance(config)) - { - var readonlyContainer = readonlyRealm.All().Single(); - Assert.That(readonlyContainer.Items.IsReadOnly); - } + using var readonlyRealm = GetRealm(config); + var readonlyContainer = readonlyRealm.All().Single(); + Assert.That(readonlyContainer.Items.IsReadOnly); } [Test] @@ -620,30 +617,21 @@ public void Results_GetFiltered_WhenPredicateIsInvalid_Throws() [Test] public void List_IndexOf_WhenObjectBelongsToADifferentRealm_ShouldThrow() { - var config = new RealmConfiguration(Path.GetTempFileName()); - try + var owner = new Owner(); + _realm.Write(() => { - var owner = new Owner(); - _realm.Write(() => - { - _realm.Add(owner); - }); - - using (var otherRealm = Realm.GetInstance(config)) - { - var otherRealmDog = new Dog(); - otherRealm.Write(() => - { - otherRealm.Add(otherRealmDog); - }); + _realm.Add(owner); + }); - Assert.That(() => owner.Dogs.IndexOf(otherRealmDog), Throws.InstanceOf()); - } - } - finally + var config = new RealmConfiguration(Guid.NewGuid().ToString()); + using var otherRealm = GetRealm(config); + var otherRealmDog = new Dog(); + otherRealm.Write(() => { - Realm.DeleteRealm(config); - } + otherRealm.Add(otherRealmDog); + }); + + Assert.That(() => owner.Dogs.IndexOf(otherRealmDog), Throws.InstanceOf()); } [Test] diff --git a/Tests/Realm.Tests/Database/ConfigurationTests.cs b/Tests/Realm.Tests/Database/ConfigurationTests.cs index c2f2d0547c..00425a4319 100644 --- a/Tests/Realm.Tests/Database/ConfigurationTests.cs +++ b/Tests/Realm.Tests/Database/ConfigurationTests.cs @@ -114,28 +114,28 @@ public void UnableToOpenWithNoKey() { // Arrange _configuration.EncryptionKey = TestHelpers.GetEncryptionKey(); - using (Realm.GetInstance(_configuration)) + using (GetRealm(_configuration)) { } _configuration.EncryptionKey = null; // Assert - Assert.That(() => Realm.GetInstance(_configuration), Throws.TypeOf()); + Assert.That(() => GetRealm(_configuration), Throws.TypeOf()); } [Test] public void UnableToOpenWithKeyIfNotEncrypted() { // Arrange - using (Realm.GetInstance(_configuration)) + using (GetRealm(_configuration)) { } _configuration.EncryptionKey = TestHelpers.GetEncryptionKey(); // Assert - Assert.That(() => Realm.GetInstance(_configuration), Throws.TypeOf()); + Assert.That(() => GetRealm(_configuration), Throws.TypeOf()); } [Test] @@ -144,14 +144,14 @@ public void UnableToOpenWithDifferentKey() // Arrange _configuration.EncryptionKey = TestHelpers.GetEncryptionKey(); - using (Realm.GetInstance(_configuration)) + using (GetRealm(_configuration)) { } _configuration.EncryptionKey[0] = 42; // Assert - Assert.That(() => Realm.GetInstance(_configuration), Throws.TypeOf()); + Assert.That(() => GetRealm(_configuration), Throws.TypeOf()); } [Test] @@ -160,7 +160,7 @@ public void AbleToReopenEncryptedWithSameKey() // Arrange _configuration.EncryptionKey = TestHelpers.GetEncryptionKey(42); - using (Realm.GetInstance(_configuration)) + using (GetRealm(_configuration)) { } @@ -172,7 +172,7 @@ public void AbleToReopenEncryptedWithSameKey() // Assert Assert.That(() => { - using (Realm.GetInstance(config2)) + using (GetRealm(config2)) { } }, Throws.Nothing); @@ -185,7 +185,7 @@ public void ReadOnlyFilesMustExist() _configuration.IsReadOnly = true; // Assert - Assert.That(() => Realm.GetInstance(_configuration), Throws.TypeOf()); + Assert.That(() => GetRealm(_configuration), Throws.TypeOf()); } [Test, TestExplicit("Currently, a RealmMismatchedConfigException is thrown. Registered as #580")] @@ -198,15 +198,14 @@ public void ReadOnlyRealmsWillNotAutoMigrate() "ForMigrationsToCopyAndMigrate.realm", Path.GetFileName(_configuration.DatabasePath)); // Assert - Assert.That(() => Realm.GetInstance(_configuration), Throws.TypeOf()); + Assert.That(() => GetRealm(_configuration), Throws.TypeOf()); } [Test] public void ReadOnlyRealmsArentWritable() { - // Arrange _configuration.SchemaVersion = 0; // must set version before file can be opened readOnly - using (var openToCreate = Realm.GetInstance(_configuration)) + using (var openToCreate = GetRealm(_configuration)) { openToCreate.Write(() => { @@ -216,17 +215,15 @@ public void ReadOnlyRealmsArentWritable() _configuration.IsReadOnly = true; - using (var openedReadonly = Realm.GetInstance(_configuration)) + using var openedReadonly = GetRealm(_configuration); + + Assert.That(() => { - // Assert - Assert.That(() => + openedReadonly.Write(() => { - openedReadonly.Write(() => - { - openedReadonly.Add(new Person()); - }); - }, Throws.TypeOf()); - } + openedReadonly.Add(new Person()); + }); + }, Throws.TypeOf()); } [Test] @@ -245,7 +242,7 @@ public void DuplicateClassNames_ThrowsException() .Message.Contains("Foo.DuplicateClass").And .Message.Contains("Bar.DuplicateClass"); - Assert.That(() => Realm.GetInstance(config), constraint); + Assert.That(() => GetRealm(config), constraint); } } } diff --git a/Tests/Realm.Tests/Database/DateTimeTests.cs b/Tests/Realm.Tests/Database/DateTimeTests.cs index b53059d422..ce71ed949f 100644 --- a/Tests/Realm.Tests/Database/DateTimeTests.cs +++ b/Tests/Realm.Tests/Database/DateTimeTests.cs @@ -18,7 +18,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using NUnit.Framework; @@ -136,17 +135,15 @@ public class IndexedDateTimeOffsetObject : RealmObject public void IndexedDateTimeOffsetTest() { // Arrange - var config = new RealmConfiguration(Path.GetTempFileName()) + var config = new RealmConfiguration(Guid.NewGuid().ToString()) { ObjectClasses = new[] { typeof(IndexedDateTimeOffsetObject) } }; // Act and "assert" that no exception is thrown here - using (Realm.GetInstance(config)) + using (GetRealm(config)) { } - - Realm.DeleteRealm(config); } [Test] diff --git a/Tests/Realm.Tests/Database/DynamicAccessTests.cs b/Tests/Realm.Tests/Database/DynamicAccessTests.cs index 86a5389e8b..0ae5eed1c4 100644 --- a/Tests/Realm.Tests/Database/DynamicAccessTests.cs +++ b/Tests/Realm.Tests/Database/DynamicAccessTests.cs @@ -20,6 +20,7 @@ using System.Runtime.CompilerServices; using Microsoft.CSharp.RuntimeBinder; using NUnit.Framework; +using Realms.Dynamic; namespace Realms.Tests.Database { @@ -41,8 +42,8 @@ public void SimpleTest() dynamic allTypesObject; using (var transaction = _realm.BeginWrite()) { - allTypesObject = _realm.CreateObject("AllTypesObject", null); - Assert.That(allTypesObject, Is.InstanceOf()); + allTypesObject = _realm.DynamicApi.CreateObject("AllTypesObject", null); + Assert.That(allTypesObject, Is.InstanceOf()); allTypesObject.CharProperty = 'F'; allTypesObject.NullableCharProperty = 'o'; @@ -62,7 +63,7 @@ public void SetAndGetValue(string propertyName, T propertyValue) object allTypesObject; using (var transaction = _realm.BeginWrite()) { - allTypesObject = _realm.CreateObject("AllTypesObject", null); + allTypesObject = _realm.DynamicApi.CreateObject("AllTypesObject", null); CreateDynamicSetter(propertyName).Invoke(allTypesObject, propertyValue); transaction.Commit(); @@ -79,7 +80,7 @@ public void SetValueAndReplaceWithNull(string propertyName, T propertyValue) object allTypesObject; using (var transaction = _realm.BeginWrite()) { - allTypesObject = _realm.CreateObject("AllTypesObject", null); + allTypesObject = _realm.DynamicApi.CreateObject("AllTypesObject", null); CreateDynamicSetter(propertyName).Invoke(allTypesObject, propertyValue); transaction.Commit(); diff --git a/Tests/Realm.Tests/Database/DynamicEmbeddedTests.cs b/Tests/Realm.Tests/Database/DynamicEmbeddedTests.cs new file mode 100644 index 0000000000..6cc943f6b3 --- /dev/null +++ b/Tests/Realm.Tests/Database/DynamicEmbeddedTests.cs @@ -0,0 +1,437 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Realms.Dynamic; + +namespace Realms.Tests.Database +{ + [TestFixture(DynamicTestObjectType.RealmObject)] + [TestFixture(DynamicTestObjectType.DynamicRealmObject)] + [Preserve(AllMembers = true)] + public class DynamicEmbeddedTests : RealmInstanceTest + { + private class DynamicTask : RealmObject + { + [PrimaryKey] + public string Id { get; set; } + + public string Summary { get; set; } + + public CompletionReport CompletionReport { get; set; } + + public IList SubTasks { get; } + + public IList SubSubTasks { get; } + } + + private class DynamicSubTask : EmbeddedObject + { + public string Summary { get; set; } + + public CompletionReport CompletionReport { get; set; } + + public IList SubSubTasks { get; } + } + + private class DynamicSubSubTask : EmbeddedObject + { + public string Summary { get; set; } + + // Singular because we only expect 1 + [Backlink(nameof(DynamicSubTask.SubSubTasks))] + public IQueryable ParentSubTask { get; } + + [Backlink(nameof(DynamicTask.SubSubTasks))] + public IQueryable ParentTask { get; } + } + + private class CompletionReport : EmbeddedObject + { + public DateTimeOffset CompletionDate { get; set; } + + public string Remarks { get; set; } + } + + private readonly DynamicTestObjectType _mode; + + public DynamicEmbeddedTests(DynamicTestObjectType mode) + { + _mode = mode; + } + + protected override RealmConfiguration CreateConfiguration(string path) + { + return new RealmConfiguration(path) + { + ObjectClasses = new[] { typeof(DynamicTask), typeof(DynamicSubTask), typeof(CompletionReport), typeof(DynamicSubSubTask) }, + IsDynamic = _mode == DynamicTestObjectType.DynamicRealmObject + }; + } + + [Test] + public void QueryAll_WhenNoObjects_ReturnsNothing() + { + var tasks = _realm.DynamicApi.All(nameof(DynamicTask)); + Assert.That(tasks.Count(), Is.EqualTo(0)); + } + + [Test] + public void QueryAll_ByEmbedded_Fails() + { + var ex = Assert.Throws(() => _realm.DynamicApi.All(nameof(DynamicSubTask))); + Assert.That(ex.Message, Does.Contain("The class DynamicSubTask represents an embedded object and thus cannot be queried directly.")); + } + + [Test] + public void CreateObject_WhenEmbedded_CanAssignToParent() + { + var id = Guid.NewGuid().ToString(); + var now = DateTimeOffset.UtcNow; + _realm.Write(() => + { + var parent = _realm.DynamicApi.CreateObject(nameof(DynamicTask), id); + parent.Summary = "do great stuff!"; + + var report = _realm.DynamicApi.CreateEmbeddedObjectForProperty(parent, nameof(DynamicTask.CompletionReport)); + report.CompletionDate = now; + parent.CompletionReport.Remarks = "success!"; + }); + + dynamic addedParent = _realm.DynamicApi.Find(nameof(DynamicTask), id); + + Assert.That(addedParent, Is.Not.Null); + Assert.That(addedParent.Summary, Is.EqualTo("do great stuff!")); + Assert.That(addedParent.CompletionReport, Is.Not.Null); + Assert.That(addedParent.CompletionReport.CompletionDate, Is.EqualTo(now)); + Assert.That(addedParent.CompletionReport.Remarks, Is.EqualTo("success!")); + } + + [Test] + public void ListAdd_WhenEmbedded_Throws() + { + var ex = Assert.Throws(() => + { + _realm.Write(() => + { + var parent = _realm.DynamicApi.CreateObject(nameof(DynamicTask), Guid.NewGuid().ToString()); + parent.SubTasks.Add(new DynamicEmbeddedObject()); + }); + }); + + Assert.That(ex.Message, Does.Contain($"{nameof(_realm.DynamicApi)}.{nameof(_realm.DynamicApi.AddEmbeddedObjectToList)}")); + } + + [Test] + public void ListInsert_WhenEmbedded_Throws() + { + var ex = Assert.Throws(() => + { + _realm.Write(() => + { + var parent = _realm.DynamicApi.CreateObject(nameof(DynamicTask), Guid.NewGuid().ToString()); + parent.SubTasks.Insert(0, new DynamicEmbeddedObject()); + }); + }); + + Assert.That(ex.Message, Does.Contain($"{nameof(_realm.DynamicApi)}.{nameof(_realm.DynamicApi.InsertEmbeddedObjectInList)}")); + } + + [Test] + public void ListSet_WhenEmbedded_Throws() + { + var ex = Assert.Throws(() => + { + _realm.Write(() => + { + var parent = _realm.DynamicApi.CreateObject(nameof(DynamicTask), Guid.NewGuid().ToString()); + parent.SubTasks[0] = new DynamicEmbeddedObject(); + }); + }); + + Assert.That(ex.Message, Does.Contain($"{nameof(_realm.DynamicApi)}.{nameof(_realm.DynamicApi.SetEmbeddedObjectInList)}")); + } + + [Test] + public void RealmAddEmbeddedObjectToList() + { + var id = Guid.NewGuid().ToString(); + + _realm.Write(() => + { + var parent = _realm.DynamicApi.CreateObject(nameof(DynamicTask), id); + + var subTask = _realm.DynamicApi.AddEmbeddedObjectToList(parent.SubTasks); + subTask.Summary = "This is subtask level 1"; + }); + + dynamic addedParent = _realm.DynamicApi.Find(nameof(DynamicTask), id); + Assert.That(addedParent.SubTasks, Is.Not.Null); + Assert.That(addedParent.SubTasks.Count, Is.EqualTo(1)); + Assert.That(addedParent.SubTasks[0].Summary, Is.EqualTo("This is subtask level 1")); + + _realm.Write(() => + { + var secondSubTask = _realm.DynamicApi.AddEmbeddedObjectToList(addedParent.SubTasks); + secondSubTask.Summary = "This is a second subtask level 1"; + + var secondLevelSubTask = _realm.DynamicApi.AddEmbeddedObjectToList(addedParent.SubTasks[0].SubSubTasks); + secondLevelSubTask.Summary = "This is subtask level 2"; + }); + + Assert.That(addedParent.SubTasks.Count, Is.EqualTo(2)); + Assert.That(addedParent.SubTasks[1].Summary, Is.EqualTo("This is a second subtask level 1")); + Assert.That(addedParent.SubTasks[1].SubSubTasks.Count, Is.EqualTo(0)); + + Assert.That(addedParent.SubTasks[0].SubSubTasks.Count, Is.EqualTo(1)); + Assert.That(addedParent.SubTasks[0].SubSubTasks[0].Summary, Is.EqualTo("This is subtask level 2")); + } + + [Test] + public void RealmInsertEmbeddedObjectInList() + { + var id = Guid.NewGuid().ToString(); + + _realm.Write(() => + { + var parent = _realm.DynamicApi.CreateObject(nameof(DynamicTask), id); + + var subTask = _realm.DynamicApi.InsertEmbeddedObjectInList(parent.SubTasks, 0); + subTask.Summary = "first"; + }); + + dynamic addedParent = _realm.DynamicApi.Find(nameof(DynamicTask), id); + Assert.That(addedParent.SubTasks, Is.Not.Null); + Assert.That(addedParent.SubTasks.Count, Is.EqualTo(1)); + Assert.That(addedParent.SubTasks[0].Summary, Is.EqualTo("first")); + + _realm.Write(() => + { + var insertAt0 = _realm.DynamicApi.InsertEmbeddedObjectInList(addedParent.SubTasks, 0); + insertAt0.Summary = "This is now at 0"; + + addedParent.SubTasks[1].Summary = "This is now at 1"; + + var insertAt2 = _realm.DynamicApi.InsertEmbeddedObjectInList(addedParent.SubTasks, 2); + insertAt2.Summary = "This is now at 2"; + }); + + Assert.That(addedParent.SubTasks.Count, Is.EqualTo(3)); + Assert.That(addedParent.SubTasks[0].Summary, Is.EqualTo("This is now at 0")); + Assert.That(addedParent.SubTasks[1].Summary, Is.EqualTo("This is now at 1")); + Assert.That(addedParent.SubTasks[2].Summary, Is.EqualTo("This is now at 2")); + } + + [Test] + public void RealmSetEmbeddedObjectInList() + { + var id = Guid.NewGuid().ToString(); + + _realm.Write(() => + { + var parent = _realm.DynamicApi.CreateObject(nameof(DynamicTask), id); + + var task0 = _realm.DynamicApi.AddEmbeddedObjectToList(parent.SubTasks); + var task1 = _realm.DynamicApi.AddEmbeddedObjectToList(parent.SubTasks); + var task2 = _realm.DynamicApi.AddEmbeddedObjectToList(parent.SubTasks); + + task0.Summary = "initial at 0"; + task1.Summary = "initial at 1"; + task2.Summary = "initial at 2"; + }); + + dynamic addedParent = _realm.DynamicApi.Find(nameof(DynamicTask), id); + Assert.That(addedParent.SubTasks.Count, Is.EqualTo(3)); + Assert.That(addedParent.SubTasks[0].Summary, Is.EqualTo("initial at 0")); + Assert.That(addedParent.SubTasks[1].Summary, Is.EqualTo("initial at 1")); + Assert.That(addedParent.SubTasks[2].Summary, Is.EqualTo("initial at 2")); + + _realm.Write(() => + { + var newAt1 = _realm.DynamicApi.SetEmbeddedObjectInList(addedParent.SubTasks, 1); + newAt1.Summary = "new at 1"; + }); + + Assert.That(addedParent.SubTasks.Count, Is.EqualTo(3)); + Assert.That(addedParent.SubTasks[0].Summary, Is.EqualTo("initial at 0")); + Assert.That(addedParent.SubTasks[1].Summary, Is.EqualTo("new at 1")); + Assert.That(addedParent.SubTasks[2].Summary, Is.EqualTo("initial at 2")); + } + + [Test] + public void RealmSetEmbeddedObjectInList_WhenOutOfBounds_Throws() + { + Assert.Throws(() => + { + _realm.Write(() => + { + var parent = _realm.DynamicApi.CreateObject(nameof(DynamicTask), Guid.NewGuid().ToString()); + _realm.DynamicApi.SetEmbeddedObjectInList(parent.SubTasks, 0); + }); + }); + + Assert.Throws(() => + { + _realm.Write(() => + { + var parent = _realm.DynamicApi.CreateObject(nameof(DynamicTask), Guid.NewGuid().ToString()); + _realm.DynamicApi.SetEmbeddedObjectInList(parent.SubTasks, -1); + }); + }); + } + + [Test] + public void RealmInsertEmbeddedObjectInList_WhenOutOfBounds_Throws() + { + Assert.Throws(() => + { + _realm.Write(() => + { + var parent = _realm.DynamicApi.CreateObject(nameof(DynamicTask), Guid.NewGuid().ToString()); + + // Insert at list.Count works like Add + _realm.DynamicApi.InsertEmbeddedObjectInList(parent.SubTasks, 1); + }); + }); + + Assert.Throws(() => + { + _realm.Write(() => + { + var parent = _realm.DynamicApi.CreateObject(nameof(DynamicTask), Guid.NewGuid().ToString()); + _realm.DynamicApi.SetEmbeddedObjectInList(parent.SubTasks, -1); + }); + }); + } + + [Test] + public void List_RemoveAt() + { + var id = Guid.NewGuid().ToString(); + + _realm.Write(() => + { + var parent = _realm.DynamicApi.CreateObject(nameof(DynamicTask), id); + var first = _realm.DynamicApi.AddEmbeddedObjectToList(parent.SubTasks); + var second = _realm.DynamicApi.AddEmbeddedObjectToList(parent.SubTasks); + + first.Summary = "first"; + second.Summary = "second"; + }); + + dynamic addedParent = _realm.DynamicApi.Find(nameof(DynamicTask), id); + Assert.That(addedParent.SubTasks.Count, Is.EqualTo(2)); + + _realm.Write(() => + { + addedParent.SubTasks.RemoveAt(0); + }); + + Assert.That(addedParent.SubTasks.Count, Is.EqualTo(1)); + Assert.That(addedParent.SubTasks[0].Summary, Is.EqualTo("second")); + } + + [Test] + public void List_Remove() + { + var id = Guid.NewGuid().ToString(); + + dynamic second = null; + + _realm.Write(() => + { + var parent = _realm.DynamicApi.CreateObject(nameof(DynamicTask), id); + var first = _realm.DynamicApi.AddEmbeddedObjectToList(parent.SubTasks); + second = _realm.DynamicApi.AddEmbeddedObjectToList(parent.SubTasks); + + first.Summary = "first"; + second.Summary = "second"; + }); + + dynamic addedParent = _realm.DynamicApi.Find(nameof(DynamicTask), id); + Assert.That(addedParent.SubTasks.Count, Is.EqualTo(2)); + + _realm.Write(() => + { + addedParent.SubTasks.Remove(second); + }); + + Assert.That(addedParent.SubTasks.Count, Is.EqualTo(1)); + Assert.That(addedParent.SubTasks[0].Summary, Is.EqualTo("first")); + + _realm.Write(() => + { + addedParent.SubTasks.Remove(addedParent.SubTasks[0]); + }); + + Assert.That(addedParent.SubTasks.Count, Is.EqualTo(0)); + } + + [Test] + public void Embedded_Backlinks() + { + _realm.Write(() => + { + var task = _realm.DynamicApi.CreateObject(nameof(DynamicTask), Guid.NewGuid().ToString()); + task.Summary = "This is the task"; + + var subTask = _realm.DynamicApi.AddEmbeddedObjectToList(task.SubTasks); + subTask.Summary = "This is level 1 subtask"; + + var subSubTask1 = _realm.DynamicApi.AddEmbeddedObjectToList(task.SubSubTasks); + var subSubtask2 = _realm.DynamicApi.AddEmbeddedObjectToList(subTask.SubSubTasks); + }); + + var addedTask = _realm.DynamicApi.All(nameof(DynamicTask)).Single(); + var addedSubTask = addedTask.SubTasks[0]; + var addedSubSubTask1 = addedTask.SubSubTasks[0]; + var addedSubSubTask2 = addedSubTask.SubSubTasks[0]; + + Assert.That(addedSubSubTask1.ParentTask.Count, Is.EqualTo(1)); + Assert.That(addedSubSubTask1.ParentSubTask.Count, Is.EqualTo(0)); + Assert.That(((IQueryable)addedSubSubTask1.ParentTask).Single(), Is.EqualTo(addedTask)); + + Assert.That(addedSubSubTask2.ParentTask.Count, Is.EqualTo(0)); + Assert.That(addedSubSubTask2.ParentSubTask.Count, Is.EqualTo(1)); + Assert.That(((IQueryable)addedSubSubTask2.ParentSubTask).Single(), Is.EqualTo(addedSubTask)); + } + + [Test] + public void Embedded_DynamicBacklinks() + { + _realm.Write(() => + { + var task = _realm.DynamicApi.CreateObject(nameof(DynamicTask), Guid.NewGuid().ToString()); + task.Summary = "This is the task"; + + var report = _realm.DynamicApi.CreateEmbeddedObjectForProperty(task, nameof(DynamicTask.CompletionReport)); + report.Remarks = "ok"; + }); + + var addedTask = _realm.DynamicApi.All(nameof(DynamicTask)).Single(); + var addedReport = addedTask.CompletionReport; + + var reportParents = addedReport.GetBacklinks(nameof(DynamicTask), nameof(CompletionReport)); + Assert.That(reportParents.Count, Is.EqualTo(1)); + Assert.That(((IQueryable)reportParents).Single(), Is.EqualTo(addedTask)); + } + } +} diff --git a/Tests/Realm.Tests/Database/DynamicRelationshipTests.cs b/Tests/Realm.Tests/Database/DynamicRelationshipTests.cs index cef6c98a3f..38414f0fcd 100644 --- a/Tests/Realm.Tests/Database/DynamicRelationshipTests.cs +++ b/Tests/Realm.Tests/Database/DynamicRelationshipTests.cs @@ -21,8 +21,6 @@ using System.Linq; using NUnit.Framework; -// NOTE some of the following data comes from Tim's data used in the Browser screenshot in the Mac app store -// unlike the Cocoa definitions, we use Pascal casing for properties namespace Realms.Tests.Database { [Preserve(AllMembers = true)] @@ -82,25 +80,25 @@ protected override void CustomSetUp() _realm.Write(() => { - var o1 = _realm.CreateObject("DynamicOwner", null); + var o1 = _realm.DynamicApi.CreateObject("DynamicOwner", null); o1.Name = "Tim"; - var d1 = _realm.CreateObject("DynamicDog", null); + var d1 = _realm.DynamicApi.CreateObject("DynamicDog", null); d1.Name = "Bilbo Fleabaggins"; d1.Color = "Black"; o1.TopDog = d1; // set a one-one relationship o1.Dogs.Add(d1); - var d2 = _realm.CreateObject("DynamicDog", null); + var d2 = _realm.DynamicApi.CreateObject("DynamicDog", null); d2.Name = "Earl Yippington III"; d2.Color = "White"; o1.Dogs.Add(d2); // lonely people and dogs - var o2 = _realm.CreateObject("DynamicOwner", null); + var o2 = _realm.DynamicApi.CreateObject("DynamicOwner", null); o2.Name = "Dani"; // the dog-less - var d3 = _realm.CreateObject("DynamicDog", null); // will remain unassigned + var d3 = _realm.DynamicApi.CreateObject("DynamicDog", null); // will remain unassigned d3.Name = "Maggie Mongrel"; d3.Color = "Grey"; }); @@ -109,14 +107,14 @@ protected override void CustomSetUp() [Test] public void TimHasATopDog() { - var tim = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var tim = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); Assert.That(tim.TopDog.Name, Is.EqualTo("Bilbo Fleabaggins")); } [Test] public void TimHasTwoIterableDogs() { - var tim = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var tim = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); var dogNames = new List(); //// using foreach here is deliberately testing that syntax @@ -134,7 +132,7 @@ public void TimHasTwoIterableDogs() [Test] public void TimHasTwoIterableDogsListed() { - var tim = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var tim = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); var dogNames = new List(); var dogList = Enumerable.ToList(tim.Dogs); // this used to crash - issue 299 foreach (var dog in dogList) @@ -148,30 +146,30 @@ public void TimHasTwoIterableDogsListed() [Test] public void TimRetiredHisTopDog() { - var tim = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var tim = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); using (var trans = _realm.BeginWrite()) { tim.TopDog = null; trans.Commit(); } - var tim2 = _realm.All("DynamicOwner").ToArray().First(p => p.Name == "Tim"); + var tim2 = _realm.DynamicApi.All("DynamicOwner").ToArray().First(p => p.Name == "Tim"); Assert.That(tim2.TopDog, Is.Null); // the dog departure was saved } [Test] public void TimAddsADogLater() { - var tim = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var tim = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); Assert.That(tim.Dogs.Count, Is.EqualTo(2)); using (var trans = _realm.BeginWrite()) { - var dog3 = _realm.All("DynamicDog").ToArray().First(p => p.Name == "Maggie Mongrel"); + var dog3 = _realm.DynamicApi.All("DynamicDog").ToArray().First(p => p.Name == "Maggie Mongrel"); tim.Dogs.Add(dog3); trans.Commit(); } - var tim2 = _realm.All("DynamicOwner").ToArray().First(p => p.Name == "Tim"); + var tim2 = _realm.DynamicApi.All("DynamicOwner").ToArray().First(p => p.Name == "Tim"); Assert.That(tim2.Dogs.Count, Is.EqualTo(3)); Assert.That(tim2.Dogs[2].Name, Is.EqualTo("Maggie Mongrel")); } @@ -179,16 +177,16 @@ public void TimAddsADogLater() [Test] public void TimAddsADogByInsert() { - var tim = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); // use Single for a change + var tim = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); // use Single for a change Assert.That(tim.Dogs.Count, Is.EqualTo(2)); using (var trans = _realm.BeginWrite()) { - var dog3 = _realm.All("DynamicDog").ToArray().First(p => p.Name == "Maggie Mongrel"); + var dog3 = _realm.DynamicApi.All("DynamicDog").ToArray().First(p => p.Name == "Maggie Mongrel"); tim.Dogs.Insert(1, dog3); trans.Commit(); } - var tim2 = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var tim2 = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); Assert.That(tim2.Dogs.Count, Is.EqualTo(3)); Assert.That(tim2.Dogs[1].Name, Is.EqualTo("Maggie Mongrel")); Assert.That(tim2.Dogs[2].Name, Is.EqualTo("Earl Yippington III")); @@ -197,7 +195,7 @@ public void TimAddsADogByInsert() [Test] public void TimLosesHisDogsByOrder() { - var tim = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var tim = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); Assert.That(tim.Dogs.Count, Is.EqualTo(2)); using (var trans = _realm.BeginWrite()) { @@ -205,7 +203,7 @@ public void TimLosesHisDogsByOrder() trans.Commit(); } - var tim2 = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var tim2 = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); Assert.That(tim2.Dogs.Count, Is.EqualTo(1)); Assert.That(tim2.Dogs[0].Name, Is.EqualTo("Earl Yippington III")); using (var trans = _realm.BeginWrite()) @@ -214,7 +212,7 @@ public void TimLosesHisDogsByOrder() trans.Commit(); } - var tim3 = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var tim3 = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); Assert.That(tim2.Dogs.Count, Is.EqualTo(0)); Assert.That(tim3.Dogs.Count, Is.EqualTo(0)); // reloaded object has same empty related set } @@ -222,7 +220,7 @@ public void TimLosesHisDogsByOrder() [Test] public void TimLosesHisDogsInOneClear() { - var tim = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var tim = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); Assert.That(tim.Dogs.Count, Is.EqualTo(2)); using (var trans = _realm.BeginWrite()) { @@ -230,15 +228,15 @@ public void TimLosesHisDogsInOneClear() trans.Commit(); } - var tim2 = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var tim2 = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); Assert.That(tim2.Dogs.Count, Is.EqualTo(0)); } [Test] public void TimLosesBilbo() { - var bilbo = _realm.All("DynamicDog").ToArray().Single(p => p.Name == "Bilbo Fleabaggins"); - var tim = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var bilbo = _realm.DynamicApi.All("DynamicDog").ToArray().Single(p => p.Name == "Bilbo Fleabaggins"); + var tim = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); Assert.That(tim.Dogs.Count, Is.EqualTo(2)); using (var trans = _realm.BeginWrite()) { @@ -246,7 +244,7 @@ public void TimLosesBilbo() trans.Commit(); } - var tim2 = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var tim2 = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); Assert.That(tim2.Dogs.Count, Is.EqualTo(1)); Assert.That(tim2.Dogs[0].Name, Is.EqualTo("Earl Yippington III")); } @@ -254,14 +252,14 @@ public void TimLosesBilbo() [Test] public void DaniHasNoTopDog() { - var dani = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Dani"); + var dani = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Dani"); Assert.That(dani.TopDog, Is.Null); } [Test] public void DaniHasNoDogs() { - var dani = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Dani"); + var dani = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Dani"); Assert.That(dani.Dogs.Count, Is.EqualTo(0)); // ToMany relationships always return a RealmList var dogsIterated = 0; foreach (var d in dani.Dogs) @@ -275,9 +273,9 @@ public void DaniHasNoDogs() [Test] public void TestExceptionsFromEmptyListOutOfRange() { - var dani = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Dani"); + var dani = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Dani"); Assert.Throws(() => dani.Dogs.RemoveAt(0)); - var bilbo = _realm.All("DynamicDog").ToArray().Single(p => p.Name == "Bilbo Fleabaggins"); + var bilbo = _realm.DynamicApi.All("DynamicDog").ToArray().Single(p => p.Name == "Bilbo Fleabaggins"); dynamic scratch; // for assignment in following getters Assert.Throws(() => dani.Dogs.Insert(-1, bilbo)); Assert.Throws(() => dani.Dogs.Insert(1, bilbo)); @@ -287,7 +285,7 @@ public void TestExceptionsFromEmptyListOutOfRange() [Test] public void TestExceptionsFromIteratingEmptyList() { - var dani = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Dani"); + var dani = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Dani"); var iter = dani.Dogs.GetEnumerator(); Assert.IsNotNull(iter); var movedOnToFirstItem = iter.MoveNext(); @@ -299,9 +297,9 @@ public void TestExceptionsFromIteratingEmptyList() [Test] public void TestExceptionsFromTimsDogsOutOfRange() { - var tim = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var tim = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); Assert.Throws(() => tim.Dogs.RemoveAt(4)); - var bilbo = _realm.All("DynamicDog").ToArray().Single(p => p.Name == "Bilbo Fleabaggins"); + var bilbo = _realm.DynamicApi.All("DynamicDog").ToArray().Single(p => p.Name == "Bilbo Fleabaggins"); dynamic scratch; // for assignment in following getters Assert.Throws(() => tim.Dogs.Insert(-1, bilbo)); Assert.Throws(() => tim.Dogs.Insert(3, bilbo)); @@ -311,14 +309,14 @@ public void TestExceptionsFromTimsDogsOutOfRange() [Test] public void Backlinks() { - var tim = _realm.All("DynamicOwner").ToArray().Single(o => o.Name == "Tim"); + var tim = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(o => o.Name == "Tim"); foreach (var dog in tim.Dogs) { Assert.That(dog.Owners, Is.EquivalentTo(new[] { tim })); } - var dani = _realm.All("DynamicOwner").ToArray().Single(o => o.Name == "Dani"); - var maggie = _realm.All("DynamicDog").ToArray().Single(d => d.Name == "Maggie Mongrel"); + var dani = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(o => o.Name == "Dani"); + var maggie = _realm.DynamicApi.All("DynamicDog").ToArray().Single(d => d.Name == "Maggie Mongrel"); Assert.That(maggie.Owners, Is.Empty); _realm.Write(() => dani.Dogs.Add(maggie)); @@ -328,13 +326,13 @@ public void Backlinks() [Test] public void DynamicBacklinks() { - var tim = _realm.All("DynamicOwner").ToArray().Single(o => o.Name == "Tim"); + var tim = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(o => o.Name == "Tim"); var topOwners = tim.TopDog.GetBacklinks("DynamicOwner", "TopDog"); Assert.That(topOwners, Is.EquivalentTo(new[] { tim })); - var dani = _realm.All("DynamicOwner").ToArray().Single(o => o.Name == "Dani"); - var maggie = _realm.All("DynamicDog").ToArray().Single(d => d.Name == "Maggie Mongrel"); + var dani = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(o => o.Name == "Dani"); + var maggie = _realm.DynamicApi.All("DynamicDog").ToArray().Single(d => d.Name == "Maggie Mongrel"); Assert.That(maggie.GetBacklinks("DynamicOwner", "TopDog"), Is.Empty); _realm.Write(() => dani.TopDog = maggie); @@ -344,7 +342,7 @@ public void DynamicBacklinks() [Test] public void PrimitiveList() { - var tim = _realm.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); + var tim = _realm.DynamicApi.All("DynamicOwner").ToArray().Single(p => p.Name == "Tim"); Assert.That(tim.Tags.Count, Is.EqualTo(0)); diff --git a/Tests/Realm.Tests/Database/EmbeddedObjectsTests.cs b/Tests/Realm.Tests/Database/EmbeddedObjectsTests.cs new file mode 100644 index 0000000000..2f01f25932 --- /dev/null +++ b/Tests/Realm.Tests/Database/EmbeddedObjectsTests.cs @@ -0,0 +1,682 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Linq; +using System.Reflection; +using NUnit.Framework; +using Realms.Exceptions; + +namespace Realms.Tests.Database +{ + [TestFixture, Preserve(AllMembers = true)] + public class EmbeddedObjectsTests : RealmInstanceTest + { + [Test] + public void EmbeddedObject_WhenUnmanaged_CanGetAndSetProperties() + { + var obj = new ObjectWithEmbeddedProperties + { + AllTypesObject = CreateEmbeddedAllTypesObject() + }; + + obj.ListOfAllTypesObjects.Add(new EmbeddedAllTypesObject()); + obj.RecursiveObject = new EmbeddedLevel1 + { + String = "first", + Child = new EmbeddedLevel2 + { + String = "second", + Child = new EmbeddedLevel3 + { + String = "third" + } + }, + Children = + { + new EmbeddedLevel2 + { + String = "I'm in a list", + Child = new EmbeddedLevel3 + { + String = "child in a list" + }, + Children = + { + new EmbeddedLevel3 + { + String = "children in a list" + } + } + } + } + }; + + Assert.That(obj.AllTypesObject.NullableSingleProperty, Is.EqualTo(1.4f)); + Assert.That(obj.ListOfAllTypesObjects.Count, Is.EqualTo(1)); + Assert.That(obj.RecursiveObject.String, Is.EqualTo("first")); + Assert.That(obj.RecursiveObject.Child.String, Is.EqualTo("second")); + Assert.That(obj.RecursiveObject.Child.Child.String, Is.EqualTo("third")); + Assert.That(obj.RecursiveObject.Children[0].Child.String, Is.EqualTo("child in a list")); + Assert.That(obj.RecursiveObject.Children[0].Children[0].String, Is.EqualTo("children in a list")); + } + + [Test] + public void EmbeddedParent_CanBeAddedToRealm() + { + var embedded = CreateEmbeddedAllTypesObject(); + var parent = new ObjectWithEmbeddedProperties + { + AllTypesObject = embedded + }; + + _realm.Write(() => + { + _realm.Add(parent); + }); + + Assert.That(parent.IsManaged); + Assert.That(parent.AllTypesObject.IsManaged); + Assert.That(embedded.IsManaged); + + var copy = CreateEmbeddedAllTypesObject(); + + var properties = typeof(EmbeddedAllTypesObject).GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(p => !p.HasCustomAttribute()); + + foreach (var prop in properties) + { + Assert.That(prop.GetValue(parent.AllTypesObject), Is.EqualTo(prop.GetValue(copy))); + } + } + + [Test] + public void EmbeddedParent_CanBeAddedWhenPropertyIsNull() + { + var parent = new ObjectWithEmbeddedProperties(); + Assert.That(parent.AllTypesObject, Is.Null); + + _realm.Write(() => + { + _realm.Add(parent); + }); + + Assert.That(parent.IsManaged); + Assert.That(parent.AllTypesObject, Is.Null); + Assert.That(parent.ListOfAllTypesObjects, Is.Empty); + } + + [Test] + public void EmbeddedParent_CanOverwriteEmbeddedProperty() + { + var parent = new ObjectWithEmbeddedProperties + { + AllTypesObject = new EmbeddedAllTypesObject + { + DoubleProperty = 123.456 + } + }; + + _realm.Write(() => + { + _realm.Add(parent); + }); + + var firstEmbedded = parent.AllTypesObject; + Assert.True(firstEmbedded.IsManaged); + Assert.True(firstEmbedded.IsValid); + + _realm.Write(() => + { + parent.AllTypesObject = new EmbeddedAllTypesObject + { + DoubleProperty = -987.654 + }; + }); + + Assert.That(parent.AllTypesObject.DoubleProperty, Is.EqualTo(-987.654)); + + Assert.True(firstEmbedded.IsManaged); + Assert.False(firstEmbedded.IsValid); + } + + [Test] + public void EmbeddedParent_WithList_CanBeAddedToRealm() + { + var parent = new ObjectWithEmbeddedProperties + { + ListOfAllTypesObjects = + { + new EmbeddedAllTypesObject + { + Int32Property = 1 + }, + new EmbeddedAllTypesObject + { + Int32Property = 2 + } + } + }; + + _realm.Write(() => + { + _realm.Add(parent); + }); + + Assert.That(parent.IsManaged); + Assert.That(parent.ListOfAllTypesObjects.AsRealmCollection().IsValid); + Assert.That(parent.ListOfAllTypesObjects.Count, Is.EqualTo(2)); + Assert.That(parent.ListOfAllTypesObjects[0].Int32Property, Is.EqualTo(1)); + Assert.That(parent.ListOfAllTypesObjects[1].Int32Property, Is.EqualTo(2)); + } + + [Test] + public void ListOfEmbeddedObjects_CanAddItems() + { + var parent = new ObjectWithEmbeddedProperties(); + _realm.Write(() => + { + _realm.Add(parent); + }); + + var list = parent.ListOfAllTypesObjects; + _realm.Write(() => + { + parent.ListOfAllTypesObjects.Add(new EmbeddedAllTypesObject + { + DecimalProperty = 123.456M + }); + }); + + Assert.That(parent.ListOfAllTypesObjects, Is.SameAs(list)); + Assert.That(list.Count, Is.EqualTo(1)); + Assert.That(list[0].DecimalProperty, Is.EqualTo(123.456M)); + } + + [Test] + public void ListOfEmbeddedObjects_CanInsertItems() + { + var parent = new ObjectWithEmbeddedProperties(); + _realm.Write(() => + { + _realm.Add(parent); + }); + + var list = parent.ListOfAllTypesObjects; + _realm.Write(() => + { + parent.ListOfAllTypesObjects.Insert(0, new EmbeddedAllTypesObject + { + DecimalProperty = 123.456M + }); + }); + + Assert.That(list.Count, Is.EqualTo(1)); + Assert.That(list[0].DecimalProperty, Is.EqualTo(123.456M)); + + _realm.Write(() => + { + list.Insert(0, new EmbeddedAllTypesObject + { + DecimalProperty = 456.789M + }); + }); + + Assert.That(list.Count, Is.EqualTo(2)); + Assert.That(list[0].DecimalProperty, Is.EqualTo(456.789M)); + Assert.That(list[1].DecimalProperty, Is.EqualTo(123.456M)); + + _realm.Write(() => + { + list.Insert(1, new EmbeddedAllTypesObject + { + DecimalProperty = 0 + }); + }); + + Assert.That(list.Count, Is.EqualTo(3)); + Assert.That(list[0].DecimalProperty, Is.EqualTo(456.789M)); + Assert.That(list[1].DecimalProperty, Is.EqualTo(0)); + Assert.That(list[2].DecimalProperty, Is.EqualTo(123.456M)); + } + + [Test] + public void ListOfEmbeddedObjects_CanSetItems() + { + var parent = new ObjectWithEmbeddedProperties(); + _realm.Write(() => + { + _realm.Add(parent); + }); + + var list = parent.ListOfAllTypesObjects; + _realm.Write(() => + { + parent.ListOfAllTypesObjects.Add(new EmbeddedAllTypesObject + { + StringProperty = "first" + }); + }); + + Assert.That(list.Count, Is.EqualTo(1)); + + var firstItem = list[0]; + Assert.That(firstItem.StringProperty, Is.EqualTo("first")); + + _realm.Write(() => + { + list[0] = new EmbeddedAllTypesObject + { + StringProperty = "updated" + }; + }); + + Assert.That(list.Count, Is.EqualTo(1)); + Assert.That(firstItem.IsValid, Is.False); + Assert.That(list[0].StringProperty, Is.EqualTo("updated")); + } + + [Test] + public void ListOfEmbeddedObjects_WhenItemIsRemoved_GetsDeleted() + { + var parent = new ObjectWithEmbeddedProperties + { + ListOfAllTypesObjects = { new EmbeddedAllTypesObject() } + }; + + _realm.Write(() => + { + _realm.Add(parent); + }); + + Assert.That(parent.ListOfAllTypesObjects.Count, Is.EqualTo(1)); + + var firstItem = parent.ListOfAllTypesObjects[0]; + + _realm.Write(() => + { + parent.ListOfAllTypesObjects.Remove(firstItem); + }); + + Assert.That(parent.ListOfAllTypesObjects.Count, Is.EqualTo(0)); + Assert.That(firstItem.IsValid, Is.False); + + _realm.Write(() => + { + parent.ListOfAllTypesObjects.Add(new EmbeddedAllTypesObject()); + }); + + Assert.That(parent.ListOfAllTypesObjects.Count, Is.EqualTo(1)); + + var secondItem = parent.ListOfAllTypesObjects[0]; + + _realm.Write(() => + { + parent.ListOfAllTypesObjects.RemoveAt(0); + }); + + Assert.That(parent.ListOfAllTypesObjects.Count, Is.EqualTo(0)); + Assert.That(secondItem.IsValid, Is.False); + } + + [Test] + public void Embedded_AddingALinkToManaged_Fails() + { + var parent = new ObjectWithEmbeddedProperties + { + RecursiveObject = new EmbeddedLevel1 { String = "a" } + }; + + var parent2 = new ObjectWithEmbeddedProperties + { + PrimaryKey = 1 + }; + + _realm.Write(() => + { + _realm.Add(parent); + _realm.Add(parent2); + }); + + var ex = Assert.Throws(() => + { + _realm.Write(() => + { + parent2.RecursiveObject = parent.RecursiveObject; + }); + }); + Assert.That(ex.Message, Is.EqualTo("Can't link to an embedded object that is already managed.")); + } + + [Test] + public void RecursiveEmbeddedList_AddingALinkToItself_Fails() + { + var parent = new ObjectWithEmbeddedProperties + { + RecursiveObject = new EmbeddedLevel1 + { + String = "a", + Child = new EmbeddedLevel2() + } + }; + + _realm.Write(() => + { + _realm.Add(parent); + }); + + var ex = Assert.Throws(() => + { + _realm.Write(() => + { + parent.RecursiveObject.Children.Add(parent.RecursiveObject.Child); + }); + }); + Assert.That(ex.Message, Is.EqualTo("Can't add, set, or insert an embedded object that is already managed.")); + } + + [Test] + public void RecursiveEmbedded_WhenDifferentReferences_Succeeds() + { + var parent = new ObjectWithEmbeddedProperties + { + RecursiveObject = new EmbeddedLevel1 + { + String = "a", + Child = new EmbeddedLevel2 + { + String = "b" + } + } + }; + + _realm.Write(() => + { + _realm.Add(parent); + }); + + Assert.That(parent.RecursiveObject.IsManaged); + Assert.That(parent.RecursiveObject.String, Is.EqualTo("a")); + Assert.That(parent.RecursiveObject.Child.IsManaged); + Assert.That(parent.RecursiveObject.Child.String, Is.EqualTo("b")); + Assert.That(parent.RecursiveObject.Child.Child, Is.Null); + + _realm.Write(() => + { + parent.RecursiveObject.Child.Child = new EmbeddedLevel3 + { + String = "c" + }; + }); + + Assert.That(parent.RecursiveObject.Child.Child.IsManaged); + Assert.That(parent.RecursiveObject.Child.Child.String, Is.EqualTo("c")); + } + + [Test] + public void EmbeddedParent_AddOrUpdate_DeletesOldChild() + { + var parent = new ObjectWithEmbeddedProperties + { + PrimaryKey = 123, + AllTypesObject = new EmbeddedAllTypesObject { StringProperty = "A" } + }; + + var firstChild = parent.AllTypesObject; + + _realm.Write(() => + { + _realm.Add(parent); + }); + + Assert.That(firstChild.IsValid); + Assert.That(firstChild.IsManaged); + + var updatingParent = new ObjectWithEmbeddedProperties + { + PrimaryKey = 123, + AllTypesObject = new EmbeddedAllTypesObject { StringProperty = "B" } + }; + + _realm.Write(() => + { + _realm.Add(updatingParent, update: true); + }); + + Assert.That(firstChild.IsValid, Is.False); + Assert.That(firstChild.IsManaged); + + Assert.That(updatingParent.AllTypesObject.StringProperty, Is.EqualTo("B")); + Assert.That(parent.AllTypesObject.StringProperty, Is.EqualTo("B")); + } + + [Test] + public void EmbeddedObject_WhenDeleted_IsRemovedFromParent() + { + var parent = new ObjectWithEmbeddedProperties + { + AllTypesObject = new EmbeddedAllTypesObject(), + ListOfAllTypesObjects = + { + new EmbeddedAllTypesObject(), + new EmbeddedAllTypesObject { StringProperty = "this will survive" } + } + }; + + _realm.Write(() => + { + _realm.Add(parent); + }); + + var directLink = parent.AllTypesObject; + var listLink = parent.ListOfAllTypesObjects[0]; + + _realm.Write(() => + { + _realm.Remove(directLink); + _realm.Remove(listLink); + }); + + Assert.That(parent.AllTypesObject, Is.Null); + Assert.That(parent.ListOfAllTypesObjects.Count, Is.EqualTo(1)); + Assert.That(parent.ListOfAllTypesObjects[0].StringProperty, Is.EqualTo("this will survive")); + } + + [Test] + public void EmbeddedParent_AddOrUpdate_DeletesOldChildren() + { + var parent = new ObjectWithEmbeddedProperties + { + PrimaryKey = 123, + ListOfAllTypesObjects = + { + new EmbeddedAllTypesObject { StringProperty = "A" } + } + }; + + var firstChild = parent.ListOfAllTypesObjects[0]; + + _realm.Write(() => + { + _realm.Add(parent, update: true); + }); + + Assert.That(firstChild.IsValid); + Assert.That(firstChild.IsManaged); + + var updatingParent = new ObjectWithEmbeddedProperties + { + PrimaryKey = 123, + ListOfAllTypesObjects = + { + new EmbeddedAllTypesObject { StringProperty = "B" } + } + }; + + _realm.Write(() => + { + _realm.Add(updatingParent, update: true); + }); + + Assert.That(firstChild.IsValid, Is.False); + Assert.That(firstChild.IsManaged); + + Assert.That(parent.ListOfAllTypesObjects.Single().StringProperty, Is.EqualTo("B")); + Assert.That(updatingParent.ListOfAllTypesObjects.Single().StringProperty, Is.EqualTo("B")); + } + + [Test] + public void EmbeddedObject_WhenDeleted_DeletesDependentTree() + { + var parent = new ObjectWithEmbeddedProperties + { + RecursiveObject = new EmbeddedLevel1 + { + Child = new EmbeddedLevel2 + { + Children = + { + new EmbeddedLevel3() + } + }, + Children = + { + new EmbeddedLevel2 + { + Child = new EmbeddedLevel3() + } + } + } + }; + + _realm.Write(() => + { + _realm.Add(parent); + }); + + var previousEmbedded1 = _realm.AllEmbedded().ToArray(); + Assert.That(previousEmbedded1.Length, Is.EqualTo(1)); + var previousEmbedded2 = _realm.AllEmbedded().ToArray(); + Assert.That(previousEmbedded2.Length, Is.EqualTo(2)); + var previousEmbedded3 = _realm.AllEmbedded().ToArray(); + Assert.That(previousEmbedded3.Length, Is.EqualTo(2)); + + _realm.Write(() => + { + parent.RecursiveObject = new EmbeddedLevel1(); + }); + + foreach (var previous in previousEmbedded3) + { + Assert.That(previous.IsValid, Is.False); + } + + Assert.That(_realm.AllEmbedded().Count(), Is.EqualTo(1)); + Assert.That(_realm.AllEmbedded().Count(), Is.EqualTo(0)); + Assert.That(_realm.AllEmbedded().Count(), Is.EqualTo(0)); + } + + [Test] + public void StaticBacklinks() + { + var parent = new ObjectWithEmbeddedProperties + { + AllTypesObject = CreateEmbeddedAllTypesObject() + }; + + _realm.Write(() => + { + _realm.Add(parent); + }); + + Assert.That(parent.AllTypesObject.ContainersObjects.Count(), Is.EqualTo(1)); + Assert.That(parent.AllTypesObject.ContainersObjects.Single(), Is.EqualTo(parent)); + } + + [Test] + public void DynamicBacklinks() + { + var parent = new ObjectWithEmbeddedProperties + { + RecursiveObject = new EmbeddedLevel1 + { + String = "level 1", + Child = new EmbeddedLevel2 + { + String = "level 2" + } + } + }; + + _realm.Write(() => + { + _realm.Add(parent); + }); + + var topLevelBacklinks = parent.RecursiveObject.GetBacklinks(nameof(ObjectWithEmbeddedProperties), nameof(ObjectWithEmbeddedProperties.RecursiveObject)); + Assert.That(topLevelBacklinks.Count(), Is.EqualTo(1)); + Assert.That(topLevelBacklinks.Single(), Is.EqualTo(parent)); + + var secondLevelBacklinks = parent.RecursiveObject.Child.GetBacklinks(nameof(EmbeddedLevel1), nameof(EmbeddedLevel1.Child)); + Assert.That(secondLevelBacklinks.Count(), Is.EqualTo(1)); + Assert.That(secondLevelBacklinks.Single(), Is.EqualTo(parent.RecursiveObject)); + + // This should be empty because no objects link to it via .Children + var secondLevelChildrenBacklinks = parent.RecursiveObject.Child.GetBacklinks(nameof(EmbeddedLevel1), nameof(EmbeddedLevel1.Children)); + Assert.That(secondLevelChildrenBacklinks.Count(), Is.EqualTo(0)); + } + + private static EmbeddedAllTypesObject CreateEmbeddedAllTypesObject() + { + return new EmbeddedAllTypesObject + { + BooleanProperty = true, + ByteCounterProperty = 2, + ByteProperty = 5, + CharProperty = 'b', + DateTimeOffsetProperty = new DateTimeOffset(2020, 1, 2, 5, 6, 3, 2, TimeSpan.Zero), + Decimal128Property = 2432546.52435893468943943643M, + DecimalProperty = 234324.2139123912041M, + DoubleProperty = 432.321, + Int16CounterProperty = 123, + Int16Property = 546, + Int32CounterProperty = 324, + Int32Property = 549, + Int64CounterProperty = 943829483486934, + Int64Property = 90439604934069, + NullableBooleanProperty = null, + NullableByteCounterProperty = 1, + NullableByteProperty = null, + NullableCharProperty = 'b', + NullableDateTimeOffsetProperty = null, + NullableDecimal128Property = 123.567M, + NullableDecimalProperty = null, + NullableDoubleProperty = 24.6, + NullableInt16CounterProperty = null, + NullableInt16Property = 2, + NullableInt32CounterProperty = 98, + NullableInt32Property = null, + NullableInt64CounterProperty = 4, + NullableInt64Property = null, + NullableSingleProperty = 1.4f, + SingleProperty = 1.6f, + StringProperty = "abcd" + }; + } + } +} \ No newline at end of file diff --git a/Tests/Realm.Tests/Database/GetPrimaryKeyTests.cs b/Tests/Realm.Tests/Database/GetPrimaryKeyTests.cs index 69e329a4e2..c4393be145 100644 --- a/Tests/Realm.Tests/Database/GetPrimaryKeyTests.cs +++ b/Tests/Realm.Tests/Database/GetPrimaryKeyTests.cs @@ -101,7 +101,7 @@ public void GetPrimaryKey_WhenClassNotManagedAndHasPK_ShouldReturnPK(Type object Assert.That(pk, Is.EqualTo(pkValue)); } - private static IRealmObjectHelper GetHelper(RealmObject obj) + private static IRealmObjectHelper GetHelper(RealmObjectBase obj) { return obj.ObjectMetadata.Helper; } diff --git a/Tests/Realm.Tests/Database/InMemoryTests.cs b/Tests/Realm.Tests/Database/InMemoryTests.cs index ab5dbfb2ac..d9f3f0eb76 100644 --- a/Tests/Realm.Tests/Database/InMemoryTests.cs +++ b/Tests/Realm.Tests/Database/InMemoryTests.cs @@ -40,7 +40,7 @@ protected override void CustomSetUp() [Test] public void InMemoryRealm_WhenDeleted_RemovesAuxiliaryFiles() { - using (var realm = Realm.GetInstance(_config)) + using (var realm = GetRealm(_config)) { realm.Write(() => realm.Add(new IntPropertyObject { @@ -53,7 +53,7 @@ public void InMemoryRealm_WhenDeleted_RemovesAuxiliaryFiles() Assert.That(File.Exists(_config.DatabasePath), Is.False); - using (var realm = Realm.GetInstance(_config)) + using (var realm = GetRealm(_config)) { Assert.That(File.Exists(_config.DatabasePath)); Assert.That(realm.All(), Is.Empty); @@ -67,7 +67,7 @@ public void InMemoryRealm_ReceivesNotifications() { var tcs = new TaskCompletionSource(); - var realm = Realm.GetInstance(_config); + var realm = GetRealm(_config); try { @@ -86,13 +86,11 @@ public void InMemoryRealm_ReceivesNotifications() await Task.Run(() => { - using (var otherRealm = Realm.GetInstance(_config)) + using var otherRealm = GetRealm(_config); + otherRealm.Write(() => otherRealm.Add(new IntPropertyObject { - otherRealm.Write(() => otherRealm.Add(new IntPropertyObject - { - Int = 42 - })); - } + Int = 42 + })); }); var backgroundChanges = await tcs.Task; @@ -112,8 +110,8 @@ public void InMemoryRealm_ReceivesNotifications() [Test] public void InMemoryRealm_WhenMultipleInstancesOpen_DoesntDeleteData() { - var first = Realm.GetInstance(_config); - var second = Realm.GetInstance(_config); + var first = GetRealm(_config); + var second = GetRealm(_config); first.Write(() => first.Add(new IntPropertyObject { @@ -163,7 +161,7 @@ public void InMemoryRealm_WhenGarbageCollected_DeletesData() Assert.That(File.Exists(_config.DatabasePath), Is.False); - using (var realm = Realm.GetInstance(_config)) + using (var realm = GetRealm(_config)) { Assert.That(realm.All(), Is.Empty); } @@ -178,17 +176,17 @@ public void InMemoryRealm_WhenEncrypted_RequiresEncryptionKey() EncryptionKey = TestHelpers.GetEncryptionKey(23) }; - using (var realm = Realm.GetInstance(encryptedConfig)) + using (var realm = GetRealm(encryptedConfig)) { realm.Write(() => realm.Add(new IntPropertyObject { Int = 42 })); - Assert.That(() => Realm.GetInstance(_config), Throws.TypeOf()); + Assert.That(() => GetRealm(_config), Throws.TypeOf()); } - Assert.That(() => Realm.GetInstance(_config), Throws.Nothing); + Assert.That(() => GetRealm(_config), Throws.Nothing); } } } \ No newline at end of file diff --git a/Tests/Realm.Tests/Database/InstanceTests.cs b/Tests/Realm.Tests/Database/InstanceTests.cs index a969d33696..bb0304c263 100644 --- a/Tests/Realm.Tests/Database/InstanceTests.cs +++ b/Tests/Realm.Tests/Database/InstanceTests.cs @@ -17,6 +17,7 @@ //////////////////////////////////////////////////////////////////////////// using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -34,14 +35,14 @@ public class InstanceTests : RealmTest public void GetInstanceTest() { // Arrange, act and "assert" that no exception is thrown, using default location - Realm.GetInstance().Dispose(); + GetRealm().Dispose(); } [Test] public void InstanceIsClosedByDispose() { Realm temp; - using (temp = Realm.GetInstance()) + using (temp = GetRealm()) { Assert.That(!temp.IsClosed); } @@ -52,21 +53,10 @@ public void InstanceIsClosedByDispose() [Test] public void GetInstanceWithJustFilenameTest() { - var filename = Path.GetTempFileName(); + var filename = Guid.NewGuid().ToString(); - try + using (GetRealm(filename)) { - Assert.That(() => - { - using (Realm.GetInstance(filename)) - { - } - }, Throws.Nothing); - } - finally - { - var config = new RealmConfiguration(filename); - Realm.DeleteRealm(config); } } @@ -75,7 +65,7 @@ public void DeleteRealmWorksIfClosed() { // Arrange var config = RealmConfiguration.DefaultConfiguration; - var openRealm = Realm.GetInstance(config); + var openRealm = GetRealm(config); // Act openRealm.Dispose(); @@ -96,12 +86,12 @@ public void GetUniqueInstancesDifferentThreads() try { // Arrange - realm1 = Realm.GetInstance(); + realm1 = GetRealm(); // Act await Task.Run(() => { - realm2 = Realm.GetInstance(); + realm2 = GetRealm(); }); // Assert @@ -121,58 +111,37 @@ public void GetUniqueInstancesDifferentThreads() public void GetCachedInstancesSameThread() { // Arrange - using (var realm1 = Realm.GetInstance()) - using (var realm2 = Realm.GetInstance()) - { - // Assert - Assert.That(ReferenceEquals(realm1, realm2), Is.False); - Assert.That(realm1, Is.EqualTo(realm1)); // check equality with self - Assert.That(realm1.IsSameInstance(realm2)); - Assert.That(realm1, Is.EqualTo(realm2)); - } + using var realm1 = GetRealm(); + using var realm2 = GetRealm(); + + // Assert + Assert.That(ReferenceEquals(realm1, realm2), Is.False); + Assert.That(realm1, Is.EqualTo(realm1)); // check equality with self + Assert.That(realm1.IsSameInstance(realm2)); + Assert.That(realm1, Is.EqualTo(realm2)); } [Test] public void InstancesHaveDifferentHashes() { // Arrange - using (var realm1 = Realm.GetInstance()) - using (var realm2 = Realm.GetInstance()) - { - // Assert - Assert.That(ReferenceEquals(realm1, realm2), Is.False); - Assert.That(realm1.GetHashCode(), Is.Not.EqualTo(0)); - Assert.That(realm1.GetHashCode(), Is.Not.EqualTo(realm2.GetHashCode())); - } + using var realm1 = GetRealm(); + using var realm2 = GetRealm(); + + // Assert + Assert.That(ReferenceEquals(realm1, realm2), Is.False); + Assert.That(realm1.GetHashCode(), Is.Not.EqualTo(0)); + Assert.That(realm1.GetHashCode(), Is.Not.EqualTo(realm2.GetHashCode())); } [Test] public void DeleteRealmFailsIfOpenSameThread() { // Arrange - var openRealm = Realm.GetInstance(); + using var openRealm = GetRealm(); - try - { - // Assert - Assert.Throws(() => Realm.DeleteRealm(RealmConfiguration.DefaultConfiguration)); - } - finally - { - openRealm.Dispose(); - } - } - - [Test, Ignore("Currently doesn't work. Ref #199")] - public void GetInstanceShouldThrowIfFileIsLocked() - { - // Arrange - var databasePath = Path.GetTempFileName(); - using (File.Open(databasePath, FileMode.Open, FileAccess.Read, FileShare.None)) // Lock the file - { - // Act and assert - Assert.Throws(() => Realm.GetInstance(databasePath)); - } + // Assert + Assert.Throws(() => Realm.DeleteRealm(RealmConfiguration.DefaultConfiguration)); } [Test] @@ -181,7 +150,7 @@ public void GetInstanceShouldThrowWithBadPath() var path = TestHelpers.IsWindows ? "C:\\Windows" : "/"; // Arrange - Assert.Throws(() => Realm.GetInstance(path)); + Assert.Throws(() => GetRealm(path)); } private class LoneClass : RealmObject @@ -196,19 +165,17 @@ public void RealmWithOneClassWritesDesiredClass() RealmConfiguration.DefaultConfiguration.ObjectClasses = new[] { typeof(LoneClass) }; // Act - using (var lonelyRealm = Realm.GetInstance()) + using var lonelyRealm = GetRealm(); + lonelyRealm.Write(() => { - lonelyRealm.Write(() => + lonelyRealm.Add(new LoneClass { - lonelyRealm.Add(new LoneClass - { - Name = "The Singular" - }); + Name = "The Singular" }); + }); - // Assert - Assert.That(lonelyRealm.All().Count(), Is.EqualTo(1)); - } + // Assert + Assert.That(lonelyRealm.All().Count(), Is.EqualTo(1)); } [Test] @@ -218,14 +185,13 @@ public void RealmWithOneClassThrowsIfUseOther() RealmConfiguration.DefaultConfiguration.ObjectClasses = new[] { typeof(LoneClass) }; // Act and assert - using (var lonelyRealm = Realm.GetInstance()) + using var lonelyRealm = GetRealm(); + + // Can't create an object with a class not included in this Realm + lonelyRealm.Write(() => { - // Can't create an object with a class not included in this Realm - lonelyRealm.Write(() => - { - Assert.That(() => lonelyRealm.Add(new Person()), Throws.TypeOf()); - }); - } + Assert.That(() => lonelyRealm.Add(new Person()), Throws.TypeOf()); + }); } [Test] @@ -236,7 +202,7 @@ public void RealmObjectClassesOnlyAllowRealmObjects() // Act and assert // Can't have classes in the list which are not RealmObjects - Assert.That(() => Realm.GetInstance(), Throws.TypeOf()); + Assert.That(() => GetRealm(), Throws.TypeOf()); } [TestCase(true)] @@ -245,7 +211,7 @@ public void ShouldCompact_IsInvokedAfterOpening(bool shouldCompact) { var config = (RealmConfiguration)RealmConfiguration.DefaultConfiguration; - using (var realm = Realm.GetInstance(config)) + using (var realm = GetRealm(config)) { AddDummyData(realm); } @@ -261,7 +227,7 @@ public void ShouldCompact_IsInvokedAfterOpening(bool shouldCompact) return shouldCompact; }; - using (var realm = Realm.GetInstance(config)) + using (var realm = GetRealm(config)) { Assert.That(hasPrompted, Is.True); var newSize = new FileInfo(config.DatabasePath).Length; @@ -277,7 +243,7 @@ public void ShouldCompact_IsInvokedAfterOpening(bool shouldCompact) Assert.That(newSize, Is.EqualTo(oldSize)); } - Assert.That(realm.All().Count(), Is.EqualTo(500)); + Assert.That(realm.All().Count(), Is.EqualTo(DummyDataSize / 2)); } } @@ -293,7 +259,7 @@ public void Compact_ShouldReduceSize(bool encrypt, bool populate) config.EncryptionKey = TestHelpers.GetEncryptionKey(5); } - using (var realm = Realm.GetInstance(config)) + using (var realm = GetRealm(config)) { if (populate) { @@ -307,25 +273,23 @@ public void Compact_ShouldReduceSize(bool encrypt, bool populate) var finalSize = new FileInfo(config.DatabasePath).Length; Assert.That(initialSize >= finalSize); - using (var realm = Realm.GetInstance(config)) + using (var realm = GetRealm(config)) { - Assert.That(realm.All().Count(), Is.EqualTo(populate ? 500 : 0)); + Assert.That(realm.All().Count(), Is.EqualTo(populate ? DummyDataSize / 2 : 0)); } } [Test] public void Compact_WhenInTransaction_ShouldThrow() { - using (var realm = Realm.GetInstance()) + using var realm = GetRealm(); + Assert.That(() => { - Assert.That(() => + realm.Write(() => { - realm.Write(() => - { - Realm.Compact(); - }); - }, Throws.TypeOf()); - } + Realm.Compact(); + }); + }, Throws.TypeOf()); } [Test] @@ -333,22 +297,20 @@ public void Compact_WhenOpenOnDifferentThread_ShouldReturnFalse() { TestHelpers.RunAsyncTest(async () => { - using (var realm = Realm.GetInstance()) - { - AddDummyData(realm); + using var realm = GetRealm(); + AddDummyData(realm); - var initialSize = new FileInfo(realm.Config.DatabasePath).Length; - bool? isCompacted = null; - await Task.Run(() => - { - isCompacted = Realm.Compact(realm.Config); - }); + var initialSize = new FileInfo(realm.Config.DatabasePath).Length; + bool? isCompacted = null; + await Task.Run(() => + { + isCompacted = Realm.Compact(realm.Config); + }); - Assert.That(isCompacted, Is.False); - var finalSize = new FileInfo(realm.Config.DatabasePath).Length; + Assert.That(isCompacted, Is.False); + var finalSize = new FileInfo(realm.Config.DatabasePath).Length; - Assert.That(finalSize, Is.EqualTo(initialSize)); - } + Assert.That(finalSize, Is.EqualTo(initialSize)); }); } @@ -361,28 +323,26 @@ public void Compact_WhenOpenOnSameThread_ShouldReturnFalse() // However, this invalidates the opened realm, but we have no way of communicating that. // That is why, things seem fine until we try to run queries on the opened realm. // Once we handle caching in managed, we should reenable the test. - using (var realm = Realm.GetInstance()) - { - var initialSize = new FileInfo(realm.Config.DatabasePath).Length; - Assert.That(() => Realm.Compact(), Is.False); - var finalSize = new FileInfo(realm.Config.DatabasePath).Length; - Assert.That(finalSize, Is.EqualTo(initialSize)); - } + using var realm = GetRealm(); + + var initialSize = new FileInfo(realm.Config.DatabasePath).Length; + Assert.That(() => Realm.Compact(), Is.False); + var finalSize = new FileInfo(realm.Config.DatabasePath).Length; + Assert.That(finalSize, Is.EqualTo(initialSize)); } [Test] public void Compact_WhenResultsAreOpen_ShouldReturnFalse() { - using (var realm = Realm.GetInstance()) + using var realm = GetRealm(); + + var token = realm.All().SubscribeForNotifications((sender, changes, error) => { - var token = realm.All().SubscribeForNotifications((sender, changes, error) => - { - Console.WriteLine(changes?.InsertedIndices); - }); + Console.WriteLine(changes?.InsertedIndices); + }); - Assert.That(() => Realm.Compact(), Is.False); - token.Dispose(); - } + Assert.That(() => Realm.Compact(), Is.False); + token.Dispose(); } [Test] @@ -390,47 +350,46 @@ public void RealmChangedShouldFireForEveryInstance() { TestHelpers.RunAsyncTest(async () => { - using (var realm1 = Realm.GetInstance()) - using (var realm2 = Realm.GetInstance()) + using var realm1 = GetRealm(); + using var realm2 = GetRealm(); + + var changed1 = 0; + realm1.RealmChanged += (sender, e) => { - var changed1 = 0; - realm1.RealmChanged += (sender, e) => - { - changed1++; - }; + changed1++; + }; - var changed2 = 0; - realm2.RealmChanged += (sender, e) => - { - changed2++; - }; + var changed2 = 0; + realm2.RealmChanged += (sender, e) => + { + changed2++; + }; - realm1.Write(() => - { - realm1.Add(new Person()); - }); + realm1.Write(() => + { + realm1.Add(new Person()); + }); - await Task.Delay(50); + await Task.Delay(50); - Assert.That(changed1, Is.EqualTo(1)); - Assert.That(changed2, Is.EqualTo(1)); + Assert.That(changed1, Is.EqualTo(1)); + Assert.That(changed2, Is.EqualTo(1)); - Assert.That(realm1.All().Count(), Is.EqualTo(1)); - Assert.That(realm2.All().Count(), Is.EqualTo(1)); + Assert.That(realm1.All().Count(), Is.EqualTo(1)); + Assert.That(realm2.All().Count(), Is.EqualTo(1)); - realm2.Write(() => - { - realm2.Add(new Person()); - }); + realm2.Write(() => + { + realm2.Add(new Person()); + }); - await Task.Delay(50); + await Task.Delay(50); - Assert.That(changed1, Is.EqualTo(2)); - Assert.That(changed2, Is.EqualTo(2)); + Assert.That(changed1, Is.EqualTo(2)); + Assert.That(changed2, Is.EqualTo(2)); - Assert.That(realm1.All().Count(), Is.EqualTo(2)); - Assert.That(realm2.All().Count(), Is.EqualTo(2)); - } + Assert.That(realm1.All().Count(), Is.EqualTo(2)); + Assert.That(realm2.All().Count(), Is.EqualTo(2)); }); } @@ -439,8 +398,8 @@ public void Dispose_WhenOnTheSameThread_ShouldNotInvalidateOtherInstances() { Realm.DeleteRealm(RealmConfiguration.DefaultConfiguration); - var realm1 = Realm.GetInstance(); - var realm2 = Realm.GetInstance(); + var realm1 = GetRealm(); + var realm2 = GetRealm(); realm1.Write(() => realm1.Add(new Person())); realm1.Dispose(); @@ -457,8 +416,8 @@ public void Dispose_WhenCalledMultipletimes_ShouldNotInvalidateOtherInstances() { Realm.DeleteRealm(RealmConfiguration.DefaultConfiguration); - var realm1 = Realm.GetInstance(); - var realm2 = Realm.GetInstance(); + var realm1 = GetRealm(); + var realm2 = GetRealm(); realm1.Write(() => realm1.Add(new Person())); for (var i = 0; i < 5; i++) @@ -480,11 +439,11 @@ public void Dispose_WhenOnDifferentThread_ShouldNotInvalidateOtherInstances() { Realm.DeleteRealm(RealmConfiguration.DefaultConfiguration); - var realm1 = Realm.GetInstance(); + var realm1 = GetRealm(); await Task.Run(() => { - var realm2 = Realm.GetInstance(); + var realm2 = GetRealm(); realm2.Write(() => realm2.Add(new Person())); realm2.Dispose(); }); @@ -504,18 +463,18 @@ public void UsingDisposedRealm_ShouldThrowObjectDisposedException() { TestHelpers.RunAsyncTest(async () => { - var realm = Realm.GetInstance(); + var realm = GetRealm(); realm.Dispose(); Assert.That(realm.IsClosed); - var other = Realm.GetInstance(); + var other = GetRealm(); Assert.That(() => realm.Add(new Person()), Throws.TypeOf()); Assert.That(() => realm.All(), Throws.TypeOf()); - Assert.That(() => realm.All(nameof(Person)), Throws.TypeOf()); + Assert.That(() => realm.DynamicApi.All(nameof(Person)), Throws.TypeOf()); Assert.That(() => realm.BeginWrite(), Throws.TypeOf()); - Assert.That(() => realm.CreateObject(nameof(Person), null), Throws.TypeOf()); + Assert.That(() => realm.DynamicApi.CreateObject(nameof(Person), null), Throws.TypeOf()); Assert.That(() => realm.Find(0), Throws.TypeOf()); Assert.That(() => realm.GetHashCode(), Throws.TypeOf()); Assert.That(() => realm.IsSameInstance(other), Throws.TypeOf()); @@ -539,11 +498,10 @@ public void GetInstanceAsync_ExecutesMigrationsInBackground() { TestHelpers.RunAsyncTest(async () => { - Realm realm = null; var config = (RealmConfiguration)RealmConfiguration.DefaultConfiguration; config.SchemaVersion = 1; - using (var firstRealm = Realm.GetInstance(config)) + using (var firstRealm = GetRealm(config)) { Assert.That(firstRealm.All().Count(), Is.Zero); } @@ -562,39 +520,16 @@ public void GetInstanceAsync_ExecutesMigrationsInBackground() hasCompletedMigration = true; }; - Exception ex = null; -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Realm.GetInstanceAsync(config) - .ContinueWith(t => - { - if (t.IsFaulted) - { - ex = t.Exception; - } - else - { - realm = t.Result; - } - }, TaskScheduler.FromCurrentSynchronizationContext()); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - - var ticks = 0; - while (realm == null) - { - await Task.Delay(100); - ticks++; + var sw = new Stopwatch(); + sw.Start(); - if (ticks > 10) - { - Assert.Fail("Migration should have completed by now."); - } - } + using var realm = await GetRealmAsync(config).Timeout(1000); + + sw.Stop(); - Assert.That(ex, Is.Null); Assert.That(hasCompletedMigration); - Assert.That(ticks, Is.GreaterThanOrEqualTo(2)); + Assert.That(sw.ElapsedMilliseconds, Is.GreaterThanOrEqualTo(200)); Assert.That(realm.All().Count(), Is.EqualTo(1)); - realm.Dispose(); }); } @@ -604,13 +539,13 @@ public void GetInstanceAsync_ExecutesMigrationsInBackground() [TestCase(false, false)] public void WriteEncryptedCopy_WhenEncryptionKeyProvided_WritesACopy(bool originalEncrypted, bool copyEncrypted) { - var originalConfig = new RealmConfiguration(Path.GetTempFileName()); + var originalConfig = new RealmConfiguration(Guid.NewGuid().ToString()); if (originalEncrypted) { originalConfig.EncryptionKey = TestHelpers.GetEncryptionKey(42); } - var copyConfig = new RealmConfiguration(Path.GetTempFileName()); + var copyConfig = new RealmConfiguration(Guid.NewGuid().ToString()); if (copyEncrypted) { copyConfig.EncryptionKey = TestHelpers.GetEncryptionKey(14); @@ -618,134 +553,117 @@ public void WriteEncryptedCopy_WhenEncryptionKeyProvided_WritesACopy(bool origin File.Delete(copyConfig.DatabasePath); - try + using var original = GetRealm(originalConfig); + original.Write(() => { - using (var original = Realm.GetInstance(originalConfig)) + original.Add(new Person + { + FirstName = "John", + LastName = "Doe" + }); + + original.WriteCopy(copyConfig); + + using (var copy = GetRealm(copyConfig)) + { + var copiedPerson = copy.All().SingleOrDefault(); + Assert.That(copiedPerson, Is.Not.Null); + Assert.That(copiedPerson.FirstName, Is.EqualTo("John")); + Assert.That(copiedPerson.LastName, Is.EqualTo("Doe")); + } + + if (copyEncrypted) { - original.Write(() => + var invalidConfig = new RealmConfiguration(copyConfig.DatabasePath) { - original.Add(new Person - { - FirstName = "John", - LastName = "Doe" - }); - - original.WriteCopy(copyConfig); - - using (var copy = Realm.GetInstance(copyConfig)) - { - var copiedPerson = copy.All().SingleOrDefault(); - Assert.That(copiedPerson, Is.Not.Null); - Assert.That(copiedPerson.FirstName, Is.EqualTo("John")); - Assert.That(copiedPerson.LastName, Is.EqualTo("Doe")); - } - - if (copyEncrypted) - { - var invalidConfig = new RealmConfiguration(copyConfig.DatabasePath) - { - EncryptionKey = originalConfig.EncryptionKey - }; - - Assert.That(() => Realm.GetInstance(invalidConfig), Throws.TypeOf()); - } - }); + EncryptionKey = originalConfig.EncryptionKey + }; + + Assert.That(() => GetRealm(invalidConfig), Throws.TypeOf()); } - } - finally - { - Realm.DeleteRealm(originalConfig); - Realm.DeleteRealm(copyConfig); - } + }); } [TestCase(true)] [TestCase(false)] public void GetInstance_WhenCacheEnabled_ReturnsSameStates(bool enableCache) { - var config = new RealmConfiguration(Path.GetTempFileName()); + var config = new RealmConfiguration(Guid.NewGuid().ToString()); Assert.That(config.EnableCache, Is.True); config.EnableCache = enableCache; - try - { - var stateAccessor = typeof(Realm).GetField("_state", BindingFlags.Instance | BindingFlags.NonPublic); - using (var first = Realm.GetInstance(config)) - using (var second = Realm.GetInstance(config)) - { - Assert.That(enableCache == ReferenceEquals(stateAccessor.GetValue(first), stateAccessor.GetValue(second))); - } - } - finally - { - Realm.DeleteRealm(config); - } + var stateAccessor = typeof(Realm).GetField("_state", BindingFlags.Instance | BindingFlags.NonPublic); + using var first = GetRealm(config); + using var second = GetRealm(config); + Assert.That(enableCache == ReferenceEquals(stateAccessor.GetValue(first), stateAccessor.GetValue(second))); } [Test] public void GetInstance_WhenDynamic_ReadsSchemaFromDisk() { - var config = new RealmConfiguration(Path.GetTempFileName()) + var config = new RealmConfiguration(Guid.NewGuid().ToString()) { - ObjectClasses = new[] { typeof(AllTypesObject) } + ObjectClasses = new[] { typeof(AllTypesObject), typeof(ObjectWithEmbeddedProperties), typeof(EmbeddedAllTypesObject), typeof(EmbeddedLevel1), typeof(EmbeddedLevel2), typeof(EmbeddedLevel3) } }; - try + // Create the realm and add some objects + using (var realm = GetRealm(config)) { - // Create the realm and add some objects - using (var realm = Realm.GetInstance(config)) + realm.Write(() => realm.Add(new AllTypesObject { - realm.Write(() => realm.Add(new AllTypesObject + Int32Property = 42, + RequiredStringProperty = "This is required!" + })); + + realm.Write(() => realm.Add(new ObjectWithEmbeddedProperties + { + AllTypesObject = new EmbeddedAllTypesObject { - Int32Property = 42, - RequiredStringProperty = "This is required!" - })); - } + Int32Property = 24, + StringProperty = "This is not required!" + } + })); + } - config.IsDynamic = true; + config.IsDynamic = true; - using (var dynamicRealm = Realm.GetInstance(config)) - { - Assert.That(dynamicRealm.Schema.Count == 1); + using var dynamicRealm = GetRealm(config); + Assert.That(dynamicRealm.Schema.Count, Is.EqualTo(6)); - var objectSchema = dynamicRealm.Schema.Find(nameof(AllTypesObject)); - Assert.That(objectSchema, Is.Not.Null); + var allTypesSchema = dynamicRealm.Schema.Find(nameof(AllTypesObject)); + Assert.That(allTypesSchema, Is.Not.Null); + Assert.That(allTypesSchema.IsEmbedded, Is.False); - var hasExpectedProp = objectSchema.TryFindProperty(nameof(AllTypesObject.RequiredStringProperty), out var requiredStringProp); - Assert.That(hasExpectedProp); - Assert.That(requiredStringProp.Type, Is.EqualTo(PropertyType.String)); + var hasExpectedProp = allTypesSchema.TryFindProperty(nameof(AllTypesObject.RequiredStringProperty), out var requiredStringProp); + Assert.That(hasExpectedProp); + Assert.That(requiredStringProp.Type, Is.EqualTo(PropertyType.String)); - var ato = dynamicRealm.All(nameof(AllTypesObject)).Single(); - Assert.That(ato.RequiredStringProperty, Is.EqualTo("This is required!")); - } - } - finally - { - Realm.DeleteRealm(config); - } + var ato = dynamicRealm.DynamicApi.All(nameof(AllTypesObject)).Single(); + Assert.That(ato.RequiredStringProperty, Is.EqualTo("This is required!")); + + var embeddedAllTypesSchema = dynamicRealm.Schema.Find(nameof(EmbeddedAllTypesObject)); + Assert.That(embeddedAllTypesSchema, Is.Not.Null); + Assert.That(embeddedAllTypesSchema.IsEmbedded, Is.True); + + Assert.That(embeddedAllTypesSchema.TryFindProperty(nameof(EmbeddedAllTypesObject.StringProperty), out var stringProp), Is.True); + Assert.That(stringProp.Type, Is.EqualTo(PropertyType.String | PropertyType.Nullable)); + + var embeddedParent = dynamicRealm.DynamicApi.All(nameof(ObjectWithEmbeddedProperties)).Single(); + Assert.That(embeddedParent.AllTypesObject.StringProperty, Is.EqualTo("This is not required!")); } [Test] public void GetInstance_WhenDynamicAndDoesntExist_ReturnsEmptySchema() { - var config = new RealmConfiguration(Path.GetTempFileName()) + var config = new RealmConfiguration(Guid.NewGuid().ToString()) { IsDynamic = true }; - try - { - using (var realm = Realm.GetInstance(config)) - { - Assert.That(realm.Schema, Is.Empty); - } - } - finally - { - Realm.DeleteRealm(config); - } + using var realm = GetRealm(config); + Assert.That(realm.Schema, Is.Empty); } [TestCase("фоо-бар")] @@ -757,122 +675,104 @@ public void GetInstance_WhenDynamicAndDoesntExist_ReturnsEmptySchema() [TestCase("søren")] public void GetInstance_WhenPathContainsNonASCIICharacters_ShouldWork(string path) { - var folder = Path.Combine(Path.GetTempPath(), path); + var folder = Path.Combine(InteropConfig.DefaultStorageFolder, path); Directory.CreateDirectory(folder); var realmPath = Path.Combine(folder, "my.realm"); var config = new RealmConfiguration(realmPath); - try - { - using (var realm = Realm.GetInstance(config)) - { - realm.Write(() => realm.Add(new Person())); - Assert.AreEqual(1, realm.All().Count()); - } - } - finally - { - Realm.DeleteRealm(config); - } + + using var realm = GetRealm(config); + realm.Write(() => realm.Add(new Person())); + Assert.AreEqual(1, realm.All().Count()); } [Test] public void Freeze_FreezesTheRealm() { - using (var realm = Realm.GetInstance()) + using var realm = GetRealm(); + realm.Write(() => { - realm.Write(() => + var dog = realm.Add(new Dog { - var dog = realm.Add(new Dog - { - Name = "Charlie" - }); - - realm.Add(new Owner - { - Name = "George", - TopDog = dog, - Dogs = { dog } - }); + Name = "Charlie" }); - using (var frozenRealm = realm.Freeze()) + realm.Add(new Owner { - Assert.That(frozenRealm.IsFrozen); + Name = "George", + TopDog = dog, + Dogs = { dog } + }); + }); - var query = frozenRealm.All(); - Assert.That(query.AsRealmCollection().IsFrozen); + using var frozenRealm = realm.Freeze(); + Assert.That(frozenRealm.IsFrozen); - var owner = query.Single(); - Assert.That(owner.IsFrozen); - Assert.That(owner.TopDog.IsFrozen); - Assert.That(owner.Dogs.AsRealmCollection().IsFrozen); - Assert.That(owner.Dogs[0].IsFrozen); - } - } + var query = frozenRealm.All(); + Assert.That(query.AsRealmCollection().IsFrozen); + + var owner = query.Single(); + Assert.That(owner.IsFrozen); + Assert.That(owner.TopDog.IsFrozen); + Assert.That(owner.Dogs.AsRealmCollection().IsFrozen); + Assert.That(owner.Dogs[0].IsFrozen); } [Test] public void FrozenRealm_DoesntUpdate() { - using (var realm = Realm.GetInstance()) + using var realm = GetRealm(); + Owner george = null; + realm.Write(() => { - Owner george = null; - realm.Write(() => + var dog = realm.Add(new Dog { - var dog = realm.Add(new Dog - { - Name = "Charlie" - }); + Name = "Charlie" + }); - george = realm.Add(new Owner - { - Name = "George", - TopDog = dog, - Dogs = { dog } - }); + george = realm.Add(new Owner + { + Name = "George", + TopDog = dog, + Dogs = { dog } }); + }); - using (var frozenRealm = realm.Freeze()) + using var frozenRealm = realm.Freeze(); + realm.Write(() => + { + realm.Add(new Owner { - realm.Write(() => - { - realm.Add(new Owner - { - Name = "Peter" - }); + Name = "Peter" + }); - george.Name = "George Jr."; - }); + george.Name = "George Jr."; + }); - var owners = frozenRealm.All(); - Assert.That(owners.Count(), Is.EqualTo(1)); + var owners = frozenRealm.All(); + Assert.That(owners.Count(), Is.EqualTo(1)); - var frozenGeorge = owners.Single(); - Assert.That(frozenGeorge.Name, Is.EqualTo("George")); - } - } + var frozenGeorge = owners.Single(); + Assert.That(frozenGeorge.Name, Is.EqualTo("George")); } [Test] public void FrozenRealm_CannotWrite() { - using (var realm = Realm.GetInstance()) - using (var frozenRealm = realm.Freeze()) - { - Assert.Throws(() => frozenRealm.Write(() => { })); - Assert.Throws(() => frozenRealm.BeginWrite()); - } + using var realm = GetRealm(); + using var frozenRealm = realm.Freeze(); + + Assert.Throws(() => frozenRealm.Write(() => { })); + Assert.Throws(() => frozenRealm.BeginWrite()); } [Test] public void FrozenRealm_CannotSubscribeForNotifications() { - using (var realm = Realm.GetInstance()) - using (var frozenRealm = realm.Freeze()) - { - Assert.Throws(() => frozenRealm.RealmChanged += (_, __) => { }); - Assert.Throws(() => frozenRealm.RealmChanged -= (_, __) => { }); - } + using var realm = GetRealm(); + using var frozenRealm = realm.Freeze(); + + Assert.Throws(() => frozenRealm.RealmChanged += (_, __) => { }); + Assert.Throws(() => frozenRealm.RealmChanged -= (_, __) => { }); } [Test] @@ -880,39 +780,37 @@ public void FrozenRealms_CanBeUsedAcrossThreads() { TestHelpers.RunAsyncTest(async () => { - using (var realm = Realm.GetInstance()) + using var realm = GetRealm(); + + realm.Write(() => { - realm.Write(() => + var dog = realm.Add(new Dog { - var dog = realm.Add(new Dog - { - Name = "Charlie" - }); - - realm.Add(new Owner - { - Name = "George", - TopDog = dog, - Dogs = { dog } - }); + Name = "Charlie" }); - using (var frozenRealm = realm.Freeze()) + realm.Add(new Owner { - var georgeOnThreadOne = frozenRealm.All().Single(); - var georgeOnThreadTwo = await Task.Run(() => - { - var bgGeorge = frozenRealm.All().Single(); - Assert.That(bgGeorge.Name, Is.EqualTo("George")); - Assert.That(georgeOnThreadOne.IsValid); - Assert.That(georgeOnThreadOne.Name, Is.EqualTo("George")); - return bgGeorge; - }); - - Assert.That(georgeOnThreadTwo.IsValid); - Assert.That(georgeOnThreadOne.Name, Is.EqualTo(georgeOnThreadTwo.Name)); - } - } + Name = "George", + TopDog = dog, + Dogs = { dog } + }); + }); + + using var frozenRealm = realm.Freeze(); + + var georgeOnThreadOne = frozenRealm.All().Single(); + var georgeOnThreadTwo = await Task.Run(() => + { + var bgGeorge = frozenRealm.All().Single(); + Assert.That(bgGeorge.Name, Is.EqualTo("George")); + Assert.That(georgeOnThreadOne.IsValid); + Assert.That(georgeOnThreadOne.Name, Is.EqualTo("George")); + return bgGeorge; + }); + + Assert.That(georgeOnThreadTwo.IsValid); + Assert.That(georgeOnThreadOne.Name, Is.EqualTo(georgeOnThreadTwo.Name)); }); } @@ -922,7 +820,7 @@ public void FrozenRealms_GetGarbageCollected() TestHelpers.RunAsyncTest(async () => { WeakReference frozenRealmRef = null; - using (var realm = Realm.GetInstance()) + using (var realm = GetRealm()) { new Action(() => { @@ -947,15 +845,15 @@ public void FrozenRealms_GetGarbageCollected() public void Realm_Freeze_WhenFrozen_ReturnsSameInstance() { var realm = GetRealm(); - var frozenRealm = realm.Freeze(); + using var frozenRealm = realm.Freeze(); Assert.That(ReferenceEquals(realm, frozenRealm), Is.False); // Freezing a frozen realm should do nothing - var deepFrozenRealm = frozenRealm.Freeze(); + using var deepFrozenRealm = frozenRealm.Freeze(); Assert.That(ReferenceEquals(deepFrozenRealm, frozenRealm)); // Freezing the same Realm again should return a new instance - var anotherFrozenRealm = realm.Freeze(); + using var anotherFrozenRealm = realm.Freeze(); Assert.That(ReferenceEquals(realm, anotherFrozenRealm), Is.False); Assert.That(ReferenceEquals(frozenRealm, anotherFrozenRealm), Is.False); @@ -964,7 +862,7 @@ public void Realm_Freeze_WhenFrozen_ReturnsSameInstance() [Test] public void Realm_HittingMaxNumberOfVersions_Throws() { - var config = new RealmConfiguration(Path.GetTempFileName()) + var config = new RealmConfiguration(Guid.NewGuid().ToString()) { MaxNumberOfActiveVersions = 1 }; @@ -973,9 +871,11 @@ public void Realm_HittingMaxNumberOfVersions_Throws() Assert.Throws(() => realm.Write(() => { }), "Number of active versions (2) in the Realm exceeded the limit of 1"); } + private const int DummyDataSize = 200; + private static void AddDummyData(Realm realm) { - for (var i = 0; i < 1000; i++) + for (var i = 0; i < DummyDataSize; i++) { realm.Write(() => { @@ -987,7 +887,7 @@ private static void AddDummyData(Realm realm) }); } - for (var i = 0; i < 500; i++) + for (var i = 0; i < DummyDataSize / 2; i++) { realm.Write(() => { diff --git a/Tests/Realm.Tests/Database/LifetimeTests.cs b/Tests/Realm.Tests/Database/LifetimeTests.cs index a4238bff1d..b74a119587 100644 --- a/Tests/Realm.Tests/Database/LifetimeTests.cs +++ b/Tests/Realm.Tests/Database/LifetimeTests.cs @@ -63,7 +63,7 @@ public void FinalizedRealmsShouldNotInvalidateSiblingRealms() TestHelpers.IgnoreOnWindows("GC blocks on Windows"); // Arrange - var realm = Realm.GetInstance(RealmConfiguration.DefaultConfiguration.DatabasePath); + using var realm = Realm.GetInstance(RealmConfiguration.DefaultConfiguration.DatabasePath); var realmThatWillBeFinalized = GetWeakRealm(); Person person = null; realm.Write(() => @@ -78,9 +78,6 @@ public void FinalizedRealmsShouldNotInvalidateSiblingRealms() // Assert Assert.That(realmThatWillBeFinalized.IsAlive, Is.False); Assert.That(person.IsValid); - - // TearDown - realm.Dispose(); } [Test] diff --git a/Tests/Realm.Tests/Database/ListOfPrimitivesTests.cs b/Tests/Realm.Tests/Database/ListOfPrimitivesTests.cs index b68cd3e3f6..0b2c75340e 100644 --- a/Tests/Realm.Tests/Database/ListOfPrimitivesTests.cs +++ b/Tests/Realm.Tests/Database/ListOfPrimitivesTests.cs @@ -18,39 +18,18 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; +using MongoDB.Bson; using NUnit.Framework; using Realms.Exceptions; namespace Realms.Tests.Database { [TestFixture, Preserve(AllMembers = true)] - public class ListOfPrimitivesTests + public class ListOfPrimitivesTests : RealmInstanceTest { private static readonly Random _random = new Random(); - private Lazy _lazyRealm; - - // We capture the current SynchronizationContext when opening a Realm. - // However, NUnit replaces the SynchronizationContext after the SetUp method and before the async test method. - // That's why we make sure we open the Realm in the test method by accessing it lazily. - private Realm _realm => _lazyRealm.Value; - - [SetUp] - public void SetUp() - { - _lazyRealm = new Lazy(() => Realm.GetInstance(Path.GetTempFileName())); - } - - [TearDown] - public void TearDown() - { - if (_lazyRealm.IsValueCreated) - { - Realm.DeleteRealm(_realm.Config); - } - } #region TestCaseSources @@ -242,6 +221,89 @@ public static IEnumerable NullableInt64TestValues() } } + private static readonly IEnumerable _decimalValues = new[] + { + new decimal?[] { 1.4M }, + new decimal?[] { null }, + new decimal?[] { decimal.MinValue, decimal.MaxValue, 0 }, + new decimal?[] { -1, 3.4M, null, 5.3M, 9.54375843758349634963634M } + }; + + public static IEnumerable DecimalTestValues() + { + yield return new object[] { null }; + var values = _decimalValues.Select(v => v.Where(b => b.HasValue).Select(b => b.Value).ToArray()); + foreach (var value in values.Where(a => a.Any())) + { + yield return new object[] { value.ToArray() }; + } + } + + public static IEnumerable NullableDecimalTestValues() + { + yield return new object[] { null }; + foreach (var value in _decimalValues) + { + yield return new object[] { value.ToArray() }; + } + } + + private static readonly IEnumerable _decimal128Values = new[] + { + new Decimal128?[] { 1.4M }, + new Decimal128?[] { null }, + new Decimal128?[] { Decimal128.MinValue, decimal.MinValue, decimal.MaxValue, Decimal128.MaxValue, 0 }, + new Decimal128?[] { -1, 3.4M, null, 5.3M, -23.424389584396384963M } + }; + + public static IEnumerable Decimal128TestValues() + { + yield return new object[] { null }; + var values = _decimal128Values.Select(v => v.Where(b => b.HasValue).Select(b => b.Value).ToArray()); + foreach (var value in values.Where(a => a.Any())) + { + yield return new object[] { value.ToArray() }; + } + } + + public static IEnumerable NullableDecimal128TestValues() + { + yield return new object[] { null }; + foreach (var value in _decimal128Values) + { + yield return new object[] { value.ToArray() }; + } + } + + private static ObjectId GenerateRepetitiveObjectId(byte value) => new ObjectId(Enumerable.Range(0, 12).Select(_ => value).ToArray()); + + private static readonly IEnumerable _objectIdValues = new[] + { + new ObjectId?[] { new ObjectId("5f651b09f6cddff534c3cddf") }, + new ObjectId?[] { null }, + new ObjectId?[] { ObjectId.Empty, GenerateRepetitiveObjectId(0), GenerateRepetitiveObjectId(byte.MaxValue) }, + new ObjectId?[] { new ObjectId("5f651b2930643efeef987e5d"), GenerateRepetitiveObjectId(byte.MaxValue), null, new ObjectId("5f651c4cf755604f2fbf7440") } + }; + + public static IEnumerable ObjectIdTestValues() + { + yield return new object[] { null }; + var values = _objectIdValues.Select(v => v.Where(b => b.HasValue).Select(b => b.Value).ToArray()); + foreach (var value in values.Where(a => a.Any())) + { + yield return new object[] { value.ToArray() }; + } + } + + public static IEnumerable NullableObjectIdTestValues() + { + yield return new object[] { null }; + foreach (var value in _objectIdValues) + { + yield return new object[] { value.ToArray() }; + } + } + private static readonly IEnumerable _dateValues = new[] { new DateTimeOffset?[] { DateTimeOffset.UtcNow.AddDays(-4) }, @@ -360,6 +422,24 @@ public void Test_ManagedInt64List(long[] values) RunManagedTests(obj => obj.Int64List, values); } + [TestCaseSource(nameof(DecimalTestValues))] + public void Test_ManagedDecimalList(decimal[] values) + { + RunManagedTests(obj => obj.DecimalList, values); + } + + [TestCaseSource(nameof(Decimal128TestValues))] + public void Test_ManagedDecimal128List(Decimal128[] values) + { + RunManagedTests(obj => obj.Decimal128List, values); + } + + [TestCaseSource(nameof(ObjectIdTestValues))] + public void Test_ManagedObjectIdList(ObjectId[] values) + { + RunManagedTests(obj => obj.ObjectIdList, values); + } + [TestCaseSource(nameof(DateTestValues))] public void Test_ManagedDateTimeOffsetList(DateTimeOffset[] values) { @@ -432,6 +512,24 @@ public void Test_ManagedNullableInt64List(long?[] values) RunManagedTests(obj => obj.NullableInt64List, values); } + [TestCaseSource(nameof(NullableDecimalTestValues))] + public void Test_ManagedNullableDecimalList(decimal?[] values) + { + RunManagedTests(obj => obj.NullableDecimalList, values); + } + + [TestCaseSource(nameof(NullableDecimal128TestValues))] + public void Test_ManagedNullableDecimal128List(Decimal128?[] values) + { + RunManagedTests(obj => obj.NullableDecimal128List, values); + } + + [TestCaseSource(nameof(NullableObjectIdTestValues))] + public void Test_ManagedNullableObjectIdList(ObjectId?[] values) + { + RunManagedTests(obj => obj.NullableObjectIdList, values); + } + [TestCaseSource(nameof(NullableDateTestValues))] public void Test_ManagedNullableDateTimeOffsetList(DateTimeOffset?[] values) { @@ -541,7 +639,7 @@ private void RunManagedTests(Func> itemsGetter, T[] toA }, timeout: 100000); } - private static async Task RunManagedTestsCore(IList items, T[] toAdd) + private async Task RunManagedTestsCore(IList items, T[] toAdd) { var realm = (items as RealmList).Realm; @@ -603,13 +701,11 @@ private static async Task RunManagedTestsCore(IList items, T[] toAdd) var reference = ThreadSafeReference.Create(items); await Task.Run(() => { - using (var bgRealm = Realm.GetInstance(realm.Config)) + using var bgRealm = GetRealm(realm.Config); + var backgroundList = bgRealm.ResolveReference(reference); + for (var i = 0; i < backgroundList.Count; i++) { - var backgroundList = bgRealm.ResolveReference(reference); - for (var i = 0; i < backgroundList.Count; i++) - { - Assert.That(backgroundList[i], Is.EqualTo(toAdd[i])); - } + Assert.That(backgroundList[i], Is.EqualTo(toAdd[i])); } }); @@ -814,6 +910,24 @@ public void Test_UnmanagedInt64List(long[] values) RunUnmanagedTests(o => o.Int64List, values); } + [TestCaseSource(nameof(DecimalTestValues))] + public void Test_UnmanagedDecimalList(decimal[] values) + { + RunUnmanagedTests(obj => obj.DecimalList, values); + } + + [TestCaseSource(nameof(Decimal128TestValues))] + public void Test_UnmanagedDecimal128List(Decimal128[] values) + { + RunUnmanagedTests(obj => obj.Decimal128List, values); + } + + [TestCaseSource(nameof(ObjectIdTestValues))] + public void Test_UnmanagedObjectIdList(ObjectId[] values) + { + RunUnmanagedTests(obj => obj.ObjectIdList, values); + } + [TestCaseSource(nameof(DateTestValues))] public void Test_UnmanagedDateTimeOffsetList(DateTimeOffset[] values) { @@ -886,6 +1000,24 @@ public void Test_UnmanagedNullableInt64List(long?[] values) RunUnmanagedTests(o => o.NullableInt64List, values); } + [TestCaseSource(nameof(NullableDecimalTestValues))] + public void Test_UnmanagedNullableDecimalList(decimal?[] values) + { + RunUnmanagedTests(obj => obj.NullableDecimalList, values); + } + + [TestCaseSource(nameof(NullableDecimal128TestValues))] + public void Test_UnmanagedNullableDecimal128List(Decimal128?[] values) + { + RunUnmanagedTests(obj => obj.NullableDecimal128List, values); + } + + [TestCaseSource(nameof(NullableObjectIdTestValues))] + public void Test_UnmanagedONullablebjectIdList(ObjectId?[] values) + { + RunUnmanagedTests(obj => obj.NullableObjectIdList, values); + } + [TestCaseSource(nameof(NullableDateTestValues))] public void Test_UnmanagedNullableDateTimeOffsetList(DateTimeOffset?[] values) { diff --git a/Tests/Realm.Tests/Database/MigrationTests.cs b/Tests/Realm.Tests/Database/MigrationTests.cs index 32e18be0a5..f49cdfe412 100644 --- a/Tests/Realm.Tests/Database/MigrationTests.cs +++ b/Tests/Realm.Tests/Database/MigrationTests.cs @@ -24,7 +24,7 @@ namespace Realms.Tests.Database { [TestFixture, Preserve(AllMembers = true)] - public class MigrationTests : RealmTest + public class MigrationTests : RealmInstanceTest { private const string FileToMigrate = "ForMigrationsToCopyAndMigrate.realm"; @@ -33,28 +33,18 @@ public void TriggerMigrationBySchemaVersion() { var config = (RealmConfiguration)RealmConfiguration.DefaultConfiguration; - // Arrange - using (var realm = Realm.GetInstance()) + using (var realm = GetRealm()) { // new database doesn't push back a version number Assert.That(realm.Config.SchemaVersion, Is.EqualTo(0)); } - // Act var config2 = config.ConfigWithPath(config.DatabasePath); config2.SchemaVersion = 99; - Realm realm2 = null; // should be updated by DoesNotThrow - try - { - // Assert - Assert.That(() => realm2 = Realm.GetInstance(config2), Throws.Nothing); // same path, different version, should auto-migrate quietly - Assert.That(realm2.Config.SchemaVersion, Is.EqualTo(99)); - } - finally - { - realm2.Dispose(); - } + // same path, different version, should auto-migrate quietly + using var realm2 = GetRealm(config2); + Assert.That(realm2.Config.SchemaVersion, Is.EqualTo(99)); } [Test] @@ -67,7 +57,7 @@ public void TriggerMigrationBySchemaEditing() // Because Realms opened during migration are not immediately disposed of, they can't be deleted. // To circumvent that, we're leaking realm files. // See https://github.com/realm/realm-dotnet/issues/1357 - var path = TestHelpers.CopyBundledFileToDocuments(FileToMigrate, Path.GetTempFileName()); + var path = TestHelpers.CopyBundledFileToDocuments(FileToMigrate, Path.Combine(InteropConfig.DefaultStorageFolder, Guid.NewGuid().ToString())); var triggersSchemaFieldValue = string.Empty; @@ -78,7 +68,7 @@ public void TriggerMigrationBySchemaEditing() { Assert.That(oldSchemaVersion, Is.EqualTo(99)); - var oldPeople = migration.OldRealm.All("Person"); + var oldPeople = migration.OldRealm.DynamicApi.All("Person"); var newPeople = migration.NewRealm.All(); Assert.That(newPeople.Count(), Is.EqualTo(oldPeople.Count())); @@ -94,11 +84,9 @@ public void TriggerMigrationBySchemaEditing() } }; - using (var realm = Realm.GetInstance(configuration)) - { - var person = realm.All().Single(); - Assert.That(person.LastName, Is.EqualTo(triggersSchemaFieldValue)); - } + using var realm = GetRealm(configuration); + var person = realm.All().Single(); + Assert.That(person.LastName, Is.EqualTo(triggersSchemaFieldValue)); } [Test] @@ -107,7 +95,7 @@ public void ExceptionInMigrationCallback() // Because Realms opened during migration are not immediately disposed of, they can't be deleted. // To circumvent that, we're leaking realm files. // See https://github.com/realm/realm-dotnet/issues/1357 - var path = TestHelpers.CopyBundledFileToDocuments(FileToMigrate, Path.GetTempFileName()); + var path = TestHelpers.CopyBundledFileToDocuments(FileToMigrate, Path.Combine(InteropConfig.DefaultStorageFolder, Guid.NewGuid().ToString())); var dummyException = new Exception(); @@ -120,7 +108,7 @@ public void ExceptionInMigrationCallback() } }; - var ex = Assert.Throws(() => Realm.GetInstance(configuration).Dispose()); + var ex = Assert.Throws(() => GetRealm(configuration).Dispose()); Assert.That(ex.Flatten().InnerException, Is.SameAs(dummyException)); } @@ -131,7 +119,7 @@ public void MigrationTriggersDelete() var oldSchema = new Schema.RealmSchema.Builder(); { - var person = new Schema.ObjectSchema.Builder("Person"); + var person = new Schema.ObjectSchema.Builder("Person", isEmbedded: false); person.Add(new Schema.Property { Name = "Name", Type = Schema.PropertyType.String }); oldSchema.Add(person.Build()); } @@ -140,21 +128,21 @@ public void MigrationTriggersDelete() { realm.Write(() => { - dynamic person = realm.CreateObject("Person", null); + dynamic person = realm.DynamicApi.CreateObject("Person", null); person.Name = "Foo"; }); } var newSchema = new Schema.RealmSchema.Builder(); { - var person = new Schema.ObjectSchema.Builder("Person"); + var person = new Schema.ObjectSchema.Builder("Person", isEmbedded: false); person.Add(new Schema.Property { Name = "Name", Type = Schema.PropertyType.Int }); newSchema.Add(person.Build()); } using (var realm = Realm.GetInstance(new RealmConfiguration(path) { IsDynamic = true, ShouldDeleteIfMigrationNeeded = true }, newSchema.Build())) { - Assert.That(realm.All("Person"), Is.Empty); + Assert.That(realm.DynamicApi.All("Person"), Is.Empty); } } } diff --git a/Tests/Realm.Tests/Database/NotificationTests.cs b/Tests/Realm.Tests/Database/NotificationTests.cs index 8fd755806d..1b8bb797ae 100644 --- a/Tests/Realm.Tests/Database/NotificationTests.cs +++ b/Tests/Realm.Tests/Database/NotificationTests.cs @@ -28,7 +28,7 @@ namespace Realms.Tests.Database { [TestFixture, Preserve(AllMembers = true)] - public class NotificationTests + public class NotificationTests : RealmInstanceTest { private class OrderedContainer : RealmObject { @@ -47,29 +47,6 @@ public override string ToString() } } - private Lazy _lazyRealm; - - // We capture the current SynchronizationContext when opening a Realm. - // However, NUnit replaces the SynchronizationContext after the SetUp method and before the async test method. - // That's why we make sure we open the Realm in the test method by accessing it lazily. - private Realm _realm => _lazyRealm.Value; - - [SetUp] - public void SetUp() - { - _lazyRealm = new Lazy(() => Realm.GetInstance(Path.GetTempFileName())); - } - - [TearDown] - public void TearDown() - { - if (_lazyRealm.IsValueCreated) - { - _realm.Dispose(); - Realm.DeleteRealm(_realm.Config); - } - } - [Test] public void ShouldTriggerRealmChangedEvent() { @@ -87,15 +64,13 @@ public void ShouldTriggerRealmChangedEvent() [Test] public void RealmError_WhenNoSubscribers_OutputsMessageInConsole() { - using (var sw = new StringWriter()) - { - var original = Console.Error; - Console.SetError(sw); - _realm.NotifyError(new Exception()); + using var sw = new StringWriter(); + var original = Console.Error; + Console.SetError(sw); + _realm.NotifyError(new Exception()); - Assert.That(sw.ToString(), Does.Contain("exception").And.Contains("Realm.Error")); - Console.SetError(original); - } + Assert.That(sw.ToString(), Does.Contain("exception").And.Contains("Realm.Error")); + Console.SetError(original); } [Test] diff --git a/Tests/Realm.Tests/Database/ObjectIntegrationTests.cs b/Tests/Realm.Tests/Database/ObjectIntegrationTests.cs index a0ecb7f839..3c9fe3875d 100644 --- a/Tests/Realm.Tests/Database/ObjectIntegrationTests.cs +++ b/Tests/Realm.Tests/Database/ObjectIntegrationTests.cs @@ -18,7 +18,6 @@ using System; using System.Diagnostics; -using System.IO; using System.Linq; using NUnit.Framework; using Realms.Exceptions; @@ -166,18 +165,9 @@ public void AddAnObjectFromAnotherRealmShouldFail() Person p = null; _realm.Write(() => p = _realm.Add(new Person())); - var secondaryConfig = new RealmConfiguration(Path.GetTempFileName()); - try - { - using (var otherRealm = Realm.GetInstance(secondaryConfig)) - { - Assert.That(() => otherRealm.Add(p), Throws.TypeOf()); - } - } - finally - { - Realm.DeleteRealm(secondaryConfig); - } + var secondaryConfig = new RealmConfiguration(Guid.NewGuid().ToString()); + using var otherRealm = GetRealm(secondaryConfig); + Assert.That(() => otherRealm.Add(p), Throws.TypeOf()); } [Test] @@ -186,12 +176,10 @@ public void AddAnObject_WhenRealmIsDifferentInstanceOnSameThread_ShouldSucceed() var firstObject = new IntPrimaryKeyWithValueObject(); _realm.Write(() => _realm.Add(firstObject)); - using (var realm2 = Realm.GetInstance(_realm.Config)) - { - Assert.That(firstObject.IsManaged); - Assert.That(realm2.IsSameInstance(firstObject.Realm)); - realm2.Write(() => realm2.Add(firstObject)); - } + using var realm2 = GetRealm(_realm.Config); + Assert.That(firstObject.IsManaged); + Assert.That(realm2.IsSameInstance(firstObject.Realm)); + realm2.Write(() => realm2.Add(firstObject)); } [Test] diff --git a/Tests/Realm.Tests/Database/PerformanceTests.cs b/Tests/Realm.Tests/Database/PerformanceTests.cs index d73399598c..eaa2dc1d43 100644 --- a/Tests/Realm.Tests/Database/PerformanceTests.cs +++ b/Tests/Realm.Tests/Database/PerformanceTests.cs @@ -40,7 +40,7 @@ public void BindingPerformanceTest(int totalRecs, int recsPerTrans) { using (var trans = _realm.BeginWrite()) { - var hangOntoObjectsUntilCommit = new List(); + var hangOntoObjectsUntilCommit = new List(); for (var iTrans = 0; iTrans < recsPerTrans; ++iTrans) { var p = _realm.Add(new Person @@ -72,7 +72,7 @@ public void BindingCreateObjectPerformanceTest(int totalRecs, int recsPerTrans) { using (var trans = _realm.BeginWrite()) { - var hangOntoObjectsUntilCommit = new List(); + var hangOntoObjectsUntilCommit = new List(); for (var iTrans = 0; iTrans < recsPerTrans; ++iTrans) { var p = _realm.Add(new Person()); diff --git a/Tests/Realm.Tests/Database/PrimaryKeyTests.cs b/Tests/Realm.Tests/Database/PrimaryKeyTests.cs index 9122e91221..30c9db2a3d 100644 --- a/Tests/Realm.Tests/Database/PrimaryKeyTests.cs +++ b/Tests/Realm.Tests/Database/PrimaryKeyTests.cs @@ -18,13 +18,11 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; -using Nito.AsyncEx; +using MongoDB.Bson; using NUnit.Framework; -using Realms; using Realms.Exceptions; namespace Realms.Tests.Database @@ -33,27 +31,42 @@ namespace Realms.Tests.Database [TestFixture, Preserve(AllMembers = true)] public class PrimaryKeyTests : RealmInstanceTest { - [TestCase(typeof(PrimaryKeyCharObject), 'x', true)] - [TestCase(typeof(PrimaryKeyNullableCharObject), 'x', true)] - [TestCase(typeof(PrimaryKeyNullableCharObject), null, true)] - [TestCase(typeof(PrimaryKeyByteObject), (byte)42, true)] - [TestCase(typeof(PrimaryKeyNullableByteObject), (byte)42, true)] - [TestCase(typeof(PrimaryKeyNullableByteObject), null, true)] - [TestCase(typeof(PrimaryKeyInt16Object), (short)4242, true)] - [TestCase(typeof(PrimaryKeyNullableInt16Object), (short)4242, true)] - [TestCase(typeof(PrimaryKeyNullableInt16Object), null, true)] - [TestCase(typeof(PrimaryKeyInt32Object), 42000042, true)] - [TestCase(typeof(PrimaryKeyNullableInt32Object), 42000042, true)] - [TestCase(typeof(PrimaryKeyNullableInt32Object), null, true)] - [TestCase(typeof(PrimaryKeyInt64Object), 42000042L, true)] - [TestCase(typeof(PrimaryKeyNullableInt64Object), 42000042L, true)] - [TestCase(typeof(PrimaryKeyNullableInt64Object), null, true)] - [TestCase(typeof(PrimaryKeyStringObject), "key", false)] - [TestCase(typeof(PrimaryKeyStringObject), null, false)] - [TestCase(typeof(PrimaryKeyStringObject), "", false)] - [TestCase(typeof(RequiredPrimaryKeyStringObject), "key", false)] - [TestCase(typeof(RequiredPrimaryKeyStringObject), "", false)] - public void FindByPrimaryKeyDynamicTests(Type type, object primaryKeyValue, bool isIntegerPK) + public enum PKType + { + Int, + String, + ObjectId, + } + + public static object[] PKTestCases = + { + new object[] { typeof(PrimaryKeyCharObject), 'x', PKType.Int }, + new object[] { typeof(PrimaryKeyNullableCharObject), 'x', PKType.Int }, + new object[] { typeof(PrimaryKeyNullableCharObject), null, PKType.Int }, + new object[] { typeof(PrimaryKeyByteObject), (byte)42, PKType.Int }, + new object[] { typeof(PrimaryKeyNullableByteObject), (byte)42, PKType.Int }, + new object[] { typeof(PrimaryKeyNullableByteObject), null, PKType.Int }, + new object[] { typeof(PrimaryKeyInt16Object), (short)4242, PKType.Int }, + new object[] { typeof(PrimaryKeyNullableInt16Object), (short)4242, PKType.Int }, + new object[] { typeof(PrimaryKeyNullableInt16Object), null, PKType.Int }, + new object[] { typeof(PrimaryKeyInt32Object), 42000042, PKType.Int }, + new object[] { typeof(PrimaryKeyNullableInt32Object), 42000042, PKType.Int }, + new object[] { typeof(PrimaryKeyNullableInt32Object), null, PKType.Int }, + new object[] { typeof(PrimaryKeyInt64Object), 42000042L, PKType.Int }, + new object[] { typeof(PrimaryKeyNullableInt64Object), 42000042L, PKType.Int }, + new object[] { typeof(PrimaryKeyNullableInt64Object), null, PKType.Int }, + new object[] { typeof(PrimaryKeyStringObject), "key", PKType.String }, + new object[] { typeof(PrimaryKeyStringObject), null, PKType.String }, + new object[] { typeof(PrimaryKeyStringObject), string.Empty, PKType.String }, + new object[] { typeof(RequiredPrimaryKeyStringObject), "key", PKType.String }, + new object[] { typeof(RequiredPrimaryKeyStringObject), string.Empty, PKType.String }, + new object[] { typeof(PrimaryKeyObjectIdObject), new ObjectId("5f64cd9f1691c361b2451d96"), PKType.ObjectId }, + new object[] { typeof(PrimaryKeyNullableObjectIdObject), new ObjectId("5f64cd9f1691c361b2451d96"), PKType.ObjectId }, + new object[] { typeof(PrimaryKeyNullableObjectIdObject), null, PKType.ObjectId }, + }; + + [TestCaseSource(nameof(PKTestCases))] + public void FindByPrimaryKeyDynamicTests(Type type, object primaryKeyValue, PKType pkType) { var obj = (RealmObject)Activator.CreateInstance(type); var pkProperty = type.GetProperties().Single(p => p.GetCustomAttribute() != null); @@ -61,89 +74,32 @@ public void FindByPrimaryKeyDynamicTests(Type type, object primaryKeyValue, bool _realm.Write(() => _realm.Add(obj)); - var foundObj = FindByPKDynamic(type, primaryKeyValue, isIntegerPK); + var foundObj = FindByPKDynamic(type, primaryKeyValue, pkType); Assert.That(foundObj, Is.Not.Null); Assert.That(pkProperty.GetValue(foundObj), Is.EqualTo(primaryKeyValue)); } - [TestCase(typeof(PrimaryKeyCharObject), 'x', true)] - [TestCase(typeof(PrimaryKeyNullableCharObject), 'x', true)] - [TestCase(typeof(PrimaryKeyNullableCharObject), null, true)] - [TestCase(typeof(PrimaryKeyByteObject), (byte)42, true)] - [TestCase(typeof(PrimaryKeyNullableByteObject), (byte)42, true)] - [TestCase(typeof(PrimaryKeyNullableByteObject), null, true)] - [TestCase(typeof(PrimaryKeyInt16Object), (short)4242, true)] - [TestCase(typeof(PrimaryKeyNullableInt16Object), (short)4242, true)] - [TestCase(typeof(PrimaryKeyNullableInt16Object), null, true)] - [TestCase(typeof(PrimaryKeyInt32Object), 42000042, true)] - [TestCase(typeof(PrimaryKeyNullableInt32Object), 42000042, true)] - [TestCase(typeof(PrimaryKeyNullableInt32Object), null, true)] - [TestCase(typeof(PrimaryKeyInt64Object), 42000042L, true)] - [TestCase(typeof(PrimaryKeyNullableInt64Object), 42000042L, true)] - [TestCase(typeof(PrimaryKeyNullableInt64Object), null, true)] - [TestCase(typeof(PrimaryKeyStringObject), "key", false)] - [TestCase(typeof(PrimaryKeyStringObject), null, false)] - [TestCase(typeof(PrimaryKeyStringObject), "", false)] - [TestCase(typeof(RequiredPrimaryKeyStringObject), "key", false)] - [TestCase(typeof(RequiredPrimaryKeyStringObject), "", false)] - public void FailToFindByPrimaryKeyDynamicTests(Type type, object primaryKeyValue, bool isIntegerPK) + [TestCaseSource(nameof(PKTestCases))] + public void FailToFindByPrimaryKeyDynamicTests(Type type, object primaryKeyValue, PKType pkType) { - var foundObj = FindByPKDynamic(type, primaryKeyValue, isIntegerPK); + var foundObj = FindByPKDynamic(type, primaryKeyValue, pkType); Assert.That(foundObj, Is.Null); } - [TestCase(typeof(PrimaryKeyCharObject), 'x')] - [TestCase(typeof(PrimaryKeyNullableCharObject), 'x')] - [TestCase(typeof(PrimaryKeyNullableCharObject), null)] - [TestCase(typeof(PrimaryKeyByteObject), (byte)42)] - [TestCase(typeof(PrimaryKeyNullableByteObject), (byte)42)] - [TestCase(typeof(PrimaryKeyNullableByteObject), null)] - [TestCase(typeof(PrimaryKeyInt16Object), (short)4242)] - [TestCase(typeof(PrimaryKeyNullableInt16Object), (short)4242)] - [TestCase(typeof(PrimaryKeyNullableInt16Object), null)] - [TestCase(typeof(PrimaryKeyInt32Object), 42000042)] - [TestCase(typeof(PrimaryKeyNullableInt32Object), 42000042)] - [TestCase(typeof(PrimaryKeyNullableInt32Object), null)] - [TestCase(typeof(PrimaryKeyInt64Object), 42000042L)] - [TestCase(typeof(PrimaryKeyNullableInt64Object), 42000042L)] - [TestCase(typeof(PrimaryKeyNullableInt64Object), null)] - [TestCase(typeof(PrimaryKeyStringObject), "key")] - [TestCase(typeof(PrimaryKeyStringObject), null)] - [TestCase(typeof(PrimaryKeyStringObject), "")] - [TestCase(typeof(RequiredPrimaryKeyStringObject), "key")] - [TestCase(typeof(RequiredPrimaryKeyStringObject), "")] - public void CreateObject_WhenPKExists_ShouldFail(Type type, object primaryKeyValue) + [TestCaseSource(nameof(PKTestCases))] + public void CreateObject_WhenPKExists_ShouldFail(Type type, object primaryKeyValue, PKType _) { - _realm.Write(() => _realm.CreateObject(type.Name, primaryKeyValue)); + _realm.Write(() => _realm.DynamicApi.CreateObject(type.Name, primaryKeyValue)); Assert.That(() => { - _realm.Write(() => _realm.CreateObject(type.Name, primaryKeyValue)); + _realm.Write(() => _realm.DynamicApi.CreateObject(type.Name, primaryKeyValue)); }, Throws.TypeOf()); } - [TestCase(typeof(PrimaryKeyCharObject), 'x')] - [TestCase(typeof(PrimaryKeyNullableCharObject), 'x')] - [TestCase(typeof(PrimaryKeyNullableCharObject), null)] - [TestCase(typeof(PrimaryKeyByteObject), (byte)42)] - [TestCase(typeof(PrimaryKeyNullableByteObject), (byte)42)] - [TestCase(typeof(PrimaryKeyNullableByteObject), null)] - [TestCase(typeof(PrimaryKeyInt16Object), (short)4242)] - [TestCase(typeof(PrimaryKeyNullableInt16Object), (short)4242)] - [TestCase(typeof(PrimaryKeyNullableInt16Object), null)] - [TestCase(typeof(PrimaryKeyInt32Object), 42000042)] - [TestCase(typeof(PrimaryKeyNullableInt32Object), 42000042)] - [TestCase(typeof(PrimaryKeyNullableInt32Object), null)] - [TestCase(typeof(PrimaryKeyInt64Object), 42000042L)] - [TestCase(typeof(PrimaryKeyNullableInt64Object), 42000042L)] - [TestCase(typeof(PrimaryKeyNullableInt64Object), null)] - [TestCase(typeof(PrimaryKeyStringObject), "key")] - [TestCase(typeof(PrimaryKeyStringObject), null)] - [TestCase(typeof(PrimaryKeyStringObject), "")] - [TestCase(typeof(RequiredPrimaryKeyStringObject), "key")] - [TestCase(typeof(RequiredPrimaryKeyStringObject), "")] - public void ManageObject_WhenPKExists_ShouldFail(Type type, object primaryKeyValue) + [TestCaseSource(nameof(PKTestCases))] + public void ManageObject_WhenPKExists_ShouldFail(Type type, object primaryKeyValue, PKType _) { var pkProperty = type.GetProperties().Single(p => p.GetCustomAttribute() != null); var first = (RealmObject)Activator.CreateInstance(type); @@ -159,47 +115,36 @@ public void ManageObject_WhenPKExists_ShouldFail(Type type, object primaryKeyVal }, Throws.TypeOf()); } - private RealmObject FindByPKDynamic(Type type, object primaryKeyValue, bool isIntegerPK) + private RealmObjectBase FindByPKDynamic(Type type, object primaryKeyValue, PKType pkType) { - if (isIntegerPK) + switch (pkType) { - long? castPKValue; - if (primaryKeyValue == null) - { - castPKValue = null; - } - else - { - castPKValue = Convert.ToInt64(primaryKeyValue); - } + case PKType.Int: + long? castPKValue; + if (primaryKeyValue == null) + { + castPKValue = null; + } + else + { + castPKValue = Convert.ToInt64(primaryKeyValue); + } - return _realm.Find(type.Name, castPKValue); - } + return _realm.DynamicApi.Find(type.Name, castPKValue); + + case PKType.String: + return _realm.DynamicApi.Find(type.Name, (string)primaryKeyValue); - return _realm.Find(type.Name, (string)primaryKeyValue); + case PKType.ObjectId: + return _realm.DynamicApi.Find(type.Name, (ObjectId?)primaryKeyValue); + + default: + throw new NotSupportedException($"Unsupported pk type: {pkType}"); + } } - [TestCase(typeof(PrimaryKeyCharObject), 'x', true)] - [TestCase(typeof(PrimaryKeyNullableCharObject), 'x', true)] - [TestCase(typeof(PrimaryKeyNullableCharObject), null, true)] - [TestCase(typeof(PrimaryKeyByteObject), (byte)42, true)] - [TestCase(typeof(PrimaryKeyNullableByteObject), (byte)42, true)] - [TestCase(typeof(PrimaryKeyNullableByteObject), null, true)] - [TestCase(typeof(PrimaryKeyInt16Object), (short)4242, true)] - [TestCase(typeof(PrimaryKeyNullableInt16Object), (short)4242, true)] - [TestCase(typeof(PrimaryKeyNullableInt16Object), null, true)] - [TestCase(typeof(PrimaryKeyInt32Object), 42000042, true)] - [TestCase(typeof(PrimaryKeyNullableInt32Object), 42000042, true)] - [TestCase(typeof(PrimaryKeyNullableInt32Object), null, true)] - [TestCase(typeof(PrimaryKeyInt64Object), 42000042L, true)] - [TestCase(typeof(PrimaryKeyNullableInt64Object), 42000042L, true)] - [TestCase(typeof(PrimaryKeyNullableInt64Object), null, true)] - [TestCase(typeof(PrimaryKeyStringObject), "key", false)] - [TestCase(typeof(PrimaryKeyStringObject), null, false)] - [TestCase(typeof(PrimaryKeyStringObject), "", false)] - [TestCase(typeof(RequiredPrimaryKeyStringObject), "key", false)] - [TestCase(typeof(RequiredPrimaryKeyStringObject), "", false)] - public void FindByPrimaryKeyGenericTests(Type type, object primaryKeyValue, bool isIntegerPK) + [TestCaseSource(nameof(PKTestCases))] + public void FindByPrimaryKeyGenericTests(Type type, object primaryKeyValue, PKType pkType) { var obj = (RealmObject)Activator.CreateInstance(type); var pkProperty = type.GetProperties().Single(p => p.GetCustomAttribute() != null); @@ -207,73 +152,52 @@ public void FindByPrimaryKeyGenericTests(Type type, object primaryKeyValue, bool _realm.Write(() => _realm.Add(obj)); - var foundObj = FindByPKGeneric(type, primaryKeyValue, isIntegerPK); + var foundObj = FindByPKGeneric(type, primaryKeyValue, pkType); Assert.That(foundObj, Is.Not.Null); Assert.That(pkProperty.GetValue(foundObj), Is.EqualTo(primaryKeyValue)); } - [TestCase(typeof(PrimaryKeyCharObject), 'x', true)] - [TestCase(typeof(PrimaryKeyNullableCharObject), 'x', true)] - [TestCase(typeof(PrimaryKeyNullableCharObject), null, true)] - [TestCase(typeof(PrimaryKeyByteObject), (byte)42, true)] - [TestCase(typeof(PrimaryKeyNullableByteObject), (byte)42, true)] - [TestCase(typeof(PrimaryKeyNullableByteObject), null, true)] - [TestCase(typeof(PrimaryKeyInt16Object), (short)4242, true)] - [TestCase(typeof(PrimaryKeyNullableInt16Object), (short)4242, true)] - [TestCase(typeof(PrimaryKeyNullableInt16Object), null, true)] - [TestCase(typeof(PrimaryKeyInt32Object), 42000042, true)] - [TestCase(typeof(PrimaryKeyNullableInt32Object), 42000042, true)] - [TestCase(typeof(PrimaryKeyNullableInt32Object), null, true)] - [TestCase(typeof(PrimaryKeyInt64Object), 42000042L, true)] - [TestCase(typeof(PrimaryKeyNullableInt64Object), 42000042L, true)] - [TestCase(typeof(PrimaryKeyNullableInt64Object), null, true)] - [TestCase(typeof(PrimaryKeyStringObject), "key", false)] - [TestCase(typeof(PrimaryKeyStringObject), null, false)] - [TestCase(typeof(PrimaryKeyStringObject), "", false)] - [TestCase(typeof(RequiredPrimaryKeyStringObject), "key", false)] - [TestCase(typeof(RequiredPrimaryKeyStringObject), "", false)] - public void FailToFindByPrimaryKeyGenericTests(Type type, object primaryKeyValue, bool isIntegerPK) + [TestCaseSource(nameof(PKTestCases))] + public void FailToFindByPrimaryKeyGenericTests(Type type, object primaryKeyValue, PKType pkType) { - var foundObj = FindByPKGeneric(type, primaryKeyValue, isIntegerPK); + var foundObj = FindByPKGeneric(type, primaryKeyValue, pkType); Assert.That(foundObj, Is.Null); } - private RealmObject FindByPKGeneric(Type type, object primaryKeyValue, bool isIntegerPK) + private RealmObjectBase FindByPKGeneric(Type type, object primaryKeyValue, PKType pkType) { - var genericArgument = isIntegerPK ? typeof(long?) : typeof(string); - var genericMethod = _realm.GetType().GetMethod(nameof(Realm.Find), new[] { genericArgument }); - - object castPKValue; - if (isIntegerPK) + var genericArgument = pkType switch { - if (primaryKeyValue == null) - { - castPKValue = (long?)null; - } - else - { - castPKValue = Convert.ToInt64(primaryKeyValue); - } - } - else + PKType.Int => typeof(long?), + PKType.String => typeof(string), + PKType.ObjectId => typeof(ObjectId?), + _ => throw new NotSupportedException(), + }; + + var genericMethod = _realm.GetType().GetMethod(nameof(Realm.Find), new[] { genericArgument }); + if (pkType == PKType.Int && primaryKeyValue != null) { - castPKValue = (string)primaryKeyValue; + primaryKeyValue = Convert.ToInt64(primaryKeyValue); } - return (RealmObject)genericMethod.MakeGenericMethod(type).Invoke(_realm, new[] { castPKValue }); + return (RealmObjectBase)genericMethod.MakeGenericMethod(type).Invoke(_realm, new[] { primaryKeyValue }); } [Test] public void ExceptionIfNoPrimaryKeyDeclared() { Assert.That(() => _realm.Find("Zaphod"), Throws.TypeOf()); + Assert.That(() => _realm.Find(42), Throws.TypeOf()); + Assert.That(() => _realm.Find(ObjectId.GenerateNewId()), Throws.TypeOf()); } [Test] public void ExceptionIfNoDynamicPrimaryKeyDeclared() { - Assert.That(() => _realm.Find("Person", "Zaphod"), Throws.TypeOf()); + Assert.That(() => _realm.DynamicApi.Find("Person", "Zaphod"), Throws.TypeOf()); + Assert.That(() => _realm.DynamicApi.Find("Person", 23), Throws.TypeOf()); + Assert.That(() => _realm.DynamicApi.Find("Person", ObjectId.GenerateNewId()), Throws.TypeOf()); } [Test] @@ -291,11 +215,9 @@ public void GetByPrimaryKeyDifferentThreads() // Act await Task.Run(() => { - using (var realm2 = Realm.GetInstance(_configuration)) - { - var foundObj = realm2.Find(42000042); - foundValue = foundObj.Int64Property; - } + using var realm2 = GetRealm(_configuration); + var foundObj = realm2.Find(42000042); + foundValue = foundObj.Int64Property; }); Assert.That(foundValue, Is.EqualTo(42000042)); @@ -342,19 +264,11 @@ public void NullAndNotNullIntPKsWorkTogether() [Test] public void PrimaryKeyFailsIfClassNotInRealm() { - var conf = ((RealmConfiguration)RealmConfiguration.DefaultConfiguration).ConfigWithPath(Path.GetTempFileName()); + var conf = ((RealmConfiguration)RealmConfiguration.DefaultConfiguration).ConfigWithPath(Guid.NewGuid().ToString()); conf.ObjectClasses = new[] { typeof(Person) }; - try - { - using (var skinny = Realm.GetInstance(conf)) - { - Assert.That(() => skinny.Find(42), Throws.TypeOf()); - } - } - finally - { - Realm.DeleteRealm(conf); - } + + using var skinny = GetRealm(conf); + Assert.That(() => skinny.Find(42), Throws.TypeOf()); } [Test] diff --git a/Tests/Realm.Tests/Database/PropertyChangedTests.cs b/Tests/Realm.Tests/Database/PropertyChangedTests.cs index 74fc4c4bb5..90129c7282 100644 --- a/Tests/Realm.Tests/Database/PropertyChangedTests.cs +++ b/Tests/Realm.Tests/Database/PropertyChangedTests.cs @@ -19,43 +19,15 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.IO; using System.Linq; using System.Threading.Tasks; -using Nito.AsyncEx; using NUnit.Framework; namespace Realms.Tests.Database { [TestFixture, Preserve(AllMembers = true)] - public class PropertyChangedTests + public class PropertyChangedTests : RealmInstanceTest { - private string _databasePath; - - private Lazy _lazyRealm; - - private Realm _realm => _lazyRealm.Value; - - // We capture the current SynchronizationContext when opening a Realm. - // However, NUnit replaces the SynchronizationContext after the SetUp method and before the async test method. - // That's why we make sure we open the Realm in the test method by accessing it lazily. - [SetUp] - public void SetUp() - { - _databasePath = Path.GetTempFileName(); - _lazyRealm = new Lazy(() => Realm.GetInstance(_databasePath)); - } - - [TearDown] - public void TearDown() - { - if (_lazyRealm.IsValueCreated) - { - _realm.Dispose(); - Realm.DeleteRealm(_realm.Config); - } - } - [Test] public void UnmanagedObject() { @@ -210,16 +182,15 @@ public void ManagedObject_WhenAnotherThreadInstanceTransactionRollback() await Task.Run(() => { - using (var otherRealm = Realm.GetInstance(_databasePath)) - using (var transaction = otherRealm.BeginWrite()) - { - var otherInstance = otherRealm.All().First(); - otherInstance.FirstName = "Peter"; + using var otherRealm = GetRealm(_realm.Config); + using var transaction = otherRealm.BeginWrite(); - Assert.That(notifiedPropertyNames, Is.Empty); + var otherInstance = otherRealm.All().First(); + otherInstance.FirstName = "Peter"; - transaction.Rollback(); - } + Assert.That(notifiedPropertyNames, Is.Empty); + + transaction.Rollback(); }); _realm.Refresh(); @@ -815,7 +786,7 @@ public void ManagedObject_WhenDeleted_NotifiesIsValidChanged() _realm.Refresh(); Assert.That(notifiedPropertyNames.Count, Is.EqualTo(1)); - Assert.That(notifiedPropertyNames[0], Is.EqualTo(nameof(RealmObject.IsValid))); + Assert.That(notifiedPropertyNames[0], Is.EqualTo(nameof(RealmObjectBase.IsValid))); Assert.That(person.IsValid, Is.False); } diff --git a/Tests/Realm.Tests/Database/RealmObjectTests.cs b/Tests/Realm.Tests/Database/RealmObjectTests.cs index 7bb7175336..40629da6d4 100644 --- a/Tests/Realm.Tests/Database/RealmObjectTests.cs +++ b/Tests/Realm.Tests/Database/RealmObjectTests.cs @@ -196,7 +196,7 @@ public void RealmObject_WhenSerialized_ShouldSkipBaseProperties() text = System.Text.Encoding.UTF8.GetString(stream.ToArray()); } - foreach (var field in typeof(RealmObject).GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)) + foreach (var field in typeof(RealmObjectBase).GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)) { Assert.That(text, Does.Not.Contains(field.Name)); } @@ -332,7 +332,7 @@ public void RealmObject_WhenFrozen_FailsToSubscribeToNotifications() FreezeInPlace(obj); - Assert.Throws(() => obj.PropertyChanged += (_, __) => { }, "It is not possible to add a change listener to a frozen RealmObject since it never changes."); + Assert.Throws(() => obj.PropertyChanged += (_, __) => { }, "It is not possible to add a change listener to a frozen RealmObjectBase since it never changes."); } [Test] diff --git a/Tests/Realm.Tests/Database/RealmResults/ConvertTests.cs b/Tests/Realm.Tests/Database/RealmResults/ConvertTests.cs index 785d249c50..a246b6d8a9 100644 --- a/Tests/Realm.Tests/Database/RealmResults/ConvertTests.cs +++ b/Tests/Realm.Tests/Database/RealmResults/ConvertTests.cs @@ -18,8 +18,8 @@ using System; using System.Linq; +using MongoDB.Bson; using NUnit.Framework; -using Realms; namespace Realms.Tests.Database { @@ -31,6 +31,8 @@ public class ConvertTests : RealmInstanceTest private const long LongOne = 1; private const float FloatOne = 1; private const double DoubleOne = 1; + private const decimal DecimalOne = 1; + private static readonly Decimal128 Decimal128One = 1; private static readonly int? NullableOne = 1; private static readonly int? NullableZero = 0; @@ -66,7 +68,11 @@ protected override void CustomSetUp() NullableInt64Property = 1, NullableSingleProperty = 1, SingleProperty = 1, - RequiredStringProperty = string.Empty + RequiredStringProperty = string.Empty, + DecimalProperty = 1, + Decimal128Property = 1, + NullableDecimalProperty = 1, + NullableDecimal128Property = 1, }); _realm.Add(new CounterObject()); @@ -117,6 +123,14 @@ public void Equal_WhenUsingBroaderType_Single() Assert.That(singleQuery[0].DoubleProperty, Is.EqualTo(DoubleOne)); } + [Test] + public void Equal_WhenUsingBroaderType_Decimal() + { + var singleQuery = _realm.All().Where(o => o.DecimalProperty == Decimal128One).ToArray(); + Assert.That(singleQuery.Length, Is.EqualTo(1)); + Assert.That(singleQuery[0].DoubleProperty, Is.EqualTo(DoubleOne)); + } + [Test] public void Equal_WhenUsingBroaderType_ByteInteger() { @@ -170,7 +184,31 @@ public void Equal_WhenUsingNarrowerType_Double() { var doubleQuery = _realm.All().Where(o => o.DoubleProperty == FloatOne).ToArray(); Assert.That(doubleQuery.Length, Is.EqualTo(1)); - Assert.That(doubleQuery[0].SingleProperty, Is.EqualTo(FloatOne)); + Assert.That(doubleQuery[0].DoubleProperty, Is.EqualTo(1.0)); + } + + [Test] + public void Equal_WhenUsingNarrowerType_Decimal() + { + var decimalQuery = _realm.All().Where(o => o.DecimalProperty == ByteOne).ToArray(); + Assert.That(decimalQuery.Length, Is.EqualTo(1)); + Assert.That(decimalQuery[0].DecimalProperty, Is.EqualTo(DecimalOne)); + } + + [Test] + public void Equal_WhenUsingNarrowerType_Decimal128() + { + var decimalByDecimalQuery = _realm.All().Where(o => o.Decimal128Property == DecimalOne).ToArray(); + Assert.That(decimalByDecimalQuery.Length, Is.EqualTo(1)); + Assert.That(decimalByDecimalQuery[0].Decimal128Property, Is.EqualTo(Decimal128One)); + + var decimalByLongQuery = _realm.All().Where(o => o.Decimal128Property == LongOne).ToArray(); + Assert.That(decimalByLongQuery.Length, Is.EqualTo(1)); + Assert.That(decimalByLongQuery[0].Decimal128Property, Is.EqualTo(Decimal128One)); + + var decimalByByteQuery = _realm.All().Where(o => o.Decimal128Property == ByteOne).ToArray(); + Assert.That(decimalByByteQuery.Length, Is.EqualTo(1)); + Assert.That(decimalByByteQuery[0].Decimal128Property, Is.EqualTo(Decimal128One)); } [Test] @@ -257,6 +295,46 @@ public void Equal_WhenPropertyIsNullable_Double() Assert.That(doubleQuery[0].NullableDoubleProperty, Is.EqualTo(1.0)); } + [Test] + public void Equal_WhenPropertyIsNullable_Decimal() + { + var decimalQuery = _realm.All().Where(o => o.NullableDecimalProperty == DecimalOne).ToArray(); + Assert.That(decimalQuery.Length, Is.EqualTo(1)); + Assert.That(decimalQuery[0].NullableDecimalProperty, Is.EqualTo(1)); + + decimalQuery = _realm.All().Where(o => o.NullableDecimalProperty == LongOne).ToArray(); + Assert.That(decimalQuery.Length, Is.EqualTo(1)); + Assert.That(decimalQuery[0].NullableDecimalProperty, Is.EqualTo(1)); + + decimalQuery = _realm.All().Where(o => o.NullableDecimalProperty == Decimal128One).ToArray(); + Assert.That(decimalQuery.Length, Is.EqualTo(1)); + Assert.That(decimalQuery[0].NullableDecimalProperty, Is.EqualTo(1)); + + decimalQuery = _realm.All().Where(o => o.NullableDecimalProperty != null).ToArray(); + Assert.That(decimalQuery.Length, Is.EqualTo(1)); + Assert.That(decimalQuery[0].NullableDecimalProperty, Is.EqualTo(1)); + } + + [Test] + public void Equal_WhenPropertyIsNullable_Decimal128() + { + var decimalQuery = _realm.All().Where(o => o.NullableDecimal128Property == Decimal128One).ToArray(); + Assert.That(decimalQuery.Length, Is.EqualTo(1)); + Assert.That(decimalQuery[0].NullableDecimal128Property, Is.EqualTo(Decimal128One)); + + decimalQuery = _realm.All().Where(o => o.NullableDecimal128Property == DecimalOne).ToArray(); + Assert.That(decimalQuery.Length, Is.EqualTo(1)); + Assert.That(decimalQuery[0].NullableDecimal128Property, Is.EqualTo(Decimal128One)); + + decimalQuery = _realm.All().Where(o => o.NullableDecimal128Property == LongOne).ToArray(); + Assert.That(decimalQuery.Length, Is.EqualTo(1)); + Assert.That(decimalQuery[0].NullableDecimal128Property, Is.EqualTo(Decimal128One)); + + decimalQuery = _realm.All().Where(o => o.NullableDecimal128Property != null).ToArray(); + Assert.That(decimalQuery.Length, Is.EqualTo(1)); + Assert.That(decimalQuery[0].NullableDecimal128Property, Is.EqualTo(Decimal128One)); + } + [Test] public void Equal_WhenPropertyIsNullable_Int16() { @@ -398,6 +476,24 @@ public void Equal_WhenVariableIsNullable_Double() Assert.That(doubleQuery[0].DoubleProperty, Is.EqualTo(doubleValue)); } + [Test] + public void Equal_WhenVariableIsNullable_Decimal() + { + decimal? decimalValue = 1M; + var doubleQuery = _realm.All().Where(o => o.DecimalProperty == decimalValue).ToArray(); + Assert.That(doubleQuery.Length, Is.EqualTo(1)); + Assert.That(doubleQuery[0].DecimalProperty, Is.EqualTo(decimalValue)); + } + + [Test] + public void Equal_WhenVariableIsNullable_Decimal128() + { + Decimal128? decimalValue = 1; + var doubleQuery = _realm.All().Where(o => o.Decimal128Property == decimalValue).ToArray(); + Assert.That(doubleQuery.Length, Is.EqualTo(1)); + Assert.That(doubleQuery[0].Decimal128Property, Is.EqualTo(decimalValue)); + } + [Test] public void Equal_WhenVariableIsNullable_Int16() { diff --git a/Tests/Realm.Tests/Database/RealmResults/DisallowedPredicateParameters.cs b/Tests/Realm.Tests/Database/RealmResults/DisallowedPredicateParameters.cs index 301916626e..bf3b0f99c0 100644 --- a/Tests/Realm.Tests/Database/RealmResults/DisallowedPredicateParameters.cs +++ b/Tests/Realm.Tests/Database/RealmResults/DisallowedPredicateParameters.cs @@ -29,40 +29,38 @@ public class DisallowedPredicateParameters : RealmTest [Test] public void DisallowedPredicateParametersShouldThrow() { - using (var realm = Realm.GetInstance()) - { - var accessPublicField = realm.All().Where(c => c.PublicField == null); - Assert.That(() => accessPublicField.ToList(), Throws.TypeOf()); + using var realm = GetRealm(); + var accessPublicField = realm.All().Where(c => c.PublicField == null); + Assert.That(() => accessPublicField.ToList(), Throws.TypeOf()); - var accessPublicMethod = realm.All().Where(c => c.PublicMethod() == null); - Assert.That(() => accessPublicMethod.ToList(), Throws.TypeOf()); + var accessPublicMethod = realm.All().Where(c => c.PublicMethod() == null); + Assert.That(() => accessPublicMethod.ToList(), Throws.TypeOf()); - var accessIgnoredProperty = realm.All().Where(c => c.IgnoredProperty == null); - Assert.That(() => accessIgnoredProperty.ToList(), Throws.TypeOf()); + var accessIgnoredProperty = realm.All().Where(c => c.IgnoredProperty == null); + Assert.That(() => accessIgnoredProperty.ToList(), Throws.TypeOf()); - var accessNonAutomaticProperty = realm.All().Where(c => c.NonAutomaticProperty == null); - Assert.That(() => accessNonAutomaticProperty.ToList(), Throws.TypeOf()); + var accessNonAutomaticProperty = realm.All().Where(c => c.NonAutomaticProperty == null); + Assert.That(() => accessNonAutomaticProperty.ToList(), Throws.TypeOf()); - var accessPropertyWithOnlyGet = realm.All().Where(c => c.PropertyWithOnlyGet == null); - Assert.That(() => accessPropertyWithOnlyGet.ToList(), Throws.TypeOf()); + var accessPropertyWithOnlyGet = realm.All().Where(c => c.PropertyWithOnlyGet == null); + Assert.That(() => accessPropertyWithOnlyGet.ToList(), Throws.TypeOf()); - var indirectAccess = realm.All().Where(c => c.RealmObjectProperty.FirstName == null); - Assert.That(() => indirectAccess.ToList(), Throws.TypeOf()); + var indirectAccess = realm.All().Where(c => c.RealmObjectProperty.FirstName == null); + Assert.That(() => indirectAccess.ToList(), Throws.TypeOf()); - var listAccess = realm.All().Where(c => c.RealmListProperty != null); - Assert.That(() => listAccess.ToArray(), Throws.TypeOf()); + var listAccess = realm.All().Where(c => c.RealmListProperty != null); + Assert.That(() => listAccess.ToArray(), Throws.TypeOf()); - var person = new Person(); - var listContains = realm.All().Where(c => c.RealmListProperty.Contains(person)); - Assert.That(() => listContains.ToArray(), Throws.TypeOf()); + var person = new Person(); + var listContains = realm.All().Where(c => c.RealmListProperty.Contains(person)); + Assert.That(() => listContains.ToArray(), Throws.TypeOf()); - var backlinkAccess = realm.All().Where(c => c.BacklinkProperty != null); - Assert.That(() => backlinkAccess.ToArray(), Throws.TypeOf()); + var backlinkAccess = realm.All().Where(c => c.BacklinkProperty != null); + Assert.That(() => backlinkAccess.ToArray(), Throws.TypeOf()); - var backlinkItem = new UnqueryableBacklinks(); - var backlinkContains = realm.All().Where(c => c.BacklinkProperty.Contains(backlinkItem)); - Assert.That(() => backlinkContains.ToArray(), Throws.TypeOf()); - } + var backlinkItem = new UnqueryableBacklinks(); + var backlinkContains = realm.All().Where(c => c.BacklinkProperty.Contains(backlinkItem)); + Assert.That(() => backlinkContains.ToArray(), Throws.TypeOf()); } } } diff --git a/Tests/Realm.Tests/Database/RealmResults/LINQvariableTests.cs b/Tests/Realm.Tests/Database/RealmResults/LINQvariableTests.cs index 004e1eebba..ac1171929b 100644 --- a/Tests/Realm.Tests/Database/RealmResults/LINQvariableTests.cs +++ b/Tests/Realm.Tests/Database/RealmResults/LINQvariableTests.cs @@ -17,6 +17,7 @@ //////////////////////////////////////////////////////////////////////////// using System.Linq; +using MongoDB.Bson; using NUnit.Framework; namespace Realms.Tests.Database @@ -82,6 +83,93 @@ public void ScoreWithinRange(float minScore, float maxScore, int expectFound) Assert.That(c0, Is.EqualTo(expectFound)); } + [TestCaseSource(nameof(DecimalTestData))] + public void DecimalTests(decimal value, int expectGreater, int expectGreaterEqual, int expectLess, int expectLessEqual) + { + SeedDecimalData(); + + var greater = _realm.All().Count(d => d.DecimalValue > value); + Assert.That(greater, Is.EqualTo(expectGreater)); + + var greaterEqual = _realm.All().Count(d => d.DecimalValue >= value); + Assert.That(greaterEqual, Is.EqualTo(expectGreaterEqual)); + + var less = _realm.All().Count(d => d.DecimalValue < value); + Assert.That(less, Is.EqualTo(expectLess)); + + var lessEqual = _realm.All().Count(d => d.DecimalValue <= value); + Assert.That(lessEqual, Is.EqualTo(expectLessEqual)); + } + + [TestCaseSource(nameof(Decimal128TestData))] + public void Decimal128Tests(Decimal128 value, int expectGreater, int expectGreaterEqual, int expectLess, int expectLessEqual) + { + SeedDecimal128Data(); + + var greater = _realm.All().Count(d => d.Decimal128Value > value); + Assert.That(greater, Is.EqualTo(expectGreater)); + + var greaterEqual = _realm.All().Count(d => d.Decimal128Value >= value); + Assert.That(greaterEqual, Is.EqualTo(expectGreaterEqual)); + + var less = _realm.All().Count(d => d.Decimal128Value < value); + Assert.That(less, Is.EqualTo(expectLess)); + + var lessEqual = _realm.All().Count(d => d.Decimal128Value <= value); + Assert.That(lessEqual, Is.EqualTo(expectLessEqual)); + } + + [Test] + public void ObjectId_Equals() + { + var id = ObjectId.GenerateNewId(); + _realm.Write(() => + { + _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty, ObjectIdProperty = id }); + _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty, ObjectIdProperty = ObjectId.GenerateNewId() }); + _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty, ObjectIdProperty = ObjectId.GenerateNewId() }); + }); + + var matches = _realm.All().Where(o => o.ObjectIdProperty == id); + Assert.That(matches.Count(), Is.EqualTo(1)); + Assert.That(matches.Single().ObjectIdProperty, Is.EqualTo(id)); + + var nonMatches = _realm.All().Where(o => o.ObjectIdProperty != id); + Assert.That(nonMatches.Count(), Is.EqualTo(2)); + Assert.That(nonMatches.ElementAt(0).ObjectIdProperty, Is.Not.EqualTo(id)); + Assert.That(nonMatches.ElementAt(1).ObjectIdProperty, Is.Not.EqualTo(id)); + } + + [Test] + public void NullableObjectId_Equals() + { + var id = ObjectId.GenerateNewId(); + _realm.Write(() => + { + _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty, NullableObjectIdProperty = id }); + _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty, NullableObjectIdProperty = ObjectId.GenerateNewId() }); + _realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty, NullableObjectIdProperty = null }); + }); + + var idMatches = _realm.All().Where(o => o.NullableObjectIdProperty == id); + Assert.That(idMatches.Count(), Is.EqualTo(1)); + Assert.That(idMatches.Single().NullableObjectIdProperty, Is.EqualTo(id)); + + var idNonMatches = _realm.All().Where(o => o.NullableObjectIdProperty != id); + Assert.That(idNonMatches.Count(), Is.EqualTo(2)); + Assert.That(idNonMatches.ElementAt(0).NullableObjectIdProperty, Is.Not.EqualTo(id)); + Assert.That(idNonMatches.ElementAt(1).NullableObjectIdProperty, Is.Not.EqualTo(id)); + + var nullMatches = _realm.All().Where(o => o.NullableObjectIdProperty == null); + Assert.That(nullMatches.Count(), Is.EqualTo(1)); + Assert.That(nullMatches.Single().NullableObjectIdProperty, Is.Null); + + var nullNonMatches = _realm.All().Where(o => o.NullableObjectIdProperty != null); + Assert.That(nullNonMatches.Count(), Is.EqualTo(2)); + Assert.That(nullNonMatches.ElementAt(0).NullableObjectIdProperty, Is.Not.Null); + Assert.That(nullNonMatches.ElementAt(1).NullableObjectIdProperty, Is.Not.Null); + } + // The following test cases exercise both Convert and Member RHS expressions [TestCase("Peter", 1)] [TestCase("Zach", 0)] @@ -142,5 +230,51 @@ public void ScoreWithinRange_TestCaseData(float minScore, float maxScore, int ex var c0 = _realm.All().Count(p => p.Score > (float)data.Arguments[0] && p.Score <= (float)data.Arguments[1]); Assert.That(c0, Is.EqualTo(data.Arguments[2])); } + + public static object[] DecimalTestData = + { + new object[] { decimal.MinValue, 4, 5, 0, 1 }, + new object[] { 3.5438693468936346437634743733M, 3, 3, 2, 2 }, + new object[] { 3.5438693468936346437634743734M, 2, 3, 2, 3 }, + new object[] { 3.5438693468936346437634743735M, 2, 2, 3, 3 }, + new object[] { decimal.MaxValue, 0, 1, 4, 5 }, + }; + + public static object[] Decimal128TestData = + { + new object[] { Decimal128.MinValue, 6, 7, 0, 1 }, + new object[] { new Decimal128(decimal.MinValue), 5, 6, 1, 2 }, + new object[] { new Decimal128(3.5438693468936346437634743733M), 4, 4, 3, 3 }, + new object[] { new Decimal128(3.5438693468936346437634743734M), 3, 4, 3, 4 }, + new object[] { new Decimal128(3.5438693468936346437634743735M), 3, 3, 4, 4 }, + new object[] { new Decimal128(decimal.MaxValue), 1, 2, 5, 6 }, + new object[] { Decimal128.MaxValue, 0, 1, 6, 7 }, + }; + + private void SeedDecimalData() + { + var decimalSeedData = new decimal[] { decimal.MinValue, 0, 3.5438693468936346437634743734M, 45, decimal.MaxValue }; + + _realm.Write(() => + { + foreach (var d in decimalSeedData) + { + _realm.Add(new DecimalsObject { DecimalValue = d }); + } + }); + } + + private void SeedDecimal128Data() + { + var decimal128SeedData = new Decimal128[] { Decimal128.MinValue, decimal.MinValue, 0, 3.5438693468936346437634743734M, 45, decimal.MaxValue, Decimal128.MaxValue }; + + _realm.Write(() => + { + foreach (var d in decimal128SeedData) + { + _realm.Add(new DecimalsObject { Decimal128Value = d }); + } + }); + } } } \ No newline at end of file diff --git a/Tests/Realm.Tests/Database/RealmResults/SimpleLINQtests.cs b/Tests/Realm.Tests/Database/RealmResults/SimpleLINQtests.cs index 5dbb8e370d..7f5cb38cb6 100644 --- a/Tests/Realm.Tests/Database/RealmResults/SimpleLINQtests.cs +++ b/Tests/Realm.Tests/Database/RealmResults/SimpleLINQtests.cs @@ -17,7 +17,6 @@ //////////////////////////////////////////////////////////////////////////// using System; -using System.IO; using System.Linq; using NUnit.Framework; using Realms.Exceptions; @@ -574,7 +573,7 @@ public void SearchComparingObjects_WhenObjectIsUnmanaged_ShouldFail() var rex = new Dog { Name = "Rex" }; Assert.That( () => _realm.All().Where(o => o.TopDog == rex).ToArray(), - Throws.TypeOf().And.Message.Contains("should be a managed RealmObject")); + Throws.TypeOf().And.Message.Contains("should be a managed RealmObjectBase")); } [Test] @@ -597,7 +596,7 @@ public void SearchComparingObjects_WhenObjectIsDeleted_ShouldFail() Assert.That( () => _realm.All().Where(o => o.TopDog == rex).ToArray(), - Throws.TypeOf().And.Message.Contains("should be a managed RealmObject")); + Throws.TypeOf().And.Message.Contains("should be a managed RealmObjectBase")); } [Test] @@ -1272,25 +1271,16 @@ public void Queryable_IndexOf_WhenObjectIsNotManaged_ShouldThrow() [Test] public void Queryable_IndexOf_WhenObjectBelongsToADifferentRealm_ShouldThrow() { - var config = new RealmConfiguration(Path.GetTempFileName()); - try + var config = new RealmConfiguration(Guid.NewGuid().ToString()); + using var otherRealm = GetRealm(config); + var otherRealmItem = new IntPrimaryKeyWithValueObject { Id = 1 }; + otherRealm.Write(() => { - using (var otherRealm = Realm.GetInstance(config)) - { - var otherRealmItem = new IntPrimaryKeyWithValueObject { Id = 1 }; - otherRealm.Write(() => - { - otherRealm.Add(otherRealmItem); - }); + otherRealm.Add(otherRealmItem); + }); - var query = _realm.All().AsRealmCollection(); - Assert.That(() => query.IndexOf(otherRealmItem), Throws.InstanceOf()); - } - } - finally - { - Realm.DeleteRealm(config); - } + var query = _realm.All().AsRealmCollection(); + Assert.That(() => query.IndexOf(otherRealmItem), Throws.InstanceOf()); } [Test] diff --git a/Tests/Realm.Tests/Database/RealmResults/SortingTests.cs b/Tests/Realm.Tests/Database/RealmResults/SortingTests.cs index a2626324f5..f795e06a0e 100644 --- a/Tests/Realm.Tests/Database/RealmResults/SortingTests.cs +++ b/Tests/Realm.Tests/Database/RealmResults/SortingTests.cs @@ -18,6 +18,7 @@ using System; using System.Linq; +using MongoDB.Bson; using NUnit.Framework; using TestExplicitAttribute = NUnit.Framework.ExplicitAttribute; @@ -329,6 +330,109 @@ public void SortsByAcceptedOrder() Assert.That(sortedCities, Is.EqualTo(new[] { "A-Place", "A Place", "Santo Domingo", "São Paulo", "Shanghai", "Sydney", "Åby" })); } + [TestCase(true)] + [TestCase(false)] + public void SortsByDecimal(bool ascending) + { + var data = new[] { 1.23M, 9.123M, 5.323423M, -123.324M, 9.123M, decimal.MinValue, decimal.Zero, decimal.MaxValue }; + _realm.Write(() => + { + foreach (var value in data) + { + _realm.Add(new DecimalsObject { DecimalValue = value }); + } + }); + + if (ascending) + { + var sortedDecimals = _realm.All().OrderBy(d => d.DecimalValue).ToArray().Select(d => d.DecimalValue); + Assert.That(sortedDecimals, Is.EqualTo(data.OrderBy(d => d))); + } + else + { + var sortedDecimals = _realm.All().OrderByDescending(d => d.DecimalValue).ToArray().Select(d => d.DecimalValue); + Assert.That(sortedDecimals, Is.EqualTo(data.OrderByDescending(d => d))); + } + } + + [TestCase(true)] + [TestCase(false)] + public void SortsByDecimal128(bool ascending) + { + var data = new Decimal128[] { 1.23M, 1, 94, 9.123M, 5.323423M, Decimal128.MinValue, Decimal128.MaxValue, Decimal128.Zero, -123.324M, 9.123M, decimal.MinValue, decimal.Zero, decimal.MaxValue }; + _realm.Write(() => + { + foreach (var value in data) + { + _realm.Add(new DecimalsObject { Decimal128Value = value }); + } + }); + + if (ascending) + { + var sortedDecimals = _realm.All().OrderBy(d => d.Decimal128Value).ToArray().Select(d => d.Decimal128Value); + Assert.That(sortedDecimals, Is.EqualTo(data.OrderBy(d => d))); + } + else + { + var sortedDecimals = _realm.All().OrderByDescending(d => d.Decimal128Value).ToArray().Select(d => d.Decimal128Value); + Assert.That(sortedDecimals, Is.EqualTo(data.OrderByDescending(d => d))); + } + } + + [TestCase(true)] + [TestCase(false)] + public void SortsByObjectId(bool ascending) + { + var data = Enumerable.Range(0, 10).Select(_ => ObjectId.GenerateNewId()).ToArray(); + _realm.Write(() => + { + foreach (var value in data) + { + _realm.Add(new AllTypesObject { ObjectIdProperty = value, RequiredStringProperty = string.Empty }); + } + }); + + if (ascending) + { + var sortedDecimals = _realm.All().OrderBy(d => d.ObjectIdProperty).ToArray().Select(d => d.ObjectIdProperty); + Assert.That(sortedDecimals, Is.EqualTo(data.OrderBy(d => d))); + } + else + { + var sortedDecimals = _realm.All().OrderByDescending(d => d.ObjectIdProperty).ToArray().Select(d => d.ObjectIdProperty); + Assert.That(sortedDecimals, Is.EqualTo(data.OrderByDescending(d => d))); + } + } + + [TestCase(true)] + [TestCase(false)] + public void SortsByNullableObjectId(bool ascending) + { + var data = Enumerable.Range(0, 10).Select(_ => (ObjectId?)ObjectId.GenerateNewId()).ToArray(); + data[5] = null; + data[3] = null; + + _realm.Write(() => + { + foreach (var value in data) + { + _realm.Add(new AllTypesObject { NullableObjectIdProperty = value, RequiredStringProperty = string.Empty }); + } + }); + + if (ascending) + { + var sortedDecimals = _realm.All().OrderBy(d => d.NullableObjectIdProperty).ToArray().Select(d => d.NullableObjectIdProperty); + Assert.That(sortedDecimals, Is.EqualTo(data.OrderBy(d => d))); + } + else + { + var sortedDecimals = _realm.All().OrderByDescending(d => d.NullableObjectIdProperty).ToArray().Select(d => d.NullableObjectIdProperty); + Assert.That(sortedDecimals, Is.EqualTo(data.OrderByDescending(d => d))); + } + } + private void MakeThreeLinkingObjects( string string1, int int1, long date1, string string2, int int2, long date2, diff --git a/Tests/Realm.Tests/Database/RefreshTests.cs b/Tests/Realm.Tests/Database/RefreshTests.cs index 5343584407..799f7ded2d 100644 --- a/Tests/Realm.Tests/Database/RefreshTests.cs +++ b/Tests/Realm.Tests/Database/RefreshTests.cs @@ -18,11 +18,8 @@ using System; using System.Linq; -using System.Threading; using System.Threading.Tasks; -using Nito.AsyncEx; using NUnit.Framework; -using Realms; namespace Realms.Tests.Database { @@ -69,9 +66,8 @@ public void CallingRefreshShouldRefreshQueriesAfterModificationsOnDifferentThrea await Task.Run(() => { - var r = Realm.GetInstance(_configuration); + using var r = GetRealm(_configuration); r.Write(() => r.Add(new Person { FullName = "Person 2" })); - r.Dispose(); }); _realm.Refresh(); diff --git a/Tests/Realm.Tests/Database/RemoveTests.cs b/Tests/Realm.Tests/Database/RemoveTests.cs index 2c41df5c8d..23e0fb6730 100644 --- a/Tests/Realm.Tests/Database/RemoveTests.cs +++ b/Tests/Realm.Tests/Database/RemoveTests.cs @@ -18,10 +18,8 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using NUnit.Framework; -using Realms; using Realms.Exceptions; namespace Realms.Tests.Database @@ -127,7 +125,7 @@ public void RemoveAll_WhenDynamic_RemovesAllObjectsOfAGivenType() Assert.That(_realm.All().Count(), Is.EqualTo(3)); // Act - _realm.Write(() => _realm.RemoveAll(nameof(Person))); + _realm.Write(() => _realm.DynamicApi.RemoveAll(nameof(Person))); // Assert Assert.That(_realm.All().Count(), Is.EqualTo(0)); @@ -224,7 +222,7 @@ public void RemoveResults_FromTheSameRealm_ShouldWork() private void PerformWithOtherRealm(string path, Action action) { Realm otherRealm; - using (otherRealm = Realm.GetInstance(path ?? Path.GetTempFileName())) + using (otherRealm = GetRealm(path ?? Guid.NewGuid().ToString())) { action(otherRealm); } diff --git a/Tests/Realm.Tests/Database/StandAloneObjectTests.cs b/Tests/Realm.Tests/Database/StandAloneObjectTests.cs index 1eb59e196a..5a339d024a 100644 --- a/Tests/Realm.Tests/Database/StandAloneObjectTests.cs +++ b/Tests/Realm.Tests/Database/StandAloneObjectTests.cs @@ -56,20 +56,18 @@ public void AddToRealm() _person.LastName = "Dent"; _person.IsInteresting = true; - using (var realm = Realm.GetInstance()) + using var realm = GetRealm(); + realm.Write(() => { - realm.Write(() => - { - realm.Add(_person); - }); - - Assert.That(_person.IsManaged); - - var p = realm.All().Single(); - Assert.That(p.FirstName, Is.EqualTo("Arthur")); - Assert.That(p.LastName, Is.EqualTo("Dent")); - Assert.That(p.IsInteresting); - } + realm.Add(_person); + }); + + Assert.That(_person.IsManaged); + + var p = realm.All().Single(); + Assert.That(p.FirstName, Is.EqualTo("Arthur")); + Assert.That(p.LastName, Is.EqualTo("Dent")); + Assert.That(p.IsInteresting); } [Test] @@ -84,43 +82,41 @@ public void RealmObject_WhenStandalone_ShouldHaveDefaultEqualsImplementation() public void RealmObject_WhenManaged_ShouldNotThrow() { // This is a test to ensure that our weaver is generating valid IL regardless of property configurations - using (var realm = Realm.GetInstance()) + using var realm = GetRealm(); + Assert.DoesNotThrow(() => realm.Write(() => + { + realm.Add(new NoListProperties()); + }), $"{nameof(NoListProperties)} add failed."); + + Assert.DoesNotThrow(() => realm.Write(() => + { + realm.Add(new OnlyListProperties()); + }), $"{nameof(OnlyListProperties)} add failed."); + + Assert.DoesNotThrow(() => realm.Write(() => + { + realm.Add(new MixedProperties1()); + }), $"{nameof(MixedProperties1)} add failed."); + + Assert.DoesNotThrow(() => realm.Write(() => + { + realm.Add(new MixedProperties2()); + }), $"{nameof(MixedProperties2)} add failed."); + + Assert.DoesNotThrow(() => realm.Write(() => + { + realm.Add(new OneNonListProperty()); + }), $"{nameof(OneNonListProperty)} add failed."); + + Assert.DoesNotThrow(() => realm.Write(() => + { + realm.Add(new OneListProperty()); + }), $"{nameof(OneListProperty)} add failed."); + + Assert.DoesNotThrow(() => realm.Write(() => { - Assert.DoesNotThrow(() => realm.Write(() => - { - realm.Add(new NoListProperties()); - }), $"{nameof(NoListProperties)} add failed."); - - Assert.DoesNotThrow(() => realm.Write(() => - { - realm.Add(new OnlyListProperties()); - }), $"{nameof(OnlyListProperties)} add failed."); - - Assert.DoesNotThrow(() => realm.Write(() => - { - realm.Add(new MixedProperties1()); - }), $"{nameof(MixedProperties1)} add failed."); - - Assert.DoesNotThrow(() => realm.Write(() => - { - realm.Add(new MixedProperties2()); - }), $"{nameof(MixedProperties2)} add failed."); - - Assert.DoesNotThrow(() => realm.Write(() => - { - realm.Add(new OneNonListProperty()); - }), $"{nameof(OneNonListProperty)} add failed."); - - Assert.DoesNotThrow(() => realm.Write(() => - { - realm.Add(new OneListProperty()); - }), $"{nameof(OneListProperty)} add failed."); - - Assert.DoesNotThrow(() => realm.Write(() => - { - realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); - }), $"{nameof(AllTypesObject)} add failed."); - } + realm.Add(new AllTypesObject { RequiredStringProperty = string.Empty }); + }), $"{nameof(AllTypesObject)} add failed."); } public class NoListProperties : RealmObject diff --git a/Tests/Realm.Tests/Database/TestObjects.cs b/Tests/Realm.Tests/Database/TestObjects.cs index bc44ea2bbc..41c4b13395 100644 --- a/Tests/Realm.Tests/Database/TestObjects.cs +++ b/Tests/Realm.Tests/Database/TestObjects.cs @@ -20,8 +20,10 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using MongoDB.Bson; +using Realms.Tests.Database; -namespace Realms.Tests.Database +namespace Realms.Tests { public class AllTypesObject : RealmObject { @@ -43,6 +45,12 @@ public class AllTypesObject : RealmObject public DateTimeOffset DateTimeOffsetProperty { get; set; } + public decimal DecimalProperty { get; set; } + + public Decimal128 Decimal128Property { get; set; } + + public ObjectId ObjectIdProperty { get; set; } + [Required] public string RequiredStringProperty { get; set; } @@ -67,6 +75,19 @@ public class AllTypesObject : RealmObject public bool? NullableBooleanProperty { get; set; } public DateTimeOffset? NullableDateTimeOffsetProperty { get; set; } + + public decimal? NullableDecimalProperty { get; set; } + + public Decimal128? NullableDecimal128Property { get; set; } + + public ObjectId? NullableObjectIdProperty { get; set; } + } + + public class DecimalsObject : RealmObject + { + public decimal DecimalValue { get; set; } + + public Decimal128 Decimal128Value { get; set; } } public class ListsObject : RealmObject @@ -87,6 +108,12 @@ public class ListsObject : RealmObject public IList BooleanList { get; } + public IList DecimalList { get; } + + public IList Decimal128List { get; } + + public IList ObjectIdList { get; } + public IList StringList { get; } public IList ByteArrayList { get; } @@ -111,6 +138,12 @@ public class ListsObject : RealmObject public IList NullableDateTimeOffsetList { get; } + public IList NullableDecimalList { get; } + + public IList NullableDecimal128List { get; } + + public IList NullableObjectIdList { get; } + public IList> ByteCounterList { get; } public IList> Int16CounterList { get; } @@ -131,6 +164,7 @@ public class ListsObject : RealmObject public class CounterObject : RealmObject { [PrimaryKey] + [MapTo("_id")] public int Id { get; set; } public RealmInteger ByteProperty { get; set; } @@ -152,9 +186,19 @@ public class CounterObject : RealmObject public override string ToString() => Id.ToString(); } + public class ObjectIdPrimaryKeyWithValueObject : RealmObject + { + [PrimaryKey] + [MapTo("_id")] + public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); + + public string StringValue { get; set; } + } + public class IntPrimaryKeyWithValueObject : RealmObject { [PrimaryKey] + [MapTo("_id")] public int Id { get; set; } public string StringValue { get; set; } @@ -163,36 +207,42 @@ public class IntPrimaryKeyWithValueObject : RealmObject public class PrimaryKeyCharObject : RealmObject { [PrimaryKey] + [MapTo("_id")] public char CharProperty { get; set; } } public class PrimaryKeyByteObject : RealmObject { [PrimaryKey] + [MapTo("_id")] public byte ByteProperty { get; set; } } public class PrimaryKeyInt16Object : RealmObject { [PrimaryKey] + [MapTo("_id")] public short Int16Property { get; set; } } public class PrimaryKeyInt32Object : RealmObject { [PrimaryKey] + [MapTo("_id")] public int Int32Property { get; set; } } public class PrimaryKeyInt64Object : RealmObject { [PrimaryKey] + [MapTo("_id")] public long Int64Property { get; set; } } public class PrimaryKeyStringObject : RealmObject { [PrimaryKey] + [MapTo("_id")] public string StringProperty { get; set; } public string Value { get; set; } @@ -202,41 +252,61 @@ public class RequiredPrimaryKeyStringObject : RealmObject { [PrimaryKey] [Required] + [MapTo("_id")] public string StringProperty { get; set; } public string Value { get; set; } } + public class PrimaryKeyObjectIdObject : RealmObject + { + [PrimaryKey] + [MapTo("_id")] + public ObjectId StringProperty { get; set; } + } + public class PrimaryKeyNullableCharObject : RealmObject { [PrimaryKey] + [MapTo("_id")] public char? CharProperty { get; set; } } public class PrimaryKeyNullableByteObject : RealmObject { [PrimaryKey] + [MapTo("_id")] public byte? ByteProperty { get; set; } } public class PrimaryKeyNullableInt16Object : RealmObject { [PrimaryKey] + [MapTo("_id")] public short? Int16Property { get; set; } } public class PrimaryKeyNullableInt32Object : RealmObject { [PrimaryKey] + [MapTo("_id")] public int? Int32Property { get; set; } } public class PrimaryKeyNullableInt64Object : RealmObject { [PrimaryKey] + [MapTo("_id")] public long? Int64Property { get; set; } } + public class PrimaryKeyNullableObjectIdObject : RealmObject + { + [PrimaryKey] + [MapTo("_id")] + public ObjectId? StringProperty { get; set; } + } + public class ClassWithUnqueryableMembers : RealmObject { public string RealPropertyToSatisfyWeaver { get; set; } @@ -352,7 +422,7 @@ public class RemappedPropertiesObject : RealmObject public class RemappedTypeObject : RealmObject { [PrimaryKey] - [MapTo("__id")] + [MapTo("_id")] public int Id { get; set; } public string StringValue { get; set; } @@ -380,4 +450,107 @@ public class ObjectWithRequiredStringList : RealmObject [Required] public IList Strings { get; } } + + public class ObjectWithEmbeddedProperties : RealmObject + { + [PrimaryKey] + public int PrimaryKey { get; set; } + + public EmbeddedAllTypesObject AllTypesObject { get; set; } + + public IList ListOfAllTypesObjects { get; } + + public EmbeddedLevel1 RecursiveObject { get; set; } + } + + public class EmbeddedAllTypesObject : EmbeddedObject + { + public char CharProperty { get; set; } + + public byte ByteProperty { get; set; } + + public short Int16Property { get; set; } + + public int Int32Property { get; set; } + + public long Int64Property { get; set; } + + public float SingleProperty { get; set; } + + public double DoubleProperty { get; set; } + + public decimal DecimalProperty { get; set; } + + public Decimal128 Decimal128Property { get; set; } + + public bool BooleanProperty { get; set; } + + public string StringProperty { get; set; } + + public DateTimeOffset DateTimeOffsetProperty { get; set; } + + public char? NullableCharProperty { get; set; } + + public byte? NullableByteProperty { get; set; } + + public short? NullableInt16Property { get; set; } + + public int? NullableInt32Property { get; set; } + + public long? NullableInt64Property { get; set; } + + public float? NullableSingleProperty { get; set; } + + public double? NullableDoubleProperty { get; set; } + + public bool? NullableBooleanProperty { get; set; } + + public decimal? NullableDecimalProperty { get; set; } + + public Decimal128? NullableDecimal128Property { get; set; } + + public DateTimeOffset? NullableDateTimeOffsetProperty { get; set; } + + public RealmInteger ByteCounterProperty { get; set; } + + public RealmInteger Int16CounterProperty { get; set; } + + public RealmInteger Int32CounterProperty { get; set; } + + public RealmInteger Int64CounterProperty { get; set; } + + public RealmInteger? NullableByteCounterProperty { get; set; } + + public RealmInteger? NullableInt16CounterProperty { get; set; } + + public RealmInteger? NullableInt32CounterProperty { get; set; } + + public RealmInteger? NullableInt64CounterProperty { get; set; } + + [Backlink(nameof(ObjectWithEmbeddedProperties.AllTypesObject))] + public IQueryable ContainersObjects { get; } + } + + public class EmbeddedLevel1 : EmbeddedObject + { + public string String { get; set; } + + public EmbeddedLevel2 Child { get; set; } + + public IList Children { get; } + } + + public class EmbeddedLevel2 : EmbeddedObject + { + public string String { get; set; } + + public EmbeddedLevel3 Child { get; set; } + + public IList Children { get; } + } + + public class EmbeddedLevel3 : EmbeddedObject + { + public string String { get; set; } + } } diff --git a/Tests/Realm.Tests/Database/ThreadHandoverTests.cs b/Tests/Realm.Tests/Database/ThreadHandoverTests.cs index 4cb53cfdea..75f9e07015 100644 --- a/Tests/Realm.Tests/Database/ThreadHandoverTests.cs +++ b/Tests/Realm.Tests/Database/ThreadHandoverTests.cs @@ -18,7 +18,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; using NUnit.Framework; @@ -38,14 +37,12 @@ public void ObjectReference_ShouldWork() await Task.Run(() => { - using (var otherRealm = Realm.GetInstance(_realm.Config)) - { - var otherObj = otherRealm.ResolveReference(objReference); + using var otherRealm = GetRealm(_realm.Config); + var otherObj = otherRealm.ResolveReference(objReference); - Assert.That(otherObj.IsManaged); - Assert.That(otherObj.IsValid); - Assert.That(otherObj.Int, Is.EqualTo(12)); - } + Assert.That(otherObj.IsManaged); + Assert.That(otherObj.IsValid); + Assert.That(otherObj.Int, Is.EqualTo(12)); }); }); } @@ -65,14 +62,12 @@ public void ListReference_ShouldWork() await Task.Run(() => { - using (var otherRealm = Realm.GetInstance(_realm.Config)) - { - var otherList = otherRealm.ResolveReference(listReference); + using var otherRealm = GetRealm(_realm.Config); + var otherList = otherRealm.ResolveReference(listReference); - Assert.That(otherList, Is.InstanceOf(typeof(RealmList))); - var dogNames = otherList.Select(d => d.Name); - Assert.That(dogNames, Is.EqualTo(new[] { "1", "2" })); - } + Assert.That(otherList, Is.InstanceOf(typeof(RealmList))); + var dogNames = otherList.Select(d => d.Name); + Assert.That(dogNames, Is.EqualTo(new[] { "1", "2" })); }); }); } @@ -96,11 +91,9 @@ public void ThreadSafeReference_CanOnlyBeConsumedOnce() await Task.Run(() => { - using (var otherRealm = Realm.GetInstance(_realm.Config)) - { - otherRealm.ResolveReference(objReference); - Assert.That(() => otherRealm.ResolveReference(objReference), Throws.InstanceOf().And.Message.Contains("Can only resolve a thread safe reference once.")); - } + using var otherRealm = GetRealm(_realm.Config); + otherRealm.ResolveReference(objReference); + Assert.That(() => otherRealm.ResolveReference(objReference), Throws.InstanceOf().And.Message.Contains("Can only resolve a thread safe reference once.")); }); }); } @@ -128,14 +121,12 @@ public void ThreadSafeReference_CanBeResolvedOnTheSameThread() { var objReference = SetupObjectReference(); - using (var otherRealm = Realm.GetInstance(_realm.Config)) - { - var otherObj = otherRealm.ResolveReference(objReference); + using var otherRealm = GetRealm(_realm.Config); + var otherObj = otherRealm.ResolveReference(objReference); - Assert.That(otherObj.IsManaged); - Assert.That(otherObj.IsValid); - Assert.That(otherObj.Int, Is.EqualTo(12)); - } + Assert.That(otherObj.IsManaged); + Assert.That(otherObj.IsValid); + Assert.That(otherObj.Int, Is.EqualTo(12)); } [Test] @@ -188,19 +179,10 @@ public void ThreadReference_WhenResolvedWithDifferentConfiguration_ShouldReturnN await Task.Run(() => { - var config = new RealmConfiguration(Path.GetTempFileName()); - try - { - using (var otherRealm = Realm.GetInstance(config)) - { - var otherObj = otherRealm.ResolveReference(objReference); - Assert.That(otherObj, Is.Null); - } - } - finally - { - Realm.DeleteRealm(config); - } + var config = new RealmConfiguration(Guid.NewGuid().ToString()); + using var otherRealm = GetRealm(config); + var otherObj = otherRealm.ResolveReference(objReference); + Assert.That(otherObj, Is.Null); }); }); } @@ -214,17 +196,15 @@ public void ThreadSafeReference_WhenTargetRealmInTransaction_ShouldSucceed() await Task.Run(() => { - using (var otherRealm = Realm.GetInstance(_realm.Config)) + using var otherRealm = GetRealm(_realm.Config); + otherRealm.Write(() => { - otherRealm.Write(() => - { - var otherObj = otherRealm.ResolveReference(objReference); - - Assert.That(otherObj.IsManaged); - Assert.That(otherObj.IsValid); - Assert.That(otherObj.Int, Is.EqualTo(12)); - }); - } + var otherObj = otherRealm.ResolveReference(objReference); + + Assert.That(otherObj.IsManaged); + Assert.That(otherObj.IsValid); + Assert.That(otherObj.Int, Is.EqualTo(12)); + }); }); }); } @@ -244,14 +224,12 @@ public void ObjectReference_WhenSourceRealmInTransaction_ShouldSucceed() await Task.Run(() => { - using (var otherRealm = Realm.GetInstance(_realm.Config)) - { - var otherObj = otherRealm.ResolveReference(objRef); + using var otherRealm = GetRealm(_realm.Config); + var otherObj = otherRealm.ResolveReference(objRef); - Assert.That(otherObj.IsManaged); - Assert.That(otherObj.IsValid); - Assert.That(otherObj.Int, Is.EqualTo(123)); - } + Assert.That(otherObj.IsManaged); + Assert.That(otherObj.IsValid); + Assert.That(otherObj.Int, Is.EqualTo(123)); }); }); } @@ -270,11 +248,9 @@ public void ObjectReference_ResolveDeletedObject_ShouldReturnNull() await Task.Run(() => { - using (var otherRealm = Realm.GetInstance(_realm.Config)) - { - var otherObj = otherRealm.ResolveReference(objReference); - Assert.That(otherObj, Is.Null); - } + using var otherRealm = GetRealm(_realm.Config); + var otherObj = otherRealm.ResolveReference(objReference); + Assert.That(otherObj, Is.Null); }); }); } @@ -296,12 +272,10 @@ public void ListReference_ResolveDeletedParentObject_ShouldReturnNull() await Task.Run(() => { - using (var otherRealm = Realm.GetInstance(_realm.Config)) - { - var otherList = otherRealm.ResolveReference(listReference); + using var otherRealm = GetRealm(_realm.Config); + var otherList = otherRealm.ResolveReference(listReference); - Assert.That(otherList, Is.Null); - } + Assert.That(otherList, Is.Null); }); }); } @@ -372,14 +346,12 @@ private Task AssertQueryReferenceAsync(ThreadSafeReference.Query { - using (var otherRealm = Realm.GetInstance(_realm.Config)) - { - var otherQuery = otherRealm.ResolveReference(reference); + using var otherRealm = GetRealm(_realm.Config); + var otherQuery = otherRealm.ResolveReference(reference); - Assert.That(otherQuery, Is.InstanceOf(typeof(RealmResults))); - var values = otherQuery.ToArray().Select(q => q.Int); - Assert.That(values, Is.EqualTo(expected)); - } + Assert.That(otherQuery, Is.InstanceOf(typeof(RealmResults))); + var values = otherQuery.ToArray().Select(q => q.Int); + Assert.That(values, Is.EqualTo(expected)); }); } } diff --git a/Tests/Realm.Tests/GlobalSuppressions.cs b/Tests/Realm.Tests/GlobalSuppressions.cs index 6ec20b1ae0..6a08f54c39 100644 --- a/Tests/Realm.Tests/GlobalSuppressions.cs +++ b/Tests/Realm.Tests/GlobalSuppressions.cs @@ -24,3 +24,4 @@ [assembly: SuppressMessage("Naming", "CA1720:Identifier contains type name", Justification = "This is fine for tests.", Scope = "module")] [assembly: SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "This is fine for tests.", Scope = "module")] [assembly: SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", Justification = "This is fine for tests.", Scope = "module")] +[assembly: SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "This is fine for tests.", Scope = "module")] diff --git a/Tests/Realm.Tests/Program.cs b/Tests/Realm.Tests/Program.cs index 8b3dea3656..5055ab3836 100644 --- a/Tests/Realm.Tests/Program.cs +++ b/Tests/Realm.Tests/Program.cs @@ -27,7 +27,7 @@ public sealed class Program public static int Main(string[] args) { var autorun = new AutoRun(typeof(Program).GetTypeInfo().Assembly); - var arguments = Sync.SyncTestHelpers.ExtractRosSettings(args); + var arguments = Sync.SyncTestHelpers.ExtractBaasSettings(args); autorun.Execute(arguments); diff --git a/Tests/Realm.Tests/Realm.Tests.csproj b/Tests/Realm.Tests/Realm.Tests.csproj index fb21d92e8c..9f9d65d550 100644 --- a/Tests/Realm.Tests/Realm.Tests.csproj +++ b/Tests/Realm.Tests/Realm.Tests.csproj @@ -5,6 +5,7 @@ $(TargetFrameworks);uap10.0.18362 Realms.Tests true + 8.0 false Exe False @@ -19,6 +20,7 @@ + diff --git a/Tests/Realm.Tests/RealmInstanceTest.cs b/Tests/Realm.Tests/RealmInstanceTest.cs index 021804e03c..7a5d922b6b 100644 --- a/Tests/Realm.Tests/RealmInstanceTest.cs +++ b/Tests/Realm.Tests/RealmInstanceTest.cs @@ -18,7 +18,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; namespace Realms.Tests @@ -32,7 +31,7 @@ public abstract class RealmInstanceTest : RealmTest protected Realm _realm => _lazyRealm.Value; - protected void FreezeInPlace(RealmObject obj) + protected void FreezeInPlace(RealmObjectBase obj) { obj.FreezeInPlace(); CleanupOnTearDown(obj.Realm); @@ -41,7 +40,7 @@ protected void FreezeInPlace(RealmObject obj) protected virtual RealmConfiguration CreateConfiguration(string path) => new RealmConfiguration(path); protected T Freeze(T obj) - where T : RealmObject + where T : RealmObjectBase { var result = obj.Freeze(); CleanupOnTearDown(result.Realm); @@ -49,7 +48,7 @@ protected T Freeze(T obj) } protected IRealmCollection Freeze(IRealmCollection collection) - where T : RealmObject + where T : RealmObjectBase { var result = collection.Freeze(); CleanupOnTearDown(result.Realm); @@ -57,7 +56,7 @@ protected IRealmCollection Freeze(IRealmCollection collection) } protected IList Freeze(IList list) - where T : RealmObject + where T : RealmObjectBase { var result = list.Freeze(); CleanupOnTearDown(result.AsRealmCollection().Realm); @@ -65,7 +64,7 @@ protected IList Freeze(IList list) } protected IQueryable Freeze(IQueryable query) - where T : RealmObject + where T : RealmObjectBase { var result = query.Freeze(); CleanupOnTearDown(result.AsRealmCollection().Realm); @@ -74,7 +73,7 @@ protected IQueryable Freeze(IQueryable query) protected override void CustomSetUp() { - _configuration = CreateConfiguration(Path.GetTempFileName()); + _configuration = CreateConfiguration(Guid.NewGuid().ToString()); _lazyRealm = new Lazy(() => GetRealm(_configuration)); base.CustomSetUp(); } diff --git a/Tests/Realm.Tests/RealmTest.cs b/Tests/Realm.Tests/RealmTest.cs index ee8b844c02..3b5310f26a 100644 --- a/Tests/Realm.Tests/RealmTest.cs +++ b/Tests/Realm.Tests/RealmTest.cs @@ -16,8 +16,11 @@ // //////////////////////////////////////////////////////////////////////////// +using System; using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Threading.Tasks; using NUnit.Framework; namespace Realms.Tests @@ -33,7 +36,7 @@ public abstract class RealmTest static RealmTest() { - InteropConfig.DefaultStorageFolder = Path.Combine(Path.GetTempPath(), $"realm-tests-${System.Diagnostics.Process.GetCurrentProcess().Id}"); + InteropConfig.DefaultStorageFolder = Path.Combine(Path.GetTempPath(), $"rt-${System.Diagnostics.Process.GetCurrentProcess().Id}"); Directory.CreateDirectory(InteropConfig.DefaultStorageFolder); } @@ -44,7 +47,7 @@ public void SetUp() { if (OverrideDefaultConfig) { - RealmConfiguration.DefaultConfiguration = new RealmConfiguration(Path.GetTempFileName()); + RealmConfiguration.DefaultConfiguration = new RealmConfiguration(Guid.NewGuid().ToString()); } CustomSetUp(); @@ -68,9 +71,14 @@ public void TearDown() { CustomTearDown(); - Realms.Sync.SharedRealmHandleExtensions.ResetForTesting(); _isSetup = false; - Realm.DeleteRealm(RealmConfiguration.DefaultConfiguration); + try + { + Realm.DeleteRealm(RealmConfiguration.DefaultConfiguration); + } + catch + { + } } } @@ -91,7 +99,28 @@ protected virtual void CustomTearDown() protected Realm GetRealm(RealmConfigurationBase config = null) { - var result = Realm.GetInstance(config ?? RealmConfiguration.DefaultConfiguration); + var result = Realm.GetInstance(config); + CleanupOnTearDown(result); + return result; + } + + protected Realm GetRealm(string path) + { + var result = Realm.GetInstance(path); + CleanupOnTearDown(result); + return result; + } + + protected async Task GetRealmAsync(RealmConfigurationBase config, CancellationToken cancellationToken = default) + { + var result = await Realm.GetInstanceAsync(config, cancellationToken); + CleanupOnTearDown(result); + return result; + } + + protected Realm Freeze(Realm realm) + { + var result = realm.Freeze(); CleanupOnTearDown(result); return result; } diff --git a/Tests/Realm.Tests/Server/NotifierIntegrationTests.cs b/Tests/Realm.Tests/Server/NotifierIntegrationTests.cs deleted file mode 100644 index c8b53e801f..0000000000 --- a/Tests/Realm.Tests/Server/NotifierIntegrationTests.cs +++ /dev/null @@ -1,115 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using NUnit.Framework; -using Realms.Server; -using Realms.Tests.Database; -using Realms.Tests.Sync; - -namespace Realms.Tests.Server -{ - public class NotifierIntegrationTests : ServerTestBase - { - [Test] - public void SmokeTest() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var (realm, userId) = await CreateRandomRealmAsync("modifications"); - - var changeDetails = new List(); - var handler = new ProxyingHandler(path => path == $"/{userId}/modifications", - details => - { - var lastChange = details.Changes.Single(); - Assert.That(lastChange.Key, Is.EqualTo(nameof(IntPropertyObject))); - - changeDetails.Add(new ChangeInfo - { - Insertions = lastChange.Value.Insertions.Select(o => (int)o.Int).ToArray(), - Modifications = lastChange.Value.Modifications.Select(o => ((int)o.CurrentObject.Int, (int)o.PreviousObject.Int, o.ChangedProperties.ToArray())).ToArray(), - Deletions = lastChange.Value.Deletions.Select(o => (int)o.Int).ToArray() - }); - return Task.CompletedTask; - }); - - var config = await GetConfiguration(handler); - - using (var notifier = await Notifier.StartAsync(config)) - using (realm) - { - var obj = new IntPropertyObject { Int = 3 }; - realm.Write(() => realm.Add(obj)); - - var insertChange = await WaitForChangeAsync(changeDetails, 1); - Assert.That(insertChange.Modifications.Length, Is.Zero); - Assert.That(insertChange.Deletions.Length, Is.Zero); - - Assert.That(insertChange.Insertions.Length, Is.EqualTo(1)); - Assert.That(insertChange.Insertions.Single(), Is.EqualTo(obj.Int)); - - realm.Write(() => obj.Int = 4); - - var modifyChange = await WaitForChangeAsync(changeDetails, 2); - Assert.That(modifyChange.Insertions.Length, Is.Zero); - Assert.That(modifyChange.Deletions.Length, Is.Zero); - - Assert.That(modifyChange.Modifications.Length, Is.EqualTo(1)); - Assert.That(modifyChange.Modifications.Single().PreviousValue, Is.EqualTo(insertChange.Insertions.Single())); - Assert.That(modifyChange.Modifications.Single().CurrentValue, Is.EqualTo(obj.Int)); - Assert.That(modifyChange.Modifications.Single().ModifiedProperties, Is.EquivalentTo(new[] { nameof(IntPropertyObject.Int) })); - - realm.Write(() => realm.Remove(obj)); - - var deleteChange = await WaitForChangeAsync(changeDetails, 3); - Assert.That(deleteChange.Insertions.Length, Is.Zero); - Assert.That(deleteChange.Modifications.Length, Is.Zero); - - Assert.That(deleteChange.Deletions.Length, Is.EqualTo(1)); - Assert.That(deleteChange.Deletions.Single(), Is.EqualTo(modifyChange.Modifications.Single().CurrentValue)); - } - }, timeout: 1000000); - } - - private static Task WaitForChangeAsync(IList changeDetails, int expectedCount) - { - return TestHelpers.WaitForConditionAsync(() => - { - if (changeDetails.Count != expectedCount) - { - return null; - } - - return changeDetails.Last(); - }, c => c != null); - } - - private class ChangeInfo - { - public int[] Insertions { get; set; } - - public (int CurrentValue, int PreviousValue, string[] ModifiedProperties)[] Modifications { get; set; } - - public int[] Deletions { get; set; } - } - } -} diff --git a/Tests/Realm.Tests/Server/ProxyingHandler.cs b/Tests/Realm.Tests/Server/ProxyingHandler.cs deleted file mode 100644 index c74bc13235..0000000000 --- a/Tests/Realm.Tests/Server/ProxyingHandler.cs +++ /dev/null @@ -1,40 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Threading.Tasks; -using Realms.Server; - -namespace Realms.Tests.Server -{ - public class ProxyingHandler : INotificationHandler - { - private readonly Func _shouldHandle; - private readonly Func _handleChanges; - - public ProxyingHandler(Func shouldHandle, Func handleChanges) - { - _shouldHandle = shouldHandle ?? (_ => false); - _handleChanges = handleChanges ?? (_ => Task.CompletedTask); - } - - public Task HandleChangeAsync(IChangeDetails details) => _handleChanges(details); - - public bool ShouldHandle(string path) => _shouldHandle(path); - } -} diff --git a/Tests/Realm.Tests/Server/ServerTestBase.cs b/Tests/Realm.Tests/Server/ServerTestBase.cs deleted file mode 100644 index 172c55b1cb..0000000000 --- a/Tests/Realm.Tests/Server/ServerTestBase.cs +++ /dev/null @@ -1,84 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Realms.Server; -using Realms.Sync; -using Realms.Tests.Sync; - -namespace Realms.Tests.Server -{ - public abstract class ServerTestBase : SyncTestBase - { - private string _baseFolder; - - private string UserRealmFolder => Path.Combine(_baseFolder, "user-realms"); - - private string NotifierFolder => Path.Combine(_baseFolder, "notifer"); - - protected override bool OverrideDefaultConfig => false; - - protected override void CustomSetUp() - { - base.CustomSetUp(); - - _baseFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - - Directory.CreateDirectory(NotifierFolder); - Directory.CreateDirectory(UserRealmFolder); - } - - protected override void CustomTearDown() - { - base.CustomTearDown(); - - try - { - Directory.Delete(_baseFolder, recursive: true); - } - catch - { - } - } - - protected async Task GetConfiguration(params INotificationHandler[] handlers) - { - var admin = await SyncTestHelpers.GetAdminUserAsync(); - return new NotifierConfiguration(admin) - { - Handlers = handlers.ToList(), - WorkingDirectory = NotifierFolder - }; - } - - protected async Task<(Realm Realm, string UserId)> CreateRandomRealmAsync(string path) - { - var user = await SyncTestHelpers.GetUserAsync(); - var location = Path.Combine(UserRealmFolder, user.Identity, path); - Directory.CreateDirectory(Path.GetDirectoryName(location)); - var config = new FullSyncConfiguration(new Uri($"~/{path}", UriKind.Relative), user, location); - var realm = GetRealm(config); - await SyncTestHelpers.WaitForUploadAsync(realm); - return (realm, user.Identity); - } - } -} diff --git a/Tests/Realm.Tests/Server/ShouldHandleTests.cs b/Tests/Realm.Tests/Server/ShouldHandleTests.cs deleted file mode 100644 index daee22aead..0000000000 --- a/Tests/Realm.Tests/Server/ShouldHandleTests.cs +++ /dev/null @@ -1,69 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using Realms.Server; -using Realms.Tests.Sync; - -namespace Realms.Tests.Server -{ - public class ShouldHandleTests : ServerTestBase - { - [Test] - public void ShouldHandle_WhenRealmExists_InvokedOnStart() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var paths = new List(); - var handler = new ProxyingHandler(path => - { - paths.Add(path); - return false; - }, null); - - var (_, userId1) = await CreateRandomRealmAsync("emails"); - var (_, userId2) = await CreateRandomRealmAsync("invoices"); - var (_, userId3) = await CreateRandomRealmAsync("invoices"); - var (_, userId4) = await CreateRandomRealmAsync("deep/folder/hierarchy"); - - var config = await GetConfiguration(handler); - using (var notifier = Notifier.StartAsync(config)) - { - var expectedPaths = new[] - { - $"/{userId1}/emails", - $"/{userId2}/invoices", - $"/{userId3}/invoices", - $"/{userId4}/deep/folder/hierarchy" - }; - - // ROS may contain other Realms and it takes some time to go over all. - // This will check if we've received ShouldHandle prompt for all expected ones - // every 100 ms for 10 seconds. - await TestHelpers.WaitForConditionAsync(() => expectedPaths.All(paths.Contains)); - - var (_, userId5) = await CreateRandomRealmAsync("newlyaddedrealm"); - - await TestHelpers.WaitForConditionAsync(() => paths.Contains($"/{userId5}/newlyaddedrealm")); - } - }, timeout: 30000); - } - } -} diff --git a/Tests/Realm.Tests/Sync/AppTests.cs b/Tests/Realm.Tests/Sync/AppTests.cs new file mode 100644 index 0000000000..5cc607f8ed --- /dev/null +++ b/Tests/Realm.Tests/Sync/AppTests.cs @@ -0,0 +1,95 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Text; +using NUnit.Framework; +using Realms.Sync; + +namespace Realms.Tests.Sync +{ + [TestFixture, Preserve(AllMembers = true)] + public class AppTests : SyncTestBase + { + [Test] + public void AppCreate_CreatesApp() + { + // This is mostly a smoke test to ensure that nothing blows up when setting all properties. + var config = new AppConfiguration("abc-123") + { + BaseUri = new Uri("http://foo.bar"), + LocalAppName = "My app", + LocalAppVersion = "1.2.3", + LogLevel = LogLevel.All, + MetadataEncryptionKey = new byte[64], + MetadataPersistenceMode = MetadataPersistenceMode.Encrypted, + BaseFilePath = InteropConfig.DefaultStorageFolder, + CustomLogger = (message, level) => { }, + DefaultRequestTimeout = TimeSpan.FromSeconds(123) + }; + + var app = CreateApp(config); + Assert.That(app.Sync, Is.Not.Null); + } + + [Test] + public void App_Login_Anonymous() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await DefaultApp.LogInAsync(Credentials.Anonymous()); + }); + } + + [TestCase(LogLevel.Debug)] + [TestCase(LogLevel.Info)] + public void App_WithCustomLogger_LogsSyncOperations(LogLevel logLevel) + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var logBuilder = new StringBuilder(); + + var appConfig = SyncTestHelpers.GetAppConfig(); + appConfig.LogLevel = logLevel; + appConfig.CustomLogger = (message, level) => + { + lock (logBuilder) + { + logBuilder.AppendLine($"[{level}] {message}"); + } + }; + + var app = CreateApp(appConfig); + + var config = await GetIntegrationConfigAsync(Guid.NewGuid().ToString()); + using var realm = await GetRealmAsync(config); + realm.Write(() => + { + realm.Add(new PrimaryKeyStringObject { StringProperty = Guid.NewGuid().ToString() }); + }); + + await WaitForUploadAsync(realm); + + var log = logBuilder.ToString(); + + Assert.That(log, Does.Contain($"[{logLevel}]")); + Assert.That(log, Does.Not.Contain($"[{logLevel - 1}]")); + }); + } + } +} diff --git a/Tests/Realm.Tests/Sync/CredentialsTests.cs b/Tests/Realm.Tests/Sync/CredentialsTests.cs deleted file mode 100644 index 911e25e597..0000000000 --- a/Tests/Realm.Tests/Sync/CredentialsTests.cs +++ /dev/null @@ -1,612 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using NUnit.Framework; -using Realms.Exceptions; -using Realms.Sync; -using Realms.Sync.Exceptions; -using Realms.Tests.Database; - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class CredentialsTests : SyncTestBase - { - [Test] - public void BasicTests() - { - // Arrange and Act - var fb = Credentials.Facebook("token"); - - // Assert - Assert.That(fb.IdentityProvider, Is.EqualTo("facebook")); - Assert.That(fb.UserInfo, Is.Empty); - } - - [Test] - public void UserCurrent_WhenThereAreNoUsers_ShouldReturnNull() - { - Assert.That(() => User.Current, Is.Null); - } - - [Test] - public void UserCurrent_WhenThereIsOneUser_ShouldReturnThatUser() - { - TestHelpers.RunAsyncTest(async () => - { - var user = await SyncTestHelpers.GetFakeUserAsync(); - var currentUser = User.Current; - - Assert.That(currentUser, Is.EqualTo(user)); - }); - } - - [Test] - public void UserCurrent_WhenThereIsMoreThanOneUser_ShouldThrow() - { - TestHelpers.RunAsyncTest(async () => - { - await SyncTestHelpers.GetFakeUserAsync(); - await SyncTestHelpers.GetFakeUserAsync(); - - Assert.That(() => User.Current, Throws.TypeOf()); - }); - } - - [Test] - public void UserAllLoggedIn_WhenThereAreNoUsers_ShouldReturnEmptyCollection() - { - var users = User.AllLoggedIn; - - Assert.That(users, Is.Empty); - } - - [Test] - public void UserAllLoggedIn_WhenThereIsOneUser_ShouldReturnThatUser() - { - TestHelpers.RunAsyncTest(async () => - { - var user = await SyncTestHelpers.GetFakeUserAsync(); - - var users = User.AllLoggedIn; - - Assert.That(users.Length, Is.EqualTo(1)); - Assert.That(users[0], Is.EqualTo(user)); - }); - } - - [Test] - public void UserAllLoggedIn_WhenThereAreNineUsers_ShouldReturnAllOfThem() - { - TestHelpers.RunAsyncTest(async () => - { - var users = new List(); - for (var i = 0; i < 9; i++) - { - users.Add(await SyncTestHelpers.GetFakeUserAsync()); - } - - var current = User.AllLoggedIn; - - Assert.That(current, Is.EquivalentTo(users)); - }); - } - - [TestCase("realm://localhost")] - [TestCase("realms://localhost")] - [TestCase("foo://bar")] - public void UserLogin_WrongProtocolTestCases(string url) - { - TestHelpers.RunAsyncTest(async () => - { - await TestHelpers.AssertThrows(() => User.LoginAsync(SyncTestHelpers.CreateCredentials(), new Uri(url))); - }); - } - - private const string OriginalPassword = "a"; - private const string NewPassword = "b"; - - [Test] - public void UserChangePasswordTest() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var userId = Guid.NewGuid().ToString(); - var credentials = Credentials.UsernamePassword(userId, OriginalPassword, createUser: true); - var user = await User.LoginAsync(credentials, SyncTestHelpers.AuthServerUri); - await user.ChangePasswordAsync(NewPassword); - await user.LogOutAsync(); - - Assert.That(async () => await user.ChangePasswordAsync("c"), Throws.TypeOf()); - - await TestNewPassword(userId); - }); - } - - [Test] - public void AdminChangePasswordTest() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var userId = Guid.NewGuid().ToString(); - var credentials = Credentials.UsernamePassword(userId, OriginalPassword, createUser: true); - var user = await User.LoginAsync(credentials, SyncTestHelpers.AuthServerUri); - var identity = user.Identity; - await user.LogOutAsync(); - - var admin = await User.LoginAsync(SyncTestHelpers.AdminCredentials(), SyncTestHelpers.AuthServerUri); - await admin.ChangePasswordAsync(identity, NewPassword); - - await admin.LogOutAsync(); - - Assert.That(async () => await admin.ChangePasswordAsync(identity, "c"), Throws.TypeOf()); - - await TestNewPassword(userId); - }); - } - - [Test] - public void UserLogout_RevokesRefreshToken() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var userId = Guid.NewGuid().ToString(); - var credentials = Credentials.UsernamePassword(userId, OriginalPassword, createUser: true); - var user = await User.LoginAsync(credentials, SyncTestHelpers.AuthServerUri); - - var token = user.RefreshToken; - await user.LogOutAsync(); - - // Changing user's password uses the RefreshToken as authorization - var json = new Dictionary - { - ["data"] = new Dictionary - { - ["new_password"] = "b" - } - }; - - await TestHelpers.AssertThrows( - () => AuthenticationHelper.MakeAuthRequestAsync(HttpMethod.Put, new Uri(SyncTestHelpers.AuthServerUri, "auth/password"), json, token), - ex => - { - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - Assert.That(ex.ErrorCode, Is.EqualTo(ErrorCode.AccessDenied)); - }); - }); - } - - [Test] - public void UserLookup_WhenTargetUserExists_ShouldReturnResponse() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var admin = await User.LoginAsync(SyncTestHelpers.AdminCredentials(), SyncTestHelpers.AuthServerUri); - - var aliceUsername = Guid.NewGuid().ToString(); - var alice = await User.LoginAsync(Credentials.UsernamePassword(aliceUsername, "a", createUser: true), SyncTestHelpers.AuthServerUri); - - var lookupResponse = await admin.RetrieveInfoForUserAsync(Credentials.Provider.UsernamePassword, aliceUsername); - - Assert.That(lookupResponse.Identity, Is.EqualTo(alice.Identity)); - Assert.That(lookupResponse.IsAdmin, Is.False); - - Assert.That(lookupResponse.Accounts, Is.Not.Empty); - var passwordAccount = lookupResponse.Accounts.SingleOrDefault(a => a.Provider == Credentials.Provider.UsernamePassword); - - Assert.That(passwordAccount, Is.Not.Null); - Assert.That(passwordAccount.ProviderUserIdentity, Is.EqualTo(aliceUsername)); - }); - } - - [Test] - public void UserLookup_WhenTargetUserDoesNotExist_ShouldReturnNull() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var admin = await User.LoginAsync(SyncTestHelpers.AdminCredentials(), SyncTestHelpers.AuthServerUri); - - var lookupResponse = await admin.RetrieveInfoForUserAsync(Credentials.Provider.UsernamePassword, "something"); - Assert.That(lookupResponse, Is.Null); - }); - } - - [Test] - public void UserLookup_WhenTargetUserIsSelf_ShouldReturnResponse() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var admin = await User.LoginAsync(SyncTestHelpers.AdminCredentials(), SyncTestHelpers.AuthServerUri); - - var lookupResponse = await admin.RetrieveInfoForUserAsync(Credentials.Provider.UsernamePassword, Constants.AdminUsername); - - Assert.That(lookupResponse.Identity, Is.EqualTo(admin.Identity)); - Assert.That(lookupResponse.IsAdmin, Is.True); - Assert.That(lookupResponse.Accounts, Is.Not.Empty); - var passwordAccount = lookupResponse.Accounts.SingleOrDefault(a => a.Provider == Credentials.Provider.UsernamePassword); - - Assert.That(passwordAccount, Is.Not.Null); - Assert.That(passwordAccount.ProviderUserIdentity, Is.EqualTo(Constants.AdminUsername)); - }); - } - - [Test] - public void UserLookup_WhenUserIsNotAdmin_ShouldThrow() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var alice = await SyncTestHelpers.GetUserAsync(); - - await TestHelpers.AssertThrows(() => alice.RetrieveInfoForUserAsync(Credentials.Provider.UsernamePassword, "some-id")); - }); - } - - [Test] - public void UserLogin_WhenAnonymous_LogsUserIn() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var credentials = Credentials.Anonymous(); - var user = await User.LoginAsync(credentials, SyncTestHelpers.AuthServerUri); - - Assert.That(user, Is.Not.Null); - Assert.That(user.Identity, Is.Not.Null); - }); - } - - [Test] - public void UserLogin_WhenAnonymousAndSameCredentials_ShouldLoginDifferentUser() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var credentials = Credentials.Anonymous(); - var first = await User.LoginAsync(credentials, SyncTestHelpers.AuthServerUri); - var second = await User.LoginAsync(credentials, SyncTestHelpers.AuthServerUri); - - Assert.That(first.Identity, Is.Not.EqualTo(second.Identity)); - Assert.That(User.AllLoggedIn.Length, Is.EqualTo(2)); - }); - } - - [Test] - public void UserLogin_WhenAnonymousAndDifferentCredentials_ShouldLoginDifferentUser() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var first = await User.LoginAsync(Credentials.Anonymous(), SyncTestHelpers.AuthServerUri); - var second = await User.LoginAsync(Credentials.Anonymous(), SyncTestHelpers.AuthServerUri); - - Assert.That(first.Identity, Is.Not.EqualTo(second.Identity)); - Assert.That(User.AllLoggedIn.Length, Is.EqualTo(2)); - }); - } - - [Test] - public void UserLogin_WhenAnonymousAndOtherUsersLoggedIn_ShouldLoginDifferentUser() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - // Login a regular user - await SyncTestHelpers.GetUserAsync(); - - // Login an anonymous user - var first = await User.LoginAsync(Credentials.Anonymous(), SyncTestHelpers.AuthServerUri); - - // Login another regular user - await SyncTestHelpers.GetUserAsync(); - - // Login a second anonymous user - var second = await User.LoginAsync(Credentials.Anonymous(), SyncTestHelpers.AuthServerUri); - - // Expect that the anonymous users to be different - Assert.That(first.Identity, Is.Not.EqualTo(second.Identity)); - Assert.That(User.AllLoggedIn.Length, Is.EqualTo(4)); - }); - } - - [Test] - public void UserLogin_WhenAnonymous_AfterLogoutShouldLoginDifferentUser() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - // Login an anonymous user - var first = await User.LoginAsync(Credentials.Anonymous(), SyncTestHelpers.AuthServerUri); - await first.LogOutAsync(); - - // Login a second anonymous user - var second = await User.LoginAsync(Credentials.Anonymous(), SyncTestHelpers.AuthServerUri); - - // Expect that the anonymous users to be different - Assert.That(first.Identity, Is.Not.EqualTo(second.Identity)); - Assert.That(User.AllLoggedIn.Length, Is.EqualTo(1)); - }); - } - -#pragma warning disable CS0618 // Type or member is obsolete - - [Test] - public void UserLogin_WhenNickname_LogsUserIn() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var credentials = Credentials.Nickname(Guid.NewGuid().ToString()); - var user = await User.LoginAsync(credentials, SyncTestHelpers.AuthServerUri); - - Assert.That(user, Is.Not.Null); - Assert.That(user.Identity, Is.Not.Null); - }); - } - - [Test] - public void UserLogin_WhenNickname_LogsSameUserIn() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var nickname = Guid.NewGuid().ToString(); - var first = await User.LoginAsync(Credentials.Nickname(nickname), SyncTestHelpers.AuthServerUri); - - Assert.That(first, Is.Not.Null); - Assert.That(first.Identity, Is.Not.Null); - - var second = await User.LoginAsync(Credentials.Nickname(nickname), SyncTestHelpers.AuthServerUri); - - Assert.That(first.Identity, Is.EqualTo(second.Identity)); - }); - } - - [Test] - public void UserLogin_WhenNicknameAfterLogout_LogsSameUserIn() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var nickname = Guid.NewGuid().ToString(); - var first = await User.LoginAsync(Credentials.Nickname(nickname), SyncTestHelpers.AuthServerUri); - await first.LogOutAsync(); - - var second = await User.LoginAsync(Credentials.Nickname(nickname), SyncTestHelpers.AuthServerUri); - - Assert.That(first.Identity, Is.EqualTo(second.Identity)); - }); - } - -#pragma warning restore CS0618 // Type or member is obsolete - - #region CustomRefreshTokenTests - - /* - // Keys - ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCMgFkzVG4bc5DW -VtSVtYgdMRbTIyj6UnhOmmS7LjcT02sPQxs8tB5ggwGJSd4/wtv8J0p1Bsq43wOF -R+LCwYwZqZ/UysLxl2qg3m4LN7NcgIeDfCSNdbc97Vwg1Li0ygBr9vQcUTq9sgKD -7IFu48inn0xF7fxG/FhqGQYo/frLE/RDV3cbchyB5KP9lRoaZ54Wj3yyoiMlnrsX -OES91aZRLld4LYWUMg7aWzrRBm41QgYqAT05SGQ5TaU/Z1x/1i61nJ5jw5w50Mn7 -AYvl3EYJ4/E+yh0XUSYjkbr9HGO6Q2+HugGpUl2WjVTmPhQAaAj3k/dDSSJFcYZE -z2yNV/MFAgMBAAECggEATB4ItU9La6HbWNOnzgeP20jJ9c75l0vwk5z/b4zlF9+V -A6q2adenEWBIB8m2F1MI/P2IUAhC8Y8YiC9ewWY78Xc8+Pp0TJBcmxSGB5vAlx+m -yuwJnX2lrW4XWE4GVyOMwPEEZQb4zOZQiIorwRi0j2M03jnFT+vMNoaiGLkoErZJ -xOw93+v83cPivQsR6PeZ8KrPSW0V/lzvH8ZqiQQjpDMm7Y90F4Hr1g6DJ2AogEjv -Tv8yWTlzPYcd5reevRV1eyzSHYwcr6dhdmGl1LQLkL9uPbwiQmXLgjWywm3xmDyX -BWJeIVI88H+E8hPb1A2yZjyr35CXdNPbcRo2B8YGLQKBgQDLQ/W24DvqUu9TZ3/B -EfTTzkXzIU+mUo9qnxtxB+2DnwksTxSK6HG4H1qxo8CCuOR8RBSoqJxqqNtKkq2L -lIYrMGoCpYRPcT0JHP7ZfqVnh15CrAkra5QvFXEzbK4aqrJR//HuzdUvFqvb/aNS -jEyuVaMNUGNiMYDreD0CX+q38wKBgQCw89waZsqdBtea3A2VKo5xMMicdssC2kNt -MJGPCXnXwATqjTHbaFQCbamUJPqlTiMnKVRC4mTr85IXM85gXonFYLYt0CCGX5wd -zyC2LcdCfvQBjgrtr9ytKhvK6gq9kBEPNgWNQO9AzuqN1BXmduLfc/8welErIfgA -HixAcdKfJwKBgQCAi9wK6Ug66nQcBOpQSXDRujOWjMx4XOICBdku5Fqa0KrWcLSH -HHU+geWzTeHjSdaFl/CQsQEqmtsEEDrcePNYwOdqAQ7pxq1Y5BNvrJ4iGQPNmkq6 -QPCXzjGm2eZJSwY2wWxZH6bgfq/1EjSFceDUp6fUNbCEWtYzE/lRVSN1bQKBgQCK -P1uc/OYbXHciM/4gpkj3QgfZxi3Bosi/DA0M1XhuCUVOAtYK9y17YDX22hVBBRUN -yYpdXwc+GOPwYLdCL1ov7OkoTcy7bwNHfsWtz4I3/3ufo1wCaz1bxORF2iheBapu -WeRogWzrEz3JZQNfNU73CWc8drPnoPhjDy+/ga3uTQKBgFJHP+wZix0efZymu0VS -SacwuyolDNg1ebQsBA7XZ/ac9HH/cxxGHxFS76cwfxM7KUpgXEhmFUtEJjuy6UME -tw/6uOA95dBQztjvCmAgzdzExq1lSfadgpnj/SYbr70YKBvEnwb1KOPbFlVBnX4f -BuwMRU4Vebrdbe6RGRg8mByy ------END PRIVATE KEY----- - ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjIBZM1RuG3OQ1lbUlbWI -HTEW0yMo+lJ4Tppkuy43E9NrD0MbPLQeYIMBiUneP8Lb/CdKdQbKuN8DhUfiwsGM -Gamf1MrC8ZdqoN5uCzezXICHg3wkjXW3Pe1cINS4tMoAa/b0HFE6vbICg+yBbuPI -p59MRe38RvxYahkGKP36yxP0Q1d3G3IcgeSj/ZUaGmeeFo98sqIjJZ67FzhEvdWm -US5XeC2FlDIO2ls60QZuNUIGKgE9OUhkOU2lP2dcf9YutZyeY8OcOdDJ+wGL5dxG -CePxPsodF1EmI5G6/RxjukNvh7oBqVJdlo1U5j4UAGgI95P3Q0kiRXGGRM9sjVfz -BQIDAQAB ------END PUBLIC KEY----- - - ROS Config: - -import { BasicServer } from ".."; -import * as path from "path"; -import { ConsoleLogger } from "../shared"; - -require("../../feature-token-for-tests.js"); - -async function main() { - const server = new BasicServer(); - const publicKey = `-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjIBZM1RuG3OQ1lbUlbWI -HTEW0yMo+lJ4Tppkuy43E9NrD0MbPLQeYIMBiUneP8Lb/CdKdQbKuN8DhUfiwsGM -Gamf1MrC8ZdqoN5uCzezXICHg3wkjXW3Pe1cINS4tMoAa/b0HFE6vbICg+yBbuPI -p59MRe38RvxYahkGKP36yxP0Q1d3G3IcgeSj/ZUaGmeeFo98sqIjJZ67FzhEvdWm -US5XeC2FlDIO2ls60QZuNUIGKgE9OUhkOU2lP2dcf9YutZyeY8OcOdDJ+wGL5dxG -CePxPsodF1EmI5G6/RxjukNvh7oBqVJdlo1U5j4UAGgI95P3Q0kiRXGGRM9sjVfz -BQIDAQAB ------END PUBLIC KEY-----`; - - await server.start({ - dataPath: path.resolve("./data"), - graphQLServiceConfigOverride: (config) => { - config.disableAuthentication = true; - }, - logger: new ConsoleLogger("debug"), - refreshTokenValidators: [ - { - algorithms: ["RS256"], - issuer: "myissuer", - publicKey, - audience: "myApp", - isAdminField: "admin" - } - ] - }); -} - -main().catch((err) => { - console.log(err); - process.exit(1); -}); - - */ - - [Test, NUnit.Framework.Explicit("Requires non-default ROS")] - public void UserLogin_WhenCustomRefreshToken_LogsUserIn() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZSwiaWF0IjoxNTE2MjM5MDIyLCJhdWQiOiJteUFwcCIsImlzcyI6Im15aXNzdWVyIn0.Xhl39nnVXIgTUqDKEfz2mDiHcfH8vZGDC4gJxAHZmQ_usf-uXTXfDxkjME2W5ynKeWUQrzIhOliHaouJq-XJpzqKPvQ4d70LwtijNC53O4SUaHHaTkhh98OLOZif0md7xHeeEJAI9sixNK4GDzA88a2K5dZ9dmv3XJJ3url481CNK5mSCMgTcN5dzChbewmJ327J7mDsHF74Nvdazevk7UyShLz0YfJaPr2ny9feUXcG7yMRTfg3XoSHGUZ1IDDyvjjslfelTZWIR3ccmiua2wyN1EKAQE0o1Ft89VFHDxIHVvfgdXr9aQvtEaPR7-GChL8rx1WiqujSMJ0DZC80gQ"; - var credentials = Credentials.CustomRefreshToken(token, "123", isAdmin: true); - - var realmPath = Guid.NewGuid().ToString(); - var user = await User.LoginAsync(credentials, SyncTestHelpers.AuthServerUri); - var config = new FullSyncConfiguration(new Uri($"/~/{realmPath}", UriKind.Relative), user); - using (var realm = await GetRealmAsync(config)) - { - realm.Write(() => - { - realm.Add(new PrimaryKeyInt32Object - { - Int32Property = 123 - }); - }); - - await GetSession(realm).WaitForUploadAsync(); - } - - var token2 = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0NTYiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZSwiaWF0IjoxNTE2MjM5MDIyLCJhdWQiOiJteUFwcCIsImlzcyI6Im15aXNzdWVyIn0.Hum9NA5KfBqKNsRN6hckbijSAME4LfH2xwmqwPrfjVEBlHRg6HIOnV4gxjY_KUhaazjsExNjAGEhxAamTiefHgvTryVlXwgLjaVs2DpR7F2t1JkpB9b7bU8fo0XV1ZhQ40s9_s3_t6Gdaf8cewSr2ADe0q71c09kP4VtxHQlzXkKuDjkwVXhaXFKglaJNy2Lhk04ybKJn0g_H-sWv2keTW1-J1RhZCzkB_o1Xv-SqoB_n5lahZ3rSUvbQalcQn20mOetTlfAkYfi3Eee4bYzc0iykDdG124uUnQVXXiQR67qlB4zqJ1LuG84KBYcO7W5g_kIBq7YzNaP68xT_x2YBw"; - var credentials2 = Credentials.CustomRefreshToken(token2, "456", isAdmin: true); - - var user2 = await User.LoginAsync(credentials2, SyncTestHelpers.AuthServerUri); - - await user.ApplyPermissionsAsync(PermissionCondition.UserId(user2.Identity), $"/~/{realmPath}", AccessLevel.Write); - - var permissions = await user2.GetGrantedPermissionsAsync(Recipient.CurrentUser); - - var userFooPermission = permissions.SingleOrDefault(p => p.Path.EndsWith($"/{realmPath}")); - Assert.That(userFooPermission, Is.Not.Null); - - var config2 = new FullSyncConfiguration(new Uri(userFooPermission.Path, UriKind.Relative), user2); - using (var realm = await GetRealmAsync(config2)) - { - var objects = realm.All(); - Assert.That(objects.Count(), Is.EqualTo(1)); - Assert.That(objects.Single().Int32Property, Is.EqualTo(123)); - } - }); - } - - [Test, NUnit.Framework.Explicit("Requires non-default ROS")] - public void User_WhenCustomRefreshToken_CanUpdateToken() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZSwiaWF0IjoxNTE2MjM5MDIyLCJhdWQiOiJteUFwcCIsImlzcyI6Im15aXNzdWVyIn0.FmRf5n3o83vduCIShxmXOTWBegJwQqZKWNakIjQN3OAxfjJBK2tkSJGBqBBARN7nkEAWypGQzk1VkjuIKAZfGC1QpSSyv3RBw3D85hNs_aRvHgh2PXIiWbxMvRdZF6N5gN4Zi_47TsL67FqthQV6btOvrwqUuY5EY3vqW8LJT9D-966j6xmLOG7ZeEpWjNVvFx9nR5DmOYIXvamWGLCND_cqYhWcgrSs0I0FMZ6IxfjoiUZST5vc_c18XIbuszongqDUMJEIPbvjmN31tCuLXDuorf3eOpALIIsfR1Dt-RnkoOYAJrPTUjg_NnVqbIj0RzPzdbx7lClP1gZbE3HAjw"; - var credentials = Credentials.CustomRefreshToken(token, "123456", isAdmin: true); - - var user = await User.LoginAsync(credentials, SyncTestHelpers.AuthServerUri); - Assert.That(token, Is.EqualTo(user.RefreshToken)); - - var config = new FullSyncConfiguration(new Uri($"/~/{Guid.NewGuid()}", UriKind.Relative), user); - - // Can't use the async version as out token is expired - using (var expiredRealm = Realm.GetInstance(config)) - { - expiredRealm.Write(() => - { - expiredRealm.Add(new PrimaryKeyInt32Object - { - Int32Property = 456 - }); - }); - } - - var newToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZSwiaWF0IjoxNTE2MjM5MDIyLCJhdWQiOiJteUFwcCIsImlzcyI6Im15aXNzdWVyIiwic29tZXRoaW5nIjoiZWxzZSJ9.PnfTPoeLmfbjuuKDDzLyZ6BgLVjOmx8qazVdEkVxbjy5jtJnGIyyl77y71E4Auf4sIOqLzqs6Tve4JnrZIltXSzLBnmC76JPW9t3LT0-t09UGG7K0eTYaySlXgzjTZ1bEyc3plnr2Vw4y3g4uonmsU6fliaKoqpWnW-UHDMsPdRJR3BzQYIBkj3SSwCCb-uDRsZWQhyx2CyVvsJgAow_jae5oi38QO5UC6kqCMflUxMHDR5MmSRuhTvtA3Uk0rYMTnh4LzWhmL5yH_uSgBwluTcTJxnxU_jf_S9HqnbuBnyWbwlDVsd-ABffF-LkWhj1uSCW9OpSVBJyF5ekTYDqNQ"; - user.RefreshToken = newToken; - Assert.That(newToken, Is.EqualTo(user.RefreshToken)); - - // Ensure we can still sync - using (var realm = await GetRealmAsync(config)) - { - realm.Write(() => - { - realm.Add(new PrimaryKeyInt32Object - { - Int32Property = 123 - }); - }); - - await GetSession(realm).WaitForUploadAsync(); - - // Ensure we have both objects - Assert.That(realm.All().Count(), Is.EqualTo(2)); - } - }); - } - - [Test] - public void User_WhenCustomRefreshToken_CanLoginAUserDirectly() - { - var token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJQZXNobzEyMyIsImlhdCI6MTUxNjIzOTAyMn0.Hu99JXT28Gq2Zf-KjM75I1nBxhU3WxWqWjJSaAc6TP6Bwy4czlR4krw8LDdjrBJ0zyI6CAyVAR_y1lRyFbz3j-jaXmmuwyIgO61PJxOJaWEmBawz1_C3_z8-XjxNeBH8lXgGFcPzDG0HqbmXMgPEAFK4LAY5tFMkzEPP3w5OslZu17ixlzsYtSkia_bQSYId6-KSkj-tZE6KExgumIyF4JnS51s8oDr6U3C4qa1Y6-QkyWqCdFMRMd6558qECbVxV5CPP0x58LFyC6Coz1Xob6zkB2b_ba5FepFO-cJtvXaIBDOYsV3GsD9NfW8cLDCNqeJADbJuCP_iHRIDT4vFdg"; - var credentials = Credentials.CustomRefreshToken(token, "Pesho123"); - var user = User.LoginAsync(credentials, new Uri($"http://{SyncTestHelpers.FakeRosUrl}")).Result; - - Assert.That(user.Identity, Is.EqualTo("Pesho123")); - } - - #endregion CustomRefreshTokenTests - - private static async Task TestNewPassword(string userId) - { - // Ensure that users are logged out - await Task.Delay(100); - - Assert.That(User.Current, Is.Null); - - // Try to login with the same credentials - await TestHelpers.AssertThrows(() => User.LoginAsync(Credentials.UsernamePassword(userId, OriginalPassword, createUser: false), SyncTestHelpers.AuthServerUri), ex => - { - Assert.That(ex.ErrorCode, Is.EqualTo(ErrorCode.InvalidCredentials)); - }); - - var newCredentials = Credentials.UsernamePassword(userId, NewPassword, createUser: false); - var newUser = await User.LoginAsync(newCredentials, SyncTestHelpers.AuthServerUri); - - Assert.That(newUser.State, Is.EqualTo(UserState.Active)); - Assert.That(newUser, Is.EqualTo(User.Current)); - await newUser.LogOutAsync(); - Assert.That(User.Current, Is.Null); - } - } -} diff --git a/Tests/Realm.Tests/Sync/FunctionsTests.cs b/Tests/Realm.Tests/Sync/FunctionsTests.cs new file mode 100644 index 0000000000..3842621336 --- /dev/null +++ b/Tests/Realm.Tests/Sync/FunctionsTests.cs @@ -0,0 +1,368 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Linq; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Conventions; +using NUnit.Framework; + +namespace Realms.Tests.Sync +{ + [TestFixture, Preserve(AllMembers = true)] + public class FunctionsTests : SyncTestBase + { + private readonly List _conventionsToRemove = new List(); + + [Test] + public void CallFunction_ReturnsResult() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + var result = await user.Functions.CallAsync("sumFunc", 1, 2, 3); + + Assert.That(result.AsInt64, Is.EqualTo(6)); + }); + } + + [Test] + public void CallFunctionGeneric_ReturnsResult() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + var result = await user.Functions.CallAsync("sumFunc", 1, 2, 3); + + Assert.That(result, Is.EqualTo(6)); + }); + } + + [Test] + public void CallFunction_NoArguments() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + var result = await user.Functions.CallAsync("sumFunc"); + + Assert.That(result, Is.EqualTo(0)); + }); + } + + [Test] + public void CallFunction_WrongGeneric() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + var ex = await TestHelpers.AssertThrows(() => user.Functions.CallAsync("sumFunc", 1, 2)); + + Assert.That(ex.Message, Does.Contain("Cannot deserialize")); + }); + } + + [Test] + public void CallFunction_WithAnonymousParams_ReturnsBsonResult() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + var first = new + { + intValue = 1, + floatValue = 1.2, + stringValue = "Hello ", + objectId = ObjectId.GenerateNewId(), + arr = new[] { 1, 2, 3 }, + date = DateTime.UtcNow, + child = new + { + intValue = 2 + } + }; + + var second = new + { + intValue = 2, + floatValue = 2.3, + stringValue = "world", + objectId = ObjectId.GenerateNewId(), + date = DateTime.UtcNow.AddHours(5), + arr = new[] { 4, 5, 6 }, + child = new + { + intValue = 3 + } + }; + + var result = await user.Functions.CallAsync("documentFunc", first, second); + + Assert.That(result["intValue"].AsInt64, Is.EqualTo(3)); + Assert.That(result["floatValue"].AsDouble, Is.EqualTo(3.5)); + Assert.That(result["stringValue"].AsString, Is.EqualTo("Hello world")); + Assert.That(result["objectId"].AsObjectId, Is.EqualTo(first.objectId)); + AssertDateTimeEquals(result["date"].ToUniversalTime(), second.date); + Assert.That(result["arr"].AsBsonArray.Select(a => a.AsInt32), Is.EquivalentTo(new[] { 1, 4 })); + Assert.That(result["child"]["intValue"].AsInt64, Is.EqualTo(5)); + }); + } + + [Test] + public void CallFunction_WithTypedParams_ReturnsTypedResult() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + AddCamelCaseConvention(); + + var user = await GetUserAsync(); + var first = new FunctionArgument + { + IntValue = 1, + FloatValue = 1.2, + StringValue = "Hello ", + ObjectId = ObjectId.GenerateNewId(), + Arr = new[] { 1, 2, 3 }, + Date = DateTime.UtcNow, + Child = new Child + { + IntValue = 2 + } + }; + + var second = new FunctionArgument + { + IntValue = 2, + FloatValue = 2.3, + StringValue = "world", + ObjectId = ObjectId.GenerateNewId(), + Date = DateTime.UtcNow.AddHours(5), + Arr = new[] { 4, 5, 6 }, + Child = new Child + { + IntValue = 3 + } + }; + + var result = await user.Functions.CallAsync("documentFunc", first, second); + + Assert.That(result.IntValue, Is.EqualTo(3)); + Assert.That(result.FloatValue, Is.EqualTo(3.5)); + Assert.That(result.StringValue, Is.EqualTo("Hello world")); + AssertDateTimeEquals(result.Date, second.Date); + Assert.That(result.ObjectId, Is.EqualTo(first.ObjectId)); + Assert.That(result.Arr, Is.EquivalentTo(new[] { 1, 4 })); + Assert.That(result.Child.IntValue, Is.EqualTo(5)); + }); + } + + [TestCaseSource(nameof(DeserializationTestCases))] + public void CallFunction_AndTestDeserialization(dynamic arg) + { + TestDeserialization(arg); + } + + [TestCaseSource(nameof(DeserializationTestCases))] + public void CallFunction_AndTestBsonValue(dynamic arg) + { + TestBsonValue(arg); + } + + [Test] + public void CallFunction_AndTestDeserialization_Null() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + string str = null; + var result = await user.Functions.CallAsync("mirror", str); + + Assert.That(result, Is.Null); + }); + } + + private void TestDeserialization(T val) + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var result = await user.Functions.CallAsync("mirror", val); + + if (val is DateTime date && result is DateTime dateResult) + { + AssertDateTimeEquals(date, dateResult); + } + else + { + Assert.That(result, Is.EqualTo(val)); + } + + var arr = new[] { val }; + var arrResult = await user.Functions.CallAsync("mirror", arr); + + if (arr is DateTime[] dateArr && arrResult is DateTime[] dateArrResult) + { + Assert.That(dateArr.Length, Is.EqualTo(dateArrResult.Length)); + for (var i = 0; i < dateArr.Length; i++) + { + AssertDateTimeEquals(dateArr[i], dateArrResult[i]); + } + } + else + { + Assert.That(arrResult, Is.EquivalentTo(arr)); + } + }); + } + + private void TestBsonValue(T val) + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var result = await user.Functions.CallAsync("mirror", val); + + if (val is DateTime date) + { + AssertDateTimeEquals(result.ToUniversalTime(), date); + } + else + { + Assert.That(result.ToString().ToLower(), Is.EqualTo(val.ToString().ToLower())); + } + + var arr = new[] { val }; + var arrResult = await user.Functions.CallAsync("mirror", arr); + + if (arr is DateTime[] dateArr) + { + var dateArrResult = arrResult.AsBsonArray.Select(a => a.ToUniversalTime()).ToArray(); + Assert.That(dateArrResult.Length, Is.EqualTo(dateArr.Length)); + for (var i = 0; i < dateArr.Length; i++) + { + AssertDateTimeEquals(dateArrResult[i], dateArr[i]); + } + } + else + { + Assert.That( + arrResult.AsBsonArray.Select(a => a.ToString().ToLower()), + Is.EquivalentTo(arr.Select(a => a.ToString().ToLower()))); + } + }); + } + + protected override void CustomTearDown() + { + base.CustomTearDown(); + + foreach (var convention in _conventionsToRemove) + { + ConventionRegistry.Remove(convention); + } + + _conventionsToRemove.Clear(); + } + + private static void AssertDateTimeEquals(DateTime first, DateTime second) + { + var diff = (first - second).TotalMilliseconds; + Assert.That(Math.Abs(diff), Is.LessThan(1), $"Expected {first} to equal {second} with millisecond precision, but it didn't."); + } + + private void AddCamelCaseConvention() + { + const string name = "camel case"; + var pack = new ConventionPack(); + pack.Add(new CamelCaseElementNameConvention()); + ConventionRegistry.Register(name, pack, _ => true); + + _conventionsToRemove.Add(name); + } + + private class FunctionArgument + { + public int IntValue { get; set; } + + public double FloatValue { get; set; } + + public string StringValue { get; set; } + + public ObjectId ObjectId { get; set; } + + public int[] Arr { get; set; } + + public Child Child { get; set; } + + public DateTime Date { get; set; } + } + + private class Child + { + public int IntValue { get; set; } + } + + public static readonly object[] DeserializationTestCases = new[] + { + new object[] { 1 }, + new object[] { 0 }, + new object[] { -1 }, + new object[] { int.MinValue }, + new object[] { int.MaxValue }, + new object[] { 1L }, + new object[] { -1L }, + new object[] { 0L }, + new object[] { long.MinValue }, + new object[] { long.MaxValue }, + new object[] { 1.54 }, + new object[] { 0.00 }, + new object[] { -1.224 }, + + // These don't roundtrip correctly: https://github.com/realm/realm-object-store/issues/1106 + // new object[] { double.MinValue }, + // new object[] { double.MaxValue }, + + // These are unsupported by core - it has a limit of 19 digits for string parsing + // new object[] { Decimal128.MinValue }, + // new object[] { Decimal128.MaxValue }, + new object[] { new Decimal128(1.2M) }, + new object[] { new Decimal128(0) }, + new object[] { new Decimal128(-1.53464399239324M) }, + new object[] { "fooo" }, + new object[] { string.Empty }, + new object[] { ObjectId.Parse("5f766ae78d273beeab5b0e6b") }, + new object[] { ObjectId.Empty }, + new object[] { new DateTime(202065954, DateTimeKind.Utc) }, + new object[] { new DateTime(3333444, DateTimeKind.Utc) }, + new object[] { new DateTime(0, DateTimeKind.Utc) }, + new object[] { DateTime.MinValue }, + new object[] { DateTime.MaxValue }, + new object[] { true }, + new object[] { false }, + new object[] { (short)1 }, + new object[] { (short)0 }, + new object[] { (short)-1 }, + new object[] { short.MinValue }, + new object[] { short.MaxValue }, + }; + } +} diff --git a/Tests/Realm.Tests/Sync/MergeByPKTests.cs b/Tests/Realm.Tests/Sync/MergeByPKTests.cs index cac926bb65..c85c1fd168 100644 --- a/Tests/Realm.Tests/Sync/MergeByPKTests.cs +++ b/Tests/Realm.Tests/Sync/MergeByPKTests.cs @@ -22,8 +22,6 @@ using System.Threading.Tasks; using NUnit.Framework; using Realms.Exceptions; -using Realms.Sync; -using Realms.Tests.Database; namespace Realms.Tests.Sync { @@ -33,7 +31,7 @@ public class MergeByPKTests : SyncTestBase [TestCaseSource(nameof(MergeTestCases))] public void WhenObjectHasPK_ShouldNotCreateDuplicates(Type objectType, object pkValue, Func pkValueChecker) { - SyncTestHelpers.RunRosTestAsync(async () => + SyncTestHelpers.RunBaasTestAsync(async () => { var pkProperty = objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public) .Single(p => p.GetCustomAttribute() != null); @@ -44,25 +42,23 @@ public void WhenObjectHasPK_ShouldNotCreateDuplicates(Type objectType, object pk pkProperty.SetValue(instance, (dynamic)pkValue); - using (var realm = await GetSyncedRealm(objectType)) + using var realm = await GetSyncedRealm(objectType); + try { - try - { - realm.Write(() => realm.Add(instance)); - } - catch (RealmDuplicatePrimaryKeyValueException) - { - // Sync went through too quickly (that's why we do 5 attempts) - } - - await SyncTestHelpers.WaitForUploadAsync(realm); + realm.Write(() => realm.Add(instance)); + } + catch (RealmDuplicatePrimaryKeyValueException) + { + // Sync went through too quickly (that's why we do 5 attempts) } + + await WaitForUploadAsync(realm); } using (var realm = await GetSyncedRealm(objectType)) { - await SyncTestHelpers.WaitForDownloadAsync(realm); - var allObjects = realm.All(objectType.Name).ToArray(); + await WaitForDownloadAsync(realm); + var allObjects = realm.DynamicApi.All(objectType.Name).ToArray(); Assert.That(allObjects.Count(pkValueChecker), Is.EqualTo(1)); } @@ -73,7 +69,9 @@ public void WhenObjectHasPK_ShouldNotCreateDuplicates(Type objectType, object pk { new object[] { typeof(PrimaryKeyInt64Object), 0L, new Func(i => Int64ValueChecker(i, 0)) }, new object[] { typeof(PrimaryKeyInt64Object), 1L, new Func(i => Int64ValueChecker(i, 1)) }, - new object[] { typeof(PrimaryKeyNullableInt64Object), (long?)null, new Func(i => NullableInt64ValueChecker(i, null)) }, + + // V10TODO: reenable this when the server adds support for null PKs + // new object[] { typeof(PrimaryKeyNullableInt64Object), (long?)null, new Func(i => NullableInt64ValueChecker(i, null)) }, new object[] { typeof(PrimaryKeyNullableInt64Object), (long?)0, new Func(i => NullableInt64ValueChecker(i, 0)) }, new object[] { typeof(PrimaryKeyNullableInt64Object), (long?)1, new Func(i => NullableInt64ValueChecker(i, 1)) }, new object[] { typeof(PrimaryKeyStringObject), string.Empty, new Func(i => StringValueChecker(i, string.Empty)) }, @@ -100,16 +98,10 @@ private static bool StringValueChecker(dynamic instance, string pkValue) private async Task GetSyncedRealm(Type objectType) { - var credentials = Credentials.UsernamePassword(Constants.AdminUsername, Constants.AdminPassword, false); - var user = await User.LoginAsync(credentials, SyncTestHelpers.AuthServerUri); - var configuration = new FullSyncConfiguration(SyncTestHelpers.RealmUri($"~/merge_by_pk_{objectType.Name}"), user, Guid.NewGuid().ToString()) - { - ObjectClasses = new[] { objectType } - }; - - Realm.DeleteRealm(configuration); + var config = await GetIntegrationConfigAsync($"merge_by_pk_{objectType.Name}"); + config.ObjectClasses = new[] { objectType }; - return GetRealm(configuration); + return GetRealm(config); } } } \ No newline at end of file diff --git a/Tests/Realm.Tests/Sync/MetadataTests.cs b/Tests/Realm.Tests/Sync/MetadataTests.cs deleted file mode 100644 index e30b241638..0000000000 --- a/Tests/Realm.Tests/Sync/MetadataTests.cs +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -using NUnit.Framework; -using Realms.Sync; - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class MetadataTests : RealmTest - { - [Test] - public void Metadata_WhenAccessed_ShouldWork() - { - Assert.That(SharedRealmHandleExtensions.IsMetadataConfigured, Is.False); - - var users = User.AllLoggedIn; - - Assert.That(SharedRealmHandleExtensions.IsMetadataConfigured, Is.True); - Assert.That(users, Is.Empty); - } - } -} diff --git a/Tests/Realm.Tests/Sync/MongoClientTests.cs b/Tests/Realm.Tests/Sync/MongoClientTests.cs new file mode 100644 index 0000000000..976e45a575 --- /dev/null +++ b/Tests/Realm.Tests/Sync/MongoClientTests.cs @@ -0,0 +1,2246 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using NUnit.Framework; +using Realms.Sync; +using Realms.Sync.Exceptions; + +namespace Realms.Tests.Sync +{ + [TestFixture, Preserve(AllMembers = true)] + public class MongoClientTests : SyncTestBase + { + private const string ServiceName = "BackingDB"; + private const string DbName = "my-db"; + private const string FoosCollectionName = "foos"; + private const string SalesCollectionName = "sales"; + + [Test] + public void MongoClient_ServiceName_ReturnsOriginalName() + { + var user = GetFakeUser(); + var client = user.GetMongoClient("foo-bar"); + + Assert.That(client.ServiceName, Is.EqualTo("foo-bar")); + } + + [Test] + public void MongoDatabase_Name_ReturnsOriginalName() + { + var user = GetFakeUser(); + var client = user.GetMongoClient("foo-bar"); + var db = client.GetDatabase("my-db"); + + Assert.That(db.Name, Is.EqualTo("my-db")); + Assert.That(db.Client.ServiceName, Is.EqualTo("foo-bar")); + } + + [Test] + public void MongoCollection_Name_ReturnsOriginalName() + { + var user = GetFakeUser(); + var client = user.GetMongoClient("foo-bar"); + var db = client.GetDatabase("my-db"); + var collection = db.GetCollection("foos"); + + Assert.That(collection.Name, Is.EqualTo("foos")); + Assert.That(collection.Database.Name, Is.EqualTo("my-db")); + Assert.That(collection.Database.Client.ServiceName, Is.EqualTo("foo-bar")); + } + + [Test] + public void MongoCollection_InsertOne() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var foo = new Foo("a", 5); + var result = await collection.InsertOneAsync(foo); + + Assert.That(result.InsertedId, Is.EqualTo(foo.Id)); + + var foos = await collection.FindAsync(); + Assert.That(foos.Length, Is.EqualTo(1)); + Assert.That(foos[0], Is.EqualTo(foo)); + }); + } + + [Test] + public void MongoCollection_InsertOne_WithNullDoc() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + await TestHelpers.AssertThrows(() => collection.InsertOneAsync(null)); + }); + } + + [Test] + public void MongoCollection_InsertOne_WithDocWithInvalidSchema() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetBsonCollection(); + + var doc = new BsonDocument + { + { "_id", ObjectId.GenerateNewId() }, + { "longValue", "this is a string!" } + }; + + var ex = await TestHelpers.AssertThrows(() => collection.InsertOneAsync(doc)); + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + Assert.That(ex.Message, Does.Contain("insert not permitted")); + Assert.That(ex.HelpLink, Does.Contain("/logs?co_id=")); + }); + } + + [Test] + public void MongoCollection_InsertOne_WithBsonDoc() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetBsonCollection(); + + var doc = new BsonDocument + { + { "_id", ObjectId.GenerateNewId() }, + { "longValue", 5L }, + { "stringValue", "bla bla" }, + }; + var result = await collection.InsertOneAsync(doc); + + Assert.That(result.InsertedId, Is.EqualTo(doc["_id"].AsObjectId)); + + var foos = await collection.FindAsync(); + Assert.That(foos.Length, Is.EqualTo(1)); + Assert.That(foos[0]["stringValue"].AsString, Is.EqualTo("bla bla")); + Assert.That(foos[0]["longValue"].AsInt64, Is.EqualTo(5)); + }); + } + + [Test] + public void MongoCollection_InsertMany() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var foos = new[] + { + new Foo("first", 123), + new Foo("second", 456) + }; + + var result = await collection.InsertManyAsync(foos); + + Assert.That(result.InsertedIds.Length, Is.EqualTo(2)); + Assert.That(result.InsertedIds, Is.EquivalentTo(foos.Select(f => f.Id))); + + var foundFoos = await collection.FindAsync(); + Assert.That(foundFoos.Length, Is.EqualTo(2)); + Assert.That(foundFoos, Is.EquivalentTo(foos)); + }); + } + + [Test] + public void MongoCollection_InsertMany_WithNullCollection() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + await TestHelpers.AssertThrows(() => collection.InsertManyAsync(null)); + }); + } + + [Test] + public void MongoCollection_InsertMany_WithNullDocuments() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var foos = new[] + { + new Foo("first", 123), + null + }; + + var ex = await TestHelpers.AssertThrows(() => collection.InsertManyAsync(foos)); + Assert.That(ex.ParamName, Is.EqualTo("docs")); + Assert.That(ex.Message, Does.Contain("null elements")); + }); + } + + [Test] + public void MongoCollection_InsertMany_WithDocWithInvalidSchema() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetBsonCollection(); + + var doc = new BsonDocument + { + { "_id", ObjectId.GenerateNewId() }, + { "longValue", "this is a string!" } + }; + + var ex = await TestHelpers.AssertThrows(() => collection.InsertManyAsync(new[] { doc })); + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + Assert.That(ex.Message, Does.Contain("insert not permitted")); + Assert.That(ex.HelpLink, Does.Contain("/logs?co_id=")); + }); + } + + [Test] + public void MongoCollection_InsertMany_WithBsonDoc() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetBsonCollection(); + + var docs = new[] + { + new BsonDocument + { + { "_id", ObjectId.GenerateNewId() }, + { "longValue", 5L }, + { "stringValue", "first" }, + }, + new BsonDocument + { + { "_id", ObjectId.GenerateNewId() }, + { "longValue", 999L }, + { "stringValue", "second" }, + }, + }; + + var result = await collection.InsertManyAsync(docs); + + Assert.That(result.InsertedIds, Is.EquivalentTo(docs.Select(d => d["_id"].AsObjectId))); + + var foos = await collection.FindAsync(); + Assert.That(foos.Length, Is.EqualTo(2)); + Assert.That(foos[0]["stringValue"].AsString, Is.EqualTo("first")); + Assert.That(foos[0]["longValue"].AsInt64, Is.EqualTo(5)); + Assert.That(foos[1]["stringValue"].AsString, Is.EqualTo("second")); + Assert.That(foos[1]["longValue"].AsInt64, Is.EqualTo(999)); + }); + } + + [Test] + public void MongoCollection_UpdateOne_WithoutFilter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var update = BsonDocument.Parse(@"{ + $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999""} + } + }"); + + var result = await collection.UpdateOneAsync(filter: null, update); + + Assert.That(result.UpsertedId, Is.Null); + Assert.That(result.MatchedCount, Is.EqualTo(1)); + Assert.That(result.ModifiedCount, Is.EqualTo(1)); + + // Update inserted with expected values after the update + inserted[0].StringValue = "this is update!"; + inserted[0].LongValue = 999; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_UpdateOne_WithFilter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var update = BsonDocument.Parse(@"{ + $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + } + }"); + + var filter = BsonDocument.Parse(@"{ + LongValue: { $gte: 1 } + }"); + + var result = await collection.UpdateOneAsync(filter, update); + + Assert.That(result.UpsertedId, Is.Null); + Assert.That(result.MatchedCount, Is.EqualTo(1)); + Assert.That(result.ModifiedCount, Is.EqualTo(1)); + + // Update inserted with expected values after the update - should have matched + // the second element + inserted[1].StringValue = "this is update!"; + inserted[1].LongValue = 999; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_UpdateOne_NoMatches_Upsert() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var upsertId = ObjectId.GenerateNewId(); + + var test = new BsonDocument + { + { "foo", upsertId } + }.ToJson(); + + var update = BsonDocument.Parse(@"{ + $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + }, + $setOnInsert: { + _id: ObjectId(""" + upsertId + @""") + } + }"); + + var filter = BsonDocument.Parse(@"{ + LongValue: { $gte: 5 } + }"); + + var result = await collection.UpdateOneAsync(filter, update, upsert: true); + + Assert.That(result.UpsertedId, Is.EqualTo(upsertId)); + Assert.That(result.MatchedCount, Is.EqualTo(0)); + Assert.That(result.ModifiedCount, Is.EqualTo(0)); + + // Update inserted with expected values after the update - should have matched + // no docs, so we expect an upsert + inserted = inserted.Concat(new[] + { + new Foo("this is update!", 999) + { + Id = upsertId + } + }).ToArray(); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_UpdateOne_NoMatches_Noupsert() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var upsertId = ObjectId.GenerateNewId(); + var update = BsonDocument.Parse(@"{ + $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + }, + $setOnInsert: { + _id: ObjectId(""" + upsertId + @""") + } + }"); + + var filter = BsonDocument.Parse(@"{ + LongValue: { $gte: 5 } + }"); + + var result = await collection.UpdateOneAsync(filter, update, upsert: false); + + Assert.That(result.UpsertedId, Is.Null); + Assert.That(result.MatchedCount, Is.EqualTo(0)); + Assert.That(result.ModifiedCount, Is.EqualTo(0)); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_UpdateOne_NullUpdate_Throws() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var ex = await TestHelpers.AssertThrows(() => collection.UpdateOneAsync(filter: null, updateDocument: null)); + Assert.That(ex.ParamName, Is.EqualTo("updateDocument")); + }); + } + + [Test] + public void MongoCollection_UpdateMany_WithoutFilter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var update = BsonDocument.Parse(@"{ $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + } }"); + + var result = await collection.UpdateManyAsync(filter: null, update); + + Assert.That(result.UpsertedId, Is.Null); + Assert.That(result.MatchedCount, Is.EqualTo(3)); + Assert.That(result.ModifiedCount, Is.EqualTo(3)); + + // Update inserted with expected values after the update + foreach (var foo in inserted) + { + foo.StringValue = "this is update!"; + foo.LongValue = 999; + } + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_UpdateMany_WithFilter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var update = BsonDocument.Parse(@"{ $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + } }"); + + var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); + + var result = await collection.UpdateManyAsync(filter, update); + + Assert.That(result.UpsertedId, Is.Null); + Assert.That(result.MatchedCount, Is.EqualTo(2)); + Assert.That(result.ModifiedCount, Is.EqualTo(2)); + + // Update inserted with expected values after the update - should have matched + // the second element + for (var i = 1; i < 3; i++) + { + inserted[i].StringValue = "this is update!"; + inserted[i].LongValue = 999; + } + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_UpdateMany_NoMatches_Upsert() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var upsertId = ObjectId.GenerateNewId(); + + var update = BsonDocument.Parse(@"{ + $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + }, + $setOnInsert: { + _id: ObjectId(""" + upsertId + @""") + } + }"); + + var filter = BsonDocument.Parse("{ LongValue: { $gte: 5 } }"); + + var result = await collection.UpdateManyAsync(filter, update, upsert: true); + + Assert.That(result.UpsertedId, Is.EqualTo(upsertId)); + Assert.That(result.MatchedCount, Is.EqualTo(0)); + Assert.That(result.ModifiedCount, Is.EqualTo(0)); + + // Update inserted with expected values after the update - should have matched + // no docs, so we expect an upsert + inserted = inserted.Concat(new[] + { + new Foo("this is update!", 999) + { + Id = upsertId + } + }).ToArray(); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_UpdateMany_NoMatches_Noupsert() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var upsertId = ObjectId.GenerateNewId(); + + var update = BsonDocument.Parse(@"{ + $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + }, + $setOnInsert: { + _id: ObjectId(""" + upsertId + @""") + } + }"); + + var filter = BsonDocument.Parse("{ LongValue: { $gte: 5 } }"); + + var result = await collection.UpdateManyAsync(filter, update, upsert: false); + + Assert.That(result.UpsertedId, Is.Null); + Assert.That(result.MatchedCount, Is.EqualTo(0)); + Assert.That(result.ModifiedCount, Is.EqualTo(0)); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_UpdateMany_NullUpdate_Throws() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var ex = await TestHelpers.AssertThrows(() => collection.UpdateManyAsync(filter: null, updateDocument: null)); + Assert.That(ex.ParamName, Is.EqualTo("updateDocument")); + }); + } + + [Test] + public void MongoCollection_DeleteOne_WithoutFilter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var result = await collection.DeleteOneAsync(); + + Assert.That(result.DeletedCount, Is.EqualTo(1)); + + // The first element is removed + inserted = inserted.Where((_, i) => i > 0).ToArray(); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_DeleteOne_WithFilter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var result = await collection.DeleteOneAsync(filter); + + Assert.That(result.DeletedCount, Is.EqualTo(1)); + + // The second element is removed + inserted = inserted.Where((_, i) => i != 1).ToArray(); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_DeleteOne_WithFilter_NoMatches() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 5 } + } + } + }; + + var result = await collection.DeleteOneAsync(filter); + + Assert.That(result.DeletedCount, Is.EqualTo(0)); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_DeleteMany_WithoutFilter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var result = await collection.DeleteManyAsync(); + + Assert.That(result.DeletedCount, Is.EqualTo(3)); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.Empty); + }); + } + + [Test] + public void MongoCollection_DeleteMany_WithFilter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var result = await collection.DeleteManyAsync(filter); + + Assert.That(result.DeletedCount, Is.EqualTo(2)); + + // The second and third elements are removed + inserted = inserted.Where((_, i) => i == 0).ToArray(); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_DeleteMany_WithFilter_NoMatches() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 5 } + } + } + }; + + var result = await collection.DeleteManyAsync(filter); + + Assert.That(result.DeletedCount, Is.EqualTo(0)); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_Count_WithoutFilter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var result = await collection.CountAsync(); + Assert.That(result, Is.EqualTo(3)); + }); + } + + [Test] + public void MongoCollection_Count_WithFilter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var result = await collection.CountAsync(filter); + + Assert.That(result, Is.EqualTo(2)); + }); + } + + [Test] + public void MongoCollection_Count_WithFilter_NoMatches() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 5 } + } + } + }; + + var result = await collection.CountAsync(filter); + + Assert.That(result, Is.EqualTo(0)); + }); + } + + [Test] + public void MongoCollection_FindOne() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var result = await collection.FindOneAsync(); + Assert.That(result, Is.EqualTo(inserted[0])); + }); + } + + [Test] + public void MongoCollection_FindOne_Sort() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var sort = new { LongValue = -1 }; + var result = await collection.FindOneAsync(sort: sort); + Assert.That(result, Is.EqualTo(inserted[2])); + }); + } + + [Test] + public void MongoCollection_FindOne_Filter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var result = await collection.FindOneAsync(filter); + Assert.That(result, Is.EqualTo(inserted[1])); + }); + } + + [Test] + public void MongoCollection_FindOne_FilterSort() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var sort = new { StringValue = -1 }; + + var result = await collection.FindOneAsync(filter, sort: sort); + Assert.That(result, Is.EqualTo(inserted[2])); + }); + } + + [Test] + public void MongoCollection_FindOne_Projection() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var projection = new + { + _id = 0, + StringValue = 1 + }; + + foreach (var foo in inserted) + { + foo.Id = default; + foo.LongValue = default; + } + + var result = await collection.FindOneAsync(projection: projection); + Assert.That(result, Is.EqualTo(inserted[0])); + }); + } + + [Test] + public void MongoCollection_FindOne_FilterProjection() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var projection = new + { + _id = 0, + LongValue = 1 + }; + + foreach (var foo in inserted) + { + foo.Id = default; + foo.StringValue = default; + } + + var result = await collection.FindOneAsync(filter, projection: projection); + Assert.That(result, Is.EqualTo(inserted[1])); + }); + } + + [Test] + public void MongoCollection_FindOne_ProjectionSort() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var projection = new + { + _id = 1, + LongValue = 1 + }; + + var sort = new { LongValue = -1 }; + + foreach (var foo in inserted) + { + foo.StringValue = default; + } + + var result = await collection.FindOneAsync(sort: sort, projection: projection); + Assert.That(result, Is.EqualTo(inserted[2])); + }); + } + + [Test] + public void MongoCollection_FindOne_FilterProjectionSort() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var projection = new + { + _id = 0, + LongValue = 1, + StringValue = 1 + }; + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var sort = new { LongValue = -1 }; + + foreach (var foo in inserted) + { + foo.Id = default; + } + + var result = await collection.FindOneAsync(filter, sort, projection); + Assert.That(result, Is.EqualTo(inserted[2])); + }); + } + + [Test] + public void MongoCollection_Find() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var result = await collection.FindAsync(); + Assert.That(result, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_Find_Sort() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var sort = new { LongValue = -1 }; + var result = await collection.FindAsync(sort: sort); + Assert.That(result, Is.EquivalentTo(inserted.Reverse())); + }); + } + + [Test] + public void MongoCollection_Find_Filter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var result = await collection.FindAsync(filter); + Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1))); + }); + } + + [Test] + public void MongoCollection_Find_FilterSort() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var sort = new { StringValue = -1 }; + + var result = await collection.FindAsync(filter, sort: sort); + Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1).Reverse())); + }); + } + + [Test] + public void MongoCollection_Find_Projection() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var projection = new + { + _id = 0, + StringValue = 1 + }; + + foreach (var foo in inserted) + { + foo.Id = default; + foo.LongValue = default; + } + + var result = await collection.FindAsync(projection: projection); + Assert.That(result, Is.EquivalentTo(inserted.Reverse())); + }); + } + + [Test] + public void MongoCollection_Find_FilterProjection() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var projection = new + { + _id = 0, + LongValue = 1 + }; + + foreach (var foo in inserted) + { + foo.Id = default; + foo.StringValue = default; + } + + var result = await collection.FindAsync(filter, projection: projection); + Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1))); + }); + } + + [Test] + public void MongoCollection_Find_ProjectionSort() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var projection = new + { + _id = 1, + LongValue = 1 + }; + + var sort = new { LongValue = -1 }; + + foreach (var foo in inserted) + { + foo.StringValue = default; + } + + var result = await collection.FindAsync(sort: sort, projection: projection); + Assert.That(result, Is.EquivalentTo(inserted.Reverse())); + }); + } + + [Test] + public void MongoCollection_Find_FilterProjectionSort() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var projection = new + { + _id = 0, + LongValue = 1, + StringValue = 1 + }; + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var sort = new { LongValue = -1 }; + + foreach (var foo in inserted) + { + foo.Id = default; + } + + var result = await collection.FindAsync(filter, sort, projection); + Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1).Reverse())); + }); + } + + [Test] + public void MongoCollection_Find_Limit() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var result = await collection.FindAsync(limit: 2); + Assert.That(result, Is.EquivalentTo(inserted.Take(2))); + }); + } + + [Test] + public void MongoCollection_Find_SortLimit() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var sort = new { LongValue = -1 }; + var result = await collection.FindAsync(sort: sort, limit: 2); + Assert.That(result, Is.EquivalentTo(inserted.Reverse().Take(2))); + }); + } + + [Test] + public void MongoCollection_Find_Filter_Limit() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var result = await collection.FindAsync(filter, limit: 1); + Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1).Take(1))); + }); + } + + [Test] + public void MongoCollection_Find_FilterSortLimit() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var sort = new { StringValue = -1 }; + + var result = await collection.FindAsync(filter, sort: sort, limit: 1); + Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1).Reverse().Take(1))); + }); + } + + [Test] + public void MongoCollection_Find_ProjectionLimit() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var projection = new + { + _id = 0, + StringValue = 1 + }; + + foreach (var foo in inserted) + { + foo.Id = default; + foo.LongValue = default; + } + + var result = await collection.FindAsync(projection: projection, limit: 100); + Assert.That(result, Is.EquivalentTo(inserted.Reverse().Take(100))); + }); + } + + [Test] + public void MongoCollection_Find_FilterProjectionLimit() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var projection = new + { + _id = 0, + LongValue = 1 + }; + + foreach (var foo in inserted) + { + foo.Id = default; + foo.StringValue = default; + } + + var result = await collection.FindAsync(filter, projection: projection, limit: 2); + Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1).Take(2))); + }); + } + + [Test] + public void MongoCollection_Find_ProjectionSortLimit() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var projection = new + { + _id = 1, + LongValue = 1 + }; + + var sort = new { LongValue = -1 }; + + foreach (var foo in inserted) + { + foo.StringValue = default; + } + + var result = await collection.FindAsync(sort: sort, projection: projection, limit: 2); + Assert.That(result, Is.EquivalentTo(inserted.Reverse().Take(2))); + }); + } + + [Test] + public void MongoCollection_Find_FilterProjectionSortLimit() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + + var inserted = await InsertSomeData(collection, 3); + + var projection = new + { + _id = 0, + LongValue = 1, + StringValue = 1 + }; + + var filter = new BsonDocument + { + { + "LongValue", new BsonDocument + { + { "$gte", 1 } + } + } + }; + + var sort = new { LongValue = -1 }; + + foreach (var foo in inserted) + { + foo.Id = default; + } + + var result = await collection.FindAsync(filter, sort, projection, limit: 1); + Assert.That(result, Is.EquivalentTo(inserted.Where((_, i) => i >= 1).Reverse().Take(1))); + }); + } + + [Test] + public void MongoCollection_Aggregate() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetSalesCollection(); + + await InsertSalesData(collection); + + var aggregation = GetSalesAggregation(); + + var result = await collection.AggregateAsync(aggregation); + + // Expected results: + // { "_id" : { "day" : 46, "year" : 2014 }, "totalAmount" : 150, "count" : 2 } + // { "_id" : { "day" : 34, "year" : 2014 }, "totalAmount" : 45, "count" : 2 } + // { "_id" : { "day" : 1, "year" : 2014 }, "totalAmount" : 20, "count" : 1 } + Assert.That(result.Length, Is.EqualTo(3)); + + // Fix the ordering to make it easier to assert the expected values + result = result.OrderByDescending(r => r["_id"]["Day"].AsInt32).ToArray(); + + Assert.That(result[0]["_id"]["Day"].AsInt32, Is.EqualTo(46)); + Assert.That(result[0]["_id"]["Year"].AsInt32, Is.EqualTo(2014)); + Assert.That(result[0]["TotalAmount"].AsDecimal, Is.EqualTo(150)); + Assert.That(result[0]["Count"].AsInt32, Is.EqualTo(2)); + + Assert.That(result[1]["_id"]["Day"].AsInt32, Is.EqualTo(34)); + Assert.That(result[1]["_id"]["Year"].AsInt32, Is.EqualTo(2014)); + Assert.That(result[1]["TotalAmount"].AsDecimal, Is.EqualTo(45)); + Assert.That(result[1]["Count"].AsInt32, Is.EqualTo(2)); + + Assert.That(result[2]["_id"]["Day"].AsInt32, Is.EqualTo(1)); + Assert.That(result[2]["_id"]["Year"].AsInt32, Is.EqualTo(2014)); + Assert.That(result[2]["TotalAmount"].AsDecimal, Is.EqualTo(20)); + Assert.That(result[2]["Count"].AsInt32, Is.EqualTo(1)); + }); + } + + [Test] + public void MongoCollection_Aggregate_GenericResult() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetSalesCollection(); + + await InsertSalesData(collection); + + var aggregation = GetSalesAggregation(); + + var result = await collection.AggregateAsync(aggregation); + + // Expected results: + // { "_id" : { "day" : 46, "year" : 2014 }, "totalAmount" : 150, "count" : 2 } + // { "_id" : { "day" : 34, "year" : 2014 }, "totalAmount" : 45, "count" : 2 } + // { "_id" : { "day" : 1, "year" : 2014 }, "totalAmount" : 20, "count" : 1 } + Assert.That(result.Length, Is.EqualTo(3)); + + // Fix the ordering to make it easier to assert the expected values + result = result.OrderByDescending(r => r.Id.Day).ToArray(); + + Assert.That(result[0].Id.Day, Is.EqualTo(46)); + Assert.That(result[0].Id.Year, Is.EqualTo(2014)); + Assert.That(result[0].TotalAmount, Is.EqualTo(150)); + Assert.That(result[0].Count, Is.EqualTo(2)); + + Assert.That(result[1].Id.Day, Is.EqualTo(34)); + Assert.That(result[1].Id.Year, Is.EqualTo(2014)); + Assert.That(result[1].TotalAmount, Is.EqualTo(45)); + Assert.That(result[1].Count, Is.EqualTo(2)); + + Assert.That(result[2].Id.Day, Is.EqualTo(1)); + Assert.That(result[2].Id.Year, Is.EqualTo(2014)); + Assert.That(result[2].TotalAmount, Is.EqualTo(20)); + Assert.That(result[2].Count, Is.EqualTo(1)); + }); + } + + [Test] + public void MongoCollection_FindOneAndUpdate() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var update = BsonDocument.Parse(@"{ $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + } }"); + + var result = await collection.FindOneAndUpdateAsync(update); + + Assert.That(result, Is.EqualTo(inserted[0])); + + // Update inserted with expected values after the update + inserted[0].StringValue = "this is update!"; + inserted[0].LongValue = 999; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndUpdate_ReturnNewDocument() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var update = BsonDocument.Parse(@"{ $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + } }"); + + var result = await collection.FindOneAndUpdateAsync(update, returnNewDocument: true); + + Assert.That(result.StringValue, Is.EqualTo("this is update!")); + Assert.That(result.LongValue, Is.EqualTo(999)); + + // Update inserted with expected values after the update + inserted[0] = result; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndUpdate_Filter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var update = BsonDocument.Parse(@"{ $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + } }"); + + var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); + + var result = await collection.FindOneAndUpdateAsync(update, filter); + + Assert.That(result, Is.EqualTo(inserted[1])); + + // Update inserted with expected values after the update + inserted[1].StringValue = "this is update!"; + inserted[1].LongValue = 999; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndUpdate_Sort() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var update = BsonDocument.Parse(@"{ $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + } }"); + + var sort = BsonDocument.Parse("{ LongValue: -1 }"); + + var result = await collection.FindOneAndUpdateAsync(update, sort: sort); + + Assert.That(result, Is.EqualTo(inserted[2])); + + // Update inserted with expected values after the update + inserted[2].StringValue = "this is update!"; + inserted[2].LongValue = 999; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndUpdate_Projection() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var update = BsonDocument.Parse(@"{ $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + } }"); + + var projection = BsonDocument.Parse("{ LongValue: 1, _id: 0 }"); + + var result = await collection.FindOneAndUpdateAsync(update, projection: projection); + + Assert.That(result.StringValue, Is.Null); + Assert.That(result.LongValue, Is.EqualTo(inserted[0].LongValue)); + Assert.That(result.Id, Is.EqualTo(default(ObjectId))); + + // Update inserted with expected values after the update + inserted[0].StringValue = "this is update!"; + inserted[0].LongValue = 999; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndUpdate_FilterUpsert_Matches() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var update = BsonDocument.Parse(@"{ $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + } }"); + + var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); + + var result = await collection.FindOneAndUpdateAsync(update, filter, upsert: true); + + Assert.That(result, Is.EqualTo(inserted[1])); + + // Update inserted with expected values after the update + inserted[1].StringValue = "this is update!"; + inserted[1].LongValue = 999; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndUpdate_FilterUpsert_NoMatches() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var update = BsonDocument.Parse(@"{ $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + } }"); + + var filter = BsonDocument.Parse("{ LongValue: { $gte: 5} }"); + + var result = await collection.FindOneAndUpdateAsync(update, filter, upsert: true); + Assert.That(result, Is.Null); + + // Update inserted with expected values after the update + inserted = inserted.Concat(new[] { new Foo("this is update!", 999) }).ToArray(); + + var docs = await collection.FindAsync(); + inserted[3].Id = docs[3].Id; + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndUpdate_FilterUpsertReturnNewDocument_NoMatches() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var update = BsonDocument.Parse(@"{ $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + } }"); + + var filter = BsonDocument.Parse("{ LongValue: { $gte: 5} }"); + + var result = await collection.FindOneAndUpdateAsync(update, filter, upsert: true, returnNewDocument: true); + Assert.That(result.StringValue, Is.EqualTo("this is update!")); + Assert.That(result.LongValue, Is.EqualTo(999)); + + // Update inserted with expected values after the update + inserted = inserted.Concat(new[] { result }).ToArray(); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndUpdate_FilterSortProjectionUpsertReturnNewDocument() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var update = BsonDocument.Parse(@"{ $set: { + StringValue: ""this is update!"", + LongValue: { $numberLong: ""999"" } + } }"); + + var sort = BsonDocument.Parse("{ LongValue: -1 }"); + var projection = BsonDocument.Parse("{ StringValue: 1, _id: 0 }"); + var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); + + var result = await collection.FindOneAndUpdateAsync(update, filter, sort, projection, upsert: true, returnNewDocument: true); + Assert.That(result.StringValue, Is.EqualTo("this is update!")); + Assert.That(result.LongValue, Is.EqualTo(default(long))); + Assert.That(result.Id, Is.EqualTo(default(ObjectId))); + + inserted[2].StringValue = "this is update!"; + inserted[2].LongValue = 999; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndReplace() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var replacement = Foo.WithoutId("this is update!", 999); + + var result = await collection.FindOneAndReplaceAsync(replacement); + + Assert.That(result, Is.EqualTo(inserted[0])); + + // Update inserted with expected values after the update + inserted[0].StringValue = replacement.StringValue; + inserted[0].LongValue = replacement.LongValue; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndReplace_ReturnNewDocument() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var replacement = Foo.WithoutId("this is update!", 999); + + var result = await collection.FindOneAndReplaceAsync(replacement, returnNewDocument: true); + + replacement.Id = inserted[0].Id; + Assert.That(result, Is.EqualTo(replacement)); + + // Update inserted with expected values after the update + inserted[0].StringValue = replacement.StringValue; + inserted[0].LongValue = replacement.LongValue; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndReplace_UpsertNoMatches_GeneratesId() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var replacement = Foo.WithoutId("this is update!", 999); + var filter = BsonDocument.Parse("{ LongValue: { $gte: 5} }"); + + var result = await collection.FindOneAndReplaceAsync(replacement, filter, upsert: true, returnNewDocument: true); + + Assert.That(result.Id, Is.Not.EqualTo(default(ObjectId))); + replacement.Id = result.Id; + Assert.That(result, Is.EqualTo(replacement)); + + // Update inserted with expected values after the update + inserted = inserted.Concat(new[] { result }).ToArray(); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndReplace_Filter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var replacement = Foo.WithoutId("this is update!", 999); + + var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); + + var result = await collection.FindOneAndReplaceAsync(replacement, filter); + + Assert.That(result, Is.EqualTo(inserted[1])); + + // Update inserted with expected values after the update + inserted[1].StringValue = replacement.StringValue; + inserted[1].LongValue = replacement.LongValue; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndReplace_Sort() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var replacement = Foo.WithoutId("this is update!", 999); + var sort = BsonDocument.Parse("{ LongValue: -1 }"); + + var result = await collection.FindOneAndReplaceAsync(replacement, sort: sort); + + Assert.That(result, Is.EqualTo(inserted[2])); + + // Update inserted with expected values after the update + inserted[2].StringValue = replacement.StringValue; + inserted[2].LongValue = replacement.LongValue; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndReplace_Projection() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var replacement = Foo.WithoutId("this is update!", 999); + var projection = BsonDocument.Parse("{ LongValue: 1, _id: 0 }"); + + var result = await collection.FindOneAndReplaceAsync(replacement, projection: projection); + + Assert.That(result.StringValue, Is.Null); + Assert.That(result.LongValue, Is.EqualTo(inserted[0].LongValue)); + Assert.That(result.Id, Is.EqualTo(default(ObjectId))); + + // Update inserted with expected values after the update + inserted[0].StringValue = replacement.StringValue; + inserted[0].LongValue = replacement.LongValue; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndReplace_FilterUpsert_Matches() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var replacement = Foo.WithoutId("this is update!", 999); + var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); + + var result = await collection.FindOneAndReplaceAsync(replacement, filter, upsert: true); + + Assert.That(result, Is.EqualTo(inserted[1])); + + // Update inserted with expected values after the update + inserted[1].StringValue = replacement.StringValue; + inserted[1].LongValue = replacement.LongValue; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndReplace_FilterUpsert_NoMatches() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var replacement = new Foo("this is update!", 999); + var filter = BsonDocument.Parse("{ LongValue: { $gte: 5} }"); + + var result = await collection.FindOneAndReplaceAsync(replacement, filter, upsert: true); + Assert.That(result, Is.Null); + + // Update inserted with expected values after the update + inserted = inserted.Concat(new[] { replacement }).ToArray(); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndReplace_FilterUpsertReturnNewDocument_NoMatches() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var replacement = new Foo("this is update!", 999); + + var filter = BsonDocument.Parse("{ LongValue: { $gte: 5} }"); + + var result = await collection.FindOneAndReplaceAsync(replacement, filter, upsert: true, returnNewDocument: true); + Assert.That(result, Is.EqualTo(replacement)); + + // Update inserted with expected values after the update + inserted = inserted.Concat(new[] { replacement }).ToArray(); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndReplace_FilterSortProjectionUpsertReturnNewDocument() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var replacement = Foo.WithoutId("this is update!", 999); + var sort = BsonDocument.Parse("{ LongValue: -1 }"); + var projection = BsonDocument.Parse("{ StringValue: 1, _id: 0 }"); + var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); + + var result = await collection.FindOneAndReplaceAsync(replacement, filter, sort, projection, upsert: true, returnNewDocument: true); + Assert.That(result.StringValue, Is.EqualTo(replacement.StringValue)); + Assert.That(result.LongValue, Is.EqualTo(default(long))); + Assert.That(result.Id, Is.EqualTo(default(ObjectId))); + + inserted[2].StringValue = replacement.StringValue; + inserted[2].LongValue = replacement.LongValue; + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndDelete() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var result = await collection.FindOneAndDeleteAsync(); + Assert.That(result, Is.EqualTo(inserted[0])); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted.Where((_, i) => i != 0))); + }); + } + + [Test] + public void MongoCollection_FindOneAndDelete_Filter() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var filter = BsonDocument.Parse("{ LongValue: { $gte: 1} }"); + + var result = await collection.FindOneAndDeleteAsync(filter); + Assert.That(result, Is.EqualTo(inserted[1])); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted.Where((_, i) => i != 1))); + }); + } + + [Test] + public void MongoCollection_FindOneAndDelete_Filter_NoMatches() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var filter = BsonDocument.Parse("{ LongValue: { $gte: 5} }"); + + var result = await collection.FindOneAndDeleteAsync(filter); + Assert.That(result, Is.Null); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted)); + }); + } + + [Test] + public void MongoCollection_FindOneAndDelete_Sort() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var sort = BsonDocument.Parse("{ LongValue: -1 }"); + + var result = await collection.FindOneAndDeleteAsync(sort: sort); + Assert.That(result, Is.EqualTo(inserted[2])); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted.Where((_, i) => i != 2))); + }); + } + + [Test] + public void MongoCollection_FindOneAndDelete_FilterSort() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var sort = BsonDocument.Parse("{ LongValue: -1 }"); + var filter = BsonDocument.Parse("{ LongValue: { $lt: 2 } }"); + + var result = await collection.FindOneAndDeleteAsync(filter, sort: sort); + Assert.That(result, Is.EqualTo(inserted[1])); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted.Where((_, i) => i != 1))); + }); + } + + [Test] + public void MongoCollection_FindOneAndDelete_FilterSortProjection() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var collection = await GetCollection(); + var inserted = await InsertSomeData(collection, 3); + + var sort = BsonDocument.Parse("{ LongValue: -1 }"); + var filter = BsonDocument.Parse("{ LongValue: { $lt: 2 } }"); + var projection = BsonDocument.Parse("{ LongValue: 1 }"); + + var result = await collection.FindOneAndDeleteAsync(filter, sort, projection); + Assert.That(result.Id, Is.EqualTo(inserted[1].Id)); + Assert.That(result.StringValue, Is.Null); + Assert.That(result.LongValue, Is.EqualTo(inserted[1].LongValue)); + + var docs = await collection.FindAsync(); + Assert.That(docs, Is.EquivalentTo(inserted.Where((_, i) => i != 1))); + }); + } + + private static async Task InsertSomeData(MongoClient.Collection collection, int documentCount) + { + var docs = Enumerable.Range(0, documentCount) + .Select(i => new Foo("Document #" + i, i)) + .ToArray(); + + await collection.InsertManyAsync(docs); + + var remoteCount = await collection.CountAsync(); + Assert.That(remoteCount, Is.EqualTo(documentCount)); + + return docs; + } + + private static async Task InsertSalesData(MongoClient.Collection collection) + { + // Example from https://docs.mongodb.com/manual/reference/operator/aggregation/sum/ + var sales = new[] + { + new Sale(1, "abc", 10, 2, new DateTime(2014, 1, 1, 8, 0, 0, DateTimeKind.Utc)), + new Sale(2, "jkl", 20, 1, new DateTime(2014, 2, 3, 9, 0, 0, DateTimeKind.Utc)), + new Sale(3, "xyz", 5, 5, new DateTime(2014, 2, 3, 9, 5, 0, DateTimeKind.Utc)), + new Sale(4, "abc", 10, 10, new DateTime(2014, 2, 15, 8, 0, 0, DateTimeKind.Utc)), + new Sale(5, "xyz", 5, 10, new DateTime(2014, 2, 15, 9, 5, 0, DateTimeKind.Utc)), + }; + + await collection.InsertManyAsync(sales); + } + + private static BsonDocument GetSalesAggregation() + { + return BsonDocument.Parse(@"{ $group: { + _id: { Day: { $dayOfYear: ""$Date""}, Year: { $year: ""$Date"" } }, + TotalAmount: { $sum: { $multiply:[ ""$Price"", ""$Quantity"" ] } }, + Count: { $sum: 1 } + }}"); + } + + private async Task> GetCollection() + { + var user = await GetUserAsync(); + var client = user.GetMongoClient(ServiceName); + var db = client.GetDatabase(DbName); + var collection = db.GetCollection(FoosCollectionName); + + await collection.DeleteManyAsync(); + + return collection; + } + + private async Task> GetSalesCollection() + { + var user = await GetUserAsync(); + var client = user.GetMongoClient(ServiceName); + var db = client.GetDatabase(DbName); + var collection = db.GetCollection(SalesCollectionName); + + await collection.DeleteManyAsync(); + + return collection; + } + + private async Task> GetBsonCollection() + { + var user = await GetUserAsync(); + var client = user.GetMongoClient(ServiceName); + var db = client.GetDatabase(DbName); + var collection = db.GetCollection(FoosCollectionName); + + await collection.DeleteManyAsync(); + + return collection; + } + + private class Foo + { + [BsonElement("_id")] + [BsonIgnoreIfDefault] + public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); + + public string StringValue { get; set; } + + public long LongValue { get; set; } + + public Foo(string stringValue, long longValue) + { + StringValue = stringValue; + LongValue = longValue; + } + + public static Foo WithoutId(string stringValue, long longValue) + { + var result = new Foo(stringValue, longValue); + result.Id = default; + return result; + } + + public override bool Equals(object obj) => + (obj is Foo foo) && + foo.Id == Id && + foo.StringValue == StringValue && + foo.LongValue == LongValue; + + public override string ToString() + { + return $"Id: {Id}, StringValue: {StringValue}, LongValue: {LongValue}"; + } + } + + private class Sale + { + [BsonElement("_id")] + public int Id { get; set; } + + public string Item { get; set; } + + public decimal Price { get; set; } + + public decimal Quantity { get; set; } + + public DateTime Date { get; set; } + + public Sale(int id, string item, decimal price, decimal quantity, DateTime date) + { + Id = id; + Item = item; + Price = price; + Quantity = quantity; + Date = date; + } + } + + private class AggregationResult + { + [BsonElement("_id")] + public IdResult Id { get; set; } + + public decimal TotalAmount { get; set; } + + public int Count { get; set; } + + public class IdResult + { + public int Day { get; set; } + + public int Year { get; set; } + } + } + } +} diff --git a/Tests/Realm.Tests/Sync/ObjectLevelPermissionsTests.cs b/Tests/Realm.Tests/Sync/ObjectLevelPermissionsTests.cs deleted file mode 100644 index 16d490cc2c..0000000000 --- a/Tests/Realm.Tests/Sync/ObjectLevelPermissionsTests.cs +++ /dev/null @@ -1,711 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2018 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using NUnit.Framework; -using Realms.Sync; - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class ObjectLevelPermissionsTests : SyncTestBase - { - [Test] - public void Test_RealmRead() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var userA = await SyncTestHelpers.GetUserAsync(); - var userB = await SyncTestHelpers.GetUserAsync(); - - var realmUri = await CreateRealm(r => - { - CreatePermissions(RealmPermission.Get(r).Permissions); - var role = PermissionRole.Get(r, "reader"); - role.Users.Add(userA); - }); - - using (var realm = GetRealm(userA, realmUri)) - { - var query = realm.All(); - var subscription = query.Subscribe(); - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - AssertRealmPrivileges(realm, RealmPrivileges.Read); - AssertClassPrivileges(realm, ClassPrivileges.Read | ClassPrivileges.Subscribe); - AssertObjectPrivileges(realm, ObjectPrivileges.Read); - - Assert.That(subscription.Results.Count(), Is.EqualTo(3)); - AddObjectsToRealm(realm, new[] { 4, 5, 6 }); - - Assert.That(subscription.Results.Count(), Is.EqualTo(6)); - - await SyncTestHelpers.WaitForSyncAsync(realm); - Assert.That(subscription.Results.Count(), Is.EqualTo(3)); - } - - using (var realm = GetRealm(userB, realmUri)) - { - var query = realm.All(); - var subscription = query.Subscribe(); - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - AssertRealmPrivileges(realm, 0); - AssertClassPrivileges(realm, 0); - - Assert.That(subscription.Results.Count(), Is.Zero); - } - }); - } - - [Test] - public void Test_RealmUpdate() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var userA = await SyncTestHelpers.GetUserAsync(); - var userB = await SyncTestHelpers.GetUserAsync(); - - var realmUri = await CreateRealm(r => - { - CreatePermissions(RealmPermission.Get(r).Permissions); - var reader = PermissionRole.Get(r, "reader"); - reader.Users.Add(userA); - reader.Users.Add(userB); - - var writer = PermissionRole.Get(r, "writer"); - writer.Users.Add(userA); - }); - - using (var realm = GetRealm(userA, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - AssertRealmPrivileges(realm, RealmPrivileges.Read | RealmPrivileges.Update); - AssertClassPrivileges(realm, ClassPrivileges.Read | ClassPrivileges.Subscribe | ClassPrivileges.Create | ClassPrivileges.SetPermissions | ClassPrivileges.Update); - AssertObjectPrivileges(realm, ObjectPrivileges.Read | ObjectPrivileges.Delete | ObjectPrivileges.SetPermissions | ObjectPrivileges.Update); - - Assert.That(subscription.Results.Count(), Is.EqualTo(3)); - AddObjectsToRealm(realm, new[] { 4, 5, 6 }); - - Assert.That(subscription.Results.Count(), Is.EqualTo(6)); - - await SyncTestHelpers.WaitForSyncAsync(realm); - Assert.That(subscription.Results.Count(), Is.EqualTo(6)); - } - - using (var realm = GetRealm(userB, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - - AssertRealmPrivileges(realm, RealmPrivileges.Read); - AssertClassPrivileges(realm, ClassPrivileges.Read | ClassPrivileges.Subscribe); - AssertObjectPrivileges(realm, ObjectPrivileges.Read); - - Assert.That(subscription.Results.Count(), Is.EqualTo(6)); - AddObjectsToRealm(realm, new[] { 7, 8, 9 }); - - Assert.That(subscription.Results.Count(), Is.EqualTo(9)); - - await SyncTestHelpers.WaitForSyncAsync(realm); - Assert.That(subscription.Results.Count(), Is.EqualTo(6)); - } - }); - } - - [Test] - public void Test_ClassRead() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var userA = await SyncTestHelpers.GetUserAsync(); - var userB = await SyncTestHelpers.GetUserAsync(); - - var realmUri = await CreateRealm(r => - { - CreatePermissions(ClassPermission.Get(r).Permissions); - var reader = PermissionRole.Get(r, "reader"); - reader.Users.Add(userA); - }); - - using (var realm = GetRealm(userA, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - AssertRealmPrivileges(realm, RealmPrivileges.Read | RealmPrivileges.Update | RealmPrivileges.ModifySchema | RealmPrivileges.SetPermissions); - AssertClassPrivileges(realm, ClassPrivileges.Read | ClassPrivileges.Subscribe); - AssertObjectPrivileges(realm, ObjectPrivileges.Read); - - Assert.That(subscription.Results.Count(), Is.EqualTo(3)); - AddObjectsToRealm(realm, new[] { 4, 5, 6 }); - - Assert.That(subscription.Results.Count(), Is.EqualTo(6)); - - await SyncTestHelpers.WaitForSyncAsync(realm); - Assert.That(subscription.Results.Count(), Is.EqualTo(3)); - } - - using (var realm = GetRealm(userB, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - AssertRealmPrivileges(realm, RealmPrivileges.Read | RealmPrivileges.Update | RealmPrivileges.ModifySchema | RealmPrivileges.SetPermissions); - AssertClassPrivileges(realm, 0); - - Assert.That(subscription.Results.Count(), Is.EqualTo(0)); - } - }); - } - - [Test] - public void Test_ClassUpdate() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var userA = await SyncTestHelpers.GetUserAsync(); - var userB = await SyncTestHelpers.GetUserAsync(); - - var realmUri = await CreateRealm(r => - { - CreatePermissions(ClassPermission.Get(r).Permissions); - - var reader = PermissionRole.Get(r, "reader"); - reader.Users.Add(userA); - reader.Users.Add(userB); - - var writer = PermissionRole.Get(r, "writer"); - writer.Users.Add(userA); - }); - - using (var realm = GetRealm(userA, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - AssertRealmPrivileges(realm, RealmPrivileges.Read | RealmPrivileges.Update | RealmPrivileges.ModifySchema | RealmPrivileges.SetPermissions); - AssertClassPrivileges(realm, ClassPrivileges.Read | ClassPrivileges.Subscribe | ClassPrivileges.Update | ClassPrivileges.Create); - AssertObjectPrivileges(realm, ObjectPrivileges.Read | ObjectPrivileges.Update | ObjectPrivileges.Delete | ObjectPrivileges.SetPermissions); - - var obj = realm.Find(1); - realm.Write(() => - { - obj.StringValue = "New value"; - }); - - await SyncTestHelpers.WaitForSyncAsync(realm); - Assert.That(obj.StringValue, Is.EqualTo("New value")); - } - - using (var realm = GetRealm(userB, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - AssertRealmPrivileges(realm, RealmPrivileges.Read | RealmPrivileges.Update | RealmPrivileges.ModifySchema | RealmPrivileges.SetPermissions); - AssertClassPrivileges(realm, ClassPrivileges.Read | ClassPrivileges.Subscribe); - AssertObjectPrivileges(realm, ObjectPrivileges.Read); - - var obj = realm.Find(1); - realm.Write(() => - { - obj.StringValue = "New value 2"; - }); - - Assert.That(obj.StringValue, Is.EqualTo("New value 2")); - await SyncTestHelpers.WaitForSyncAsync(realm); - - // Change is reverted - Assert.That(obj.StringValue, Is.EqualTo("New value")); - } - }); - } - - [Test] - public void Test_ClassCreate() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var userA = await SyncTestHelpers.GetUserAsync(); - var userB = await SyncTestHelpers.GetUserAsync(); - - var realmUri = await CreateRealm(r => - { - CreatePermissions(ClassPermission.Get(r).Permissions); - var reader = PermissionRole.Get(r, "reader"); - reader.Users.Add(userA); - reader.Users.Add(userB); - - var writer = PermissionRole.Get(r, "writer"); - writer.Users.Add(userA); - }); - - using (var realm = GetRealm(userA, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - AssertRealmPrivileges(realm, RealmPrivileges.Read | RealmPrivileges.Update | RealmPrivileges.ModifySchema | RealmPrivileges.SetPermissions); - AssertClassPrivileges(realm, ClassPrivileges.Read | ClassPrivileges.Subscribe | ClassPrivileges.Update | ClassPrivileges.Create); - AssertObjectPrivileges(realm, ObjectPrivileges.Read | ObjectPrivileges.Update | ObjectPrivileges.Delete | ObjectPrivileges.SetPermissions); - Assert.That(subscription.Results.Count(), Is.EqualTo(3)); - - AddObjectsToRealm(realm, new[] { 4, 5, 6 }); - - Assert.That(subscription.Results.Count(), Is.EqualTo(6)); - await SyncTestHelpers.WaitForSyncAsync(realm); - Assert.That(subscription.Results.Count(), Is.EqualTo(6)); - } - - using (var realm = GetRealm(userB, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - AssertRealmPrivileges(realm, RealmPrivileges.Read | RealmPrivileges.Update | RealmPrivileges.ModifySchema | RealmPrivileges.SetPermissions); - AssertClassPrivileges(realm, ClassPrivileges.Read | ClassPrivileges.Subscribe); - AssertObjectPrivileges(realm, ObjectPrivileges.Read); - - Assert.That(subscription.Results.Count(), Is.EqualTo(6)); - - AddObjectsToRealm(realm, new[] { 7, 8, 9 }); - - Assert.That(subscription.Results.Count(), Is.EqualTo(9)); - await SyncTestHelpers.WaitForSyncAsync(realm); - Assert.That(subscription.Results.Count(), Is.EqualTo(6)); - } - }); - } - - [Test] - public void Test_ClassSetPermissions() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var userA = await SyncTestHelpers.GetUserAsync(); - var userB = await SyncTestHelpers.GetUserAsync(); - var userC = await SyncTestHelpers.GetUserAsync(); - - var realmUri = await CreateRealm(r => - { - CreatePermissions(ClassPermission.Get(r).Permissions); - - var reader = PermissionRole.Get(r, "reader"); - reader.Users.Add(userA); - reader.Users.Add(userB); - reader.Users.Add(userC); - - var writer = PermissionRole.Get(r, "writer"); - writer.Users.Add(userA); - writer.Users.Add(userB); - - var admin = PermissionRole.Get(r, "admin"); - admin.Users.Add(userA); - }); - - using (var realm = GetRealm(userB, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - - // B is 'writer' - shouldn't be able to update the role access level - realm.Write(() => - { - var readerPermission = Permission.Get("reader", realm); - readerPermission.CanUpdate = true; - readerPermission.CanCreate = true; - }); - - await SyncTestHelpers.WaitForSyncAsync(realm); - } - - using (var realm = GetRealm(userC, realmUri)) - { - // C shouldn't be able to create objects - var subscription = await SubscribeToObjectsAsync(realm); - AssertRealmPrivileges(realm, RealmPrivileges.Read | RealmPrivileges.Update | RealmPrivileges.ModifySchema | RealmPrivileges.SetPermissions); - AssertClassPrivileges(realm, ClassPrivileges.Read | ClassPrivileges.Subscribe); - AssertObjectPrivileges(realm, ObjectPrivileges.Read); - - Assert.That(subscription.Results.Count(), Is.EqualTo(3)); - - AddObjectsToRealm(realm, new[] { 4, 5, 6 }); - - Assert.That(subscription.Results.Count(), Is.EqualTo(6)); - await SyncTestHelpers.WaitForSyncAsync(realm); - Assert.That(subscription.Results.Count(), Is.EqualTo(3)); - } - - using (var realm = GetRealm(userA, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - - // A should be able to update role access level - realm.Write(() => - { - var readerPermission = Permission.Get("reader", realm); - readerPermission.CanUpdate = true; - readerPermission.CanCreate = true; - }); - - await SyncTestHelpers.WaitForSyncAsync(realm); - } - - using (var realm = GetRealm(userC, realmUri)) - { - // C should now be able to create objects - // Why does my subscription timeout? - // var subscription = await SubscribeToObjectsAsync(realm); - await SyncTestHelpers.WaitForSyncAsync(realm); - AssertRealmPrivileges(realm, RealmPrivileges.Read | RealmPrivileges.Update | RealmPrivileges.ModifySchema | RealmPrivileges.SetPermissions); - AssertClassPrivileges(realm, ClassPrivileges.Read | ClassPrivileges.Subscribe | ClassPrivileges.Update | ClassPrivileges.Create); - AssertObjectPrivileges(realm, ObjectPrivileges.Read | ObjectPrivileges.Update | ObjectPrivileges.Delete | ObjectPrivileges.SetPermissions); - - var objects = realm.All(); - Assert.That(objects.Count(), Is.EqualTo(3)); - - AddObjectsToRealm(realm, new[] { 4, 5, 6 }); - - Assert.That(objects.Count(), Is.EqualTo(6)); - await SyncTestHelpers.WaitForSyncAsync(realm); - Assert.That(objects.Count(), Is.EqualTo(6)); - } - }); - } - - [Test] - public void Test_ObjectRead() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var userA = await SyncTestHelpers.GetUserAsync(); - var userB = await SyncTestHelpers.GetUserAsync(); - - var realmUri = await CreateRealm(r => - { - var reader = PermissionRole.Get(r, "reader"); - - reader.Users.Add(userA); - - var obj1 = r.Add(new ObjectWithPermissions - { - Id = 1, - StringValue = "Value 1" - }); - CreatePermissions(obj1.Permissions); - - r.Add(new ObjectWithPermissions - { - Id = 2, - StringValue = "Value 2" - }); - }, addObjects: false); - - using (var realm = GetRealm(userA, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - Assert.That(subscription.Results.Count(), Is.EqualTo(1)); - } - - using (var realm = GetRealm(userB, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - Assert.That(subscription.Results.Count(), Is.EqualTo(0)); - } - }); - } - - [Test] - public void Test_ObjectUpdate() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var userA = await SyncTestHelpers.GetUserAsync(); - var userB = await SyncTestHelpers.GetUserAsync(); - - var realmUri = await CreateRealm(r => - { - var reader = PermissionRole.Get(r, "reader"); - reader.Users.Add(userA); - reader.Users.Add(userB); - - var writer = PermissionRole.Get(r, "writer"); - writer.Users.Add(userA); - - var obj1 = r.Add(new ObjectWithPermissions - { - Id = 1, - StringValue = "Value 1" - }); - CreatePermissions(obj1.Permissions); - }, addObjects: false); - - using (var realm = GetRealm(userA, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - var obj1 = subscription.Results.Single(); - realm.Write(() => - { - obj1.StringValue = "New value"; - }); - - await SyncTestHelpers.WaitForSyncAsync(realm); - - Assert.That(obj1.StringValue, Is.EqualTo("New value")); - } - - using (var realm = GetRealm(userB, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - var obj1 = subscription.Results.Single(); - realm.Write(() => - { - obj1.StringValue = "New value #2"; - }); - - Assert.That(obj1.StringValue, Is.EqualTo("New value #2")); - await SyncTestHelpers.WaitForSyncAsync(realm); - - Assert.That(obj1.StringValue, Is.EqualTo("New value")); - } - }); - } - - [Test] - public void Test_ObjectDelete() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var userA = await SyncTestHelpers.GetUserAsync(); - var userB = await SyncTestHelpers.GetUserAsync(); - - var realmUri = await CreateRealm(r => - { - var reader = PermissionRole.Get(r, "reader"); - reader.Users.Add(userA); - reader.Users.Add(userB); - - var writer = PermissionRole.Get(r, "writer"); - writer.Users.Add(userA); - - var obj1 = r.Add(new ObjectWithPermissions - { - Id = 1, - StringValue = "Value 1" - }); - CreatePermissions(obj1.Permissions); - }, addObjects: false); - - using (var realmA = GetRealm(userA, realmUri)) - using (var realmB = GetRealm(userB, realmUri)) - { - var subscriptionB = await SubscribeToObjectsAsync(realmB); - var objB = subscriptionB.Results.Single(); - realmB.Write(() => - { - realmB.Remove(objB); - }); - - Assert.That(subscriptionB.Results.Count(), Is.Zero); - await SyncTestHelpers.WaitForSyncAsync(realmB); - Assert.That(subscriptionB.Results.Count(), Is.EqualTo(1)); - objB = subscriptionB.Results.Single(); - - var subscriptionA = await SubscribeToObjectsAsync(realmA); - var objA = subscriptionA.Results.Single(); - realmA.Write(() => - { - realmA.Remove(objA); - }); - - await SyncTestHelpers.WaitForSyncAsync(realmA); - await SyncTestHelpers.WaitForSyncAsync(realmB); - - Assert.That(subscriptionA.Results.Count(), Is.Zero); - Assert.That(subscriptionB.Results.Count(), Is.Zero); - - Assert.That(objA.IsValid, Is.False); - Assert.That(objB.IsValid, Is.False); - } - }); - } - - [Test] - public void Test_ObjectSetPermissions() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var userA = await SyncTestHelpers.GetUserAsync(); - var userB = await SyncTestHelpers.GetUserAsync(); - - var realmUri = await CreateRealm(assignRoles: null, addObjects: false); - - using (var realm = GetRealm(userA, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - - realm.Write(() => - { - var obj1 = realm.Add(new ObjectWithPermissions - { - Id = 1, - StringValue = "1" - }); - - var foo = PermissionRole.Get(realm, "foo"); - var permission = Permission.Get(foo, obj1); - permission.CanRead = true; - foo.Users.Add(userB); - }); - - Assert.That(subscription.Results.Count(), Is.EqualTo(1)); - await SyncTestHelpers.WaitForSyncAsync(realm); - Assert.That(subscription.Results.Count(), Is.EqualTo(0)); - } - - using (var realm = GetRealm(userB, realmUri)) - { - var subscription = await SubscribeToObjectsAsync(realm); - Assert.That(subscription.Results.Count(), Is.EqualTo(1)); - } - }); - } - - private Realm GetRealm(User user, Uri uri) - { - var config = new QueryBasedSyncConfiguration(uri, user, Guid.NewGuid().ToString()) - { - ObjectClasses = new[] { typeof(ObjectWithPermissions) } - }; - - return GetRealm(config); - } - - private async Task CreateRealm(Action assignRoles = null, bool addObjects = true) - { - var uri = SyncTestHelpers.RealmUri(Guid.NewGuid().ToString()); - var admin = await SyncTestHelpers.GetAdminUserAsync(); - var config = new QueryBasedSyncConfiguration(uri, admin) - { - ObjectClasses = new[] { typeof(ObjectWithPermissions) } - }; - - using (var realm = GetRealm(config)) - { - var objects = realm.All(); - var subscription = await SubscribeToObjectsAsync(realm); - if (addObjects) - { - Assert.That(subscription.Results.Count(), Is.EqualTo(0)); - AddObjectsToRealm(realm, new[] { 1, 2, 3 }); - - await SyncTestHelpers.WaitForSyncAsync(realm); - - Assert.That(subscription.Results.Count(), Is.EqualTo(3)); - } - - if (assignRoles != null) - { - realm.Write(() => - { - assignRoles(realm); - }); - } - - await SyncTestHelpers.WaitForSyncAsync(realm); - } - - return uri; - } - - private static async Task> SubscribeToObjectsAsync(Realm realm) - { - var query = realm.All(); - var subscription = query.Subscribe(); - await subscription.WaitForSynchronizationAsync().Timeout(5000); - return subscription; - } - - private static void CreatePermissions(IList permissions) - { - var everyone = Permission.Get("everyone", permissions); - everyone.CanCreate = false; - everyone.CanDelete = false; - everyone.CanModifySchema = false; - everyone.CanQuery = false; - everyone.CanRead = false; - everyone.CanSetPermissions = false; - everyone.CanUpdate = false; - - var reader = Permission.Get("reader", permissions); - reader.CanQuery = true; - reader.CanRead = true; - - var writer = Permission.Get("writer", permissions); - writer.CanCreate = true; - writer.CanDelete = true; - writer.CanUpdate = true; - - var adminPermission = Permission.Get("admin", permissions); - adminPermission.CanSetPermissions = true; - } - - private static void AssertRealmPrivileges(Realm realm, RealmPrivileges expected) - { - var realmPrivileges = realm.GetPrivileges(); - Assert.That(realmPrivileges, Is.EqualTo(expected)); - } - - private static void AssertClassPrivileges(Realm realm, ClassPrivileges expected) - { - var classPrivileges = realm.GetPrivileges(); - Assert.That(classPrivileges, Is.EqualTo(expected)); - } - - private static void AssertObjectPrivileges(Realm realm, ObjectPrivileges expected) - { - foreach (var obj in realm.All()) - { - var objectPrivileges = realm.GetPrivileges(obj); - Assert.That(objectPrivileges, Is.EqualTo(expected)); - } - } - - private static void AddObjectsToRealm(Realm realm, int[] ids) - { - realm.Write(() => - { - foreach (var id in ids) - { - var obj = realm.Add(new ObjectWithPermissions - { - Id = id, - StringValue = $"Object #{id}" - }); - - var permission = Permission.Get("everyone", obj); - permission.CanRead = true; - permission.CanUpdate = true; - permission.CanDelete = true; - permission.CanSetPermissions = true; - } - }); - } - - [Realms.Explicit] - private class ObjectWithPermissions : RealmObject - { - [PrimaryKey] - public int Id { get; set; } - - public string StringValue { get; set; } - - public IList Permissions { get; } - } - } -} diff --git a/Tests/Realm.Tests/Sync/PermissionsTests.cs b/Tests/Realm.Tests/Sync/PermissionsTests.cs deleted file mode 100644 index 10a4d73d5d..0000000000 --- a/Tests/Realm.Tests/Sync/PermissionsTests.cs +++ /dev/null @@ -1,341 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using Nito.AsyncEx; -using NUnit.Framework; -using Realms.Schema; -using Realms.Sync; -using Realms.Sync.Exceptions; -using Realms.Tests.Database; - -using File = System.IO.File; - -namespace Realms.Tests.Sync -{ - [TestFixture, Preserve(AllMembers = true)] - public class PermissionsTests : SyncTestBase - { - [Test] - public void User_ApplyPermissions_WithUserId_GrantsAndRevokesPermissions() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var alice = await SyncTestHelpers.GetUserAsync(); - var bob = await SyncTestHelpers.GetUserAsync(); - - await TestApplyPermissions(alice, bob, PermissionCondition.UserId(bob.Identity)).Timeout(20000); - }); - } - - [Test] - public void User_ApplyPermissions_WithEmail_GrantsAndRevokesPermissions() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var alice = await SyncTestHelpers.GetUserAsync(); - var bobEmail = $"{Guid.NewGuid()}@foo.bar"; - var bobCredentials = Credentials.UsernamePassword(bobEmail, "a", createUser: true); - var bob = await User.LoginAsync(bobCredentials, SyncTestHelpers.AuthServerUri); - - await TestApplyPermissions(alice, bob, PermissionCondition.Email(bobEmail)).Timeout(20000); - }); - } - - [Test] - public void User_OfferPermissions_GrantsPermissions() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var alice = await SyncTestHelpers.GetUserAsync(); - var bob = await SyncTestHelpers.GetUserAsync(); - - var realmPath = $"/{alice.Identity}/testPermission"; - var realmUrl = SyncTestHelpers.RealmUri(realmPath).AbsoluteUri; - await EnsureRealmExists(alice, realmUrl); - - var token = await alice.OfferPermissionsAsync(realmUrl, AccessLevel.Write).Timeout(2000); - var alicesUrl = await bob.AcceptPermissionOfferAsync(token).Timeout(2000); - - Assert.That(alicesUrl, Is.EqualTo(realmPath)); - - await AssertPermissions(alice, bob, realmPath, AccessLevel.Write).Timeout(10000); - }); - } - - [Test] - public void User_OfferPermissions_WhenExpired_ShouldThrow() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var alice = await SyncTestHelpers.GetUserAsync(); - - var realmUrl = SyncTestHelpers.RealmUri($"{alice.Identity}/testPermission").AbsoluteUri; - await EnsureRealmExists(alice, realmUrl); - - await TestHelpers.AssertThrows(() => alice.OfferPermissionsAsync(realmUrl, AccessLevel.Write, DateTimeOffset.UtcNow.AddDays(-1))); - }); - } - - [Test] - public void User_OfferPermissions_WhenNoAccess_ShouldThrow() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var alice = await SyncTestHelpers.GetUserAsync(); - - var realmUrl = SyncTestHelpers.RealmUri($"{alice.Identity}/testPermission").AbsoluteUri; - await EnsureRealmExists(alice, realmUrl); - - await TestHelpers.AssertThrows(() => alice.OfferPermissionsAsync(realmUrl, AccessLevel.None)); - }); - } - - [Test] - public void User_AcceptPermissionOffer_WhenOfferExpired_ShouldGetError() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var alice = await SyncTestHelpers.GetUserAsync(); - var bob = await SyncTestHelpers.GetUserAsync(); - - var realmUrl = SyncTestHelpers.RealmUri($"{alice.Identity}/testPermission").AbsoluteUri; - await EnsureRealmExists(alice, realmUrl); - - var token = await alice.OfferPermissionsAsync(realmUrl, AccessLevel.Write, expiresAt: DateTimeOffset.UtcNow.AddSeconds(1)); - - Assert.That(token, Is.Not.Null); - - await Task.Delay(2000); - - await TestHelpers.AssertThrows(() => bob.AcceptPermissionOfferAsync(token), ex => - { - Assert.That(ex.ErrorCode, Is.EqualTo(ErrorCode.ExpiredPermissionOffer)); - }); - }); - } - - [Test] - public void User_AcceptPermissionOffer_WhenTokenIsInvalid_ShouldGetError() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var user = await SyncTestHelpers.GetUserAsync(); - - await TestHelpers.AssertThrows(() => user.AcceptPermissionOfferAsync("some string"), ex => - { - Assert.That(ex.ErrorCode, Is.EqualTo(ErrorCode.InvalidParameters)); - }); - }); - } - - private async Task TestApplyPermissions(User alice, User bob, PermissionCondition condition) - { - var realmPath = $"/{alice.Identity}/testPermission"; - var realmUrl = SyncTestHelpers.RealmUri(realmPath).AbsoluteUri; - await EnsureRealmExists(alice, realmUrl); - - // Grant write permissions - await alice.ApplyPermissionsAsync(condition, realmUrl, AccessLevel.Write); - - await ValidateWriteAndSync(realmUrl, alice, bob, 1, 2); - await AssertPermissions(alice, bob, realmPath, AccessLevel.Write); - - // Revoke permissions - await alice.ApplyPermissionsAsync(condition, realmUrl, AccessLevel.None); - - await AssertPermissions(alice, bob, realmPath, AccessLevel.None); - } - - private static async Task AssertPermissions(User granter, User receiver, string path, AccessLevel level) - { - // Seems like there's some time delay before the permission realm is updated - var granted = (await granter.GetGrantedPermissionsAsync(Recipient.OtherUser)).SingleOrDefault(p => p.UserId == receiver.Identity); - var received = (await receiver.GetGrantedPermissionsAsync(Recipient.CurrentUser)).SingleOrDefault(p => p.Path == path); - - if (level > AccessLevel.None) - { - Assert.That(granted, Is.Not.Null); - Assert.That(granted.Path, Is.EqualTo(path)); - - Assert.That(received, Is.Not.Null); - Assert.That(received.AccessLevel, Is.EqualTo(level)); - } - else - { - Assert.That(granted, Is.Null); - Assert.That(received, Is.Null); - } - } - - [Test] - public void WriteToReadOnlyRealm_ThrowsPermissionDenied() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var alice = await SyncTestHelpers.GetUserAsync(); - var bob = await SyncTestHelpers.GetUserAsync(); - - var realmPath = $"/{alice.Identity}/willBeReadonly"; - var realmUrl = SyncTestHelpers.RealmUri(realmPath).AbsoluteUri; - await EnsureRealmExists(alice, realmUrl); - - // Give Bob just read permissions - await alice.ApplyPermissionsAsync(PermissionCondition.UserId(bob.Identity), realmUrl, AccessLevel.Read); - - var config = new FullSyncConfiguration(new Uri(realmUrl), bob, Guid.NewGuid().ToString()); - - var sessionErrorTask = TestHelpers.EventToTask(h => Session.Error += h, h => Session.Error -= h); - - using (var realm = GetRealm(config)) - { - realm.Write(() => realm.Add(new Person())); - - try - { - // Sometimes PermissionDenied will be thrown too fast moving the session to an error state - await SyncTestHelpers.WaitForUploadAsync(realm).Timeout(1000); - } - catch - { - } - } - - var sessionError = await sessionErrorTask.Timeout(1000); - Assert.That(sessionError.Exception, Is.TypeOf()); - - var pde = (PermissionDeniedException)sessionError.Exception; - - Assert.That(pde.ErrorCode, Is.EqualTo(ErrorCode.PermissionDenied)); - Assert.That(File.Exists(config.DatabasePath), Is.True); - - var result = pde.DeleteRealmUserInfo(); - - Assert.That(result, Is.True); - Assert.That(File.Exists(config.DatabasePath), Is.False); - }); - } - - [Test] - public void User_InvalidateOffer_InvalidatesTheOffer() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var alice = await SyncTestHelpers.GetUserAsync(); - var bob = await SyncTestHelpers.GetUserAsync(); - var charlie = await SyncTestHelpers.GetUserAsync(); - - var realmUrl = SyncTestHelpers.RealmUri($"{alice.Identity}/testPermission").AbsoluteUri; - await EnsureRealmExists(alice, realmUrl); - - var token = await alice.OfferPermissionsAsync(realmUrl, AccessLevel.Write); - var bobUrl = await bob.AcceptPermissionOfferAsync(token); - Assert.That(bobUrl, Is.EqualTo($"/{alice.Identity}/testPermission")); - - await alice.InvalidateOfferAsync(token); - - await TestHelpers.AssertThrows(() => charlie.AcceptPermissionOfferAsync(token), ex => - { - Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - Assert.That(ex.ErrorCode, Is.EqualTo(ErrorCode.ExpiredPermissionOffer)); - Assert.That(ex.Message, Is.EqualTo("The permission offer is expired.")); - }); - }); - } - - [Test] - public void User_GetPermissionOffers_Test() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var alice = await SyncTestHelpers.GetUserAsync(); - - var realmUrl = SyncTestHelpers.RealmUri($"{alice.Identity}/testPermission").AbsoluteUri; - await EnsureRealmExists(alice, realmUrl); - - var readToken = await alice.OfferPermissionsAsync(realmUrl, AccessLevel.Read); - var writeToken = await alice.OfferPermissionsAsync(realmUrl, AccessLevel.Write); - - var offers = await alice.GetPermissionOffersAsync(); - Assert.That(offers.Count(), Is.EqualTo(2)); - - await alice.InvalidateOfferAsync(readToken); - var offersAfterDeletion = await alice.GetPermissionOffersAsync(); - - Assert.That(offersAfterDeletion.Count(), Is.EqualTo(1)); - Assert.That(offersAfterDeletion.Single().Token, Is.EqualTo(writeToken)); - }); - } - - private async Task ValidateWriteAndSync(string realmUrl, User first, User second, long firstObjectId, long secondObjectId) - { - await Task.Delay(500); - - var firstRealm = GetRealm(new FullSyncConfiguration(new Uri(realmUrl), first, Guid.NewGuid().ToString())); - var secondRealm = GetRealm(new FullSyncConfiguration(new Uri(realmUrl), second, Guid.NewGuid().ToString())); - - var firstObjects = firstRealm.All(); - var secondObjects = secondRealm.All(); - - await Task.Delay(1000); - - Assert.That(firstObjects.Count(), Is.EqualTo(secondObjects.Count())); - - // Assert that second's realm doesn't contain object with Id = firstObjectId - Assert.That(secondRealm.Find(firstObjectId), Is.Null); - - // first adds an object - firstRealm.Write(() => firstRealm.Add(new PrimaryKeyInt64Object - { - Int64Property = firstObjectId - })); - - await Task.Delay(1000); - - Assert.That(firstObjects.Count(), Is.EqualTo(secondObjects.Count())); - Assert.That(secondRealm.Find(firstObjectId), Is.Not.Null); - - // Assert that first's realm doesn't contain object with Id = secondObjectId - Assert.That(firstRealm.Find(secondObjectId), Is.Null); - - // second adds an object - secondRealm.Write(() => secondRealm.Add(new PrimaryKeyInt64Object - { - Int64Property = secondObjectId - })); - - await Task.Delay(1000); - - Assert.That(firstObjects.Count(), Is.EqualTo(secondObjects.Count())); - Assert.That(firstRealm.Find(secondObjectId), Is.Not.Null); - } - - private async Task EnsureRealmExists(User user, string realmUrl) - { - var syncConfig = new FullSyncConfiguration(new Uri(realmUrl), user, Guid.NewGuid().ToString()); - using (var realm = GetRealm(syncConfig)) - { - // Make sure the realm exists - await GetSession(realm).WaitForUploadAsync(); - } - } - } -} \ No newline at end of file diff --git a/Tests/Realm.Tests/Sync/QueryBasedSyncTests.cs b/Tests/Realm.Tests/Sync/QueryBasedSyncTests.cs deleted file mode 100644 index 84c93dda47..0000000000 --- a/Tests/Realm.Tests/Sync/QueryBasedSyncTests.cs +++ /dev/null @@ -1,579 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using NUnit.Framework; -using NUnit.Framework.Constraints; -using Realms.Exceptions; -using Realms.Sync; - -namespace Realms.Tests.Sync -{ - [TestFixture(Explicit = true, Description = "This causes a race condition in OS resulting in Access to invalidated Results objects."), Preserve(AllMembers = true)] - public class QueryBasedSyncTests : SyncTestBase - { - [TestCase(true)] - [TestCase(false)] - public void SubscribeForObjects_ReturnsExpectedQuery(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var query = realm.All().Where(o => o.IntValue < 5); - var subscription = query.Subscribe(); - Assert.That(subscription.State, Is.EqualTo(SubscriptionState.Creating)); - - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - Assert.That(subscription.State, Is.EqualTo(SubscriptionState.Complete)); - Assert.That(subscription.Results.Count(), Is.EqualTo(5)); - - foreach (var a in subscription.Results) - { - Assert.That(a.IntValue < 5); - Assert.That(a.B, Is.Not.Null); - Assert.That(a.B.BoolValue, Is.EqualTo(a.IntValue % 2 == 0)); - } - } - }); - } - - [TestCase(true)] - [TestCase(false)] - public void SubscribeForObjects_SynchronizesOnlyMatchingObjects(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var query = realm.All().Where(o => o.IntValue < 5); - var subscription = query.Subscribe(); - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - var queriedObjectAs = realm.All().Where(o => o.IntValue < 5); - - Assert.That(queriedObjectAs.Count(), Is.EqualTo(5)); - - foreach (var a in queriedObjectAs) - { - Assert.That(a.IntValue < 5); - Assert.That(a.B, Is.Not.Null); - Assert.That(a.B.BoolValue, Is.EqualTo(a.IntValue % 2 == 0)); - } - } - }); - } - - [TestCase(true)] - [TestCase(false)] - public void SubscribeForObjects_WhenTwoQueriesOverlap_SynchronizesTheUnion(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var youngerThan3Subscription = realm.All().Where(o => o.IntValue < 3).Subscribe(); - var range1to6Subscription = realm.All().Where(o => o.IntValue > 1 && o.IntValue < 6).Subscribe(); - - await youngerThan3Subscription.WaitForSynchronizationAsync().Timeout(2000); - await range1to6Subscription.WaitForSynchronizationAsync().Timeout(2000); - - var youngerThan3 = youngerThan3Subscription.Results; - var range1to6 = range1to6Subscription.Results; - - Assert.That(youngerThan3.Count(), Is.EqualTo(3)); - Assert.That(range1to6.Count(), Is.EqualTo(4)); - - Assert.That(youngerThan3.ToArray().All(o => o.IntValue < 3)); - Assert.That(range1to6.ToArray().All(o => o.IntValue > 1 && o.IntValue < 6)); - - var allInRealm = realm.All(); - - Assert.That(allInRealm.Count(), Is.EqualTo(6)); - Assert.That(allInRealm.ToArray().All(o => o.IntValue < 6)); - } - }); - } - - [TestCase(true)] - [TestCase(false)] - public void SubscribeForObjects_WhenTwoQueriesDontOverlap_SynchronizesTheUnion(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var youngerThan3Subscription = realm.All().Where(o => o.IntValue < 3).Subscribe(); - var olderThan6Subscription = realm.All().Where(o => o.IntValue > 6).Subscribe(); - - await youngerThan3Subscription.WaitForSynchronizationAsync().Timeout(2000); - await olderThan6Subscription.WaitForSynchronizationAsync().Timeout(2000); - - var youngerThan3 = youngerThan3Subscription.Results; - var olderThan6 = olderThan6Subscription.Results; - - Assert.That(youngerThan3.Count(), Is.EqualTo(3)); - Assert.That(olderThan6.Count(), Is.EqualTo(3)); - - Assert.That(youngerThan3.ToArray().All(o => o.IntValue < 3)); - Assert.That(olderThan6.ToArray().All(o => o.IntValue > 6)); - - var allInRealm = realm.All(); - - Assert.That(allInRealm.Count(), Is.EqualTo(6)); - Assert.That(allInRealm.ToArray().All(o => o.IntValue < 3 || o.IntValue > 6)); - } - }); - } - - [TestCase(true, true)] - [TestCase(false, true)] - [TestCase(true, false)] - [TestCase(false, false)] - public void Subscription_GetAll_FindsSubscription(bool openAsync, bool isNamed) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var name = isNamed ? "some subscription" : null; - var query = realm.All().Where(o => o.IntValue < 5); - var now = DateTimeOffset.UtcNow; - var subscription = query.Subscribe(new SubscriptionOptions { Name = name }); - - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - var sub = realm.GetAllSubscriptions().FirstOrDefault(s => s.ObjectType == nameof(ObjectA)); - - Assert.That(sub, Is.Not.Null); - Assert.That(sub.Error, Is.Null); - Assert.That(sub.Name, isNamed ? (IResolveConstraint)Is.EqualTo(name) : Is.Not.Null); - Assert.That(sub.ObjectType, Is.EqualTo(nameof(ObjectA))); - Assert.That(sub.CreatedAt, Is.InRange(now, now.AddSeconds(2))); - Assert.That(sub.CreatedAt, Is.EqualTo(sub.UpdatedAt)); - Assert.That(sub.ExpiresAt, Is.Null); - Assert.That(sub.TimeToLive, Is.Null); - } - }); - } - - [TestCase(true)] - [TestCase(false)] - public void Subscribe_WhenTtlSet_ExpiresSubscription(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - RealmConfigurationBase config; - using (var realm = await GetQueryBasedRealm(openAsync)) - { - config = realm.Config; - var ttl = TimeSpan.FromMilliseconds(500); - var query = realm.All().Where(o => o.IntValue < 5); - var subscription = query.Subscribe(new SubscriptionOptions { TimeToLive = ttl }); - - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - var sub = realm.GetAllSubscriptions().FirstOrDefault(s => s.ObjectType == nameof(ObjectA)); - - Assert.That(sub, Is.Not.Null); - Assert.That(sub.ExpiresAt, Is.EqualTo(sub.CreatedAt.Add(ttl))); - Assert.That(sub.TimeToLive, Is.EqualTo(ttl)); - } - }); - } - - [TestCase(true)] - [TestCase(false)] - public void Subscribe_UpdatesQuery(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var subscription = realm.All() - .Where(o => o.IntValue < 5) - .Subscribe(new SubscriptionOptions { Name = "foo", ShouldUpdate = true }); - - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - Assert.That(subscription.Results.Count(), Is.EqualTo(5)); - - var namedSub = realm.GetAllSubscriptions().Single(s => s.ObjectType == nameof(ObjectA)); - var originalQuery = namedSub.Query; - var originalUpdatedAt = namedSub.UpdatedAt; - var originalCreatedAt = namedSub.CreatedAt; - - Assert.That(originalCreatedAt, Is.EqualTo(originalUpdatedAt)); - - var updatedSub = realm.All() - .Where(o => o.IntValue < 3) - .Subscribe(new SubscriptionOptions { Name = "foo", ShouldUpdate = true }); - - await updatedSub.WaitForSynchronizationAsync().Timeout(2000); - Assert.That(subscription.Results.Count(), Is.EqualTo(3)); - - // NamedSub is a Realm object so it should have updated itself. - Assert.That(originalQuery, Is.Not.EqualTo(namedSub.Query)); - Assert.That(originalCreatedAt, Is.EqualTo(namedSub.CreatedAt)); - Assert.That(originalUpdatedAt, Is.LessThan(namedSub.UpdatedAt)); - } - }); - } - - [TestCase(true, true)] - [TestCase(false, true)] - [TestCase(true, false)] - [TestCase(false, false)] - public void Subscribe_UpdatesTtl(bool openAsync, bool changeTtlValue) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var originalTtl = TimeSpan.FromSeconds(1); - var subscription = realm.All() - .Where(o => o.IntValue < 5) - .Subscribe(new SubscriptionOptions { Name = "foo", ShouldUpdate = true, TimeToLive = originalTtl }); - - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - var namedSub = realm.GetAllSubscriptions().Single(s => s.ObjectType == nameof(ObjectA)); - var originalUpdatedAt = namedSub.UpdatedAt; - var originalCreatedAt = namedSub.CreatedAt; - - Assert.That(originalCreatedAt, Is.EqualTo(originalUpdatedAt)); - Assert.That(namedSub.TimeToLive, Is.EqualTo(originalTtl)); - Assert.That(namedSub.ExpiresAt, Is.EqualTo(namedSub.UpdatedAt.Add(originalTtl))); - - var updatedTtl = changeTtlValue ? TimeSpan.FromSeconds(2) : originalTtl; - var updatedSub = realm.All() - .Where(o => o.IntValue < 5) - .Subscribe(new SubscriptionOptions { Name = "foo", ShouldUpdate = true, TimeToLive = updatedTtl }); - - await updatedSub.WaitForSynchronizationAsync().Timeout(2000); - - // NamedSub is a Realm object so it should have updated itself. - Assert.That(originalCreatedAt, Is.EqualTo(namedSub.CreatedAt)); - Assert.That(originalUpdatedAt, Is.LessThan(namedSub.UpdatedAt)); - Assert.That(namedSub.TimeToLive, Is.EqualTo(updatedTtl)); - Assert.That(namedSub.ExpiresAt, Is.EqualTo(namedSub.UpdatedAt.Add(updatedTtl))); - } - }); - } - - [TestCase(true)] - [TestCase(false)] - public void NamedSubscription_CanResubscribe(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var query = realm.All().Where(o => o.IntValue < 5); - var subscription = query.Subscribe(new SubscriptionOptions { Name = "less than 5" }); - - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - Assert.That(subscription.Results.Count(), Is.EqualTo(5)); - - var subscription2 = realm.All().Where(o => o.IntValue < 5).Subscribe(new SubscriptionOptions { Name = "less than 5" }); - await subscription2.WaitForSynchronizationAsync().Timeout(2000); - - Assert.That(subscription.Results.Count(), Is.EqualTo(5)); - Assert.That(subscription2.Results.Count(), Is.EqualTo(5)); - } - }); - } - - [TestCase(true)] - [TestCase(false)] - public void NamedSubscription_CannotChangeQuery(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var query = realm.All().Where(o => o.IntValue < 5); - var subscription = query.Subscribe(new SubscriptionOptions { Name = "foo" }); - - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - Assert.That(subscription.Results.Count(), Is.EqualTo(5)); - - var subscription2 = realm.All() - .Where(o => o.IntValue > 5) - .Subscribe(new SubscriptionOptions { Name = "foo" }); - - await TestHelpers.AssertThrows(() => subscription2.WaitForSynchronizationAsync(), ex => - { - Assert.That(subscription2.State, Is.EqualTo(SubscriptionState.Error)); - Assert.That(subscription2.Error, Is.Not.Null); - Assert.That(subscription2.Error.Message, Does.Contain("An existing subscription exists with the name")); - Assert.That(ex, Is.EqualTo(subscription2.Error)); - }); - } - }); - } - - [TestCase(true)] - [TestCase(false)] - public void UnnamedSubscription_CanUnsubscribe(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var query = realm.All().Where(o => o.IntValue < 5); - var subscription = query.Subscribe(); - - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - Assert.That(subscription.Results.Count(), Is.EqualTo(5)); - - await subscription.UnsubscribeAsync(); - - Assert.That(subscription.State, Is.EqualTo(SubscriptionState.Invalidated)); - - await TestHelpers.WaitForConditionAsync(() => !query.Any()); - Assert.That(query.Count(), Is.EqualTo(0)); - Assert.That(subscription.Results.Count(), Is.EqualTo(0)); - } - }); - } - - [TestCase(true)] - [TestCase(false)] - public void NamedSubscription_CanUnsubscribe(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var query = realm.All().Where(o => o.IntValue < 5); - var subscription = query.Subscribe(new SubscriptionOptions { Name = "query" }); - - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - Assert.That(subscription.Results.Count(), Is.EqualTo(5)); - - await subscription.UnsubscribeAsync(); - - Assert.That(subscription.State, Is.EqualTo(SubscriptionState.Invalidated)); - - await TestHelpers.WaitForConditionAsync(() => !query.Any()); - Assert.That(query.Count(), Is.EqualTo(0)); - Assert.That(subscription.Results.Count(), Is.EqualTo(0)); - } - }); - } - - [TestCase(true)] - [TestCase(false)] - public void NamedSubscription_CanUnsubscribeByName(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var query = realm.All().Where(o => o.IntValue < 5); - var subscription = query.Subscribe(new SubscriptionOptions { Name = "query" }); - - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - Assert.That(subscription.Results.Count(), Is.EqualTo(5)); - - await realm.UnsubscribeAsync("query"); - - await TestHelpers.WaitForConditionAsync(() => !query.Any()); - Assert.That(subscription.Results.Count(), Is.EqualTo(0)); - } - }); - } - - // https://github.com/realm/realm-dotnet/issues/1716 - [TestCase(true)] - [TestCase(false)] - public void Subcribe_WaitForSynchronization_Multiple(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var subscription = realm.All().Where(f => f.IntValue > 0).Subscribe(); - await subscription.WaitForSynchronizationAsync().Timeout(5000); - - var subscription2 = realm.All().Where(f => f.IntValue > 0).Subscribe(); - await subscription2.WaitForSynchronizationAsync().Timeout(5000); - } - }); - } - - [TestCase(true)] - [TestCase(false)] - public void WaitForSynchronization_OnBackgroundThread_Throws(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var config = realm.Config; - await Task.Run(() => - { - using (var bgRealm = GetRealm(config)) - { - var sub = bgRealm.All().Subscribe(); - Assert.Throws(() => sub.WaitForSynchronizationAsync()); - } - }).Timeout(2000); - } - }); - } - - [TestCase(true)] - [TestCase(false)] - public void Subscribe_WithInclusions(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var subscription = realm.All() - .Where(b => b.BoolValue) - .Subscribe(includedBacklinks: b => b.As); - - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - Assert.That(realm.All().Count(), Is.EqualTo(5)); - Assert.That(realm.All().Count(), Is.EqualTo(5)); - Assert.That(realm.All().ToArray().All(a => a.IntValue % 2 == 0)); - } - }); - } - - [TestCase(true)] - [TestCase(false)] - public void Subscribe_WithInclusions_ExtraHop(bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - using (var realm = await GetQueryBasedRealm(openAsync)) - { - var subscription = realm.All() - .Filter("B.BoolValue == true") - .Subscribe(includedBacklinks: c => c.B.As); - - await subscription.WaitForSynchronizationAsync().Timeout(2000); - - Assert.That(realm.All().Count(), Is.EqualTo(5)); - Assert.That(realm.All().Count(), Is.EqualTo(5)); - Assert.That(realm.All().Count(), Is.EqualTo(5)); - Assert.That(realm.All().ToArray().All(a => a.IntValue % 2 == 0)); - } - }); - } - - private async Task GetQueryBasedRealm(bool openAsync, [CallerMemberName] string realmPath = null) - { - var user = await SyncTestHelpers.GetUserAsync().Timeout(5000); - var config = new QueryBasedSyncConfiguration(SyncTestHelpers.RealmUri($"~/{realmPath}_{openAsync}"), user, Guid.NewGuid().ToString()) - { - ObjectClasses = new[] { typeof(ObjectA), typeof(ObjectB), typeof(ObjectC) } - }; - - using (var original = GetRealm(config)) - { - original.Write(() => - { - for (var i = 0; i < 10; i++) - { - var a = original.Add(new ObjectA - { - StringValue = "A #" + i, - IntValue = i, - B = new ObjectB - { - StringValue = "B #" + i, - BoolValue = i % 2 == 0, - } - }); - - original.Add(new ObjectC - { - IntValue = i, - B = a.B - }); - } - }); - - await SyncTestHelpers.WaitForUploadAsync(original).Timeout(5000); - } - - try - { - Realm.DeleteRealm(config); - } - catch - { - } - - config = new QueryBasedSyncConfiguration(config.ServerUri, config.User, config.DatabasePath + "_partial") - { - ObjectClasses = config.ObjectClasses - }; - - // We test both `GetInstance` and `GetInstanceAsync` to guard against regressions: - // https://github.com/realm/realm-dotnet/issues/1814 - var result = await GetRealmAsync(config, openAsync).Timeout(5000); - - Assert.That(result.All().Count(), Is.EqualTo(0)); - Assert.That(result.All().Count(), Is.EqualTo(0)); - - return result; - } - - public class ObjectA : RealmObject - { - public string StringValue { get; set; } - - public int IntValue { get; set; } - - public ObjectB B { get; set; } - } - - public class ObjectB : RealmObject - { - public string StringValue { get; set; } - - public bool BoolValue { get; set; } - - [Backlink(nameof(ObjectA.B))] - public IQueryable As { get; } - } - - public class ObjectC : RealmObject - { - public int IntValue { get; set; } - - public ObjectB B { get; set; } - } - } -} diff --git a/Tests/Realm.Tests/Sync/SSLConfigurationTests.cs b/Tests/Realm.Tests/Sync/SSLConfigurationTests.cs deleted file mode 100644 index 14fd9489b9..0000000000 --- a/Tests/Realm.Tests/Sync/SSLConfigurationTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.IO; -using System.Linq; -using Nito.AsyncEx; -using NUnit.Framework; -using Realms.Sync; -using Realms.Tests.Database; - -namespace Realms.Tests.Sync -{ - [Ignore("Reenable when ROS fixes SSL support.")] - [TestFixture, Preserve(AllMembers = true)] - public class SSLConfigurationTests : SyncTestBase - { -#if __IOS__ - [Ignore("On iOS TrustedCAPath is ignored.")] -#endif - [TestCase(true)] - [TestCase(false)] - public void TrustedCA_WhenProvided_ValidatesCorrectly(bool openAsync) - { - TestSSLCore(config => - { - config.TrustedCAPath = TestHelpers.CopyBundledFileToDocuments("trusted_ca.pem", "trusted_ca.pem"); - }, openAsync); - } - - [Test] - public void TrustedCA_WhenFileDoesntExist_Throws() - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var user = await SyncTestHelpers.GetUserAsync(); - var config = new FullSyncConfiguration(SyncTestHelpers.RealmUri("~/TrustedCA_WhenFileDoesntExist_Throws"), user) - { - TrustedCAPath = "something.pem" - }; - Assert.That(() => GetRealm(config), Throws.TypeOf()); - }); - } - - [TestCase(true)] - [TestCase(false)] - public void EnableSSLValidation_WhenFalse_ValidatesCorrectly(bool openAsync) - { - TestSSLCore(config => - { - config.EnableSSLValidation = false; - }, openAsync); - } - - private void TestSSLCore(Action setupSecureConfig, bool openAsync) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var user = await SyncTestHelpers.GetUserAsync(); - const string path = "~/TestSSLCore"; - var realmUri = SyncTestHelpers.RealmUri(path); - var config = new FullSyncConfiguration(realmUri, user); - - var secureRealmUri = SyncTestHelpers.SecureRealmUri(path); - var secureConfig = new FullSyncConfiguration(secureRealmUri, user, config.DatabasePath + "2"); - setupSecureConfig(secureConfig); - - using (var realm = GetRealm(config)) - { - realm.Write(() => - { - realm.Add(new IntPrimaryKeyWithValueObject - { - Id = 1, - StringValue = "some value" - }); - }); - - await SyncTestHelpers.WaitForUploadAsync(realm); - } - - using (var newRealm = await GetRealmAsync(secureConfig, openAsync)) - { - var items = newRealm.All(); - - Assert.That(items.Count(), Is.EqualTo(1)); - Assert.That(items.Single().StringValue, Is.EqualTo("some value")); - } - }); - } - } -} diff --git a/Tests/Realm.Tests/Sync/SessionTests.cs b/Tests/Realm.Tests/Sync/SessionTests.cs index 15661c7d09..e0b8e834f4 100644 --- a/Tests/Realm.Tests/Sync/SessionTests.cs +++ b/Tests/Realm.Tests/Sync/SessionTests.cs @@ -17,15 +17,11 @@ //////////////////////////////////////////////////////////////////////////// using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reactive.Linq; using System.Threading.Tasks; using NUnit.Framework; using Realms.Sync; using Realms.Sync.Exceptions; -using TestExplicitAttribute = NUnit.Framework.ExplicitAttribute; namespace Realms.Tests.Sync { @@ -37,46 +33,32 @@ public class SessionTests : SyncTestBase [Test] public void Realm_GetSession_WhenSyncedRealm() { - TestHelpers.RunAsyncTest(async () => - { - var user = await SyncTestHelpers.GetFakeUserAsync(); - var serverUri = new Uri("realm://localhost:9080/foobar"); - var config = new FullSyncConfiguration(serverUri, user); + var user = GetFakeUser(); + var config = GetSyncConfiguration("foo-bar", user); - using (var realm = GetRealm(config)) - { - var session = GetSession(realm); + using var realm = GetRealm(config); + var session = GetSession(realm); - Assert.That(session.User, Is.EqualTo(user)); - Assert.That(session.ServerUri, Is.EqualTo(serverUri)); - } - }); + Assert.That(session.User, Is.EqualTo(user)); } [Test] public void Realm_GetSession_WhenLocalRealm_ShouldThrow() { - using (var realm = Realm.GetInstance()) - { - Assert.Throws(() => GetSession(realm)); - } + using var realm = GetRealm(); + Assert.Throws(() => GetSession(realm)); } [Test] public void Realm_GetSession_ShouldReturnSameObject() { - TestHelpers.RunAsyncTest(async () => - { - var config = await SyncTestHelpers.GetFakeConfigAsync(); - using (var realm = GetRealm(config)) - { - var session1 = GetSession(realm); - var session2 = GetSession(realm); + var config = GetFakeConfig(); + using var realm = GetRealm(config); + var session1 = GetSession(realm); + var session2 = GetSession(realm); - Assert.That(session1, Is.EqualTo(session2)); - Assert.That(session1.GetHashCode(), Is.EqualTo(session2.GetHashCode())); - } - }); + Assert.That(session1, Is.EqualTo(session2)); + Assert.That(session1.GetHashCode(), Is.EqualTo(session2.GetHashCode())); } [Test] @@ -84,119 +66,23 @@ public void Session_Error_ShouldPassCorrectSession() { TestHelpers.RunAsyncTest(async () => { - var config = await SyncTestHelpers.GetFakeConfigAsync(); - using (var realm = GetRealm(config)) - { - var session = GetSession(realm); - - const ErrorCode code = (ErrorCode)102; - const string message = "Some fake error has occurred"; - - var result = await SyncTestHelpers.SimulateSessionErrorAsync(session, code, message); - CleanupOnTearDown(result.Item1); - - var error = result.Item2; - Assert.That(error.Message, Is.EqualTo(message)); - Assert.That(error.ErrorCode, Is.EqualTo(code)); - - var errorSession = result.Item1; - Assert.That(errorSession.ServerUri, Is.EqualTo(((SyncConfigurationBase)realm.Config).ServerUri)); - } - }); - } - - [Test] - [Ignore("This is no longer relevant with the automatic client reset recovery.")] - public void Session_DivergingHistories_ShouldRaiseClientResetException() - { - TestHelpers.RunAsyncTest(async () => - { - var config = await SyncTestHelpers.GetFakeConfigAsync(); - ClientResetException error = null; - using (var realm = GetRealm(config)) - { - var session = GetSession(realm); - - var result = await SyncTestHelpers.SimulateSessionErrorAsync(session, - ErrorCode.DivergingHistories, - "Fake client reset is required"); - CleanupOnTearDown(result.Item1); - - error = result.Item2; - } - - Assert.That(error.BackupFilePath, Is.Not.Null); - Assert.That(error.BackupFilePath, Does.Contain(Path.Combine("io.realm.object-server-recovered-realms", "recovered_realm"))); - Assert.That(File.Exists(error.BackupFilePath), Is.False); - - var clientResetSuccess = error.InitiateClientReset(); - - Assert.That(clientResetSuccess, Is.True); - Assert.That(File.Exists(error.BackupFilePath), Is.True); - }); - } - - [Test, TestExplicit("Fails with obscure error.")] - public void Session_Error_WhenInvalidRefreshToken() - { - TestHelpers.RunAsyncTest(async () => - { - var errors = new List(); - var config = await SyncTestHelpers.GetFakeConfigAsync(); - using (var realm = GetRealm(config)) - { - EventHandler handler = null; - handler = new EventHandler((sender, e) => - { - errors.Add(e.Exception); - CleanupOnTearDown((Session)sender); - }); - - Session.Error += handler; - - while (!errors.Any()) - { - await Task.Yield(); - } - - Session.Error -= handler; + var config = GetFakeConfig(); + using var realm = GetRealm(config); + var session = GetSession(realm); - var authErrors = errors.OfType().ToArray(); - Assert.That(authErrors.Count, Is.EqualTo(1)); - Assert.That(authErrors[0].ErrorCode, Is.EqualTo(ErrorCode.InvalidCredentials)); - } - }); - } + const ErrorCode code = (ErrorCode)102; + const string message = "Some fake error has occurred"; - [Test, TestExplicit("Fails with obscure error.")] - public void Session_Error_WhenInvalidAccessToken() - { - TestHelpers.RunAsyncTest(async () => - { - var errors = new List(); - var config = await SyncTestHelpers.GetFakeConfigAsync(); - using (var realm = GetRealm(config)) - { - EventHandler handler = null; - handler = new EventHandler((sender, e) => - { - errors.Add(e.Exception); - CleanupOnTearDown((Session)sender); - }); + var result = await SyncTestHelpers.SimulateSessionErrorAsync(session, code, message); + CleanupOnTearDown(result.Item1); - Session.Error += handler; + var error = result.Item2; + Assert.That(error.Message, Is.EqualTo(message)); + Assert.That(error.ErrorCode, Is.EqualTo(code)); - while (!errors.Any()) - { - await Task.Yield(); - } + var errorSession = result.Item1; - Session.Error -= handler; - - var sessionErrors = errors.OfType().ToArray(); - Assert.That(sessionErrors.Count, Is.EqualTo(1)); - Assert.That(sessionErrors[0].ErrorCode, Is.EqualTo(ErrorCode.BadUserAuthentication)); - } + Assert.That(errorSession, Is.EqualTo(session)); }); } @@ -206,10 +92,11 @@ public void Session_ProgressObservable_IntegrationTests(ProgressMode mode) { const int ObjectSize = 1000000; const int ObjectsToRecord = 2; - SyncTestHelpers.RunRosTestAsync(async () => + SyncTestHelpers.RunBaasTestAsync(async () => { - var config = await SyncTestHelpers.GetIntegrationConfigAsync("progress"); - var realm = GetRealm(config); + var config = await GetIntegrationConfigAsync("progress"); + using var realm = GetRealm(config); + var completionTCS = new TaskCompletionSource(); var callbacksInvoked = 0; @@ -225,7 +112,7 @@ public void Session_ProgressObservable_IntegrationTests(ProgressMode mode) }); } - var token = observable.Subscribe(p => + using var token = observable.Subscribe(p => { try { @@ -240,9 +127,9 @@ public void Session_ProgressObservable_IntegrationTests(ProgressMode mode) if (mode == ProgressMode.ForCurrentlyOutstandingWork) { if (p.TransferableBytes <= ObjectSize || - p.TransferableBytes >= (ObjectsToRecord + 1) * ObjectSize) + p.TransferableBytes >= (ObjectsToRecord + 2) * ObjectSize) { - throw new Exception($"Expected: {p.TransferredBytes} to be in the ({ObjectSize}, {(ObjectsToRecord + 1) * ObjectSize}) range."); + throw new Exception($"Expected: {p.TransferableBytes} to be in the ({ObjectSize}, {(ObjectsToRecord + 1) * ObjectSize}) range."); } } } @@ -257,82 +144,67 @@ public void Session_ProgressObservable_IntegrationTests(ProgressMode mode) } }); - using (token) + realm.Write(() => { - realm.Write(() => - { - realm.Add(new HugeSyncObject(ObjectSize)); - }); + realm.Add(new HugeSyncObject(ObjectSize)); + }); - var totalTransferred = await completionTCS.Task; + var totalTransferred = await completionTCS.Task; - if (mode == ProgressMode.ForCurrentlyOutstandingWork) - { - Assert.That(totalTransferred, Is.GreaterThanOrEqualTo(ObjectSize)); - - // We add ObjectsToRecord + 1 items, but the last item is added after subscribing - // so in the fixed mode, we should not get updates for it. - Assert.That(totalTransferred, Is.LessThan((ObjectsToRecord + 5) * ObjectSize)); - } - else - { - Assert.That(totalTransferred, Is.GreaterThanOrEqualTo((ObjectsToRecord + 1) * ObjectSize)); - } + if (mode == ProgressMode.ForCurrentlyOutstandingWork) + { + Assert.That(totalTransferred, Is.GreaterThanOrEqualTo(ObjectSize)); - Assert.That(callbacksInvoked, Is.GreaterThan(1)); + // We add ObjectsToRecord + 1 items, but the last item is added after subscribing + // so in the fixed mode, we should not get updates for it. + Assert.That(totalTransferred, Is.LessThan((ObjectsToRecord + 5) * ObjectSize)); } + else + { + Assert.That(totalTransferred, Is.GreaterThanOrEqualTo((ObjectsToRecord + 1) * ObjectSize)); + } + + Assert.That(callbacksInvoked, Is.GreaterThan(1)); }); } [Test] public void Session_Stop_StopsSession() { - TestHelpers.RunAsyncTest(async () => - { - // OpenRealmAndStopSession will call Stop and assert the state changed - await OpenRealmAndStopSession(); - }); + // OpenRealmAndStopSession will call Stop and assert the state changed + OpenRealmAndStopSession(); } [Test] public void Session_Start_ResumesSession() { - TestHelpers.RunAsyncTest(async () => - { - var session = await OpenRealmAndStopSession(); + var session = OpenRealmAndStopSession(); - session.Start(); - Assert.That(session.State, Is.EqualTo(SessionState.Active)); - }); + session.Start(); + Assert.That(session.State, Is.EqualTo(SessionState.Active)); } [Test] public void Session_Stop_IsIdempotent() { - TestHelpers.RunAsyncTest(async () => - { - var session = await OpenRealmAndStopSession(); + var session = OpenRealmAndStopSession(); - // Stop it again - session.Stop(); - Assert.That(session.State, Is.EqualTo(SessionState.Inactive)); - }); + // Stop it again + session.Stop(); + Assert.That(session.State, Is.EqualTo(SessionState.Inactive)); } [Test] public void Session_Start_IsIdempotent() { - TestHelpers.RunAsyncTest(async () => - { - var session = await OpenRealmAndStopSession(); + var session = OpenRealmAndStopSession(); - session.Start(); - Assert.That(session.State, Is.EqualTo(SessionState.Active)); + session.Start(); + Assert.That(session.State, Is.EqualTo(SessionState.Active)); - // Start it again - session.Start(); - Assert.That(session.State, Is.EqualTo(SessionState.Active)); - }); + // Start it again + session.Start(); + Assert.That(session.State, Is.EqualTo(SessionState.Active)); } /// @@ -340,9 +212,9 @@ public void Session_Start_IsIdempotent() /// to Inactive. /// /// The stopped session. - private async Task OpenRealmAndStopSession() + private Session OpenRealmAndStopSession() { - var config = await SyncTestHelpers.GetFakeConfigAsync(); + var config = GetFakeConfig(); var realm = GetRealm(config); var session = GetSession(realm); diff --git a/Tests/Realm.Tests/Sync/SyncConfigurationTests.cs b/Tests/Realm.Tests/Sync/SyncConfigurationTests.cs index 228b9c9a45..3ba149b435 100644 --- a/Tests/Realm.Tests/Sync/SyncConfigurationTests.cs +++ b/Tests/Realm.Tests/Sync/SyncConfigurationTests.cs @@ -19,10 +19,8 @@ using System; using System.IO; using System.Linq; -using System.Text; using NUnit.Framework; using Realms.Sync; -using Realms.Tests.Database; namespace Realms.Tests.Sync { @@ -32,196 +30,86 @@ public class SyncConfigurationTests : SyncTestBase [Test] public void SyncConfiguration_WithoutPath() { - TestHelpers.RunAsyncTest(async () => - { - var user = await SyncTestHelpers.GetFakeUserAsync(); - var serverUri = new Uri("realm://localhost:9080/foobar"); - var config = new FullSyncConfiguration(serverUri, user); + var user = GetFakeUser(); + var config = GetSyncConfiguration("foo-bar", user); - var file = new FileInfo(config.DatabasePath); - Assert.That(file.Exists, Is.False); + var file = new FileInfo(config.DatabasePath); + Assert.That(file.Exists, Is.False); - using (var realm = GetRealm(config)) - { - } + using (var realm = GetRealm(config)) + { + } - file = new FileInfo(config.DatabasePath); - Assert.That(file.Exists); - }); + file = new FileInfo(config.DatabasePath); + Assert.That(file.Exists); } [Test] public void SyncConfiguration_WithRelativePath() { - TestHelpers.RunAsyncTest(async () => - { - var user = await SyncTestHelpers.GetFakeUserAsync(); - var serverUri = new Uri("realm://localhost:9080/foobar"); - var config = new FullSyncConfiguration(serverUri, user, "myrealm.realm"); - - var file = new FileInfo(config.DatabasePath); - Assert.That(file.Exists, Is.False); - - using (var realm = GetRealm(config)) - { - } + var user = GetFakeUser(); + var config = GetSyncConfiguration("foo-bar", user, "myrealm.realm"); - file = new FileInfo(config.DatabasePath); - Assert.That(file.Exists); - Assert.That(config.DatabasePath.EndsWith("myrealm.realm")); - }); - } + var file = new FileInfo(config.DatabasePath); + Assert.That(file.Exists, Is.False); - [Test] - public void SyncConfiguration_WithAbsolutePath() - { - TestHelpers.RunAsyncTest(async () => + using (var realm = GetRealm(config)) { - var user = await SyncTestHelpers.GetFakeUserAsync(); - var serverUri = new Uri("realm://localhost:9080/foobar"); - - var path = Path.GetTempFileName(); - var config = new FullSyncConfiguration(serverUri, user, path); + } - Realm.DeleteRealm(config); - var file = new FileInfo(config.DatabasePath); - Assert.That(file.Exists, Is.False); - - using (var realm = GetRealm(config)) - { - } - - file = new FileInfo(config.DatabasePath); - Assert.That(file.Exists); - Assert.That(config.DatabasePath, Is.EqualTo(path)); - }); + file = new FileInfo(config.DatabasePath); + Assert.That(file.Exists); + Assert.That(config.DatabasePath.EndsWith("myrealm.realm")); } [Test] - public void SyncConfiguration_WithEncryptionKey_DoesntThrow() + public void SyncConfiguration_WithAbsolutePath() { - TestHelpers.RunAsyncTest(async () => - { - var user = await SyncTestHelpers.GetFakeUserAsync(); - var key = Enumerable.Range(0, 63).Select(i => (byte)i).ToArray(); + var user = GetFakeUser(); - var config = new FullSyncConfiguration(new Uri("realm://foobar"), user) - { - EncryptionKey = TestHelpers.GetEncryptionKey(key) - }; + var path = Path.Combine(InteropConfig.DefaultStorageFolder, Guid.NewGuid().ToString()); + var config = GetSyncConfiguration("foo-bar", user, path); - Assert.That(() => GetRealm(config), Throws.Nothing); - }); - } + Realm.DeleteRealm(config); + var file = new FileInfo(config.DatabasePath); + Assert.That(file.Exists, Is.False); - [TestCase("http://localhost/~/foo")] - [TestCase("https://localhost/~/foo")] - [TestCase("foo://bar/~/foo")] - public void SyncConfiguration_WrongProtocolTests(string url) - { - TestHelpers.RunAsyncTest(async () => + using (var realm = GetRealm(config)) { - var user = await SyncTestHelpers.GetFakeUserAsync(); - - Assert.That(() => new FullSyncConfiguration(new Uri(url), user), Throws.TypeOf()); - }); - } + } - [Test] - public void DefaultConfiguration_WhenNoUserLoggedIn_ShouldThrow() - { - Assert.That(() => new QueryBasedSyncConfiguration(), Throws.TypeOf().And.Message.Contains("The user must be explicitly specified when the number of logged-in users is not 1.")); + file = new FileInfo(config.DatabasePath); + Assert.That(file.Exists); + Assert.That(config.DatabasePath, Is.EqualTo(path)); } [Test] - public void DefaultConfiguration_WhenMoreThanOneUserLoggedIn_ShouldThrow() + public void SyncConfiguration_WithEncryptionKey_DoesntThrow() { - TestHelpers.RunAsyncTest(async () => - { - await SyncTestHelpers.GetFakeUserAsync(); - await SyncTestHelpers.GetFakeUserAsync(); - - Assert.That(() => new QueryBasedSyncConfiguration(), Throws.TypeOf().And.Message.Contains("The user must be explicitly specified when the number of logged-in users is not 1.")); - }); - } + var user = GetFakeUser(); + var key = Enumerable.Range(0, 63).Select(i => (byte)i).ToArray(); - [TestCase("http", "realm")] - [TestCase("https", "realms")] - public void DefaultConfiguration_WhenOneUserLoggedIn_ShouldWork(string userScheme, string realmScheme) - { - TestHelpers.RunAsyncTest(async () => - { - await SyncTestHelpers.GetFakeUserAsync(scheme: userScheme); + var config = GetSyncConfiguration("foo-bar", user); + config.EncryptionKey = TestHelpers.GetEncryptionKey(key); - var config = new QueryBasedSyncConfiguration(); - Assert.That(!config.IsFullSync); - Assert.That(config.ServerUri.Scheme, Is.EqualTo(realmScheme)); - Assert.That(config.ServerUri.Segments, Is.EqualTo(new[] { "/", "default" })); - }); + Assert.That(() => GetRealm(config), Throws.Nothing); } [Test] public void SyncConfiguration_CanBeSetAsRealmConfigurationDefault() { - SyncTestHelpers.RunRosTestAsync(async () => - { - var user = await SyncTestHelpers.GetUserAsync(); - - RealmConfiguration.DefaultConfiguration = new QueryBasedSyncConfiguration(); - - using (var realm = GetRealm(null)) - { - Assert.That(realm.Config, Is.TypeOf()); - var syncConfig = (QueryBasedSyncConfiguration)realm.Config; - Assert.That(syncConfig.User.Identity, Is.EqualTo(user.Identity)); - Assert.That(syncConfig.ServerUri.Segments, Is.EqualTo(new[] { "/", "default" })); - } - }); - } - - [TestCase("bar", "/bar")] - [TestCase("/bar", "/bar")] - [TestCase("/~/bar", "/~/bar")] - [TestCase("~/bar", "/~/bar")] - public void SyncConfiguration_WithRelativeUri_ResolvesCorrectly(string path, string expected) - { - TestHelpers.RunAsyncTest(async () => - { - var user = await SyncTestHelpers.GetFakeUserAsync(); - var syncConfiguration = new FullSyncConfiguration(new Uri(path, UriKind.Relative), user); - Assert.That(syncConfiguration.ServerUri.AbsoluteUri, Is.EqualTo($"realm://{SyncTestHelpers.FakeRosUrl}{expected}")); - }); - } - - [TestCase(LogLevel.Debug)] - [TestCase(LogLevel.Info)] - public void SyncConfiguration_LoggerFactory_Test(LogLevel logLevel) - { - SyncTestHelpers.RunRosTestAsync(async () => + SyncTestHelpers.RunBaasTestAsync(async () => { - var logBuilder = new StringBuilder(); - - SyncConfigurationBase.CustomLogger = (message, level) => - { - logBuilder.AppendLine($"[{level}] {message}"); - }; - SyncConfigurationBase.LogLevel = logLevel; - - var config = await SyncTestHelpers.GetIntegrationConfigAsync(Guid.NewGuid().ToString()); - using (var realm = await GetRealmAsync(config)) - { - realm.Write(() => - { - realm.Add(new Person()); - }); + var user = await GetUserAsync(); - await SyncTestHelpers.WaitForUploadAsync(realm); - } + RealmConfiguration.DefaultConfiguration = GetSyncConfiguration("abc", user); - var log = logBuilder.ToString(); + using var realm = GetRealm(); - Assert.That(log, Does.Contain($"[{logLevel}]")); - Assert.That(log, Does.Not.Contain($"[{logLevel - 1}]")); + Assert.That(realm.Config, Is.TypeOf()); + var syncConfig = (SyncConfiguration)realm.Config; + Assert.That(syncConfig.User.Id, Is.EqualTo(user.Id)); + Assert.That(syncConfig.Partition, Is.EqualTo("abc")); }); } } diff --git a/Tests/Realm.Tests/Sync/SyncTestBase.cs b/Tests/Realm.Tests/Sync/SyncTestBase.cs index ca7875bae5..3ee976b194 100644 --- a/Tests/Realm.Tests/Sync/SyncTestBase.cs +++ b/Tests/Realm.Tests/Sync/SyncTestBase.cs @@ -16,9 +16,8 @@ // //////////////////////////////////////////////////////////////////////////// +using System; using System.Collections.Generic; -using System.IO; -using System.Threading; using System.Threading.Tasks; using Realms.Sync; @@ -28,36 +27,31 @@ namespace Realms.Tests.Sync public abstract class SyncTestBase : RealmTest { private readonly List _sessions = new List(); + private readonly List _apps = new List(); - protected override void CustomSetUp() - { - base.CustomSetUp(); + private App _defaultApp; - var defaultFolder = InteropConfig.DefaultStorageFolder; - if (TestHelpers.IsWindows) + protected App DefaultApp + { + get { - // We do this to reduce the length of the folders in Windows - var testsIndex = defaultFolder.IndexOf("\\Tests\\"); - var docsIndex = defaultFolder.IndexOf("\\Documents") + 1; + return _defaultApp ?? CreateApp(); + } + } - if (testsIndex > -1 && docsIndex > testsIndex) - { - defaultFolder = Path.Combine(defaultFolder.Substring(0, testsIndex), defaultFolder.Substring(docsIndex)) - .Replace("\\Documents", "\\D"); + protected App CreateApp(AppConfiguration config = null) + { + config ??= SyncTestHelpers.GetAppConfig(); - Directory.CreateDirectory(defaultFolder); - } - } + var app = App.Create(config); + _apps.Add(app); - if (TestHelpers.IsMacOS) + if (_defaultApp == null) { - // VS for Mac hangs when Realm files are written in a location it doesn't ignore. - defaultFolder = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Documents"); - Directory.CreateDirectory(defaultFolder); + _defaultApp = app; } - SyncConfigurationBase.UserAgent = GetType().Name; - SyncConfigurationBase.Initialize(UserPersistenceMode.NotEncrypted, null, false, defaultFolder); + return app; } protected override void CustomTearDown() @@ -68,6 +62,13 @@ protected override void CustomTearDown() { session?.CloseHandle(); } + + foreach (var app in _apps) + { + app.Handle.ResetForTesting(); + } + + _defaultApp = null; } protected void CleanupOnTearDown(Session session) @@ -96,21 +97,49 @@ protected static async Task WaitForDownloadAsync(Realm realm) session.CloseHandle(); } - protected async Task GetRealmAsync(RealmConfigurationBase config, bool openAsync = true, CancellationToken cancellationToken = default) + protected async Task GetUserAsync(App app = null) { - Realm result; - if (openAsync) - { - result = await Realm.GetInstanceAsync(config, cancellationToken); - } - else + app ??= DefaultApp; + + var username = SyncTestHelpers.GetVerifiedUsername(); + await app.EmailPasswordAuth.RegisterUserAsync(username, SyncTestHelpers.DefaultPassword); + + var credentials = Credentials.EmailPassword(username, SyncTestHelpers.DefaultPassword); + return await app.LogInAsync(credentials); + } + + protected User GetFakeUser(App app = null, string id = null, string refreshToken = null, string accessToken = null) + { + app ??= DefaultApp; + id ??= Guid.NewGuid().ToString(); + refreshToken ??= "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoicmVmcmVzaCB0b2tlbiIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoyNTM2MjM5MDIyfQ.SWH98a-UYBEoJ7DLxpP7mdibleQFeCbGt4i3CrsyT2M"; + accessToken ??= "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWNjZXNzIHRva2VuIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MzYyMzkwMjJ9.bgnlxP_mGztBZsImn7HaF-6lDevFDn2U_K7D8WUC2GQ"; + var handle = app.Handle.GetUserForTesting(id, refreshToken, accessToken); + return new User(handle); + } + + protected async Task GetIntegrationConfigAsync(string partition = null, App app = null) + { + app ??= DefaultApp; + partition ??= Guid.NewGuid().ToString(); + + var user = await GetUserAsync(app); + return GetSyncConfiguration(partition, user); + } + + protected static SyncConfiguration GetSyncConfiguration(string partition, User user, string optionalPath = null) + { + return new SyncConfiguration(partition, user, optionalPath) { - result = Realm.GetInstance(config); - await SyncTestHelpers.WaitForDownloadAsync(result); - } + ObjectClasses = new[] { typeof(HugeSyncObject), typeof(PrimaryKeyStringObject), typeof(ObjectIdPrimaryKeyWithValueObject) }, + SessionStopPolicy = SessionStopPolicy.Immediately, + }; + } - CleanupOnTearDown(result); - return result; + public SyncConfiguration GetFakeConfig(App app = null, string userId = null, string optionalPath = null) + { + var user = GetFakeUser(app, userId); + return GetSyncConfiguration(Guid.NewGuid().ToString(), user, optionalPath); } } } diff --git a/Tests/Realm.Tests/Sync/SyncTestHelpers.cs b/Tests/Realm.Tests/Sync/SyncTestHelpers.cs index e51a037f8b..f771b56cba 100644 --- a/Tests/Realm.Tests/Sync/SyncTestHelpers.cs +++ b/Tests/Realm.Tests/Sync/SyncTestHelpers.cs @@ -28,44 +28,44 @@ namespace Realms.Tests.Sync { public static class SyncTestHelpers { - public const string FakeRosUrl = "some.fake.server:9080"; + public const string DefaultPassword = "123456"; - public static Credentials CreateCredentials() - { - return Credentials.UsernamePassword(Guid.NewGuid().ToString(), "a", createUser: true); - } + private static AppConfiguration _baseConfig; - public static Credentials AdminCredentials() + public static AppConfiguration GetAppConfig() => new AppConfiguration(_baseConfig?.AppId ?? "myapp-123") { - return Credentials.UsernamePassword(Constants.AdminUsername, Constants.AdminPassword, createUser: false); - } + BaseUri = _baseConfig?.BaseUri ?? new Uri("http://localhost:12345"), + MetadataPersistenceMode = MetadataPersistenceMode.NotEncrypted, + }; - public static void RunRosTestAsync(Func testFunc, int timeout = 30000) + public static void RunBaasTestAsync(Func testFunc, int timeout = 30000) { - if (Constants.RosUrl == null) + if (_baseConfig == null) { - Assert.Ignore("ROS is not setup."); + Assert.Ignore("MongoDB Realm is not setup."); } TestHelpers.RunAsyncTest(testFunc, timeout); } - public static string[] ExtractRosSettings(string[] args) + public static string GetVerifiedUsername() => $"realm_tests_do_autoverify-{Guid.NewGuid()}"; + + public static string[] ExtractBaasSettings(string[] args) { var result = new List(); + string baasUrl = null; + string baasAppId = null; + for (var i = 0; i < args.Length; i++) { switch (args[i]) { - case "--ros": - Constants.RosUrl = args[++i]; - break; - case "--rosport": - Constants.RosPort = args[++i]; + case "--baasurl": + baasUrl = args[++i]; break; - case "--rossecureport": - Constants.RosSecurePort = args[++i]; + case "--baasappid": + baasAppId = args[++i]; break; default: result.Add(args[i]); @@ -73,44 +73,15 @@ public static string[] ExtractRosSettings(string[] args) } } - return result.ToArray(); - } - - public static Uri AuthServerUri => new Uri($"http://{Constants.RosUrl}:{Constants.RosPort}"); - - public static Uri RealmUri(string path) => new Uri($"realm://{Constants.RosUrl}:{Constants.RosPort}/{path.TrimStart('/')}"); - - public static Uri SecureRealmUri(string path) => new Uri($"realms://{Constants.RosUrl}:{Constants.RosSecurePort}/{path.TrimStart('/')}"); - - public static Task GetUserAsync() - { - var credentials = CreateCredentials(); - return User.LoginAsync(credentials, AuthServerUri); - } - - public static Task GetAdminUserAsync() - { - var credentials = AdminCredentials(); - return User.LoginAsync(credentials, AuthServerUri); - } - - public static async Task GetFakeConfigAsync(string userId = null, string optionalPath = null) - { - var user = await GetFakeUserAsync(userId); - var serverUri = new Uri($"realm://localhost:9080/{Guid.NewGuid()}"); - return new FullSyncConfiguration(serverUri, user, optionalPath); - } - - public static Task GetFakeUserAsync(string id = null, string scheme = "http") - { - var handle = SyncUserHandle.GetSyncUser(id ?? Guid.NewGuid().ToString(), $"{scheme}://{FakeRosUrl}", string.Empty, isAdmin: true); - return Task.FromResult(new User(handle)); - } + if (baasUrl != null && baasAppId != null) + { + _baseConfig = new AppConfiguration(baasAppId) + { + BaseUri = new Uri(baasUrl), + }; + } - public static async Task GetIntegrationConfigAsync(string path) - { - var user = await GetUserAsync(); - return new FullSyncConfiguration(RealmUri($"~/{path}"), user); + return result.ToArray(); } public static Task> SimulateSessionErrorAsync(Session session, ErrorCode code, string message) @@ -140,35 +111,5 @@ public static async Task GetIntegrationConfigAsync(string return tcs.Task; } - - public static Task WaitForUploadAsync(Realm realm) => WaitForSyncAsync(realm, upload: true, download: false); - - public static Task WaitForDownloadAsync(Realm realm) => WaitForSyncAsync(realm, upload: false, download: true); - - public static async Task WaitForSyncAsync(Realm realm, bool upload = true, bool download = true) - { - var session = realm.GetSession(); - try - { - if (upload) - { - await session.WaitForUploadAsync(); - } - - if (upload && download) - { - await Task.Delay(50); - } - - if (download) - { - await session.WaitForDownloadAsync(); - } - } - finally - { - session.CloseHandle(); - } - } } } \ No newline at end of file diff --git a/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs b/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs index c5e4a1eee7..2ff71fb397 100644 --- a/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs +++ b/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs @@ -23,11 +23,9 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; -using Realms.Exceptions; using Realms.Schema; using Realms.Sync; using Realms.Sync.Exceptions; -using Realms.Tests.Database; namespace Realms.Tests.Sync { @@ -45,10 +43,7 @@ public void Compact_ShouldReduceSize(bool encrypt, bool populate) { TestHelpers.RunAsyncTest(async () => { - var user = await SyncTestHelpers.GetFakeUserAsync(); - var serverUri = new Uri($"/~/compactrealm_{encrypt}_{populate}.realm", UriKind.Relative); - - var config = new FullSyncConfiguration(serverUri, user); + var config = GetFakeConfig(); if (encrypt) { config.EncryptionKey = TestHelpers.GetEncryptionKey(5); @@ -64,174 +59,112 @@ public void Compact_ShouldReduceSize(bool encrypt, bool populate) var initialSize = new FileInfo(config.DatabasePath).Length; - Assert.That(Realm.Compact(config)); + var attempts = 20; - var finalSize = new FileInfo(config.DatabasePath).Length; - Assert.That(initialSize >= finalSize); - - using (var realm = GetRealm(config)) + // Give core a chance to close the Realm + while (!Realm.Compact(config) && (attempts-- > 0)) { - Assert.That(realm.All().Count(), Is.EqualTo(populate ? 500 : 0)); + await Task.Delay(50); } - }); - } - [TestCase(true)] - [TestCase(false)] - public void GetInstanceAsync_ShouldDownloadRealm(bool singleTransaction) - { - SyncTestHelpers.RunRosTestAsync(async () => - { - var user = await SyncTestHelpers.GetUserAsync(); - - var realmUri = SyncTestHelpers.RealmUri("~/GetInstanceAsync_ShouldDownloadRealm"); + Assert.That(attempts > 0); - var config = new FullSyncConfiguration(realmUri, user, Guid.NewGuid().ToString()); - var asyncConfig = new FullSyncConfiguration(realmUri, user, config.DatabasePath + "_async"); + var finalSize = new FileInfo(config.DatabasePath).Length; + Assert.That(initialSize >= finalSize); using (var realm = GetRealm(config)) { - AddDummyData(realm, singleTransaction); - - await SyncTestHelpers.WaitForUploadAsync(realm); - } - - using (var asyncRealm = await GetRealmAsync(asyncConfig)) - { - Assert.That(asyncRealm.All().Count(), Is.EqualTo(500)); + Assert.That(realm.All().Count(), Is.EqualTo(populate ? DummyDataSize / 2 : 0)); } }); } [TestCase(true)] [TestCase(false)] - public void GetInstanceAsync_OpensReadonlyRealm(bool singleTransaction) + public void GetInstanceAsync_ShouldDownloadRealm(bool singleTransaction) { - SyncTestHelpers.RunRosTestAsync(async () => + SyncTestHelpers.RunBaasTestAsync(async () => { - var alice = await SyncTestHelpers.GetUserAsync(); - var bob = await SyncTestHelpers.GetUserAsync(); - - var realmUri = SyncTestHelpers.RealmUri($"{alice.Identity}/GetInstanceAsync_OpensReadonlyRealm"); - var aliceConfig = new FullSyncConfiguration(realmUri, alice, Guid.NewGuid().ToString()); - var aliceRealm = GetRealm(aliceConfig); - - await alice.ApplyPermissionsAsync(PermissionCondition.UserId(bob.Identity), realmUri.AbsoluteUri, AccessLevel.Read).Timeout(1000); - - AddDummyData(aliceRealm, singleTransaction); - - await WaitForUploadAsync(aliceRealm); + var user = await GetUserAsync(); - var bobConfig = new FullSyncConfiguration(realmUri, bob, Guid.NewGuid().ToString()); - var bobRealm = await GetRealmAsync(bobConfig); + var partition = Guid.NewGuid().ToString(); - var bobsObjects = bobRealm.All(); - var alicesObjects = aliceRealm.All(); - Assert.That(bobsObjects.Count(), Is.EqualTo(alicesObjects.Count())); - - aliceRealm.Write(() => - { - aliceRealm.Add(new IntPrimaryKeyWithValueObject - { - Id = 9999, - StringValue = "Some value" - }); - }); + var config = GetSyncConfiguration(partition, user, Guid.NewGuid().ToString()); + var asyncConfig = GetSyncConfiguration(partition, user, config.DatabasePath + "_async"); - await WaitForUploadAsync(aliceRealm); - await WaitForDownloadAsync(bobRealm); + using var realm = GetRealm(config); + AddDummyData(realm, singleTransaction); - await bobRealm.RefreshAsync(); + await WaitForUploadAsync(realm); - Assert.That(bobsObjects.Count(), Is.EqualTo(alicesObjects.Count())); - - var bobObject = bobRealm.Find(9999); - Assert.That(bobObject, Is.Not.Null); - Assert.That(bobObject.StringValue, Is.EqualTo("Some value")); - }); + using var asyncRealm = await GetRealmAsync(asyncConfig); + Assert.That(asyncRealm.All().Count(), Is.EqualTo(DummyDataSize / 2)); + }, timeout: 120000); } [Test] public void GetInstanceAsync_CreatesNonExistentRealm() { - SyncTestHelpers.RunRosTestAsync(async () => + SyncTestHelpers.RunBaasTestAsync(async () => { - var user = await SyncTestHelpers.GetUserAsync(); - var realmUri = SyncTestHelpers.RealmUri("~/GetInstanceAsync_CreatesNonExistentRealm"); - var config = new FullSyncConfiguration(realmUri, user, Guid.NewGuid().ToString()); - - try - { - await GetRealmAsync(config); - } - catch (Exception ex) - { - Assert.That(ex, Is.TypeOf().And.InnerException.TypeOf()); - var sessionException = (SessionException)ex.InnerException; - Assert.That(sessionException.ErrorCode, Is.EqualTo((ErrorCode)89)); - Assert.That(sessionException.Message, Contains.Substring("Operation canceled")); - } + var config = await GetIntegrationConfigAsync(); + await GetRealmAsync(config); }); } [Test] public void GetInstanceAsync_ReportsProgress() { - SyncTestHelpers.RunRosTestAsync(async () => + SyncTestHelpers.RunBaasTestAsync(async () => { - var config = await SyncTestHelpers.GetIntegrationConfigAsync("foo"); + var config = await GetIntegrationConfigAsync(); + await PopulateData(config); var callbacksInvoked = 0; var lastProgress = default(SyncProgress); - config = new FullSyncConfiguration(config.ServerUri, config.User, config.DatabasePath + "1") + config = GetSyncConfiguration((string)config.Partition, config.User, config.DatabasePath + "_download"); + config.OnProgress = (progress) => { - OnProgress = (progress) => - { - callbacksInvoked++; - lastProgress = progress; - } + callbacksInvoked++; + lastProgress = progress; }; - using (var realm = await GetRealmAsync(config)) - { - Assert.That(realm.All().Count(), Is.EqualTo(NumberOfObjects)); - Assert.That(callbacksInvoked, Is.GreaterThan(0)); - Assert.That(lastProgress.TransferableBytes, Is.EqualTo(lastProgress.TransferredBytes)); - } - }); + using var realm = await GetRealmAsync(config); + Assert.That(realm.All().Count(), Is.EqualTo(NumberOfObjects)); + Assert.That(callbacksInvoked, Is.GreaterThan(0)); + Assert.That(lastProgress.TransferableBytes, Is.EqualTo(lastProgress.TransferredBytes)); + }, 60000); } [Test] public void GetInstanceAsync_Cancel_ShouldCancelWait() { - SyncTestHelpers.RunRosTestAsync(async () => + SyncTestHelpers.RunBaasTestAsync(async () => { - var config = await SyncTestHelpers.GetIntegrationConfigAsync("foo"); + var config = await GetIntegrationConfigAsync(); await PopulateData(config); // Update config to make sure we're not opening the same Realm file. - config = new FullSyncConfiguration(config.ServerUri, config.User, config.DatabasePath + "1"); + config = GetSyncConfiguration((string)config.Partition, config.User, config.DatabasePath + "1"); - using (var cts = new CancellationTokenSource()) + using var cts = new CancellationTokenSource(); + _ = Task.Run(async () => { - _ = Task.Run(async () => - { - await Task.Delay(1); - cts.Cancel(); - }); + await Task.Delay(10); + cts.Cancel(); + }); - try - { - var realm = await Realm.GetInstanceAsync(config, cts.Token); - CleanupOnTearDown(realm); - Assert.Fail("Expected task to be cancelled."); - } - catch (Exception ex) - { - Assert.That(ex, Is.InstanceOf()); - } + try + { + var realm = await Realm.GetInstanceAsync(config, cts.Token); + CleanupOnTearDown(realm); + Assert.Fail("Expected task to be cancelled."); + } + catch (Exception ex) + { + Assert.That(ex, Is.InstanceOf()); } }); } @@ -239,192 +172,109 @@ public void GetInstanceAsync_Cancel_ShouldCancelWait() [Test] public void GetInstance_WhenDynamic_ReadsSchemaFromDisk() { - TestHelpers.RunAsyncTest(async () => - { - var config = await SyncTestHelpers.GetFakeConfigAsync(); - config.ObjectClasses = new[] { typeof(AllTypesObject) }; + var config = GetFakeConfig(); + config.ObjectClasses = new[] { typeof(IntPrimaryKeyWithValueObject) }; - // Create the realm and add some objects - using (var realm = GetRealm(config)) + // Create the realm and add some objects + using (var realm = GetRealm(config)) + { + realm.Write(() => realm.Add(new IntPrimaryKeyWithValueObject { - realm.Write(() => realm.Add(new AllTypesObject - { - Int32Property = 42, - RequiredStringProperty = "This is required!" - })); - } + Id = 42, + StringValue = "This is a string!" + })); + } - config.IsDynamic = true; + config.IsDynamic = true; - using (var dynamicRealm = GetRealm(config)) - { - Assert.That(dynamicRealm.Schema.Count == 1); + using var dynamicRealm = GetRealm(config); + Assert.That(dynamicRealm.Schema.Count == 1); - var objectSchema = dynamicRealm.Schema.Find(nameof(AllTypesObject)); - Assert.That(objectSchema, Is.Not.Null); + var objectSchema = dynamicRealm.Schema.Find(nameof(IntPrimaryKeyWithValueObject)); + Assert.That(objectSchema, Is.Not.Null); - var hasExpectedProp = objectSchema.TryFindProperty(nameof(AllTypesObject.RequiredStringProperty), out var requiredStringProp); - Assert.That(hasExpectedProp); - Assert.That(requiredStringProp.Type, Is.EqualTo(PropertyType.String)); + Assert.That(objectSchema.TryFindProperty(nameof(IntPrimaryKeyWithValueObject.StringValue), out var stringProp)); + Assert.That(stringProp.Type, Is.EqualTo(PropertyType.String | PropertyType.Nullable)); - var ato = dynamicRealm.All(nameof(AllTypesObject)).Single(); - Assert.That(ato.RequiredStringProperty, Is.EqualTo("This is required!")); - } - }); + var dynamicObj = dynamicRealm.DynamicApi.All(nameof(IntPrimaryKeyWithValueObject)).Single(); + Assert.That(dynamicObj.StringValue, Is.EqualTo("This is a string!")); } [Test] public void GetInstance_WhenDynamicAndDoesntExist_ReturnsEmptySchema() { - TestHelpers.RunAsyncTest(async () => - { - var config = await SyncTestHelpers.GetFakeConfigAsync(); - config.IsDynamic = true; - - using (var realm = GetRealm(config)) - { - Assert.That(realm.Schema, Is.Empty); - } - }); - } - - // Used by TestClientResync and TestClientResync2. Must be either RecoverLocal or DiscardLocal. Manual is tested - // by TestManualClientResync. - private ClientResyncMode _clientResyncMode = ClientResyncMode.DiscardLocalRealm; - - private async Task GetClientResyncConfig(ClientResyncMode? _mode = null) - { - if (!_mode.HasValue) - { - _mode = _clientResyncMode; - } + var config = GetFakeConfig(); + config.ObjectClasses = null; + config.IsDynamic = true; - var user = await User.LoginAsync(Credentials.UsernamePassword("foo", "bar"), SyncTestHelpers.AuthServerUri); - return new FullSyncConfiguration(SyncTestHelpers.RealmUri($"~/{_mode.Value}"), user, $"{_mode}.realm") - { - ClientResyncMode = _mode.Value, - ObjectClasses = new[] { typeof(IntPrimaryKeyWithValueObject) }, - }; + using var realm = GetRealm(config); + Assert.That(realm.Schema, Is.Empty); } - [Test, NUnit.Framework.Explicit("Requires debugger and a lot of manual steps")] - public void TestClientResync() + [Test] + [Ignore("doesn't work due to a OS bug")] + public void InvalidSchemaChange_RaisesClientReset() { - SyncTestHelpers.RunRosTestAsync(async () => + SyncTestHelpers.RunBaasTestAsync(async () => { - var config = await GetClientResyncConfig(); + var config = await GetIntegrationConfigAsync(); - // Let's delete anything local. - Realm.DeleteRealm(config); - Exception ex = null; - Session.Error += (s, e) => + var backupLocation = config.DatabasePath + "_backup"; + using (var realm = await GetRealmAsync(config)) { - if (e.Exception.Message != "End of input") - { - Debugger.Break(); - ex = e.Exception; - } - }; + // Backup the file + File.Copy(config.DatabasePath, backupLocation); - using (var realm = await Realm.GetInstanceAsync(config)) - { - realm.Write(() => - { - realm.Add(new IntPrimaryKeyWithValueObject - { - Id = 1, - StringValue = "1" - }); - }); + realm.Write(() => realm.Add(new HugeSyncObject(1024))); await WaitForUploadAsync(realm); } - // Stop ROS and backup the file. Then restart - Debugger.Break(); - - using (var realm = await GetRealmAsync(config)) + // restore the backup + while (true) { - realm.Write(() => + try { - realm.Add(new IntPrimaryKeyWithValueObject - { - Id = 2, - StringValue = "2" - }); - }); - - await WaitForUploadAsync(realm); - - // Stop ROS - Debugger.Break(); - - realm.Write(() => + File.Copy(backupLocation, config.DatabasePath, overwrite: true); + break; + } + catch { - realm.Add(new IntPrimaryKeyWithValueObject - { - Id = 3, - StringValue = "3" - }); - }); + await Task.Delay(50); + } } - // Replace the file from backup. Restart ROS and run TestClientResync2 - Debugger.Break(); + var errorTcs = new TaskCompletionSource(); + Session.Error += (s, e) => + { + errorTcs.TrySetResult(e.Exception); + }; - Assert.That(ex, Is.Null); - }, (int)TimeSpan.FromMinutes(10).TotalMilliseconds); - } + using var realm2 = GetRealm(config); - [Test, NUnit.Framework.Explicit("Requires debugger and a lot of manual steps")] - public void TestClientResync2() - { - Assert.That(new[] { ClientResyncMode.DiscardLocalRealm, ClientResyncMode.RecoverLocalRealm }, Does.Contain(_clientResyncMode)); + var ex = await errorTcs.Task.Timeout(5000); - SyncTestHelpers.RunRosTestAsync(async () => - { - var config = await GetClientResyncConfig(); + Assert.That(ex, Is.InstanceOf()); + var clientEx = (ClientResetException)ex; - Exception ex = null; - Session.Error += (s, e) => - { - if (e.Exception.Message != "End of input") - { - ex = e.Exception; - } - }; + Assert.That(clientEx.ErrorCode, Is.EqualTo(ErrorCode.InvalidSchemaChange)); - using (var realm = await GetRealmAsync(config)) - { - var values = realm.All().AsEnumerable().Select(i => i.StringValue).ToArray(); + var realmPath = config.DatabasePath; - // Verify expected result: - // - RecoverLocalRealm: we have 2 objects - "1" and "3". The "2" is lost because the client had uploaded them to the server already. - // - DiscardLocalRealm: we have 1 object - "1". The "2" is lost because we restored from backup and the "3" is discarded. - switch (_clientResyncMode) - { - case ClientResyncMode.DiscardLocalRealm: - Assert.That(values.Length, Is.EqualTo(1)); - Assert.That(values[0], Is.EqualTo("1")); - Assert.That(ex, Is.Null); - break; - - case ClientResyncMode.RecoverLocalRealm: - Assert.That(values.Length, Is.EqualTo(2)); - CollectionAssert.AreEquivalent(values, new[] { "1", "3" }); - Assert.That(ex, Is.Null); - break; - } - } - }, (int)TimeSpan.FromMinutes(10).TotalMilliseconds); + Assert.That(File.Exists(realmPath)); + + Assert.That(clientEx.InitiateClientReset(), Is.True); + + Assert.That(File.Exists(realmPath), Is.False); + }); } [Test, NUnit.Framework.Explicit("Requires debugger and a lot of manual steps")] public void TestManualClientResync() { - SyncTestHelpers.RunRosTestAsync(async () => + SyncTestHelpers.RunBaasTestAsync(async () => { - var config = await GetClientResyncConfig(ClientResyncMode.Manual); + var config = await GetIntegrationConfigAsync(); Realm.DeleteRealm(config); using (var realm = await Realm.GetInstanceAsync(config)) @@ -455,6 +305,8 @@ public void TestManualClientResync() }); } + private const int DummyDataSize = 100; + private static void AddDummyData(Realm realm, bool singleTransaction) { Action write; @@ -470,13 +322,12 @@ private static void AddDummyData(Realm realm, bool singleTransaction) write = realm.Write; } - for (var i = 0; i < 1000; i++) + for (var i = 0; i < DummyDataSize; i++) { write(() => { - realm.Add(new IntPrimaryKeyWithValueObject + realm.Add(new ObjectIdPrimaryKeyWithValueObject { - Id = i, StringValue = "Super secret product " + i }); }); @@ -488,11 +339,12 @@ private static void AddDummyData(Realm realm, bool singleTransaction) currentTransaction = realm.BeginWrite(); } - for (var i = 0; i < 500; i++) + var objs = realm.All(); + for (var i = 0; i < DummyDataSize / 2; i++) { write(() => { - var item = realm.Find(2 * i); + var item = objs.ElementAt(i); realm.Remove(item); }); } @@ -503,20 +355,31 @@ private static void AddDummyData(Realm realm, bool singleTransaction) } } - private async Task PopulateData(FullSyncConfiguration config) + private async Task PopulateData(SyncConfiguration config) { - using (var realm = GetRealm(config)) + using var realm = GetRealm(config); + + // Split in 2 because MDB Realm has a limit of 16 MB per changeset + var firstBatch = NumberOfObjects / 2; + var secondBatch = NumberOfObjects - firstBatch; + + realm.Write(() => { - realm.Write(() => + for (var i = 0; i < firstBatch; i++) { - for (var i = 0; i < NumberOfObjects; i++) - { - realm.Add(new HugeSyncObject(OneMegabyte)); - } - }); + realm.Add(new HugeSyncObject(OneMegabyte)); + } + }); - await GetSession(realm).WaitForUploadAsync(); - } + realm.Write(() => + { + for (var i = 0; i < secondBatch; i++) + { + realm.Add(new HugeSyncObject(OneMegabyte)); + } + }); + + await WaitForUploadAsync(realm); } /* Code to generate the legacy Realm diff --git a/Tests/Realm.Tests/Sync/TestObjects.cs b/Tests/Realm.Tests/Sync/TestObjects.cs index 4444b28029..5d625ce3d0 100644 --- a/Tests/Realm.Tests/Sync/TestObjects.cs +++ b/Tests/Realm.Tests/Sync/TestObjects.cs @@ -16,12 +16,16 @@ // //////////////////////////////////////////////////////////////////////////// -using Realms; +using MongoDB.Bson; namespace Realms.Tests.Sync { public class HugeSyncObject : RealmObject { + [PrimaryKey] + [MapTo("_id")] + public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); + public byte[] Data { get; set; } public HugeSyncObject() diff --git a/Tests/Realm.Tests/Sync/UserManagementTests.cs b/Tests/Realm.Tests/Sync/UserManagementTests.cs new file mode 100644 index 0000000000..06cf70941c --- /dev/null +++ b/Tests/Realm.Tests/Sync/UserManagementTests.cs @@ -0,0 +1,932 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using NUnit.Framework; +using Realms.Sync; +using Realms.Sync.Exceptions; + +namespace Realms.Tests.Sync +{ + [TestFixture, Preserve(AllMembers = true)] + public class UserManagementTests : SyncTestBase + { + [Test] + public void AppCurrentUser_WhenThereAreNoUsers_ShouldReturnNull() + { + Assert.That(() => DefaultApp.CurrentUser, Is.Null); + } + + [Test] + public void AppCurrentUser_WhenThereIsOneUser_ShouldReturnThatUser() + { + var user = GetFakeUser(); + var currentUser = DefaultApp.CurrentUser; + + Assert.That(currentUser, Is.EqualTo(user)); + } + + [Test] + public void AppCurrentUser_WhenThereIsMoreThanOneUser_ShouldReturnLastOne() + { + var first = GetFakeUser(); + var second = GetFakeUser(); + + Assert.That(DefaultApp.CurrentUser, Is.EqualTo(second)); + Assert.That(DefaultApp.CurrentUser, Is.Not.EqualTo(first)); + } + + [Test] + public void AppAllUsers_WhenThereAreNoUsers_ShouldReturnEmptyCollection() + { + var users = DefaultApp.AllUsers; + Assert.That(users, Is.Empty); + } + + [Test] + public void AppAllUsers_WhenThereIsOneUser_ShouldReturnThatUser() + { + var user = GetFakeUser(); + + var users = DefaultApp.AllUsers; + + Assert.That(users.Length, Is.EqualTo(1)); + Assert.That(users[0], Is.EqualTo(user)); + } + + [Test] + public void AppAllUsers_WhenThereAreNineUsers_ShouldReturnAllOfThem() + { + var users = new List(); + for (var i = 0; i < 9; i++) + { + users.Add(GetFakeUser()); + } + + var current = DefaultApp.AllUsers; + + Assert.That(current, Is.EquivalentTo(users)); + } + + [Test] + public void AppSwitchUser_SwitchesCurrentUser() + { + var first = GetFakeUser(); + var second = GetFakeUser(); + + Assert.That(DefaultApp.CurrentUser, Is.EqualTo(second)); + + DefaultApp.SwitchUser(first); + + Assert.That(DefaultApp.CurrentUser, Is.EqualTo(first)); + } + + [Test] + public void AppSwitchUser_WhenUserIsCurrent_DoesNothing() + { + var first = GetFakeUser(); + Assert.That(DefaultApp.CurrentUser, Is.EqualTo(first)); + + var second = GetFakeUser(); + + Assert.That(DefaultApp.CurrentUser, Is.EqualTo(second)); + + DefaultApp.SwitchUser(second); + + Assert.That(DefaultApp.CurrentUser, Is.EqualTo(second)); + } + + [Test] + public void AppSwitchUser_WhenUserIsNull_Throws() + { + Assert.That(() => DefaultApp.SwitchUser(null), Throws.InstanceOf()); + } + + [Test] + public void AppRemoveUser_RemovesUser() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var first = await GetUserAsync(); + var second = await GetUserAsync(); + + Assert.That(DefaultApp.CurrentUser, Is.EqualTo(second)); + + var rereshToken = second.RefreshToken; + var secondId = second.Id; + + await DefaultApp.RemoveUserAsync(second); + + // TODO: validate that the refresh token is invalidated. + Assert.That(second.State, Is.EqualTo(UserState.Removed)); + Assert.That(second.AccessToken, Is.Empty); + Assert.That(second.RefreshToken, Is.Empty); + Assert.That(second.Id, Is.EqualTo(secondId)); + + Assert.That(DefaultApp.CurrentUser, Is.EqualTo(first)); + }); + } + + [Test] + public void EmailPasswordRegisterUser_Works() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var username = SyncTestHelpers.GetVerifiedUsername(); + await DefaultApp.EmailPasswordAuth.RegisterUserAsync(username, SyncTestHelpers.DefaultPassword); + + var user = await DefaultApp.LogInAsync(Credentials.EmailPassword(username, SyncTestHelpers.DefaultPassword)); + + Assert.That(user, Is.Not.Null); + Assert.That(user.State, Is.EqualTo(UserState.LoggedIn)); + Assert.That(user.Provider, Is.EqualTo(Credentials.AuthProvider.EmailPassword)); + Assert.That(user.AccessToken, Is.Not.Empty); + Assert.That(user.RefreshToken, Is.Not.Empty); + + Assert.That(DefaultApp.CurrentUser, Is.EqualTo(user)); + }); + } + + [Test] + public void UserCustomData_ReadsFromAccessToken() + { + const string tokenWithCustomData = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWNjZXNzIHRva2VuIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MzYyMzkwMjIsInVzZXJfZGF0YSI6eyJuYW1lIjoiVGltb3RoeSIsImVtYWlsIjoiYmlnX3RpbUBnbWFpbC5jb20iLCJhZGRyZXNzZXMiOlt7ImNpdHkiOiJOWSIsInN0cmVldCI6IjQybmQifSx7ImNpdHkiOiJTRiIsInN0cmVldCI6Ik1haW4gU3QuIn1dLCJmYXZvcml0ZUlkcyI6WzEsMiwzXX19.wYYtavafunx-iEKFNwXC6DR0C3vBDunwhvIox6XgqDE"; + var user = GetFakeUser(accessToken: tokenWithCustomData); + + var customData = user.GetCustomData(); + Assert.That(customData, Is.Not.Null); + Assert.That(customData["name"].AsString, Is.EqualTo("Timothy")); + Assert.That(customData["email"].AsString, Is.EqualTo("big_tim@gmail.com")); + Assert.That(customData["addresses"].AsBsonArray.Count, Is.EqualTo(2)); + Assert.That(customData["addresses"][0]["city"].AsString, Is.EqualTo("NY")); + Assert.That(customData["addresses"][0]["street"].AsString, Is.EqualTo("42nd")); + Assert.That(customData["addresses"][1]["city"].AsString, Is.EqualTo("SF")); + Assert.That(customData["addresses"][1]["street"].AsString, Is.EqualTo("Main St.")); + Assert.That(customData["favoriteIds"].AsBsonArray.Select(i => i.AsInt64), Is.EquivalentTo(new[] { 1, 2, 3 })); + } + + private class AccessTokenCustomData + { + [BsonElement("name")] + public string Name { get; set; } + + [BsonElement("email")] + public string Email { get; set; } + + [BsonElement("addresses")] + public Address[] Addresses { get; set; } + + [BsonElement("favoriteIds")] + public long[] FavoriteIds { get; set; } + + public class Address + { + [BsonElement("city")] + public string City { get; set; } + + [BsonElement("street")] + public string Street { get; set; } + } + } + + [Test] + public void UserCustomData_Generic_ReadsFromAccessToken() + { + const string tokenWithCustomData = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWNjZXNzIHRva2VuIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MzYyMzkwMjIsInVzZXJfZGF0YSI6eyJuYW1lIjoiVGltb3RoeSIsImVtYWlsIjoiYmlnX3RpbUBnbWFpbC5jb20iLCJhZGRyZXNzZXMiOlt7ImNpdHkiOiJOWSIsInN0cmVldCI6IjQybmQifSx7ImNpdHkiOiJTRiIsInN0cmVldCI6Ik1haW4gU3QuIn1dLCJmYXZvcml0ZUlkcyI6WzEsMiwzXX19.wYYtavafunx-iEKFNwXC6DR0C3vBDunwhvIox6XgqDE"; + var user = GetFakeUser(accessToken: tokenWithCustomData); + + var customData = user.GetCustomData(); + Assert.That(customData, Is.Not.Null); + Assert.That(customData.Name, Is.EqualTo("Timothy")); + Assert.That(customData.Email, Is.EqualTo("big_tim@gmail.com")); + Assert.That(customData.Addresses.Length, Is.EqualTo(2)); + Assert.That(customData.Addresses[0].City, Is.EqualTo("NY")); + Assert.That(customData.Addresses[0].Street, Is.EqualTo("42nd")); + Assert.That(customData.Addresses[1].City, Is.EqualTo("SF")); + Assert.That(customData.Addresses[1].Street, Is.EqualTo("Main St.")); + Assert.That(customData.FavoriteIds, Is.EquivalentTo(new[] { 1, 2, 3 })); + } + + [Test] + public void UserCustomData_WhenEmpty_ReturnsNull() + { + var user = GetFakeUser(); + + Assert.That(user.GetCustomData(), Is.Null); + } + + [Test] + public void User_LinkCredentials_AllowsLoginWithNewCredentials() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await DefaultApp.LogInAsync(Credentials.Anonymous()); + + Assert.That(user.Identities, Has.Length.EqualTo(1)); + Assert.That(user.Identities[0].Provider, Is.EqualTo(Credentials.AuthProvider.Anonymous)); + Assert.That(user.Identities[0].Id, Is.Not.Null); + + var email = SyncTestHelpers.GetVerifiedUsername(); + await DefaultApp.EmailPasswordAuth.RegisterUserAsync(email, SyncTestHelpers.DefaultPassword); + var linkedUser = await user.LinkCredentialsAsync(Credentials.EmailPassword(email, SyncTestHelpers.DefaultPassword)); + + Assert.That(user.Identities, Has.Length.EqualTo(2)); + Assert.That(user.Identities[1].Provider, Is.EqualTo(Credentials.AuthProvider.EmailPassword)); + Assert.That(user.Identities[1].Id, Is.Not.Null); + + Assert.That(linkedUser.Identities, Has.Length.EqualTo(2)); + Assert.That(linkedUser.Id, Is.EqualTo(user.Id)); + Assert.That(linkedUser.Identities, Is.EquivalentTo(user.Identities)); + + var emailPasswordUser = await DefaultApp.LogInAsync(Credentials.EmailPassword(email, SyncTestHelpers.DefaultPassword)); + + Assert.That(emailPasswordUser.Id, Is.EqualTo(user.Id)); + Assert.That(emailPasswordUser.Identities, Is.EquivalentTo(user.Identities)); + }); + } + + [Test] + public void User_LinkCredentials_MultipleTimes_AllowsLoginWithAllCredentials() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await DefaultApp.LogInAsync(Credentials.Anonymous()); + + var email = SyncTestHelpers.GetVerifiedUsername(); + await DefaultApp.EmailPasswordAuth.RegisterUserAsync(email, SyncTestHelpers.DefaultPassword); + var linkedUser1 = await user.LinkCredentialsAsync(Credentials.EmailPassword(email, SyncTestHelpers.DefaultPassword)); + Assert.That(linkedUser1.Id, Is.EqualTo(user.Id)); + + var functionId = Guid.NewGuid().ToString(); + var linkedUser2 = await user.LinkCredentialsAsync(Credentials.Function(new { realmCustomAuthFuncUserId = functionId })); + Assert.That(linkedUser2.Id, Is.EqualTo(user.Id)); + + var emailPasswordUser = await DefaultApp.LogInAsync(Credentials.EmailPassword(email, SyncTestHelpers.DefaultPassword)); + Assert.That(emailPasswordUser.Id, Is.EqualTo(user.Id)); + + var functionUser = await DefaultApp.LogInAsync(Credentials.Function(new { realmCustomAuthFuncUserId = functionId })); + Assert.That(functionUser.Id, Is.EqualTo(user.Id)); + + Assert.That(user.Identities, Has.Length.EqualTo(3)); + Assert.That(user.Identities[0].Provider, Is.EqualTo(Credentials.AuthProvider.Anonymous)); + Assert.That(user.Identities[0].Id, Is.Not.Null); + + Assert.That(user.Identities[1].Provider, Is.EqualTo(Credentials.AuthProvider.EmailPassword)); + Assert.That(user.Identities[1].Id, Is.Not.Null); + + Assert.That(user.Identities[2].Provider, Is.EqualTo(Credentials.AuthProvider.Function)); + Assert.That(user.Identities[2].Id, Is.EqualTo(functionId)); + }); + } + + [Test] + public void User_LinkCredentials_MultipleTimesSameCredentials_IsNoOp() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await DefaultApp.LogInAsync(Credentials.Anonymous()); + + var functionId = Guid.NewGuid().ToString(); + var linkedUser = await user.LinkCredentialsAsync(Credentials.Function(new { realmCustomAuthFuncUserId = functionId })); + Assert.That(linkedUser.Id, Is.EqualTo(user.Id)); + + var sameLinkedUser = await user.LinkCredentialsAsync(Credentials.Function(new { realmCustomAuthFuncUserId = functionId })); + Assert.That(sameLinkedUser.Id, Is.EqualTo(user.Id)); + + var functionUser = await DefaultApp.LogInAsync(Credentials.Function(new { realmCustomAuthFuncUserId = functionId })); + Assert.That(functionUser.Id, Is.EqualTo(user.Id)); + + Assert.That(user.Identities, Has.Length.EqualTo(2)); + Assert.That(user.Identities[1].Id, Is.EqualTo(functionId)); + Assert.That(user.Identities[1].Provider, Is.EqualTo(Credentials.AuthProvider.Function)); + }); + } + + [Test] + public void User_LinkCredentials_WhenMultipleEmailPassword_Throws() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var email2 = SyncTestHelpers.GetVerifiedUsername(); + await DefaultApp.EmailPasswordAuth.RegisterUserAsync(email2, SyncTestHelpers.DefaultPassword); + + var ex = await TestHelpers.AssertThrows(() => user.LinkCredentialsAsync(Credentials.EmailPassword(email2, SyncTestHelpers.DefaultPassword))); + + // TODO: this should be bad request when https://jira.mongodb.org/browse/REALMC-7028 is fixed + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.InternalServerError)); + Assert.That(ex.Message, Does.Contain("linking a local-userpass identity is not allowed when one is already linked")); + }); + } + + [Test] + public void User_LinkCredentials_WhenAnonymous_Throws() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var ex = await TestHelpers.AssertThrows(() => user.LinkCredentialsAsync(Credentials.Anonymous())); + + // TODO: this should be bad request when https://jira.mongodb.org/browse/REALMC-7028 is fixed + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.InternalServerError)); + Assert.That(ex.Message, Does.Contain("linking an anonymous identity is not allowed")); + }); + } + + [Test] + public void User_LinkCredentials_WhenInUse_Throws() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var existingEmail = SyncTestHelpers.GetVerifiedUsername(); + await DefaultApp.EmailPasswordAuth.RegisterUserAsync(existingEmail, SyncTestHelpers.DefaultPassword); + var emailUser = await DefaultApp.LogInAsync(Credentials.EmailPassword(existingEmail, SyncTestHelpers.DefaultPassword)); + + var anonUser = await DefaultApp.LogInAsync(Credentials.Anonymous()); + + var ex = await TestHelpers.AssertThrows(() => anonUser.LinkCredentialsAsync(Credentials.EmailPassword(existingEmail, SyncTestHelpers.DefaultPassword))); + + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + Assert.That(ex.Message, Does.Contain("a user already exists with the specified provider")); + }); + } + + [Test] + public void User_Push_RegisterDevice() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + await user.GetPushClient("gcm").RegisterDeviceAsync("hello"); + }); + } + + [Test] + public void User_Push_RegisterDevice_WrongService() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + var ex = await TestHelpers.AssertThrows(() => user.GetPushClient("non-existent").RegisterDeviceAsync("hello")); + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + Assert.That(ex.Message, Does.Contain("service not found: 'non-existent'")); + }); + } + + [Test] + public void User_Push_DeregisterDevice() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + await user.GetPushClient("gcm").DeregisterDeviceAsync(); + }); + } + + [Test] + public void User_JWT_LogsInAndReadsDataFromToken() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + const string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NTY3ODkwIiwic3ViIjoiMTIzNDU2Nzg5MCIsIm5hbWUiOnsiZmlyc3QiOiJKb2huIiwibGFzdCI6IkRvZSJ9LCJqb2JUaXRsZSI6IkJyZWFrZXIgb2YgdGhpbmdzIiwiZW1haWwiOiJqb2huQGRvZS5jb20iLCJwaWN0dXJlVXJsIjoiaHR0cHM6Ly9kb2UuY29tL215cGljdHVyZSIsImdlbmRlciI6Im90aGVyIiwiYmlydGhkYXkiOiIxOTM0LTA1LTE1IiwibWluQWdlIjoiODAiLCJtYXhBZ2UiOiI5MCIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoyMDE2MjM5MDIyLCJhdWQiOiJteS1hdWRpZW5jZSJ9.B6u3SkU-pzCH_LA_HsevAJF1EI1LbAOfL6GP3bhjVpP4FBtrmZYQD_b7Z_wJLE0vaffX1eN6U_vE9t26bmXz2ig4jJRmbg7Kx9ka1BkcE7MF9nmdC90ffHgNBvU40yKpMBtVL9VNQCe-F6mSvUqpox2tQQpNKaXf8yQslAf_tfvqTvF0mPXnqU1v_5KtieMybOb7O8nV6LITrjsAA5ff4spWSgcskjXcyjq6DIdWbLlVJycodr-MjKu94fNXXsBLf0iK5XHYpL1Bs-ILs494_aK_Pf2GD3pYa56XjqN-nO_cYbIxzmsBkNtAp0hvg_Gp0O6QFi66Qkr7ORbkRasGAg"; + var credentials = Credentials.JWT(token); + var user = await DefaultApp.LogInAsync(credentials); + + Assert.That(user.Profile.FirstName, Is.EqualTo("John")); + Assert.That(user.Profile.LastName, Is.EqualTo("Doe")); + Assert.That(user.Profile.Email, Is.EqualTo("john@doe.com")); + Assert.That(user.Profile.Birthday, Is.EqualTo("1934-05-15")); + Assert.That(user.Profile.Gender, Is.EqualTo("other")); + Assert.That(user.Profile.MinAge, Is.EqualTo("80")); + Assert.That(user.Profile.MaxAge, Is.EqualTo("90")); + Assert.That(user.Profile.PictureUrl.AbsoluteUri, Is.EqualTo("https://doe.com/mypicture")); + + // TODO: add other checks once https://github.com/realm/realm-object-store/issues/1123 is implemented. + }); + } + + [Test, NUnit.Framework.Explicit("Requires manually getting a fb token")] + public void User_Facebook_LogsInAndReadsDataFromFacebook() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + const string fbToken = "EAAFYw2aZAL1EBAHBBH22XBDZAutJFQ65KxH0bZAexYul5KtsHcjhI722XYEr4jKlaNvlosFsdZCT8dGUQNy2euZB684mpvtIIJEWWYMoH66bbEbKIrHRWqZBC8KMpSscoyzhFTJMpDYsrIilZBRN1A6bicXGaUNXVz5A0ucyZB7WkmQ8uUmdRWel9q6S8BJH3ZBCZAzWtcZCYmgEwZDZD"; + var credentials = Credentials.Facebook(fbToken); + var user = await DefaultApp.LogInAsync(credentials); + + Assert.That(user.Id, Is.Not.Null); + + Assert.That(user.Profile.FirstName, Is.Not.Null); + Assert.That(user.Profile.LastName, Is.Not.Null); + }); + } + + #region API Keys + + [Test] + public void UserApiKeys_Create_CreatesApiKeyAndRevealsValue() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + var apiKey = await user.ApiKeys.CreateAsync("my-api-key"); + + Assert.That(apiKey.IsEnabled); + Assert.That(apiKey.Name, Is.EqualTo("my-api-key")); + Assert.That(apiKey.Value, Is.Not.Null); + Assert.That(apiKey.Id, Is.Not.EqualTo(default(ObjectId))); + }); + } + + [Test] + public void UserApiKeys_Create_WithInvalidName_Throws() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var ex = await TestHelpers.AssertThrows(() => user.ApiKeys.CreateAsync("My very cool key")); + + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + Assert.That(ex.Message, Does.Contain("InvalidParameter")); + Assert.That(ex.Message, Does.Contain("can only contain ASCII letters, numbers, underscores, and hyphens")); + Assert.That(ex.HelpLink, Does.Contain("logs?co_id=")); + }); + } + + [Test] + public void UserApiKeys_Fetch_WhenNoneExist_ReturnsNull() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var apiKey = await user.ApiKeys.FetchAsync(ObjectId.GenerateNewId()); + + Assert.That(apiKey, Is.Null); + }); + } + + [Test] + public void UserApiKeys_Fetch_WhenIdDoesntMatch_ReturnsNull() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + await user.ApiKeys.CreateAsync("foo"); + + var apiKey = await user.ApiKeys.FetchAsync(ObjectId.GenerateNewId()); + + Assert.That(apiKey, Is.Null); + }); + } + + [Test] + public void UserApiKeys_Fetch_WhenIdMatches_ReturnsKey() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var key = await user.ApiKeys.CreateAsync("foo"); + + var fetched = await user.ApiKeys.FetchAsync(key.Id); + + Assert.That(fetched, Is.Not.Null); + AssertKeysAreSame(key, fetched); + }); + } + + [Test] + public void UserApiKeys_FetchAll_WithNoKeys() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var keys = await user.ApiKeys.FetchAllAsync(); + Assert.That(keys, Is.Empty); + }); + } + + [Test] + public void UserApiKeys_FetchAll_WithOneKey() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var key1 = await user.ApiKeys.CreateAsync("foo"); + + var keys = await user.ApiKeys.FetchAllAsync(); + Assert.That(keys.Count(), Is.EqualTo(1)); + + AssertKeysAreSame(key1, keys.Single()); + }); + } + + [Test] + public void UserApiKeys_FetchAll_WithMultipleKeys() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var originals = new List(); + for (var i = 0; i < 5; i++) + { + originals.Add(await user.ApiKeys.CreateAsync($"key-{i}")); + } + + var keys = await user.ApiKeys.FetchAllAsync(); + Assert.That(keys.Count(), Is.EqualTo(originals.Count)); + + for (var i = 0; i < originals.Count; i++) + { + AssertKeysAreSame(originals[i], keys.ElementAt(i)); + } + }); + } + + [Test] + public void UserApiKeys_DeleteKey_WithExistingId() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var toDelete = await user.ApiKeys.CreateAsync("to-delete"); + var toRemain = await user.ApiKeys.CreateAsync("to-remain"); + + await user.ApiKeys.DeleteAsync(toDelete.Id); + + var fetchedDeleted = await user.ApiKeys.FetchAsync(toDelete.Id); + Assert.That(fetchedDeleted, Is.Null); + + var fetchedRemained = await user.ApiKeys.FetchAsync(toRemain.Id); + AssertKeysAreSame(toRemain, fetchedRemained); + + var allKeys = await user.ApiKeys.FetchAllAsync(); + + Assert.That(allKeys.Count(), Is.EqualTo(1)); + AssertKeysAreSame(toRemain, allKeys.Single()); + }); + } + + [Test] + public void UserApiKeys_DeleteKey_WithNonExistingId() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var first = await user.ApiKeys.CreateAsync("first"); + var second = await user.ApiKeys.CreateAsync("second"); + + await user.ApiKeys.DeleteAsync(ObjectId.GenerateNewId()); + + var allKeys = await user.ApiKeys.FetchAllAsync(); + + Assert.That(allKeys.Count(), Is.EqualTo(2)); + AssertKeysAreSame(first, allKeys.ElementAt(0)); + AssertKeysAreSame(second, allKeys.ElementAt(1)); + }); + } + + [Test] + public void UserApiKeys_DisableApiKey_WhenNonExistent_Throws() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var id = ObjectId.GenerateNewId(); + var ex = await TestHelpers.AssertThrows(() => user.ApiKeys.DisableAsync(id)); + + Assert.That(ex.Message, Does.Contain("doesn't exist")); + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + Assert.That(ex.Message, Does.Contain(id.ToString())); + Assert.That(ex.HelpLink, Does.Contain("logs?co_id=")); + }); + } + + [Test] + public void UserApiKeys_EnableApiKey_WhenNonExistent_Throws() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var id = ObjectId.GenerateNewId(); + var ex = await TestHelpers.AssertThrows(() => user.ApiKeys.EnableAsync(id)); + + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + Assert.That(ex.Message, Does.Contain("doesn't exist")); + Assert.That(ex.Message, Does.Contain(id.ToString())); + Assert.That(ex.HelpLink, Does.Contain("logs?co_id=")); + }); + } + + [Test] + public void UserApiKeys_EnableApiKey_WhenEnabled_IsNoOp() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var key = await user.ApiKeys.CreateAsync("foo"); + Assert.That(key.IsEnabled); + + await user.ApiKeys.EnableAsync(key.Id); + + var fetched = await user.ApiKeys.FetchAsync(key.Id); + Assert.That(fetched.IsEnabled); + }); + } + + [Test] + public void UserApiKeys_DisableApiKey_WhenDisabled_IsNoOp() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var key = await user.ApiKeys.CreateAsync("foo"); + Assert.That(key.IsEnabled); + + await user.ApiKeys.DisableAsync(key.Id); + + var fetched = await user.ApiKeys.FetchAsync(key.Id); + Assert.IsFalse(fetched.IsEnabled); + + await user.ApiKeys.DisableAsync(key.Id); + + var refetched = await user.ApiKeys.FetchAsync(key.Id); + Assert.IsFalse(refetched.IsEnabled); + }); + } + + [Test] + public void UserApiKeys_Disable_DisablesKey() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var first = await user.ApiKeys.CreateAsync("first"); + var second = await user.ApiKeys.CreateAsync("second"); + + Assert.That(first.IsEnabled); + Assert.That(second.IsEnabled); + + await user.ApiKeys.DisableAsync(first.Id); + + var keys = await user.ApiKeys.FetchAllAsync(); + + Assert.That(keys.ElementAt(0).Id, Is.EqualTo(first.Id)); + Assert.IsFalse(keys.ElementAt(0).IsEnabled); + + Assert.That(keys.ElementAt(1).Id, Is.EqualTo(second.Id)); + Assert.IsTrue(keys.ElementAt(1).IsEnabled); + }); + } + + [Test] + public void UserApiKeys_Enable_ReenablesKey() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + + var first = await user.ApiKeys.CreateAsync("first"); + var second = await user.ApiKeys.CreateAsync("second"); + + Assert.That(first.IsEnabled); + Assert.That(second.IsEnabled); + + await user.ApiKeys.DisableAsync(first.Id); + + var keys = await user.ApiKeys.FetchAllAsync(); + + Assert.That(keys.ElementAt(0).Id, Is.EqualTo(first.Id)); + Assert.IsFalse(keys.ElementAt(0).IsEnabled); + + Assert.That(keys.ElementAt(1).Id, Is.EqualTo(second.Id)); + Assert.IsTrue(keys.ElementAt(1).IsEnabled); + + await user.ApiKeys.EnableAsync(first.Id); + + keys = await user.ApiKeys.FetchAllAsync(); + + Assert.That(keys.ElementAt(0).Id, Is.EqualTo(first.Id)); + Assert.IsTrue(keys.ElementAt(0).IsEnabled); + + Assert.That(keys.ElementAt(1).Id, Is.EqualTo(second.Id)); + Assert.IsTrue(keys.ElementAt(1).IsEnabled); + }); + } + + [Test] + public void UserApiKeys_CanLoginWithGeneratedKey() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + var apiKey = await user.ApiKeys.CreateAsync("my-api-key"); + + var credentials = Credentials.ApiKey(apiKey.Value); + var apiKeyUser = await DefaultApp.LogInAsync(credentials); + + Assert.That(apiKeyUser.Id, Is.EqualTo(user.Id)); + + Assert.That(apiKeyUser.Provider, Is.EqualTo(Credentials.AuthProvider.ApiKey)); + Assert.That(apiKeyUser.RefreshToken, Is.Not.EqualTo(user.RefreshToken)); + }); + } + + [Test] + public void UserApiKeys_CanLoginWithReenabledKey() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + var apiKey = await user.ApiKeys.CreateAsync("my-api-key"); + + await user.ApiKeys.DisableAsync(apiKey.Id); + + var credentials = Credentials.ApiKey(apiKey.Value); + + var ex = await TestHelpers.AssertThrows(() => DefaultApp.LogInAsync(credentials)); + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + Assert.That(ex.HelpLink, Does.Contain("logs?co_id=")); + Assert.That(ex.Message, Is.EqualTo("AuthError: invalid API key")); + + await user.ApiKeys.EnableAsync(apiKey.Id); + + var apiKeyUser = await DefaultApp.LogInAsync(credentials); + + Assert.That(apiKeyUser.Id, Is.EqualTo(user.Id)); + + Assert.That(apiKeyUser.Provider, Is.EqualTo(Credentials.AuthProvider.ApiKey)); + Assert.That(apiKeyUser.RefreshToken, Is.Not.EqualTo(user.RefreshToken)); + }); + } + + [Test] + public void UserApiKeys_CantLoginWithDisabledKey() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + var apiKey = await user.ApiKeys.CreateAsync("my-api-key"); + + await user.ApiKeys.DisableAsync(apiKey.Id); + + var credentials = Credentials.ApiKey(apiKey.Value); + + var ex = await TestHelpers.AssertThrows(() => DefaultApp.LogInAsync(credentials)); + + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + Assert.That(ex.Message, Is.EqualTo("AuthError: invalid API key")); + Assert.That(ex.HelpLink, Does.Contain("logs?co_id=")); + }); + } + + [Test] + public void UserApiKeys_CantLoginWithDeletedKey() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + var apiKey = await user.ApiKeys.CreateAsync("my-api-key"); + + await user.ApiKeys.DeleteAsync(apiKey.Id); + + var credentials = Credentials.ApiKey(apiKey.Value); + + var ex = await TestHelpers.AssertThrows(() => DefaultApp.LogInAsync(credentials)); + + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + Assert.That(ex.Message, Is.EqualTo("AuthError: invalid API key")); + Assert.That(ex.HelpLink, Does.Contain("logs?co_id=")); + }); + } + + private static void AssertKeysAreSame(ApiKey original, ApiKey fetched) + { + Assert.That(fetched.Id, Is.EqualTo(original.Id)); + Assert.That(fetched.IsEnabled, Is.EqualTo(original.IsEnabled)); + Assert.That(fetched.Name, Is.EqualTo(original.Name)); + Assert.That(fetched.Value, Is.Null); + } + + #endregion + + [Test] + public void UserCustomData() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + Assert.That(user.GetCustomData(), Is.Null); + + var updatedData = await user.RefreshCustomDataAsync(); + Assert.That(updatedData, Is.Null); + + var collection = user.GetMongoClient("BackingDB").GetDatabase("my-db").GetCollection("users"); + + var customDataDoc = BsonDocument.Parse(@"{ + _id: ObjectId(""" + ObjectId.GenerateNewId() + @"""), + user_id: """ + user.Id + @""", + age: 153, + interests: [ ""painting"", ""sci-fi"" ] + }"); + + await collection.InsertOneAsync(customDataDoc); + + updatedData = await user.RefreshCustomDataAsync(); + + Assert.That(updatedData, Is.Not.Null); + Assert.That(updatedData["age"].AsInt32, Is.EqualTo(153)); + Assert.That(updatedData["interests"].AsBsonArray.Select(i => i.AsString), Is.EquivalentTo(new[] { "painting", "sci-fi" })); + + Assert.That(user.GetCustomData(), Is.Not.Null); + Assert.That(user.GetCustomData()["age"].AsInt32, Is.EqualTo(153)); + }); + } + + [Test] + public void UserCustomData_Generic() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var user = await GetUserAsync(); + Assert.That(user.GetCustomData(), Is.Null); + + var updatedData = await user.RefreshCustomDataAsync(); + Assert.That(updatedData, Is.Null); + + var collection = user.GetMongoClient("BackingDB").GetDatabase("my-db").GetCollection("users"); + + var customDataDoc = new CustomDataDocument + { + UserId = user.Id, + Age = 45, + Interests = new[] { "swimming", "biking" } + }; + + await collection.InsertOneAsync(customDataDoc); + + updatedData = await user.RefreshCustomDataAsync(); + + Assert.That(updatedData, Is.Not.Null); + Assert.That(updatedData.Age, Is.EqualTo(45)); + Assert.That(updatedData.Interests, Is.EquivalentTo(new[] { "swimming", "biking" })); + + var customData = user.GetCustomData(); + + Assert.That(customData, Is.Not.Null); + Assert.That(customData.Age, Is.EqualTo(45)); + Assert.That(customData.Interests, Is.EquivalentTo(new[] { "swimming", "biking" })); + }); + } + + private class CustomDataDocument + { + [BsonElement("_id")] + public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); + + [BsonElement("user_id")] + public string UserId { get; set; } + + [BsonElement("age")] + public int Age { get; set; } + + [BsonElement("interests")] + public string[] Interests { get; set; } + } + } +} diff --git a/Tests/Realm.Tests/TestHelpers.cs b/Tests/Realm.Tests/TestHelpers.cs index c564f50a26..7f017157de 100644 --- a/Tests/Realm.Tests/TestHelpers.cs +++ b/Tests/Realm.Tests/TestHelpers.cs @@ -17,6 +17,7 @@ //////////////////////////////////////////////////////////////////////////// using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -200,20 +201,24 @@ public static void RunAsyncTest(Func testFunc, int timeout = 30000) AsyncContext.Run(() => testFunc().Timeout(timeout)); } - public static async Task AssertThrows(Func function, Action exceptionAsserts = null) + public static async Task AssertThrows(Func function) where T : Exception { try { await function().Timeout(5000); - Assert.Fail($"Exception of type {typeof(T)} expected."); } catch (T ex) { - exceptionAsserts?.Invoke(ex); + return ex; } + + Assert.Fail($"Exception of type {typeof(T)} expected."); + return null; } + [SuppressMessage("Security", "CA3075:Insecure DTD processing in XML", Justification = "The xml is static and trusted.")] + [SuppressMessage("Security", "CA5372:Use XmlReader For XPathDocument", Justification = "The xml is static and trusted.")] public static void TransformTestResults(string resultPath) { CopyBundledFileToDocuments("nunit3-junit.xslt", "nunit3-junit.xslt"); @@ -222,10 +227,8 @@ public static void TransformTestResults(string resultPath) var xpathDocument = new XPathDocument(resultPath); var transform = new XslCompiledTransform(); transform.Load(transformFile); - using (var writer = new XmlTextWriter(resultPath, null)) - { - transform.Transform(xpathDocument, null, writer); - } + using var writer = new XmlTextWriter(resultPath, null); + transform.Transform(xpathDocument, null, writer); } } } diff --git a/Tests/TestApps/dotnet-integration-tests/.gitignore b/Tests/TestApps/dotnet-integration-tests/.gitignore new file mode 100644 index 0000000000..6885540ac8 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/.gitignore @@ -0,0 +1 @@ +app_id diff --git a/Tests/TestApps/dotnet-integration-tests/auth_providers/anon-user.json b/Tests/TestApps/dotnet-integration-tests/auth_providers/anon-user.json new file mode 100755 index 0000000000..8aba66deb6 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/auth_providers/anon-user.json @@ -0,0 +1,6 @@ +{ + "id": "5f6d082a5baa3f73d8933b56", + "name": "anon-user", + "type": "anon-user", + "disabled": false +} diff --git a/Tests/TestApps/dotnet-integration-tests/auth_providers/api-key.json b/Tests/TestApps/dotnet-integration-tests/auth_providers/api-key.json new file mode 100755 index 0000000000..d35f4590bb --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/auth_providers/api-key.json @@ -0,0 +1,6 @@ +{ + "id": "5f6d082a5baa3f73d8933b57", + "name": "api-key", + "type": "api-key", + "disabled": false +} diff --git a/Tests/TestApps/dotnet-integration-tests/auth_providers/custom-function.json b/Tests/TestApps/dotnet-integration-tests/auth_providers/custom-function.json new file mode 100644 index 0000000000..5010a50626 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/auth_providers/custom-function.json @@ -0,0 +1,9 @@ +{ + "id": "5f6d082a5baa3f73d8933b58", + "name": "custom-function", + "type": "custom-function", + "config": { + "authFunctionName": "authFunc" + }, + "disabled": false +} diff --git a/Tests/TestApps/dotnet-integration-tests/auth_providers/custom-token.json b/Tests/TestApps/dotnet-integration-tests/auth_providers/custom-token.json new file mode 100644 index 0000000000..86cd1df6af --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/auth_providers/custom-token.json @@ -0,0 +1,68 @@ +{ + "id": "5f841cbd5add8179e6fc6620", + "name": "custom-token", + "type": "custom-token", + "config": { + "audience": "my-audience", + "signingAlgorithm": "RS256", + "useJWKURI": false + }, + "secret_config": { + "signingKeys": [ + "jwt-public-key" + ] + }, + "disabled": false, + "metadata_fields": [ + { + "required": true, + "name": "userId", + "field_name": "externalUserId" + }, + { + "required": false, + "name": "name.first", + "field_name": "first_name" + }, + { + "required": false, + "name": "name.last", + "field_name": "last_name" + }, + { + "required": false, + "name": "jobTitle", + "field_name": "title" + }, + { + "required": false, + "name": "email", + "field_name": "email" + }, + { + "required": false, + "name": "pictureUrl", + "field_name": "picture_url" + }, + { + "required": false, + "name": "gender", + "field_name": "gender" + }, + { + "required": false, + "name": "birthday", + "field_name": "birthday" + }, + { + "required": false, + "name": "minAge", + "field_name": "min_age" + }, + { + "required": false, + "name": "maxAge", + "field_name": "max_age" + } + ] +} diff --git a/Tests/TestApps/dotnet-integration-tests/auth_providers/local-userpass.json b/Tests/TestApps/dotnet-integration-tests/auth_providers/local-userpass.json new file mode 100755 index 0000000000..71fa0c523e --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/auth_providers/local-userpass.json @@ -0,0 +1,17 @@ +{ + "id": "5f6d082a5baa3f73d8933b59", + "name": "local-userpass", + "type": "local-userpass", + "config": { + "autoConfirm": false, + "confirmEmailSubject": "", + "confirmationFunctionName": "confirmFunc", + "emailConfirmationUrl": "http://localhost/confirmEmail", + "resetFunctionName": "resetFunc", + "resetPasswordSubject": "", + "resetPasswordUrl": "http://localhost/resetPassword", + "runConfirmationFunction": true, + "runResetFunction": true + }, + "disabled": false +} diff --git a/Tests/TestApps/dotnet-integration-tests/functions/authFunc/config.json b/Tests/TestApps/dotnet-integration-tests/functions/authFunc/config.json new file mode 100644 index 0000000000..194819d31e --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/functions/authFunc/config.json @@ -0,0 +1,6 @@ +{ + "can_evaluate": {}, + "id": "5f6d082a5baa3f73d8933b52", + "name": "authFunc", + "private": false +} diff --git a/Tests/TestApps/dotnet-integration-tests/functions/authFunc/source.js b/Tests/TestApps/dotnet-integration-tests/functions/authFunc/source.js new file mode 100644 index 0000000000..590f3177eb --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/functions/authFunc/source.js @@ -0,0 +1,17 @@ + + /* + + This function will be run when a user logs in with this provider. + + The return object must contain a string id, this string id will be used to login with an existing + or create a new user. This is NOT the Stitch user id, but it is the id used to identify which user has + been created or logged in with. + + If an error is thrown within the function the login will fail. + + The default function provided below will always result in failure. + */ + + exports = (loginPayload) => { + return loginPayload["realmCustomAuthFuncUserId"]; + }; diff --git a/Tests/TestApps/dotnet-integration-tests/functions/confirmFunc/config.json b/Tests/TestApps/dotnet-integration-tests/functions/confirmFunc/config.json new file mode 100644 index 0000000000..e6cd0b88de --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/functions/confirmFunc/config.json @@ -0,0 +1,6 @@ +{ + "can_evaluate": {}, + "id": "5f6d082a5baa3f73d8933b53", + "name": "confirmFunc", + "private": false +} diff --git a/Tests/TestApps/dotnet-integration-tests/functions/confirmFunc/source.js b/Tests/TestApps/dotnet-integration-tests/functions/confirmFunc/source.js new file mode 100644 index 0000000000..6a3d024046 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/functions/confirmFunc/source.js @@ -0,0 +1,49 @@ + + /* + + This function will be run AFTER a user registers their username and password and is called with an object parameter + which contains three keys: 'token', 'tokenId', and 'username'. + + The return object must contain a 'status' key which can be empty or one of three string values: + 'success', 'pending', or 'fail'. + + 'success': the user is confirmed and is able to log in. + + 'pending': the user is not confirmed and the UserPasswordAuthProviderClient 'confirmUser' function would + need to be called with the token and tokenId via an SDK. (see below) + + const emailPassClient = Stitch.defaultAppClient.auth + .getProviderClient(UserPasswordAuthProviderClient.factory); + + return emailPassClient.confirmUser(token, tokenId); + + 'fail': the user is not confirmed and will not be able to log in. + + If an error is thrown within the function the result is the same as 'fail'. + + Example below: + + exports = ({ token, tokenId, username }) => { + // process the confirm token, tokenId and username + if (context.functions.execute('isValidUser', username)) { + // will confirm the user + return { status: 'success' }; + } else { + context.functions.execute('sendConfirmationEmail', username, token, tokenId); + return { status: 'pending' }; + } + + return { status: 'fail' }; + }; + + The uncommented function below is just a placeholder and will result in failure. + */ + + exports = ({ token, tokenId, username }) => { + // process the confirm token, tokenId and username + if (username.includes("realm_tests_do_autoverify")) { + return { status: 'success' } + } + // do not confirm the user + return { status: 'fail' }; + }; diff --git a/Tests/TestApps/dotnet-integration-tests/functions/documentFunc/config.json b/Tests/TestApps/dotnet-integration-tests/functions/documentFunc/config.json new file mode 100644 index 0000000000..f0f2e49e77 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/functions/documentFunc/config.json @@ -0,0 +1,5 @@ +{ + "id": "5f76279987b35713b6756dc2", + "name": "documentFunc", + "private": false +} diff --git a/Tests/TestApps/dotnet-integration-tests/functions/documentFunc/source.js b/Tests/TestApps/dotnet-integration-tests/functions/documentFunc/source.js new file mode 100644 index 0000000000..cd1bffb60a --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/functions/documentFunc/source.js @@ -0,0 +1,13 @@ +exports = function(first, second){ + return { + intValue: parseInt(first.intValue + second.intValue), + floatValue: parseFloat(first.floatValue + second.floatValue), + stringValue: first.stringValue + second.stringValue, + objectId: first.objectId, + date: second.date, + child: { + intValue: parseInt(first.child.intValue + second.child.intValue) + }, + arr: [ first.arr[0], second.arr[0] ] + } +}; \ No newline at end of file diff --git a/Tests/TestApps/dotnet-integration-tests/functions/mirror/config.json b/Tests/TestApps/dotnet-integration-tests/functions/mirror/config.json new file mode 100644 index 0000000000..d0ff3586d1 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/functions/mirror/config.json @@ -0,0 +1,5 @@ +{ + "id": "5f7656e14820b47464514080", + "name": "mirror", + "private": false +} diff --git a/Tests/TestApps/dotnet-integration-tests/functions/mirror/source.js b/Tests/TestApps/dotnet-integration-tests/functions/mirror/source.js new file mode 100644 index 0000000000..10107978cd --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/functions/mirror/source.js @@ -0,0 +1,16 @@ +exports = function(arg){ + /* + Accessing application's values: + var x = context.values.get("value_name"); + + Accessing a mongodb service: + var collection = context.services.get("mongodb-atlas").db("dbname").collection("coll_name"); + var doc = collection.findOne({owner_id: context.user.id}); + + To call other named functions: + var result = context.functions.execute("function_name", arg1, arg2); + + Try running in the console below. + */ + return arg; +}; \ No newline at end of file diff --git a/Tests/TestApps/dotnet-integration-tests/functions/resetFunc/config.json b/Tests/TestApps/dotnet-integration-tests/functions/resetFunc/config.json new file mode 100644 index 0000000000..9f8551aef1 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/functions/resetFunc/config.json @@ -0,0 +1,6 @@ +{ + "can_evaluate": {}, + "id": "5f6d082a5baa3f73d8933b54", + "name": "resetFunc", + "private": false +} diff --git a/Tests/TestApps/dotnet-integration-tests/functions/resetFunc/source.js b/Tests/TestApps/dotnet-integration-tests/functions/resetFunc/source.js new file mode 100644 index 0000000000..aeaf51d1c9 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/functions/resetFunc/source.js @@ -0,0 +1,51 @@ + + /* + This function will be run when the client SDK 'callResetPasswordFunction' and is called with an object parameter + which contains four keys: 'token', 'tokenId', 'username', and 'password', and additional parameters + for each parameter passed in as part of the argument list from the SDK. + + The return object must contain a 'status' key which can be empty or one of three string values: + 'success', 'pending', or 'fail' + + 'success': the user's password is set to the passed in 'password' parameter. + + 'pending': the user's password is not reset and the UserPasswordAuthProviderClient 'resetPassword' function would + need to be called with the token, tokenId, and new password via an SDK. (see below) + + const emailPassClient = Stitch.defaultAppClient.auth + .getProviderClient(UserPasswordAuthProviderClient.factory); + + emailPassClient.resetPassword(token, tokenId, newPassword) + + 'fail': the user's password is not reset and will not be able to log in with that password. + + If an error is thrown within the function the result is the same as 'fail'. + + Example below: + + exports = ({ token, tokenId, username, password }, sendEmail, securityQuestionAnswer) => { + // process the reset token, tokenId, username and password + if (sendEmail) { + context.functions.execute('sendResetPasswordEmail', username, token, tokenId); + // will wait for SDK resetPassword to be called with the token and tokenId + return { status: 'pending' }; + } else if (context.functions.execute('validateSecurityQuestionAnswer', username, securityQuestionAnswer)) { + // will set the users password to the password parameter + return { status: 'success' }; + } + + // will not reset the password + return { status: 'fail' }; + }; + + The uncommented function below is just a placeholder and will result in failure. + */ + + exports = ({ token, tokenId, username, password }) => { + // process the reset token, tokenId, username and password + if (password.includes("realm_tests_do_reset")) { + return { status: 'success' }; + } + // will not reset the password + return { status: 'fail' }; + }; diff --git a/Tests/TestApps/dotnet-integration-tests/functions/sumFunc/config.json b/Tests/TestApps/dotnet-integration-tests/functions/sumFunc/config.json new file mode 100644 index 0000000000..0f63dad23c --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/functions/sumFunc/config.json @@ -0,0 +1,6 @@ +{ + "can_evaluate": {}, + "id": "5f6d082a5baa3f73d8933b55", + "name": "sumFunc", + "private": false +} diff --git a/Tests/TestApps/dotnet-integration-tests/functions/sumFunc/source.js b/Tests/TestApps/dotnet-integration-tests/functions/sumFunc/source.js new file mode 100644 index 0000000000..7e76cbbaba --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/functions/sumFunc/source.js @@ -0,0 +1,3 @@ +exports = function(...args) { + return parseInt(args.reduce((a,b) => a + b, 0)); +}; diff --git a/Tests/TestApps/dotnet-integration-tests/graphql/config.json b/Tests/TestApps/dotnet-integration-tests/graphql/config.json new file mode 100644 index 0000000000..c1d7285a0a --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/graphql/config.json @@ -0,0 +1,3 @@ +{ + "use_natural_pluralization": true +} diff --git a/Tests/TestApps/dotnet-integration-tests/jwt-pk.pem b/Tests/TestApps/dotnet-integration-tests/jwt-pk.pem new file mode 100644 index 0000000000..4d90a3953e --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/jwt-pk.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCe2NwZMSybWvtS +oRiCi+JRfdJXr7boTFEcU4Z9RVcb6dR6Ork/H+9geXl/TD4wFnk/PwiXJO4+h3bO +byLJH8imqt46Ok0f5F2YxC4miE13Nc9QcIeRxlYhTzhykjZqn79b/Qa5sfGDoi0e +WWwO6f/1OBzjuwkZEvAasfskAT8ixQMt0D5rKXXwgETlqrhKp1duotDDrCL7FWPg +8X4hOmndkeDeUlQ3ZAh7Z7B1S56wYOTFMzt3isA70y6JVCjN1WI15buRgfwkb5bJ +dGtlfS+Kx5AfI4uICEmuzKTgn8w3Su2bavMdgUMVoexA8iS/ufwbuHJIltB9mk8b +YmwxDYvLAgMBAAECggEAV7smNLO4E0W4sPZQdymTIbcASXW+66Xa1m47RUzaw4vt +0mREk9yrdPkiZXBSmHSRGqFRuUioXNisgW9YIwTu6T5N5pL0rbyPBSS06Is3XbPW +gHLmLVkBkPwUDRM8Ar+gDMo+wKjK3wzfJnf0+ne66lDHg+91IugWvGe4cTZPDIAs +dPY32uyzBgWiZADa1mh7hseuItzVXI0xOu3DIUKf5ng+Fv9Ym6eBEo27GY4MfOkP +vE4FDO9qwcDv8idkcWZ0wrPCLw3ZC5yodzyxXsuLsCTs0ZtfHgdxLte8qdI/BP9O +N3TLtzLv+X5RQllO1lNnt8Rs1TNpDtX3iiZFcuTeAQKBgQDJtRXTLGz1XsYGaIRr +YMte30xl3Drsx970a3k5HscT36YtUhCYvl3zgIkCTOtqaBcQcI4GD+VSDGCS3c1W +0uHmwPmNE1G0XzPICMBhOodBmcaPfsUi+lt9v5cMDqqp6HUG2X3Yc6XNfmw2Yysz +LI3iOVh8EQn4a2Nb1/f9Twb2wQKBgQDJmm50EEeQa8HSsNdvWSFaiOvXUsFM4RFH +B++5tucXk1JHSSuG8ct1hUZo2esYgjAb3hFaccVYHEwXwhtpVBLejMSKXD1oChvW +wno1tdwyJt1SSHDZQKZ+UVBkN5zcqYvmM1I4T9i/0llO/2VyTIPmbgsr0Nqu68Zd +qaw9T1LRiwKBgEV6OJBKpyVFhdRDQAl0h9if9cLIPwu8NNHgCxtfvJbd3HGT6N+U +AuyTA55lPFmkjP0+p8zywLyoQsOT3tzfOqyvH8Y2N1h3ynBGg216KYixHTHkKqdH +sSf5TF/81VA9H8JCPBTIkP3dZAy/z+uKlzyrIA1sGREWfFzwYR1BkVaBAoGAFA23 +hPYFmFkPq5yJoXrVNYM/KmkB71rH9C/W6btG23J9Ebg2izLMEUGTkmRy3To9/Nen +FD8S6UPPUeGuBlklk1ufRUANn6a5xDAOJf+UCq0RANlR2Fp09UWzYHSjttNqphhe +ylku703GMnR3K9pumGJD3DTnO9UaI0D4cBTO4ScCgYEApe/ItKnWbLAGmpuaHyGc +x3dcClGM2gbA9iB1JdCehlzOR/zp9sNHnXLiuG4ZxT//+EwUQ8Tut3Gnq7MX6JE+ +inStraQ+IC6cJaJMajWrVaqepkyr/+uKWtHkEOcs/DZDcMfqidNN1SbBfu/zeice +3CJN2DGHcztlntQVbdsRRWk= +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/Tests/TestApps/dotnet-integration-tests/secrets.json b/Tests/TestApps/dotnet-integration-tests/secrets.json new file mode 100644 index 0000000000..3bc0082583 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/secrets.json @@ -0,0 +1,5 @@ +{ + "BackingDB_uri": "mongodb://localhost:26000", + "gcm": "gcm", + "jwt-public-key":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntjcGTEsm1r7UqEYgovi\nUX3SV6+26ExRHFOGfUVXG+nUejq5Px/vYHl5f0w+MBZ5Pz8IlyTuPod2zm8iyR/I\npqreOjpNH+RdmMQuJohNdzXPUHCHkcZWIU84cpI2ap+/W/0GubHxg6ItHllsDun/\n9Tgc47sJGRLwGrH7JAE/IsUDLdA+ayl18IBE5aq4SqdXbqLQw6wi+xVj4PF+ITpp\n3ZHg3lJUN2QIe2ewdUuesGDkxTM7d4rAO9MuiVQozdViNeW7kYH8JG+WyXRrZX0v\niseQHyOLiAhJrsyk4J/MN0rtm2rzHYFDFaHsQPIkv7n8G7hySJbQfZpPG2JsMQ2L\nywIDAQAB\n-----END PUBLIC KEY-----" +} diff --git a/Tests/TestApps/dotnet-integration-tests/services/BackingDB/config.json b/Tests/TestApps/dotnet-integration-tests/services/BackingDB/config.json new file mode 100644 index 0000000000..b6847a6d37 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/services/BackingDB/config.json @@ -0,0 +1,23 @@ +{ + "id": "5f6d082a5baa3f73d8933b50", + "name": "BackingDB", + "type": "mongodb", + "config": { + "sync": { + "state": "enabled", + "database_name": "test_data", + "partition": { + "key": "realm_id", + "type": "string", + "permissions": { + "read": true, + "write": true + } + } + } + }, + "secret_config": { + "uri": "BackingDB_uri" + }, + "version": 1 +} diff --git a/Tests/TestApps/dotnet-integration-tests/services/BackingDB/rules/my-db.foos.json b/Tests/TestApps/dotnet-integration-tests/services/BackingDB/rules/my-db.foos.json new file mode 100644 index 0000000000..8959819d09 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/services/BackingDB/rules/my-db.foos.json @@ -0,0 +1,36 @@ +{ + "collection": "foos", + "database": "my-db", + "id": "5f7da8783e6680322d48fab9", + "roles": [ + { + "name": "default", + "apply_when": {}, + "insert": true, + "delete": true, + "search": true, + "additional_fields": {} + } + ], + "schema": { + "bsonType": "object", + "properties": { + "_id": { + "bsonType": "objectId" + }, + "longValue": { + "bsonType": "long" + }, + "realm_id": { + "bsonType": "string" + }, + "stringValue": { + "bsonType": "string" + } + }, + "required": [ + "_id" + ], + "title": "Foo" + } +} diff --git a/Tests/TestApps/dotnet-integration-tests/services/BackingDB/rules/my-db.sales.json b/Tests/TestApps/dotnet-integration-tests/services/BackingDB/rules/my-db.sales.json new file mode 100644 index 0000000000..6e18b50066 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/services/BackingDB/rules/my-db.sales.json @@ -0,0 +1,42 @@ +{ + "collection": "sales", + "database": "my-db", + "id": "5f7ee6dc61f39e935419256c", + "roles": [ + { + "name": "default", + "apply_when": {}, + "insert": true, + "delete": true, + "search": true, + "additional_fields": {} + } + ], + "schema": { + "bsonType": "object", + "properties": { + "_id": { + "bsonType": "int" + }, + "date": { + "bsonType": "date" + }, + "item": { + "bsonType": "string" + }, + "price": { + "bsonType": "decimal" + }, + "quantity": { + "bsonType": "decimal" + }, + "realm_id": { + "bsonType": "string" + } + }, + "required": [ + "_id" + ], + "title": "Sale" + } +} diff --git a/Tests/TestApps/dotnet-integration-tests/services/BackingDB/rules/my-db.users.json b/Tests/TestApps/dotnet-integration-tests/services/BackingDB/rules/my-db.users.json new file mode 100644 index 0000000000..566a43fe24 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/services/BackingDB/rules/my-db.users.json @@ -0,0 +1,33 @@ +{ + "collection": "users", + "database": "my-db", + "id": "5f857ef7121096b6d11a70fb", + "roles": [ + { + "name": "default", + "apply_when": {}, + "insert": true, + "delete": true, + "search": true, + "additional_fields": {} + } + ], + "schema": { + "bsonType": "object", + "properties": { + "_id": { + "bsonType": "objectId" + }, + "realm_id": { + "bsonType": "string" + }, + "user_id": { + "bsonType": "string" + } + }, + "required": [ + "_id" + ], + "title": "User" + } +} diff --git a/Tests/TestApps/dotnet-integration-tests/services/gcm/config.json b/Tests/TestApps/dotnet-integration-tests/services/gcm/config.json new file mode 100644 index 0000000000..d26b9b3343 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/services/gcm/config.json @@ -0,0 +1,12 @@ +{ + "id": "5f6d082a5baa3f73d8933b51", + "name": "gcm", + "type": "gcm", + "config": { + "senderId": "gcm" + }, + "secret_config": { + "apiKey": "gcm" + }, + "version": 1 +} diff --git a/Tests/TestApps/dotnet-integration-tests/stitch.json b/Tests/TestApps/dotnet-integration-tests/stitch.json new file mode 100644 index 0000000000..2dfa84acf9 --- /dev/null +++ b/Tests/TestApps/dotnet-integration-tests/stitch.json @@ -0,0 +1,18 @@ +{ + "app_id": "dotnet-integration-tests-nluph", + "config_version": 20180301, + "name": "dotnet-integration-tests", + "location": "US-VA", + "deployment_model": "GLOBAL", + "security": {}, + "custom_user_data_config": { + "enabled": true, + "mongo_service_name": "BackingDB", + "database_name": "my-db", + "collection_name": "users", + "user_id_field": "user_id" + }, + "sync": { + "development_mode_enabled": true + } +} diff --git a/Tests/Tests.iOS/Tests.iOS.csproj b/Tests/Tests.iOS/Tests.iOS.csproj index 7573fc2559..5d67a4c95c 100644 --- a/Tests/Tests.iOS/Tests.iOS.csproj +++ b/Tests/Tests.iOS/Tests.iOS.csproj @@ -29,7 +29,7 @@ x86_64 SdkOnly true - --nosymbolstrip --linkskip=nunit.runner.iOS --linkskip=nunit.framework --nolinkaway + --nosymbolstrip --linkskip=nunit.runner.iOS --linkskip=nunit.framework --linkskip=MongoDB.Bson --nolinkaway none @@ -40,7 +40,7 @@ Full x86_64 false - --linkskip=nunit.runner.iOS --linkskip=nunit.framework --nolinkaway + --linkskip=nunit.runner.iOS --linkskip=nunit.framework --linkskip=MongoDB.Bson --nolinkaway true diff --git a/Tests/Weaver/AssemblyToProcess/AssemblyToProcess.csproj b/Tests/Weaver/AssemblyToProcess/AssemblyToProcess.csproj index cf42290145..da052f003a 100644 --- a/Tests/Weaver/AssemblyToProcess/AssemblyToProcess.csproj +++ b/Tests/Weaver/AssemblyToProcess/AssemblyToProcess.csproj @@ -4,6 +4,10 @@ net472;netcoreapp2.0 + + + + diff --git a/Tests/Weaver/AssemblyToProcess/CopyToRealmTestObjects.cs b/Tests/Weaver/AssemblyToProcess/CopyToRealmTestObjects.cs index 33847f0fe2..acfe599592 100644 --- a/Tests/Weaver/AssemblyToProcess/CopyToRealmTestObjects.cs +++ b/Tests/Weaver/AssemblyToProcess/CopyToRealmTestObjects.cs @@ -18,6 +18,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using MongoDB.Bson; using Realms; namespace AssemblyToProcess @@ -52,18 +53,36 @@ public class DateTimeOffsetProperty : RealmObject public DateTimeOffset DateTimeOffset { get; set; } } + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] + public class DecimalProperty : RealmObject + { + public decimal Decimal { get; set; } + } + + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] + public class Decimal128Property : RealmObject + { + public Decimal128 Decimal128 { get; set; } + } + + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] + public class ObjectIdProperty : RealmObject + { + public ObjectId ObjectId { get; set; } + } + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] public class NullableProperties : RealmObject { public char? Char { get; set; } - public byte? Byte { get; set; } + public byte? NullableByte { get; set; } - public short? Int16 { get; set; } + public short? NullableInt16 { get; set; } - public int? Int32 { get; set; } + public int? NullableInt32 { get; set; } - public long? Int64 { get; set; } + public long? NullableInt64 { get; set; } public float? Single { get; set; } @@ -71,7 +90,13 @@ public class NullableProperties : RealmObject public DateTimeOffset? DateTimeOffset { get; set; } + public decimal? Decimal { get; set; } + + public Decimal128? Decimal128 { get; set; } + public bool? Boolean { get; set; } + + public ObjectId? ObjectId { get; set; } } [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] diff --git a/Tests/Weaver/AssemblyToProcess/FaultyClasses.cs b/Tests/Weaver/AssemblyToProcess/FaultyClasses.cs index cb75d0fa25..e25868d099 100644 --- a/Tests/Weaver/AssemblyToProcess/FaultyClasses.cs +++ b/Tests/Weaver/AssemblyToProcess/FaultyClasses.cs @@ -32,6 +32,13 @@ public class RealmListWithSetter : RealmObject public int PropertyToEnsureOtherwiseHealthyClass { get; set; } } + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] + public class EmbeddedWithPrimaryKey : EmbeddedObject + { + [PrimaryKey] + public int NotAllowed { get; set; } + } + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] public class IndexedProperties : RealmObject { diff --git a/Tests/Weaver/AssemblyToProcess/TestObjects.cs b/Tests/Weaver/AssemblyToProcess/TestObjects.cs index 3884b7c84a..d48b0b91d9 100644 --- a/Tests/Weaver/AssemblyToProcess/TestObjects.cs +++ b/Tests/Weaver/AssemblyToProcess/TestObjects.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using MongoDB.Bson; using Realms; namespace AssemblyToProcess @@ -40,12 +41,18 @@ public class AllTypesObject : RealmObject public double DoubleProperty { get; set; } + public decimal DecimalProperty { get; set; } + + public Decimal128 Decimal128Property { get; set; } + public bool BooleanProperty { get; set; } public string StringProperty { get; set; } public DateTimeOffset DateTimeOffsetProperty { get; set; } + public ObjectId ObjectIdProperty { get; set; } + public char? NullableCharProperty { get; set; } public byte? NullableByteProperty { get; set; } @@ -62,6 +69,10 @@ public class AllTypesObject : RealmObject public bool? NullableBooleanProperty { get; set; } + public decimal? NullableDecimalProperty { get; set; } + + public Decimal128? NullableDecimal128Property { get; set; } + public DateTimeOffset? NullableDateTimeOffsetProperty { get; set; } public RealmInteger ByteCounterProperty { get; set; } @@ -79,6 +90,8 @@ public class AllTypesObject : RealmObject public RealmInteger? NullableInt32CounterProperty { get; set; } public RealmInteger? NullableInt64CounterProperty { get; set; } + + public ObjectId? NullableObjectIdProperty { get; set; } } [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] @@ -104,6 +117,12 @@ public class ListsObject : RealmObject public IList DateTimeOffsetList { get; } + public IList DecimalList { get; } + + public IList Decimal128List { get; } + + public IList ObjectIdList { get; } + public IList NullableCharList { get; } public IList NullableByteList { get; } @@ -122,6 +141,12 @@ public class ListsObject : RealmObject public IList NullableDateTimeOffsetList { get; } + public IList NullableDecimalList { get; } + + public IList NullableDecimal128List { get; } + + public IList NullableObjectIdList { get; } + public IList> ByteCounterList { get; } public IList> Int16CounterList { get; } @@ -181,6 +206,13 @@ public class PrimaryKeyStringObject : RealmObject public string StringProperty { get; set; } } + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] + public class PrimaryKeyObjectIdObject : RealmObject + { + [PrimaryKey] + public ObjectId ObjectIdProperty { get; set; } + } + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] public class PrimaryKeyNullableCharObject : RealmObject { @@ -216,6 +248,13 @@ public class PrimaryKeyNullableInt64Object : RealmObject public long? Int64Property { get; set; } } + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] + public class PrimaryKeyNullableObjectIdObject : RealmObject + { + [PrimaryKey] + public ObjectId? ObjectIdProperty { get; set; } + } + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] public class GetterOnlyUnsupportedProperty : RealmObject { @@ -229,4 +268,82 @@ public enum MyEnum { } } + + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] + public class ObjectWithEmbeddedProperties : RealmObject + { + public EmbeddedAllTypesObject AllTypesObject { get; set; } + + public IList ListOfAllTypesObjects { get; } + } + + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] + public class EmbeddedAllTypesObject : EmbeddedObject + { + public char CharProperty { get; set; } + + public byte ByteProperty { get; set; } + + public short Int16Property { get; set; } + + public int Int32Property { get; set; } + + public long Int64Property { get; set; } + + public float SingleProperty { get; set; } + + public double DoubleProperty { get; set; } + + public decimal DecimalProperty { get; set; } + + public Decimal128 Decimal128Property { get; set; } + + public bool BooleanProperty { get; set; } + + public string StringProperty { get; set; } + + public DateTimeOffset DateTimeOffsetProperty { get; set; } + + public ObjectId ObjectIdProperty { get; set; } + + public char? NullableCharProperty { get; set; } + + public byte? NullableByteProperty { get; set; } + + public short? NullableInt16Property { get; set; } + + public int? NullableInt32Property { get; set; } + + public long? NullableInt64Property { get; set; } + + public float? NullableSingleProperty { get; set; } + + public double? NullableDoubleProperty { get; set; } + + public bool? NullableBooleanProperty { get; set; } + + public decimal? NullableDecimalProperty { get; set; } + + public Decimal128? NullableDecimal128Property { get; set; } + + public DateTimeOffset? NullableDateTimeOffsetProperty { get; set; } + + public RealmInteger ByteCounterProperty { get; set; } + + public RealmInteger Int16CounterProperty { get; set; } + + public RealmInteger Int32CounterProperty { get; set; } + + public RealmInteger Int64CounterProperty { get; set; } + + public RealmInteger? NullableByteCounterProperty { get; set; } + + public RealmInteger? NullableInt16CounterProperty { get; set; } + + public RealmInteger? NullableInt32CounterProperty { get; set; } + + public RealmInteger? NullableInt64CounterProperty { get; set; } + + public ObjectId? NullableObjectIdProperty { get; set; } + } } \ No newline at end of file diff --git a/Tests/Weaver/Realm.FakeForWeaverTests/Realm.FakeForWeaverTests.csproj b/Tests/Weaver/Realm.FakeForWeaverTests/Realm.FakeForWeaverTests.csproj index 335513f282..bf32865399 100644 --- a/Tests/Weaver/Realm.FakeForWeaverTests/Realm.FakeForWeaverTests.csproj +++ b/Tests/Weaver/Realm.FakeForWeaverTests/Realm.FakeForWeaverTests.csproj @@ -13,6 +13,7 @@ + Attributes\ExplicitAttribute.cs diff --git a/Tests/Weaver/Realm.FakeForWeaverTests/RealmObject.cs b/Tests/Weaver/Realm.FakeForWeaverTests/RealmObject.cs index 1df98da769..a5cc7afe9f 100644 --- a/Tests/Weaver/Realm.FakeForWeaverTests/RealmObject.cs +++ b/Tests/Weaver/Realm.FakeForWeaverTests/RealmObject.cs @@ -21,10 +21,19 @@ using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; +using Realms.Schema; namespace Realms { - public class RealmObject : INotifyPropertyChanged + public class RealmObject : RealmObjectBase + { + } + + public class EmbeddedObject : RealmObjectBase + { + } + + public abstract class RealmObjectBase : INotifyPropertyChanged { public List LogList = new List(); @@ -67,138 +76,34 @@ public Realm Realm } } - protected string GetStringValue(string propertyName) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\""); - return string.Empty; - } - - protected void SetStringValue(string propertyName, string value) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\", {nameof(value)} = {value}"); - } - - protected void SetStringValueUnique(string propertyName, string value) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\", {nameof(value)} = {value}"); - } - - protected char GetCharValue(string propertyName) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\""); - return char.MinValue; - } - - protected void SetCharValue(string propertyName, char value) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\", {nameof(value)} = {value}"); - } - - protected void SetCharValueUnique(string propertyName, char value) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\", {nameof(value)} = {value}"); - } - - protected void SetNullableCharValueUnique(string propertyName, char? value) + protected void SetPrimitiveValue(string propertyName, T value, PropertyType propertyType) { LogCall($"{nameof(propertyName)} = \"{propertyName}\", {nameof(value)} = {value}"); } - protected float GetSingleValue(string propertyName) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\""); - return 0; - } - - protected void SetSingleValue(string propertyName, float value) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\", {nameof(value)} = {value}"); - } - - protected double GetDoubleValue(string propertyName) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\""); - return 0; - } - - protected void SetDoubleValue(string propertyName, double value) + protected void SetPrimitiveValueUnique(string propertyName, T value, PropertyType propertyType) { LogCall($"{nameof(propertyName)} = \"{propertyName}\", {nameof(value)} = {value}"); } - protected bool GetBooleanValue(string propertyName) + protected T GetPrimitiveValue(string propertyName, PropertyType propertyType) { LogCall($"{nameof(propertyName)} = \"{propertyName}\""); - return false; - } - - protected void SetBooleanValue(string propertyName, bool value) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\", {nameof(value)} = {value}"); - } - - protected DateTimeOffset GetDateTimeOffsetValue(string propertyName) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\""); - return DateTimeOffset.MinValue; - } - - protected void SetDateTimeOffsetValue(string propertyName, DateTimeOffset value) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\", {nameof(value)} = {value}"); - } - - protected DateTimeOffset? GetNullableDateTimeOffsetValue(string propertyName) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\""); - return DateTimeOffset.MinValue; - } - - protected void SetNullableDateTimeOffsetValue(string propertyName, DateTimeOffset? value) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\", {nameof(value)} = {value}"); - } - - protected char? GetNullableCharValue(string propertyName) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\""); - return char.MinValue; - } - - protected void SetNullableCharValue(string propertyName, char? value) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\", {nameof(value)} = {value}"); - } - - protected float? GetNullableSingleValue(string propertyName) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\""); - return 0; - } - - protected void SetNullableSingleValue(string propertyName, float? value) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\", {nameof(value)} = {value}"); + return default(T); } - protected double? GetNullableDoubleValue(string propertyName) + protected string GetStringValue(string propertyName) { LogCall($"{nameof(propertyName)} = \"{propertyName}\""); - return 0; + return string.Empty; } - protected void SetNullableDoubleValue(string propertyName, double? value) + protected void SetStringValue(string propertyName, string value) { LogCall($"{nameof(propertyName)} = \"{propertyName}\", {nameof(value)} = {value}"); } - protected bool? GetNullableBooleanValue(string propertyName) - { - LogCall($"{nameof(propertyName)} = \"{propertyName}\""); - return false; - } - - protected void SetNullableBooleanValue(string propertyName, bool? value) + protected void SetStringValueUnique(string propertyName, string value) { LogCall($"{nameof(propertyName)} = \"{propertyName}\", {nameof(value)} = {value}"); } diff --git a/Tests/Weaver/Realm.Fody.Tests/WeaverTests.cs b/Tests/Weaver/Realm.Fody.Tests/WeaverTests.cs index 742490149c..23914f2f39 100644 --- a/Tests/Weaver/Realm.Fody.Tests/WeaverTests.cs +++ b/Tests/Weaver/Realm.Fody.Tests/WeaverTests.cs @@ -27,6 +27,7 @@ using System.Reflection; using System.Xml.Linq; using Fody; +using MongoDB.Bson; using NUnit.Framework; using Realms; using Realms.Weaving; @@ -95,6 +96,11 @@ private static string GetCoreMethodName(string type) return (type.StartsWith("Nullable") ? "Nullable" : string.Empty) + "RealmInteger"; } + if (_primitiveValueTypes.Contains(type)) + { + return "Primitive"; + } + return type; } @@ -112,6 +118,26 @@ private static string GetCoreMethodName(string type) "NullableInt64" }; + private static readonly IEnumerable _primitiveValueTypes = new[] + { + "Char", + "Single", + "Double", + "Boolean", + "DateTimeOffset", + "Decimal", + "Decimal128", + "ObjectId", + "NullableChar", + "NullableSingle", + "NullableDouble", + "NullableBoolean", + "NullableDateTimeOffset", + "NullableDecimal", + "NullableDecimal128", + "NullableObjectId", + }; + public enum PropertyChangedWeaver { NoPropertyChanged, @@ -196,6 +222,9 @@ public void FixtureSetup() new object[] { "Double", 123.123, 0.0 }, new object[] { "Boolean", true, false }, new object[] { "String", "str", null }, + new object[] { "Decimal", 123.456M, 0M }, + new object[] { "Decimal128", new Decimal128(123.456), new Decimal128() }, + new object[] { "ObjectId", ObjectId.GenerateNewId(), default(ObjectId) }, new object[] { "NullableChar", '0', null }, new object[] { "NullableByte", (byte)100, null }, new object[] { "NullableInt16", (short)100, null }, @@ -204,6 +233,9 @@ public void FixtureSetup() new object[] { "NullableSingle", 123.123f, null }, new object[] { "NullableDouble", 123.123, null }, new object[] { "NullableBoolean", true, null }, + new object[] { "NullableDecimal", 123.456M, null }, + new object[] { "NullableDecimal128", new Decimal128(123.456), null }, + new object[] { "NullableObjectId", ObjectId.GenerateNewId(), null }, new object[] { "ByteCounter", (RealmInteger)100, (byte)0 }, new object[] { "Int16Counter", (RealmInteger)100, (short)0 }, new object[] { "Int32Counter", (RealmInteger)100, 0 }, @@ -518,6 +550,7 @@ public void MatchErrorsAndWarnings() var expectedErrors = new[] { "RealmListWithSetter.People has a setter but its type is a IList which only supports getters.", + "Class EmbeddedWithPrimaryKey is an EmbeddedObject but has a primary key NotAllowed defined.", "IndexedProperties.SingleProperty is marked as [Indexed] which is only allowed on integral types as well as string, bool and DateTimeOffset, not on System.Single.", "PrimaryKeyProperties.BooleanProperty is marked as [PrimaryKey] which is only allowed on integral and string types, not on System.Boolean.", "PrimaryKeyProperties.DateTimeOffsetProperty is marked as [PrimaryKey] which is only allowed on integral and string types, not on System.DateTimeOffset.", @@ -581,11 +614,17 @@ public void WovenCopyToRealm_ShouldSetNonDefaultProperties(string propertyName, } [Test] - public void WovenCopyToRealm_ShouldAlwaysSetDateTimeOffsetProperties() + public void WovenCopyToRealm_ShouldAlwaysSetStructProperties() { // DateTimeOffset can't be set as a constant WovenCopyToRealm_ShouldSetNonDefaultProperties("DateTimeOffset", default(DateTimeOffset), "DateTimeOffsetProperty"); WovenCopyToRealm_ShouldSetNonDefaultProperties("DateTimeOffset", new DateTimeOffset(1, 1, 1, 1, 1, 1, TimeSpan.Zero), "DateTimeOffsetProperty"); + WovenCopyToRealm_ShouldSetNonDefaultProperties("Decimal", default(decimal), "DecimalProperty"); + WovenCopyToRealm_ShouldSetNonDefaultProperties("Decimal", 1234.3225352352M, "DecimalProperty"); + WovenCopyToRealm_ShouldSetNonDefaultProperties("Decimal128", default(Decimal128), "Decimal128Property"); + WovenCopyToRealm_ShouldSetNonDefaultProperties("Decimal128", new Decimal128(124.3124214), "Decimal128Property"); + WovenCopyToRealm_ShouldSetNonDefaultProperties("ObjectId", default(ObjectId), "ObjectIdProperty"); + WovenCopyToRealm_ShouldSetNonDefaultProperties("ObjectId", ObjectId.GenerateNewId(), "ObjectIdProperty"); } [Test] @@ -610,7 +649,7 @@ public void WovenCopyToRealm_ShouldAlwaysSetNullableProperties() return new[] { "IsManaged", - $"RealmObject.SetNullable{GetCoreMethodName(p.Name)}Value(propertyName = \"{p.Name}\", value = )" + $"RealmObject.Set{GetCoreMethodName(p.Name)}Value(propertyName = \"{p.Name}\", value = )" }; }) .ToList(); diff --git a/wrappers/CMakeLists.txt b/wrappers/CMakeLists.txt index 0976c8b119..37b1fdbe51 100644 --- a/wrappers/CMakeLists.txt +++ b/wrappers/CMakeLists.txt @@ -24,8 +24,13 @@ function(download_realm_tarball url target libraries) if (NOT EXISTS ${tarball_path}) if (NOT EXISTS ${temp_tarball_path}) message("Downloading ${url}.") - file(DOWNLOAD ${url} ${temp_tarball_path}.tmp SHOW_PROGRESS) - file(RENAME ${temp_tarball_path}.tmp ${temp_tarball_path}) + string(RANDOM suffix) + set(temp_download_path "${temp_tarball_path}-${suffix}.tmp") + message("Downloading ${url} to ${temp_download_path}.") + file(DOWNLOAD ${url} ${temp_download_path} SHOW_PROGRESS) + if (NOT EXISTS ${temp_tarball_path}) + file(RENAME ${temp_download_path} ${temp_tarball_path}) + endif() endif() file(COPY ${temp_tarball_path} DESTINATION ${tarball_parent_directory}) endif() @@ -51,14 +56,14 @@ endfunction() function(download_android_openssl) if(ANDROID) string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE) - set(OPENSSL_URL "http://static.realm.io/downloads/openssl/${ANDROID_OPENSSL_VERSION}/Android/${CMAKE_ANDROID_ARCH_ABI}/openssl.tgz") + set(OPENSSL_URL "http://static.realm.io/downloads/openssl/${ANDROID_OPENSSL_VERSION}/Android/${CMAKE_ANDROID_ARCH_ABI}/openssl.tar.gz") message(STATUS "Downloading OpenSSL...s") - file(DOWNLOAD "${OPENSSL_URL}" "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/openssl.tgz") + file(DOWNLOAD "${OPENSSL_URL}" "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/openssl.tar.gz") message(STATUS "Uncompressing OpenSSL...") file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/openssl") - execute_process(COMMAND ${CMAKE_COMMAND} -E tar xfz ../openssl.tgz + execute_process(COMMAND ${CMAKE_COMMAND} -E tar xfz ../openssl.tar.gz WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/openssl") message(STATUS "Importing OpenSSL...") @@ -76,11 +81,10 @@ endif() include(CompilerFlags) set(REALM_ENABLE_SYNC ON) -if(NOT ANDROID) - set(REALM_ENABLE_SERVER ON) -endif() if(APPLE) + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] "i386 x86_64") + find_package(Threads) if(CMAKE_SYSTEM_NAME STREQUAL "iOS") set(platform ios) @@ -235,7 +239,7 @@ if(APPLE) set(CMAKE_CXX_VISIBILITY_PRESET default) set(CMAKE_VISIBILITY_INLINES_HIDDEN TRUE) if (CMAKE_SYSTEM_NAME STREQUAL "iOS") - set(CMAKE_OSX_DEPLOYMENT_TARGET 8.0) + set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0) set(BUILD_APPLE_FRAMEWORK TRUE) else() set(CMAKE_OSX_DEPLOYMENT_TARGET 10.10) diff --git a/wrappers/dependencies.list b/wrappers/dependencies.list index 282476805c..55fe9b283a 100644 --- a/wrappers/dependencies.list +++ b/wrappers/dependencies.list @@ -1,3 +1,3 @@ -REALM_CORE_VERSION=6.1.3 -REALM_SYNC_VERSION=5.0.28 -ANDROID_OPENSSL_VERSION=1.1.1b +REALM_CORE_VERSION=10.0.0 +REALM_SYNC_VERSION=10.0.0 +ANDROID_OPENSSL_VERSION=1.1.1g diff --git a/wrappers/src/CMakeLists.txt b/wrappers/src/CMakeLists.txt index 41b271a16a..07a2eac132 100644 --- a/wrappers/src/CMakeLists.txt +++ b/wrappers/src/CMakeLists.txt @@ -27,21 +27,18 @@ set(HEADERS if(REALM_ENABLE_SYNC) list(APPEND SOURCES + app_cs.cpp async_open_task_cs.cpp - sync_manager_cs.cpp + mongo_collection_cs.cpp sync_session_cs.cpp sync_user_cs.cpp - subscription_cs.cpp + transport_cs.cpp ) list(APPEND HEADERS + app_cs.hpp sync_session_cs.hpp - ) -endif() - -if(REALM_ENABLE_SERVER) - list(APPEND SOURCES - global_notifier_cs.cpp + transport_cs.hpp ) endif() diff --git a/wrappers/src/app_cs.cpp b/wrappers/src/app_cs.cpp new file mode 100644 index 0000000000..d2d570f2c2 --- /dev/null +++ b/wrappers/src/app_cs.cpp @@ -0,0 +1,387 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include +#include "marshalling.hpp" +#include "error_handling.hpp" +#include "realm_export_decls.hpp" +#include "shared_realm_cs.hpp" +#include "sync_session_cs.hpp" +#include "transport_cs.hpp" +#include "debug.hpp" +#include "app_cs.hpp" + +#include "sync/sync_manager.hpp" +#include "sync/app_credentials.hpp" +#include "sync/app.hpp" +#include "sync/sync_config.hpp" +#include "thread_safe_reference.hpp" +#include "sync/sync_session.hpp" + +using namespace realm; +using namespace realm::binding; +using namespace app; + +using SharedSyncUser = std::shared_ptr; +using SharedSyncSession = std::shared_ptr; + +namespace realm { + namespace binding { + std::string s_platform; + std::string s_platform_version; + std::string s_sdk_version; + + void (*s_log_message_callback)(void* managed_handler, const char* message, size_t message_len, util::Logger::Level level); + void (*s_user_callback)(void* tcs_ptr, SharedSyncUser* user, MarshaledAppError err); + void (*s_void_callback)(void* tcs_ptr, MarshaledAppError err); + void (*s_bson_callback)(void* tcs_ptr, BsonPayload response, MarshaledAppError err); + + struct AppConfiguration + { + uint16_t* app_id; + size_t app_id_len; + + uint16_t* base_file_path; + size_t base_file_path_len; + + uint16_t* base_url; + size_t base_url_len; + + uint16_t* local_app_name; + size_t local_app_name_len; + + uint16_t* local_app_version; + size_t local_app_version_len; + + uint64_t request_timeout_ms; + + realm::SyncClientConfig::MetadataMode metadata_mode; + + bool metadata_mode_has_value; + + util::Logger::Level log_level; + + void* managed_log_handler; + }; + + class SyncLogger : public util::RootLogger { + public: + SyncLogger(void* delegate) + : m_log_message_delegate(delegate) + { + } + + void do_log(util::Logger::Level level, std::string message) { + s_log_message_callback(m_log_message_delegate, message.c_str(), message.length(), level); + } + private: + void* m_log_message_delegate; + }; + + class SyncLoggerFactory : public realm::SyncLoggerFactory { + public: + SyncLoggerFactory(void* managed_log_handler) + : m_managed_log_handler(managed_log_handler) + { + } + + std::unique_ptr make_logger(util::Logger::Level level) + { + auto logger = std::make_unique(m_managed_log_handler); + logger->set_level_threshold(level); + return std::unique_ptr(logger.release()); + } + private: + void* m_managed_log_handler; + }; + } +} + +extern "C" { + REALM_EXPORT void shared_app_initialize(uint16_t* platform, size_t platform_len, + uint16_t* platform_version, size_t platform_version_len, + uint16_t* sdk_version, size_t sdk_version_len, + decltype(s_user_callback) user_callback, + decltype(s_void_callback) void_callback, + decltype(s_bson_callback) bson_callback, + decltype(s_log_message_callback) log_message_callback) + { + s_platform = Utf16StringAccessor(platform, platform_len); + s_platform_version = Utf16StringAccessor(platform_version, platform_version_len); + s_sdk_version = Utf16StringAccessor(sdk_version, sdk_version_len); + + s_user_callback = user_callback; + s_void_callback = void_callback; + s_bson_callback = bson_callback; + s_log_message_callback = log_message_callback; + } + + REALM_EXPORT SharedApp* shared_app_create(AppConfiguration app_config, uint8_t* encryption_key, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&]() { + App::Config config; + config.app_id = Utf16StringAccessor(app_config.app_id, app_config.app_id_len); + config.platform = s_platform; + config.platform_version = s_platform_version; + config.sdk_version = s_sdk_version; + config.transport_generator = realm::binding::s_transport_factory; + + if (app_config.base_url != nullptr) { + config.base_url = Utf16StringAccessor(app_config.base_url, app_config.base_url_len).to_string(); + } + + if (app_config.local_app_name != nullptr) { + config.local_app_name = Utf16StringAccessor(app_config.local_app_name, app_config.local_app_name_len).to_string(); + } + + if (app_config.local_app_version != nullptr) { + config.local_app_version = Utf16StringAccessor(app_config.local_app_version, app_config.local_app_version_len).to_string(); + } + + if (app_config.request_timeout_ms > 0) { + config.default_request_timeout_ms = app_config.request_timeout_ms; + } + + SyncClientConfig sync_client_config; + sync_client_config.log_level = app_config.log_level; + sync_client_config.base_file_path = Utf16StringAccessor(app_config.base_file_path, app_config.base_file_path_len); + + if (app_config.metadata_mode_has_value) { + sync_client_config.metadata_mode = app_config.metadata_mode; + } + else { +#if REALM_PLATFORM_APPLE && !TARGET_OS_SIMULATOR + sync_client_config.metadata_mode = SyncManager::MetadataMode::Encryption; +#else + sync_client_config.metadata_mode = SyncManager::MetadataMode::NoEncryption; +#endif + } + + if (encryption_key) { + auto& key = *reinterpret_cast*>(encryption_key); + sync_client_config.custom_encryption_key = std::vector(key.begin(), key.end()); + } + + if (app_config.managed_log_handler) { + sync_client_config.logger_factory = new realm::binding::SyncLoggerFactory(app_config.managed_log_handler); + } + + return new SharedApp(App::get_shared_app(std::move(config), std::move(sync_client_config))); + }); + } + + REALM_EXPORT SharedSyncUser* shared_app_get_current_user(SharedApp& app, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&]() -> SharedSyncUser* { + auto ptr = app->current_user(); + if (ptr == nullptr) { + return nullptr; + } + + return new SharedSyncUser(std::move(ptr)); + }); + } + + REALM_EXPORT size_t shared_app_get_logged_in_users(SharedApp& app, SharedSyncUser** buffer, size_t buffer_length, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&]() -> size_t { + auto users = app->all_users(); + if (users.size() > buffer_length) { + return users.size(); + } + + if (users.size() <= 0) { + return 0; + } + + for (size_t i = 0; i < users.size(); i++) { + buffer[i] = new SharedSyncUser(users.at(i)); + } + + return users.size(); + }); + } + + REALM_EXPORT void shared_app_switch_user(SharedApp& app, SharedSyncUser& user, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&]() { + app->switch_user(user); + }); + } + + REALM_EXPORT void shared_app_login_user(SharedApp& app, Credentials credentials, void* tcs_ptr, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&]() { + auto app_credentials = credentials.to_app_credentials(); + app->log_in_with_credentials(app_credentials, get_user_callback_handler(tcs_ptr)); + }); +} + + REALM_EXPORT SharedSyncUser* shared_app_get_user_for_testing( + SharedApp& app, + uint16_t* id_buf, size_t id_len, + uint16_t* refresh_token_buf, size_t refresh_token_len, + uint16_t* access_token_buf, size_t access_token_len, + NativeException::Marshallable& ex) + { + return handle_errors(ex, [&]() { + Utf16StringAccessor id(id_buf, id_len); + Utf16StringAccessor refresh_token(refresh_token_buf, refresh_token_len); + Utf16StringAccessor access_token(access_token_buf, access_token_len); + return new SharedSyncUser( + app->sync_manager()->get_user( + id, + refresh_token, + access_token, + "testing", + "my-device-id")); + }); + } + + REALM_EXPORT void shared_app_remove_user(SharedApp& app, SharedSyncUser& user, void* tcs_ptr, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&]() { + app->remove_user(user, [tcs_ptr](util::Optional) { + // ignore errors + s_void_callback(tcs_ptr, MarshaledAppError()); + }); + }); + } + + REALM_EXPORT SharedSyncSession* shared_app_sync_get_session_from_path(SharedApp& app, SharedRealm& realm, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&] { + return new SharedSyncSession(app->sync_manager()->get_existing_active_session(realm->config().path)); + }); + } + + REALM_EXPORT size_t shared_app_sync_get_path_for_realm(SharedApp& app, SharedSyncUser& user, uint16_t* partition_buf, size_t partition_len, uint16_t* pathbuffer, size_t pathbuffer_len, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&]() { + Utf16StringAccessor partition(partition_buf, partition_len); + auto sync_config = SyncConfig(user, partition); + auto path = app->sync_manager()->path_for_realm(std::move(sync_config)); + + return stringdata_to_csharpstringbuffer(path, pathbuffer, pathbuffer_len); + }); + } + + REALM_EXPORT bool shared_app_sync_immediately_run_file_actions(SharedApp& app, uint16_t* pathbuffer, size_t pathbuffer_len, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&]() { + std::string path(Utf16StringAccessor(pathbuffer, pathbuffer_len)); + return app->sync_manager()->immediately_run_file_actions(path); + }); + } + + REALM_EXPORT void shared_app_sync_reconnect(SharedApp& app) + { + app->sync_manager()->reconnect(); + } + + REALM_EXPORT void shared_app_destroy(SharedApp* app) + { + delete app; + } + + REALM_EXPORT void shared_app_reset_for_testing(SharedApp& app) { + auto users = app->all_users(); + for (size_t i = 0; i < users.size(); i++) { + auto user = users[i]; + user->log_out(); + } + + while (app->sync_manager()->has_existing_sessions()) { + sleep_ms(5); + } + + bool did_reset = false; + while (!did_reset) { + try { + app->sync_manager()->reset_for_testing(); + did_reset = true; + } + catch (...) { + + } + } + + App::clear_cached_apps(); + } + +#pragma region EmailPassword + + REALM_EXPORT void shared_app_email_register_user(SharedApp& app, uint16_t* username_buf, size_t username_len, uint16_t* password_buf, size_t password_len, void* tcs_ptr, NativeException::Marshallable& ex) + { + handle_errors(ex, [&]() { + Utf16StringAccessor username(username_buf, username_len); + Utf16StringAccessor password(password_buf, password_len); + app->provider_client().register_email(username, password, get_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void shared_app_email_confirm_user(SharedApp& app, uint16_t* token_buf, size_t token_len, uint16_t* token_id_buf, size_t token_id_len, void* tcs_ptr, NativeException::Marshallable& ex) + { + handle_errors(ex, [&]() { + Utf16StringAccessor token(token_buf, token_len); + Utf16StringAccessor token_id(token_id_buf, token_id_len); + app->provider_client().confirm_user(token, token_id, get_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void shared_app_email_resend_confirmation_email(SharedApp& app, uint16_t* email_buf, size_t email_len, void* tcs_ptr, NativeException::Marshallable& ex) + { + handle_errors(ex, [&]() { + Utf16StringAccessor email(email_buf, email_len); + app->provider_client().resend_confirmation_email(email, get_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void shared_app_email_send_reset_password_email(SharedApp& app, uint16_t* email_buf, size_t email_len, void* tcs_ptr, NativeException::Marshallable& ex) + { + handle_errors(ex, [&]() { + Utf16StringAccessor email(email_buf, email_len); + app->provider_client().send_reset_password_email(email, get_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void shared_app_email_reset_password(SharedApp& app, uint16_t* password_buf, size_t password_len, uint16_t* token_buf, size_t token_len, uint16_t* token_id_buf, size_t token_id_len, void* tcs_ptr, NativeException::Marshallable& ex) + { + handle_errors(ex, [&]() { + Utf16StringAccessor password(password_buf, password_len); + Utf16StringAccessor token(token_buf, token_len); + Utf16StringAccessor token_id(token_id_buf, token_id_len); + app->provider_client().reset_password(password, token, token_id, get_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void shared_app_email_call_reset_password_function(SharedApp& app, uint16_t* username_buf, size_t username_len, uint16_t* password_buf, size_t password_len, uint16_t* args_buf, size_t args_len, void* tcs_ptr, NativeException::Marshallable& ex) + { + handle_errors(ex, [&]() { + Utf16StringAccessor username(username_buf, username_len); + Utf16StringAccessor password(password_buf, password_len); + Utf16StringAccessor serialized_args(args_buf, args_len); + + auto args = static_cast(bson::parse(serialized_args.to_string())); + app->provider_client().call_reset_password_function(std::move(username), std::move(password), std::move(args), get_callback_handler(tcs_ptr)); + }); + } + +#pragma endregion + +} diff --git a/wrappers/src/app_cs.hpp b/wrappers/src/app_cs.hpp new file mode 100644 index 0000000000..138a5ae159 --- /dev/null +++ b/wrappers/src/app_cs.hpp @@ -0,0 +1,185 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef APP_CS_HPP +#define APP_CS_HPP + +#include "sync/generic_network_transport.hpp" +#include "sync/app_credentials.hpp" +#include "sync/app.hpp" +#include "sync/sync_user.hpp" +#include "sync/sync_manager.hpp" + +using namespace realm; +using namespace realm::app; + +using SharedSyncUser = std::shared_ptr; + +namespace realm { +namespace binding { + struct MarshaledAppError + { + bool is_null = true; + const char* message_buf = nullptr; + size_t message_len = 0; + const char* error_category_buf = nullptr; + size_t error_category_len = 0; + const char* logs_link_buf = nullptr; + size_t logs_link_len = 0; + int http_status_code = 0; + + MarshaledAppError() + { + } + + MarshaledAppError(const std::string& message, const std::string& error_category, const std::string& logs_link, util::Optional http_code) + { + is_null = false; + + message_buf = message.c_str(); + message_len = message.size(); + + error_category_buf = error_category.c_str(); + error_category_len = error_category.size(); + + logs_link_buf = logs_link.c_str(); + logs_link_len = logs_link.size(); + + http_status_code = http_code.value_or(0); + } + }; + + struct Credentials + { + AuthProvider provider; + + uint16_t* token; + size_t token_len; + + uint16_t* password; + size_t password_len; + + AppCredentials to_app_credentials() { + switch (provider) + { + case AuthProvider::ANONYMOUS: + return AppCredentials::anonymous(); + + case AuthProvider::FACEBOOK: + return AppCredentials::facebook(Utf16StringAccessor(token, token_len)); + + case AuthProvider::GOOGLE: + return AppCredentials::google(Utf16StringAccessor(token, token_len)); + case AuthProvider::APPLE: + return AppCredentials::apple(Utf16StringAccessor(token, token_len)); + + case AuthProvider::CUSTOM: + return AppCredentials::custom(Utf16StringAccessor(token, token_len)); + + case AuthProvider::USERNAME_PASSWORD: + return AppCredentials::username_password(Utf16StringAccessor(token, token_len), Utf16StringAccessor(password, password_len)); + + case AuthProvider::FUNCTION: + return AppCredentials::function(Utf16StringAccessor(token, token_len)); + + case AuthProvider::USER_API_KEY: + return AppCredentials::user_api_key(Utf16StringAccessor(token, token_len)); + + case AuthProvider::SERVER_API_KEY: + return AppCredentials::server_api_key(Utf16StringAccessor(token, token_len)); + + default: + REALM_UNREACHABLE(); + } + } + }; + + struct BsonPayload { + const char* serialized; + size_t serialized_len; + }; + + extern void (*s_void_callback)(void* tcs_ptr, MarshaledAppError err); + extern void (*s_user_callback)(void* tcs_ptr, SharedSyncUser* user, MarshaledAppError err); + extern void (*s_bson_callback)(void* tcs_ptr, BsonPayload response, MarshaledAppError err); + + inline std::function user, util::Optional)> get_user_callback_handler(void* tcs_ptr) { + return [tcs_ptr](std::shared_ptr user, util::Optional err) { + if (err) { + std::string error_category = err->error_code.message(); + MarshaledAppError app_error(err->message, error_category, err->link_to_server_logs, err->http_status_code); + + s_user_callback(tcs_ptr, nullptr, app_error); + } + else { + s_user_callback(tcs_ptr, new SharedSyncUser(user), MarshaledAppError()); + } + }; + } + + inline std::function)> get_callback_handler(void* tcs_ptr) { + return [tcs_ptr](util::Optional err) { + if (err) { + std::string error_category = err->error_code.message(); + MarshaledAppError app_error(err->message, error_category, err->link_to_server_logs, err->http_status_code); + s_void_callback(tcs_ptr, app_error); + } + else { + s_void_callback(tcs_ptr, MarshaledAppError()); + } + }; + } + + inline std::function, util::Optional)> get_bson_callback_handler(void* tcs_ptr) { + return [tcs_ptr](util::Optional err, util::Optional response) { + if (err) { + std::string error_category = err->error_code.message(); + MarshaledAppError app_error(err->message, error_category, err->link_to_server_logs, err->http_status_code); + + s_bson_callback(tcs_ptr, BsonPayload(), app_error); + } + else if (response) { + BsonPayload payload; + std::string serialized = response->to_string(); + payload.serialized = serialized.c_str(); + payload.serialized_len = serialized.size(); + s_bson_callback(tcs_ptr, payload, MarshaledAppError()); + } + else { + s_bson_callback(tcs_ptr, BsonPayload(), MarshaledAppError()); + } + }; + } + + inline bson::BsonDocument to_document(uint16_t* buf, size_t len) { + if (buf == nullptr) { + return bson::BsonDocument(); + } + + Utf16StringAccessor json(buf, len); + return static_cast(bson::parse(json.to_string())); + } + + inline bson::BsonArray to_array(uint16_t* buf, size_t len) { + Utf16StringAccessor json(buf, len); + return static_cast(bson::parse(json.to_string())); + } +} +} + +#endif /* defined(APP_CS_HPP) */ diff --git a/wrappers/src/debug.cpp b/wrappers/src/debug.cpp index 8065d1e313..3a4ee378db 100644 --- a/wrappers/src/debug.cpp +++ b/wrappers/src/debug.cpp @@ -20,10 +20,9 @@ #include "impl/realm_coordinator.hpp" -#if REALM_ENABLE_SYNC #include "sync/sync_manager.hpp" #include "sync/sync_user.hpp" -#endif +#include "sync/app.hpp" namespace realm { @@ -40,23 +39,13 @@ void debug_log(const std::string message) } extern "C" { - -REALM_EXPORT void set_debug_logger(realm::DebugLoggerT debug_logger) -{ - realm::debug_log_function = debug_logger; -} - -REALM_EXPORT void realm_reset_for_testing() -{ - realm::_impl::RealmCoordinator::clear_all_caches(); - -#if REALM_ENABLE_SYNC - for (auto user : realm::SyncManager::shared().all_logged_in_users()) { - user->log_out(); + REALM_EXPORT void set_debug_logger(realm::DebugLoggerT debug_logger) + { + realm::debug_log_function = debug_logger; } - realm::SyncManager::shared().reset_for_testing(); -#endif -} - + REALM_EXPORT void realm_reset_for_testing() + { + realm::_impl::RealmCoordinator::clear_all_caches(); + } } diff --git a/wrappers/src/debug.hpp b/wrappers/src/debug.hpp index e73a5c4404..9f71faf94a 100644 --- a/wrappers/src/debug.hpp +++ b/wrappers/src/debug.hpp @@ -14,16 +14,40 @@ // See the License for the specific language governing permissions and // limitations under the License. // -//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// #pragma once #include +#ifdef WIN32 +#include +#elif _POSIX_C_SOURCE >= 199309L +#include // for nanosleep +#else +#include // for usleep +#endif + namespace realm { #if defined(DEBUG) || !defined(NDEBUG) void debug_log(std::string message); #endif +// https://stackoverflow.com/a/28827188/1649102 +inline void sleep_ms(int milliseconds){ // cross-platform sleep function +#ifdef WIN32 + Sleep(milliseconds); +#elif _POSIX_C_SOURCE >= 199309L + struct timespec ts; + ts.tv_sec = milliseconds / 1000; + ts.tv_nsec = (milliseconds % 1000) * 1000000; + nanosleep(&ts, NULL); +#else + if (milliseconds >= 1000) + sleep(milliseconds / 1000); + usleep((milliseconds % 1000) * 1000); +#endif +} + } // namespace realm diff --git a/wrappers/src/error_handling.cpp b/wrappers/src/error_handling.cpp index 874ac0d3d0..6fff92f25b 100644 --- a/wrappers/src/error_handling.cpp +++ b/wrappers/src/error_handling.cpp @@ -31,6 +31,8 @@ #include "realm/alloc_slab.hpp" #include "object_accessor.hpp" +using namespace realm::app; + namespace realm { SetDuplicatePrimaryKeyValueException::SetDuplicatePrimaryKeyValueException(std::string object_type, std::string property, std::string value) @@ -117,8 +119,28 @@ namespace realm { catch (const ObjectManagedByAnotherRealmException& e) { return { RealmErrorType::ObjectManagedByAnotherRealm, e.what() }; } - catch (const RealmFeatureUnavailableException& e) { - return { RealmErrorType::RealmFeatureUnavailable, e.what() }; + catch (const AppError& e) { + if (e.is_client_error()) { + return { RealmErrorType::AppClientError, e.message }; + } + + if (e.is_custom_error()) { + return { RealmErrorType::AppCustomError, e.message }; + } + + if (e.is_http_error()) { + return { RealmErrorType::AppHttpError, e.message }; + } + + if (e.is_json_error()) { + return { RealmErrorType::AppJsonError, e.message }; + } + + if (e.is_service_error()) { + return { RealmErrorType::AppCustomError, e.message }; + } + + return { RealmErrorType::AppUnknownError, e.message }; } catch (const std::bad_alloc& e) { return { RealmErrorType::RealmOutOfMemory, e.what() }; diff --git a/wrappers/src/error_handling.hpp b/wrappers/src/error_handling.hpp index 286e41dbd4..8b44aac3f9 100644 --- a/wrappers/src/error_handling.hpp +++ b/wrappers/src/error_handling.hpp @@ -75,12 +75,6 @@ class ObjectManagedByAnotherRealmException : public std::runtime_error { ObjectManagedByAnotherRealmException(std::string message) : std::runtime_error(message) {} }; -class RealmFeatureUnavailableException : public std::runtime_error { -public: - RealmFeatureUnavailableException(std::string message) : std::runtime_error(message) {} -}; - - REALM_EXPORT NativeException convert_exception(); void throw_managed_exception(const NativeException& exception); diff --git a/wrappers/src/global_notifier_cs.cpp b/wrappers/src/global_notifier_cs.cpp deleted file mode 100644 index d196d475fd..0000000000 --- a/wrappers/src/global_notifier_cs.cpp +++ /dev/null @@ -1,228 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include "realm_export_decls.hpp" -#include "error_handling.hpp" -#include "marshalling.hpp" -#include "notifications_cs.hpp" -#include "sync_manager_cs.hpp" -#include "sync_session_cs.hpp" - -#include -#include - -using namespace realm; -using namespace realm::binding; -using NotifierHandle = std::shared_ptr; - -struct MarshaledModificationInfo { - ObjKey obj; - MarshaledVector changed_columns; -}; - -struct MarshaledChangeSet { - StringData class_name = {}; - - MarshaledVector deletions; - MarshaledVector insertions; - MarshaledVector modifications; -}; - -struct MarshaledChangeNotification { - StringData path = {}; - - StringData path_on_disk = {}; - - SharedRealm* previous = nullptr; - SharedRealm* current = nullptr; - - MarshaledVector changesets; -}; - -bool (*s_should_handle_callback)(const void* managed_instance, const char* path, size_t path_len); -void (*s_enqueue_calculation_callback)(const void* managed_instance, const char* path, size_t path_len, GlobalNotifier::ChangeNotification*); -void (*s_start_callback)(const void* managed_instance, int32_t error_code, const char* message, size_t message_len); -void (*s_calculation_complete_callback)(MarshaledChangeNotification& change, const void* managed_callback); - -class Callback : public GlobalNotifier::Callback { -public: - Callback(void* managed_instance) - : m_managed_instance(managed_instance) - , m_logger(SyncManager::shared().make_logger()) - { } - - virtual void download_complete() { - m_did_download = true; - m_logger->trace("ManagedGlobalNotifier: download_complete()"); - s_start_callback(m_managed_instance, 0, nullptr, 0); - } - - virtual void error(std::exception_ptr error) { - m_logger->trace("ManagedGlobalNotifier: error()"); - if (!m_did_download) { - try { - std::rethrow_exception(error); - } catch (const std::system_error& system_error) { - const std::error_code& ec = system_error.code(); - s_start_callback(m_managed_instance, ec.value(), ec.message().c_str(), ec.message().length()); - } catch (const std::exception& e) { - m_logger->fatal("ManagedGlobalNotifier fatal error: %1", e.what()); - realm::util::terminate("Unhandled GlobalNotifier exception type", __FILE__, __LINE__); - } - } else { - realm::util::terminate("Unhandled GlobalNotifier runtime error", __FILE__, __LINE__); - } - } - - virtual bool realm_available(StringData, StringData virtual_path) { - m_logger->trace("ManagedGlobalNotifier: realm_available(%1)", virtual_path); - return s_should_handle_callback(m_managed_instance, virtual_path.data(), virtual_path.size()); - } - - virtual void realm_changed(GlobalNotifier* notifier) { - m_logger->trace("ManagedGlobalNotifier: realm_changed()"); - while (auto change = notifier->next_changed_realm()) { - s_enqueue_calculation_callback(m_managed_instance, change->realm_path.c_str(), change->realm_path.size(), new GlobalNotifier::ChangeNotification(std::move(change.value()))); - } - } -private: - const void* m_managed_instance; - const std::unique_ptr m_logger; - bool m_did_download = false; -}; - -extern "C" { -REALM_EXPORT void realm_server_install_callbacks(decltype(s_should_handle_callback) should_handle_callback, - decltype(s_enqueue_calculation_callback) enqueue_calculation_callback, - decltype(s_start_callback) start_callback, - decltype(s_calculation_complete_callback) calculation_complete_callback) -{ - s_should_handle_callback = should_handle_callback; - s_enqueue_calculation_callback = enqueue_calculation_callback; - s_start_callback = start_callback; - s_calculation_complete_callback = calculation_complete_callback; -} - -REALM_EXPORT NotifierHandle* realm_server_create_global_notifier(void* managed_instance, - SyncConfiguration configuration, - uint8_t* encryption_key, - NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - Utf16StringAccessor realm_url(configuration.url, configuration.url_len); - SyncConfig config(*configuration.user, std::move(realm_url)); - - config.bind_session_handler = bind_session; - config.error_handler = handle_session_error; - config.stop_policy = SyncSessionStopPolicy::Immediately; - - config.client_validate_ssl = configuration.client_validate_ssl; - config.ssl_trust_certificate_path = std::string(Utf16StringAccessor(configuration.trusted_ca_path, configuration.trusted_ca_path_len)); - - // the partial_sync_identifier field was hijacked to carry the working directory - Utf16StringAccessor working_dir(configuration.partial_sync_identifier, configuration.partial_sync_identifier_len); - - auto callback = std::make_unique(managed_instance); - auto notifier = std::make_shared(std::move(callback), std::move(working_dir), std::move(config)); - notifier->start(); - return new NotifierHandle(std::move(notifier)); - }); -} - -REALM_EXPORT SharedRealm* realm_server_global_notifier_get_realm_for_writing(SharedRealm& current_realm, - NativeException::Marshallable& ex) -{ - return handle_errors(ex, [current_realm] { - return new SharedRealm(Realm::get_shared_realm(current_realm->config())); - }); -} - -REALM_EXPORT void realm_server_global_notifier_destroy(NotifierHandle* notifier) -{ - delete notifier; -} - -REALM_EXPORT void realm_server_global_notifier_notification_get_changes(GlobalNotifier::ChangeNotification& change, - void* managed_callback, - NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - MarshaledChangeNotification notification; - - auto changes = change.get_changes(); - std::vector changesets; - changesets.reserve(changes.size()); - - std::vector> objkeys_storage; - std::vector> modifications_storage; - std::vector> columns_storage; - objkeys_storage.reserve(changes.size() * 2); - modifications_storage.reserve(changes.size()); - columns_storage.reserve(changes.size()); - - for (auto& changeset : changes) { - MarshaledChangeSet c; - c.class_name = changeset.first; - - auto get_objkeys = [](const ObjectChangeSet::ObjectSet& set) { - return std::vector(set.begin(), set.end()); - }; - - auto get_colkeys = [](const ObjectChangeSet::ObjectMapToColumnSet::mapped_type& set) { - return std::vector(set.begin(), set.end()); - }; - - objkeys_storage.push_back(get_objkeys(changeset.second.get_deletions())); - c.deletions = objkeys_storage.back(); - - objkeys_storage.push_back(get_objkeys(changeset.second.get_insertions())); - c.insertions = objkeys_storage.back(); - - modifications_storage.emplace_back(); - for (auto& modification : changeset.second.get_modifications()) { - columns_storage.push_back(get_colkeys(modification.second)); - modifications_storage.back().push_back({ ObjKey(modification.first), columns_storage.back() }); - } - c.modifications = modifications_storage.back(); - - changesets.push_back(std::move(c)); - } - - notification.changesets = changesets; - - notification.path = change.realm_path; - - if (auto previous = change.get_old_realm()) { - notification.previous = new SharedRealm(std::move(previous)); - } - auto newRealm = change.get_new_realm(); - - notification.path_on_disk = newRealm->config().path; - - notification.current = new SharedRealm(std::move(newRealm)); - - s_calculation_complete_callback(notification, managed_callback); - }); -} - -REALM_EXPORT void realm_server_global_notifier_notification_destroy(GlobalNotifier::ChangeNotification* notification) -{ - delete notification; -} - -} diff --git a/wrappers/src/list_cs.cpp b/wrappers/src/list_cs.cpp index ed6c37db73..61e2ce0b6f 100644 --- a/wrappers/src/list_cs.cpp +++ b/wrappers/src/list_cs.cpp @@ -37,7 +37,7 @@ inline void insert(List& list, size_t list_ndx, T value, NativeException::Marsha if (list_ndx > count) { throw IndexOutOfRangeException("Insert into RealmList", list_ndx, count); } - + list.insert(list_ndx, value); }); } @@ -50,7 +50,7 @@ inline void set(List& list, size_t list_ndx, T value, NativeException::Marshalla if (list_ndx >= count) { throw IndexOutOfRangeException("Set in RealmList", list_ndx, count); } - + list.set(list_ndx, value); }); } @@ -72,14 +72,14 @@ inline size_t find(List& list, T value, NativeException::Marshallable& ex) } extern "C" { - + REALM_EXPORT void list_add_object(List& list, const Object& object_ptr, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { list.add(object_ptr.obj()); }); } - + REALM_EXPORT void list_add_primitive(List& list, PrimitiveValue& value, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { @@ -90,25 +90,25 @@ REALM_EXPORT void list_add_primitive(List& list, PrimitiveValue& value, NativeEx list.add(value.value.bool_value); break; case realm::PropertyType::Bool | realm::PropertyType::Nullable: - list.add(value.has_value ? Optional(value.value.bool_value) : Optional(none)); + list.add(value.has_value ? util::Optional(value.value.bool_value) : util::Optional(none)); break; case realm::PropertyType::Int: list.add(value.value.int_value); break; case realm::PropertyType::Int | realm::PropertyType::Nullable: - list.add(value.has_value ? Optional(value.value.int_value) : Optional(none)); + list.add(value.has_value ? util::Optional(value.value.int_value) : util::Optional(none)); break; case realm::PropertyType::Float: list.add(value.value.float_value); break; case realm::PropertyType::Float | realm::PropertyType::Nullable: - list.add(value.has_value ? Optional(value.value.float_value) : Optional(none)); + list.add(value.has_value ? util::Optional(value.value.float_value) : util::Optional(none)); break; case realm::PropertyType::Double: list.add(value.value.double_value); break; case realm::PropertyType::Double | realm::PropertyType::Nullable: - list.add(value.has_value ? Optional(value.value.double_value) : Optional(none)); + list.add(value.has_value ? util::Optional(value.value.double_value) : util::Optional(none)); break; case realm::PropertyType::Date: list.add(from_ticks(value.value.int_value)); @@ -116,13 +116,25 @@ REALM_EXPORT void list_add_primitive(List& list, PrimitiveValue& value, NativeEx case realm::PropertyType::Date | realm::PropertyType::Nullable: list.add(value.has_value ? from_ticks(value.value.int_value) : Timestamp()); break; + case realm::PropertyType::Decimal: + list.add(realm::Decimal128(value.value.decimal_bits)); + break; + case realm::PropertyType::Decimal | realm::PropertyType::Nullable: + list.add(value.has_value ? realm::Decimal128(value.value.decimal_bits) : Decimal128(null())); + break; + case realm::PropertyType::ObjectId: + list.add(to_object_id(value)); + break; + case realm::PropertyType::ObjectId | realm::PropertyType::Nullable: + list.add(value.has_value ? util::Optional(to_object_id(value)) : util::Optional()); + break; default: REALM_UNREACHABLE(); } #pragma GCC diagnostic pop }); } - + REALM_EXPORT void list_add_string(List& list, uint16_t* value, size_t value_len, bool has_value, NativeException::Marshallable& ex) { if (has_value) { @@ -133,7 +145,7 @@ REALM_EXPORT void list_add_string(List& list, uint16_t* value, size_t value_len, add(list, StringData(), ex); } } - + REALM_EXPORT void list_add_binary(List& list, char* value, size_t value_len, bool has_value, NativeException::Marshallable& ex) { if (has_value) { @@ -143,7 +155,14 @@ REALM_EXPORT void list_add_binary(List& list, char* value, size_t value_len, boo add(list, BinaryData(), ex); } } - + +REALM_EXPORT Object* list_add_embedded(List& list, NativeException::Marshallable& ex) +{ + return handle_errors(ex, [&]() { + return new Object(list.get_realm(), list.get_object_schema(), list.add_embedded()); + }); +} + REALM_EXPORT void list_set_object(List& list, size_t list_ndx, const Object& object_ptr, NativeException::Marshallable& ex) { set(list, list_ndx, object_ptr.obj(), ex); @@ -154,7 +173,7 @@ REALM_EXPORT void list_set_primitive(List& list, size_t list_ndx, PrimitiveValue handle_errors(ex, [&]() { const size_t count = list.size(); if (list_ndx >= count) { - throw IndexOutOfRangeException("Insert into RealmList", list_ndx, count); + throw IndexOutOfRangeException("Set into RealmList", list_ndx, count); } #pragma GCC diagnostic push @@ -164,25 +183,25 @@ REALM_EXPORT void list_set_primitive(List& list, size_t list_ndx, PrimitiveValue list.set(list_ndx, value.value.bool_value); break; case realm::PropertyType::Bool | realm::PropertyType::Nullable: - list.set(list_ndx, value.has_value ? Optional(value.value.bool_value) : Optional(none)); + list.set(list_ndx, value.has_value ? util::Optional(value.value.bool_value) : util::Optional()); break; case realm::PropertyType::Int: list.set(list_ndx, value.value.int_value); break; case realm::PropertyType::Int | realm::PropertyType::Nullable: - list.set(list_ndx, value.has_value ? Optional(value.value.int_value) : Optional(none)); + list.set(list_ndx, value.has_value ? util::Optional(value.value.int_value) : util::Optional()); break; case realm::PropertyType::Float: list.set(list_ndx, value.value.float_value); break; case realm::PropertyType::Float | realm::PropertyType::Nullable: - list.set(list_ndx, value.has_value ? Optional(value.value.float_value) : Optional(none)); + list.set(list_ndx, value.has_value ? util::Optional(value.value.float_value) : util::Optional()); break; case realm::PropertyType::Double: list.set(list_ndx, value.value.double_value); break; case realm::PropertyType::Double | realm::PropertyType::Nullable: - list.set(list_ndx, value.has_value ? Optional(value.value.double_value) : Optional(none)); + list.set(list_ndx, value.has_value ? util::Optional(value.value.double_value) : util::Optional()); break; case realm::PropertyType::Date: list.set(list_ndx, from_ticks(value.value.int_value)); @@ -190,6 +209,18 @@ REALM_EXPORT void list_set_primitive(List& list, size_t list_ndx, PrimitiveValue case realm::PropertyType::Date | realm::PropertyType::Nullable: list.set(list_ndx, value.has_value ? from_ticks(value.value.int_value) : Timestamp()); break; + case realm::PropertyType::Decimal: + list.set(list_ndx, realm::Decimal128(value.value.decimal_bits)); + break; + case realm::PropertyType::Decimal | realm::PropertyType::Nullable: + list.set(list_ndx, value.has_value ? realm::Decimal128(value.value.decimal_bits) : Decimal128(null())); + break; + case realm::PropertyType::ObjectId: + list.set(list_ndx, to_object_id(value)); + break; + case realm::PropertyType::ObjectId | realm::PropertyType::Nullable: + list.set(list_ndx, value.has_value ? util::Optional(to_object_id(value)) : util::Optional()); + break; default: REALM_UNREACHABLE(); } @@ -217,12 +248,24 @@ REALM_EXPORT void list_set_binary(List& list, size_t list_ndx, char* value, size set(list, list_ndx, BinaryData(), ex); } } - + +REALM_EXPORT Object* list_set_embedded(List& list, size_t list_ndx, NativeException::Marshallable& ex) +{ + return handle_errors(ex, [&]() { + const size_t count = list.size(); + if (list_ndx >= count) { + throw IndexOutOfRangeException("Set in RealmList", list_ndx, count); + } + + return new Object(list.get_realm(), list.get_object_schema(), list.set_embedded(list_ndx)); + }); +} + REALM_EXPORT void list_insert_object(List& list, size_t list_ndx, const Object& object_ptr, NativeException::Marshallable& ex) { insert(list, list_ndx, object_ptr.obj(), ex); } - + REALM_EXPORT void list_insert_primitive(List& list, size_t list_ndx, PrimitiveValue& value, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { @@ -238,25 +281,25 @@ REALM_EXPORT void list_insert_primitive(List& list, size_t list_ndx, PrimitiveVa list.insert(list_ndx, value.value.bool_value); break; case realm::PropertyType::Bool | realm::PropertyType::Nullable: - list.insert(list_ndx, value.has_value ? Optional(value.value.bool_value) : Optional(none)); + list.insert(list_ndx, value.has_value ? util::Optional(value.value.bool_value) : util::Optional(none)); break; case realm::PropertyType::Int: list.insert(list_ndx, value.value.int_value); break; case realm::PropertyType::Int | realm::PropertyType::Nullable: - list.insert(list_ndx, value.has_value ? Optional(value.value.int_value) : Optional(none)); + list.insert(list_ndx, value.has_value ? util::Optional(value.value.int_value) : util::Optional(none)); break; case realm::PropertyType::Float: list.insert(list_ndx, value.value.float_value); break; case realm::PropertyType::Float | realm::PropertyType::Nullable: - list.insert(list_ndx, value.has_value ? Optional(value.value.float_value) : Optional(none)); + list.insert(list_ndx, value.has_value ? util::Optional(value.value.float_value) : util::Optional(none)); break; case realm::PropertyType::Double: list.insert(list_ndx, value.value.double_value); break; case realm::PropertyType::Double | realm::PropertyType::Nullable: - list.insert(list_ndx, value.has_value ? Optional(value.value.double_value) : Optional(none)); + list.insert(list_ndx, value.has_value ? util::Optional(value.value.double_value) : util::Optional(none)); break; case realm::PropertyType::Date: list.insert(list_ndx, from_ticks(value.value.int_value)); @@ -264,6 +307,18 @@ REALM_EXPORT void list_insert_primitive(List& list, size_t list_ndx, PrimitiveVa case realm::PropertyType::Date | realm::PropertyType::Nullable: list.insert(list_ndx, value.has_value ? from_ticks(value.value.int_value) : Timestamp()); break; + case realm::PropertyType::Decimal: + list.insert(list_ndx, realm::Decimal128(value.value.decimal_bits)); + break; + case realm::PropertyType::Decimal | realm::PropertyType::Nullable: + list.insert(list_ndx, value.has_value ? realm::Decimal128(value.value.decimal_bits) : Decimal128(null())); + break; + case realm::PropertyType::ObjectId: + list.insert(list_ndx, to_object_id(value)); + break; + case realm::PropertyType::ObjectId | realm::PropertyType::Nullable: + list.insert(list_ndx, value.has_value ? util::Optional(to_object_id(value)) : util::Optional()); + break; default: REALM_UNREACHABLE(); } @@ -292,32 +347,44 @@ REALM_EXPORT void list_insert_binary(List& list, size_t list_ndx, char* value, s } } +REALM_EXPORT Object* list_insert_embedded(List& list, size_t list_ndx, NativeException::Marshallable& ex) +{ + return handle_errors(ex, [&]() { + const size_t count = list.size(); + if (list_ndx > count) { + throw IndexOutOfRangeException("Insert into RealmList", list_ndx, count); + } + + return new Object(list.get_realm(), list.get_object_schema(), list.insert_embedded(list_ndx)); + }); +} + REALM_EXPORT Object* list_get_object(List& list, size_t ndx, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() -> Object* { const size_t count = list.size(); if (ndx >= count) throw IndexOutOfRangeException("Get from RealmList", ndx, count); - + return new Object(list.get_realm(), list.get_object_schema(), list.get(ndx)); }); } - + REALM_EXPORT void list_get_primitive(List& list, size_t ndx, PrimitiveValue& value, NativeException::Marshallable& ex) { collection_get_primitive(list, ndx, value, ex); } - + REALM_EXPORT size_t list_get_string(List& list, size_t ndx, uint16_t* value, size_t value_len, bool* is_null, NativeException::Marshallable& ex) { return collection_get_string(list, ndx, value, value_len, is_null, ex); } - + REALM_EXPORT size_t list_get_binary(List& list, size_t ndx, char* return_buffer, size_t buffer_size, bool* is_null, NativeException::Marshallable& ex) { return collection_get_binary(list, ndx, return_buffer, buffer_size, is_null, ex); } - + REALM_EXPORT size_t list_find_object(List& list, const Object& object_ptr, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { @@ -328,7 +395,7 @@ REALM_EXPORT size_t list_find_object(List& list, const Object& object_ptr, Nativ return list.find(object_ptr.obj()); }); } - + REALM_EXPORT size_t list_find_primitive(List& list, PrimitiveValue& value, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { @@ -338,46 +405,54 @@ REALM_EXPORT size_t list_find_primitive(List& list, PrimitiveValue& value, Nativ case realm::PropertyType::Bool: return list.find(value.value.bool_value); case realm::PropertyType::Bool | realm::PropertyType::Nullable: - return list.find(value.has_value ? Optional(value.value.bool_value) : Optional(none)); + return list.find(value.has_value ? util::Optional(value.value.bool_value) : util::Optional(none)); case realm::PropertyType::Int: return list.find(value.value.int_value); case realm::PropertyType::Int | realm::PropertyType::Nullable: - return list.find(value.has_value ? Optional(value.value.int_value) : Optional(none)); + return list.find(value.has_value ? util::Optional(value.value.int_value) : util::Optional(none)); case realm::PropertyType::Float: return list.find(value.value.float_value); case realm::PropertyType::Float | realm::PropertyType::Nullable: - return list.find(value.has_value ? Optional(value.value.float_value) : Optional(none)); + return list.find(value.has_value ? util::Optional(value.value.float_value) : util::Optional(none)); case realm::PropertyType::Double: return list.find(value.value.double_value); case realm::PropertyType::Double | realm::PropertyType::Nullable: - return list.find(value.has_value ? Optional(value.value.double_value) : Optional(none)); + return list.find(value.has_value ? util::Optional(value.value.double_value) : util::Optional(none)); case realm::PropertyType::Date: return list.find(from_ticks(value.value.int_value)); case realm::PropertyType::Date | realm::PropertyType::Nullable: return list.find(value.has_value ? from_ticks(value.value.int_value) : Timestamp()); + case realm::PropertyType::Decimal: + return list.find(realm::Decimal128(value.value.decimal_bits)); + case realm::PropertyType::Decimal | realm::PropertyType::Nullable: + return list.find(value.has_value ? realm::Decimal128(value.value.decimal_bits) : Decimal128(null())); + case realm::PropertyType::ObjectId: + return list.find(to_object_id(value)); + case realm::PropertyType::ObjectId | realm::PropertyType::Nullable: + return list.find(value.has_value ? util::Optional(to_object_id(value)) : util::Optional()); default: REALM_UNREACHABLE(); } #pragma GCC diagnostic pop }); } - + REALM_EXPORT size_t list_find_string(List& list, uint16_t* value, size_t value_len, bool has_value, NativeException::Marshallable& ex) { if (has_value) { Utf16StringAccessor str(value, value_len); return find(list, (StringData)str, ex); } - + return find(list, StringData(), ex); } - + REALM_EXPORT size_t list_find_binary(List& list, char* value, size_t value_len, bool has_value, NativeException::Marshallable& ex) { if (has_value) { return find(list, BinaryData(value, value_len), ex); } - + return find(list, BinaryData(), ex); } @@ -388,7 +463,7 @@ REALM_EXPORT void list_erase(List& list, size_t link_ndx, NativeException::Marsh const size_t count = list.size(); if (link_ndx >= count) throw IndexOutOfRangeException("Erase item in RealmList", link_ndx, count); - + list.remove(link_ndx); }); } @@ -406,12 +481,12 @@ REALM_EXPORT size_t list_size(List& list, NativeException::Marshallable& ex) return list.size(); }); } - + REALM_EXPORT void list_destroy(List* list) { delete list; } - + REALM_EXPORT ManagedNotificationTokenContext* list_add_notification_callback(List* list, void* managed_list, ManagedNotificationCallback callback, NativeException::Marshallable& ex) { return handle_errors(ex, [=]() { @@ -420,17 +495,17 @@ REALM_EXPORT ManagedNotificationTokenContext* list_add_notification_callback(Lis }); }); } - + REALM_EXPORT void list_move(List& list, size_t source_ndx, size_t dest_ndx, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { const size_t count = list.size(); - + // Indices are >= 0 validated by .NET if (dest_ndx >= count) { throw IndexOutOfRangeException("Move within RealmList", dest_ndx, count); } - + if (source_ndx >= count) { throw IndexOutOfRangeException("Move within RealmList", source_ndx, count); } @@ -438,7 +513,7 @@ REALM_EXPORT void list_move(List& list, size_t source_ndx, size_t dest_ndx, Nati list.move(source_ndx, dest_ndx); }); } - + REALM_EXPORT bool list_get_is_valid(const List& list, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { diff --git a/wrappers/src/marshalling.hpp b/wrappers/src/marshalling.hpp index 65cc355a6f..15c0aa9bdc 100644 --- a/wrappers/src/marshalling.hpp +++ b/wrappers/src/marshalling.hpp @@ -30,18 +30,26 @@ namespace binding { struct PrimitiveValue { - realm::PropertyType type; - bool has_value; - char padding[6]; - union { bool bool_value; int64_t int_value; float float_value; double double_value; + realm::Decimal128::Bid128 decimal_bits; + uint8_t object_id_bytes[12]; } value; + + realm::PropertyType type; + bool has_value; }; - + +inline ObjectId to_object_id(PrimitiveValue primitive) +{ + std::array bytes; + std::copy(std::begin(primitive.value.object_id_bytes), std::end(primitive.value.object_id_bytes), bytes.begin()); + return ObjectId(std::move(bytes)); +} + struct StringValue { const char* value; @@ -67,7 +75,7 @@ struct MarshaledVector { } }; - + template void collection_get_primitive(Collection& collection, size_t ndx, PrimitiveValue& value, NativeException::Marshallable& ex) { @@ -75,63 +83,97 @@ void collection_get_primitive(Collection& collection, size_t ndx, PrimitiveValue const size_t count = collection.size(); if (ndx >= count) throw IndexOutOfRangeException("Get from Collection", ndx, count); - + value.has_value = true; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch" switch (value.type) { - case realm::PropertyType::Bool: - value.value.bool_value = collection.template get(ndx); - break; - case realm::PropertyType::Bool | realm::PropertyType::Nullable: { - auto result = collection.template get>(ndx); - value.has_value = !!result; - value.value.bool_value = result.value_or(false); - break; - } - case realm::PropertyType::Int: - value.value.int_value = collection.template get(ndx); - break; - case realm::PropertyType::Int | realm::PropertyType::Nullable: { - auto result = collection.template get>(ndx); - value.has_value = !!result; - value.value.int_value = result.value_or(0); - break; - } - case realm::PropertyType::Float: - value.value.float_value = collection.template get(ndx); - break; - case realm::PropertyType::Float | realm::PropertyType::Nullable: { - auto result = collection.template get>(ndx); - value.has_value = !!result; - value.value.float_value = result.value_or((float)0); - break; + case realm::PropertyType::Bool: + value.value.bool_value = collection.template get(ndx); + break; + case realm::PropertyType::Bool | realm::PropertyType::Nullable: { + auto result = collection.template get>(ndx); + value.has_value = !!result; + value.value.bool_value = result.value_or(false); + break; + } + case realm::PropertyType::Int: + value.value.int_value = collection.template get(ndx); + break; + case realm::PropertyType::Int | realm::PropertyType::Nullable: { + auto result = collection.template get>(ndx); + value.has_value = !!result; + value.value.int_value = result.value_or(0); + break; + } + case realm::PropertyType::Float: + value.value.float_value = collection.template get(ndx); + break; + case realm::PropertyType::Float | realm::PropertyType::Nullable: { + auto result = collection.template get>(ndx); + value.has_value = !!result; + value.value.float_value = result.value_or((float)0); + break; + } + case realm::PropertyType::Double: + value.value.double_value = collection.template get(ndx); + break; + case realm::PropertyType::Double | realm::PropertyType::Nullable: { + auto result = collection.template get>(ndx); + value.has_value = !!result; + value.value.double_value = result.value_or((double)0); + break; + } + case realm::PropertyType::Date: + value.value.int_value = to_ticks(collection.template get(ndx)); + break; + case realm::PropertyType::Date | realm::PropertyType::Nullable: { + auto result = collection.template get(ndx); + value.has_value = !result.is_null(); + value.value.int_value = result.is_null() ? 0 : to_ticks(result); + break; + } + case realm::PropertyType::Decimal: { + auto result = collection.template get(ndx); + value.value.decimal_bits = *result.raw(); + break; + } + case realm::PropertyType::Decimal | realm::PropertyType::Nullable: { + auto result = collection.template get(ndx); + value.has_value = !result.is_null(); + if (value.has_value) { + value.value.decimal_bits = *result.raw(); } - case realm::PropertyType::Double: - value.value.double_value = collection.template get(ndx); - break; - case realm::PropertyType::Double | realm::PropertyType::Nullable: { - auto result = collection.template get>(ndx); - value.has_value = !!result; - value.value.double_value = result.value_or((double)0); - break; + break; + } + case realm::PropertyType::ObjectId: { + auto result = collection.template get(ndx); + auto bytes = result.to_bytes(); + for (int i = 0; i < 12; i++) + { + value.value.object_id_bytes[i] = bytes[i]; } - case realm::PropertyType::Date: - value.value.int_value = to_ticks(collection.template get(ndx)); - break; - case realm::PropertyType::Date | realm::PropertyType::Nullable: { - auto result = collection.template get(ndx); - value.has_value = !result.is_null(); - value.value.int_value = result.is_null() ? 0 : to_ticks(result); - break; + break; + } + case realm::PropertyType::ObjectId | realm::PropertyType::Nullable: { + auto result = collection.template get>(ndx); + value.has_value = !!result; + if (value.has_value) { + auto bytes = result.value().to_bytes(); + for (int i = 0; i < 12; i++) + { + value.value.object_id_bytes[i] = bytes[i]; + } } - default: - REALM_UNREACHABLE(); + break; + } + default: + REALM_UNREACHABLE(); } #pragma GCC diagnostic pop }); } - + template inline T get(Collection& collection, size_t ndx, NativeException::Marshallable& ex) { @@ -139,11 +181,11 @@ inline T get(Collection& collection, size_t ndx, NativeException::Marshallable& const size_t count = collection.size(); if (ndx >= count) throw IndexOutOfRangeException("Get from RealmList", ndx, count); - + return collection.template get(ndx); }); } - + class Utf16StringAccessor { public: Utf16StringAccessor(const uint16_t* csbuffer, size_t csbufsize); @@ -162,7 +204,7 @@ class Utf16StringAccessor { { return std::string(m_data.get(), m_size); } - + const char* data() const { return m_data.get(); } size_t size() const { return m_size; } @@ -172,16 +214,16 @@ class Utf16StringAccessor { std::size_t m_size; }; -size_t stringdata_to_csharpstringbuffer(StringData str, uint16_t * csharpbuffer, size_t bufsize); //note bufsize is _in_16bit_words +size_t stringdata_to_csharpstringbuffer(StringData str, uint16_t * csharpbuffer, size_t bufsize); //note bufsize is _in_16bit_words template size_t collection_get_string(Collection& collection, size_t ndx, uint16_t* value, size_t value_len, bool* is_null, NativeException::Marshallable& ex) { auto result = get(collection, ndx, ex); - + if ((*is_null = result.is_null())) return 0; - + return stringdata_to_csharpstringbuffer(result, value, value_len); } @@ -189,16 +231,16 @@ template size_t collection_get_binary(Collection& collection, size_t ndx, char* return_buffer, size_t buffer_size, bool* is_null, NativeException::Marshallable& ex) { auto result = get(collection, ndx, ex); - + if ((*is_null = result.is_null())) return 0; - + const size_t data_size = result.size(); if (data_size <= buffer_size) std::copy(result.data(), result.data() + data_size, return_buffer); - + return data_size; } - + } // namespace binding } // namespace realm diff --git a/wrappers/src/mongo_collection_cs.cpp b/wrappers/src/mongo_collection_cs.cpp new file mode 100644 index 0000000000..9f2fddc2c8 --- /dev/null +++ b/wrappers/src/mongo_collection_cs.cpp @@ -0,0 +1,200 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include +#include "error_handling.hpp" +#include "marshalling.hpp" +#include "realm_export_decls.hpp" +#include "sync/sync_manager.hpp" +#include "sync/sync_user.hpp" +#include "sync/sync_session.hpp" +#include "sync/app.hpp" +#include "sync/remote_mongo_client.hpp" +#include "sync/remote_mongo_database.hpp" +#include "sync/remote_mongo_collection.hpp" +#include "app_cs.hpp" + +using namespace realm; +using namespace realm::binding; +using namespace app; + +using SharedSyncUser = std::shared_ptr; + +struct FindAndModifyOptions +{ + uint16_t* projection_buf; + size_t projection_len; + uint16_t* sort_buf; + size_t sort_len; + + bool upsert; + bool return_new_document; + int64_t limit; + + MongoCollection::FindOneAndModifyOptions to_find_and_modify_options() { + MongoCollection::FindOneAndModifyOptions options; + + if (projection_buf != nullptr) { + options.projection_bson = to_document(projection_buf, projection_len); + } + + if (sort_buf != nullptr) { + options.sort_bson = to_document(sort_buf, sort_len); + } + + options.upsert = upsert; + options.return_new_document = return_new_document; + + return options; + } + + MongoCollection::FindOptions to_find_options() { + MongoCollection::FindOptions options; + + if (projection_buf != nullptr) { + options.projection_bson = to_document(projection_buf, projection_len); + } + + if (sort_buf != nullptr) { + options.sort_bson = to_document(sort_buf, sort_len); + } + + if (limit != 0) { + options.limit = limit; + } + + return options; + } +}; + +extern "C" { + REALM_EXPORT MongoCollection* realm_mongo_collection_get(SharedSyncUser& user, + uint16_t* service_buf, size_t service_len, + uint16_t* database_buf, size_t database_len, + uint16_t* collection_buf, size_t collection_len, + NativeException::Marshallable& ex) { + return handle_errors(ex, [&]() { + Utf16StringAccessor service(service_buf, service_len); + Utf16StringAccessor database(database_buf, database_len); + Utf16StringAccessor collection(collection_buf, collection_len); + + auto col = user->mongo_client(service).db(database).collection(collection); + return new MongoCollection(col); + }); + } + + REALM_EXPORT void realm_mongo_collection_destroy(MongoCollection* collection) + { + delete collection; + } + + REALM_EXPORT void realm_mongo_collection_find(MongoCollection& collection, uint16_t* filter_buf, size_t filter_len, FindAndModifyOptions options, void* tcs_ptr, NativeException::Marshallable& ex) { + handle_errors(ex, [&]() { + auto filter = to_document(filter_buf, filter_len); + collection.find_bson(filter, options.to_find_options(), get_bson_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_mongo_collection_find_one(MongoCollection& collection, uint16_t* filter_buf, size_t filter_len, FindAndModifyOptions options, void* tcs_ptr, NativeException::Marshallable& ex) { + handle_errors(ex, [&]() { + auto filter = to_document(filter_buf, filter_len); + collection.find_one_bson(filter, options.to_find_options(), get_bson_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_mongo_collection_aggregate(MongoCollection& collection, uint16_t* pipeline_buf, size_t pipeline_len, void* tcs_ptr, NativeException::Marshallable& ex) { + handle_errors(ex, [&]() { + auto pipeline = to_array(pipeline_buf, pipeline_len); + collection.aggregate_bson(pipeline, get_bson_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_mongo_collection_count(MongoCollection& collection, uint16_t* filter_buf, size_t filter_len, int64_t limit, void* tcs_ptr, NativeException::Marshallable& ex) { + handle_errors(ex, [&]() { + auto filter = to_document(filter_buf, filter_len); + collection.count_bson(filter, limit, get_bson_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_mongo_collection_insert_one(MongoCollection& collection, uint16_t* doc_buf, size_t doc_len, void* tcs_ptr, NativeException::Marshallable& ex) { + handle_errors(ex, [&]() { + auto doc = to_document(doc_buf, doc_len); + collection.insert_one_bson(doc, get_bson_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_mongo_collection_insert_many(MongoCollection& collection, uint16_t* docs_buf, size_t docs_len, void* tcs_ptr, NativeException::Marshallable& ex) { + handle_errors(ex, [&]() { + auto docs = to_array(docs_buf, docs_len); + collection.insert_many_bson(docs, get_bson_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_mongo_collection_delete_one(MongoCollection& collection, uint16_t* filter_buf, size_t filter_len, void* tcs_ptr, NativeException::Marshallable& ex) { + handle_errors(ex, [&]() { + auto filter = to_document(filter_buf, filter_len); + collection.delete_one_bson(filter, get_bson_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_mongo_collection_delete_many(MongoCollection& collection, uint16_t* filter_buf, size_t filter_len, void* tcs_ptr, NativeException::Marshallable& ex) { + handle_errors(ex, [&]() { + auto filter = to_document(filter_buf, filter_len); + collection.delete_many_bson(filter, get_bson_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_mongo_collection_update_one(MongoCollection& collection, uint16_t* filter_buf, size_t filter_len, uint16_t* doc_buf, size_t doc_len, bool upsert, void* tcs_ptr, NativeException::Marshallable& ex) { + handle_errors(ex, [&]() { + auto filter = to_document(filter_buf, filter_len); + auto doc = to_document(doc_buf, doc_len); + collection.update_one_bson(filter, doc, upsert, get_bson_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_mongo_collection_update_many(MongoCollection& collection, uint16_t* filter_buf, size_t filter_len, uint16_t* doc_buf, size_t doc_len, bool upsert, void* tcs_ptr, NativeException::Marshallable& ex) { + handle_errors(ex, [&]() { + auto filter = to_document(filter_buf, filter_len); + auto doc = to_document(doc_buf, doc_len); + collection.update_many_bson(filter, doc, upsert, get_bson_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_mongo_collection_find_one_and_update(MongoCollection& collection, uint16_t* filter_buf, size_t filter_len, uint16_t* doc_buf, size_t doc_len, FindAndModifyOptions options, void* tcs_ptr, NativeException::Marshallable& ex) { + handle_errors(ex, [&]() { + auto filter = to_document(filter_buf, filter_len); + auto doc = to_document(doc_buf, doc_len); + collection.find_one_and_update_bson(filter, doc, options.to_find_and_modify_options(), get_bson_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_mongo_collection_find_one_and_replace(MongoCollection& collection, uint16_t* filter_buf, size_t filter_len, uint16_t* doc_buf, size_t doc_len, FindAndModifyOptions options, void* tcs_ptr, NativeException::Marshallable& ex) { + handle_errors(ex, [&]() { + auto filter = to_document(filter_buf, filter_len); + auto doc = to_document(doc_buf, doc_len); + collection.find_one_and_replace_bson(filter, doc, options.to_find_and_modify_options(), get_bson_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_mongo_collection_find_one_and_delete(MongoCollection& collection, uint16_t* filter_buf, size_t filter_len, FindAndModifyOptions options, void* tcs_ptr, NativeException::Marshallable& ex) { + handle_errors(ex, [&]() { + auto filter = to_document(filter_buf, filter_len); + collection.find_one_and_delete_bson(filter, options.to_find_and_modify_options(), get_bson_callback_handler(tcs_ptr)); + }); + } +} diff --git a/wrappers/src/object-store b/wrappers/src/object-store index 61531b5d76..d0ac41bee0 160000 --- a/wrappers/src/object-store +++ b/wrappers/src/object-store @@ -1 +1 @@ -Subproject commit 61531b5d76d5ae68c8fb4aac500827e045c765f1 +Subproject commit d0ac41bee0ccab83eb887b9fff8f417875ecf771 diff --git a/wrappers/src/object_cs.cpp b/wrappers/src/object_cs.cpp index 08cec71370..74406729f1 100644 --- a/wrappers/src/object_cs.cpp +++ b/wrappers/src/object_cs.cpp @@ -27,7 +27,6 @@ #include #include - using namespace realm; using namespace realm::binding; @@ -37,24 +36,7 @@ inline T object_get(const Object& object, size_t property_ndx, NativeException:: return handle_errors(ex, [&]() { verify_can_get(object); - const ColKey column_key = get_column_key(object, property_ndx); - return object.obj().get(column_key); - }); -} - -template -inline bool object_get_nullable(const Object& object, size_t property_ndx, T& ret_value, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - verify_can_get(object); - - auto result = object.obj().get>(get_column_key(object, property_ndx)); - if (!result) { - return false; - } - - ret_value = *result; - return true; + return object.obj().get(get_column_key(object, property_ndx)); }); } @@ -93,8 +75,7 @@ extern "C" { return handle_errors(ex, [&]() -> Object* { verify_can_get(object); - const ColKey column_key = get_column_key(object, property_ndx); - const Obj link_obj = object.obj().get_linked_object(column_key); + const Obj link_obj = object.obj().get_linked_object(get_column_key(object, property_ndx)); if (!link_obj) return nullptr; @@ -109,49 +90,169 @@ extern "C" { return handle_errors(ex, [&]() -> List* { verify_can_get(object); - const ColKey column_key = get_column_key(object, property_ndx); - return new List(object.realm(), object.obj(), column_key); + return new List(object.realm(), object.obj(), get_column_key(object, property_ndx)); }); } - REALM_EXPORT bool object_get_bool(const Object& object, size_t property_ndx, NativeException::Marshallable& ex) - { - return object_get(object, property_ndx, ex); - } - - REALM_EXPORT bool object_get_nullable_bool(const Object& object, size_t property_ndx, bool& ret_value, NativeException::Marshallable& ex) - { - return object_get_nullable(object, property_ndx, ret_value, ex); - } - - REALM_EXPORT int64_t object_get_int64(const Object& object, size_t property_ndx, NativeException::Marshallable& ex) - { - return object_get(object, property_ndx, ex); - } - - REALM_EXPORT bool object_get_nullable_int64(const Object& object, size_t property_ndx, int64_t& ret_value, NativeException::Marshallable& ex) - { - return object_get_nullable(object, property_ndx, ret_value, ex); - } - - REALM_EXPORT float object_get_float(const Object& object, size_t property_ndx, NativeException::Marshallable& ex) + REALM_EXPORT void object_get_primitive(const Object& object, size_t property_ndx, PrimitiveValue& value, NativeException::Marshallable& ex) { - return object_get(object, property_ndx, ex); - } + handle_errors(ex, [&]() { + verify_can_get(object); - REALM_EXPORT bool object_get_nullable_float(const Object& object, size_t property_ndx, float& ret_value, NativeException::Marshallable& ex) - { - return object_get_nullable(object, property_ndx, ret_value, ex); + value.has_value = true; + auto column_key = get_column_key(object, property_ndx); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" + switch (value.type) { + case realm::PropertyType::Bool: + value.value.bool_value = object.obj().get(std::move(column_key)); + break; + case realm::PropertyType::Bool | realm::PropertyType::Nullable: { + auto result = object.obj().get>(std::move(column_key)); + value.has_value = !!result; + value.value.bool_value = result.value_or(false); + break; + } + case realm::PropertyType::Int: + value.value.int_value = object.obj().get(std::move(column_key)); + break; + case realm::PropertyType::Int | realm::PropertyType::Nullable: { + auto result = object.obj().get>(std::move(column_key)); + value.has_value = !!result; + value.value.int_value = result.value_or(0); + break; + } + case realm::PropertyType::Float: + value.value.float_value = object.obj().get(std::move(column_key)); + break; + case realm::PropertyType::Float | realm::PropertyType::Nullable: { + auto result = object.obj().get>(std::move(column_key)); + value.has_value = !!result; + value.value.float_value = result.value_or((float)0); + break; + } + case realm::PropertyType::Double: + value.value.double_value = object.obj().get(std::move(column_key)); + break; + case realm::PropertyType::Double | realm::PropertyType::Nullable: { + auto result = object.obj().get>(std::move(column_key)); + value.has_value = !!result; + value.value.double_value = result.value_or((double)0); + break; + } + case realm::PropertyType::Date: + value.value.int_value = to_ticks(object.obj().get(std::move(column_key))); + break; + case realm::PropertyType::Date | realm::PropertyType::Nullable: { + auto result = object.obj().get(std::move(column_key)); + value.has_value = !result.is_null(); + value.value.int_value = result.is_null() ? 0 : to_ticks(result); + break; + } + case realm::PropertyType::Decimal: { + auto result = object.obj().get(std::move(column_key)); + value.value.decimal_bits = *result.raw(); + break; + } + case realm::PropertyType::Decimal | realm::PropertyType::Nullable: { + auto result = object.obj().get(std::move(column_key)); + value.has_value = !result.is_null(); + if (value.has_value) { + value.value.decimal_bits = *result.raw(); + } + break; + } + case realm::PropertyType::ObjectId: { + auto result = object.obj().get(std::move(column_key)); + auto bytes = result.to_bytes(); + for (int i = 0; i < 12; i++) + { + value.value.object_id_bytes[i] = bytes[i]; + } + break; + } + case realm::PropertyType::ObjectId | realm::PropertyType::Nullable: { + auto result = object.obj().get>(std::move(column_key)); + value.has_value = !!result; + if (value.has_value) { + auto bytes = result.value().to_bytes(); + for (int i = 0; i < 12; i++) + { + value.value.object_id_bytes[i] = bytes[i]; + } + } + break; + } + default: + REALM_UNREACHABLE(); + } +#pragma GCC diagnostic pop + }); } - REALM_EXPORT double object_get_double(const Object& object, size_t property_ndx, NativeException::Marshallable& ex) + REALM_EXPORT void object_set_primitive(const Object& object, size_t property_ndx, PrimitiveValue& value, NativeException::Marshallable& ex) { - return object_get(object, property_ndx, ex); - } + handle_errors(ex, [&]() { + verify_can_set(object); - REALM_EXPORT bool object_get_nullable_double(const Object& object, size_t property_ndx, double& ret_value, NativeException::Marshallable& ex) - { - return object_get_nullable(object, property_ndx, ret_value, ex); + auto column_key = get_column_key(object, property_ndx); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" + switch (value.type) { + case realm::PropertyType::Bool: + object.obj().set(std::move(column_key), value.value.bool_value); + break; + case realm::PropertyType::Bool | realm::PropertyType::Nullable: + object.obj().set(std::move(column_key), value.has_value ? util::Optional(value.value.bool_value) : util::Optional(none)); + break; + case realm::PropertyType::Int: + object.obj().set(std::move(column_key), value.value.int_value); + break; + case realm::PropertyType::Int | realm::PropertyType::Nullable: + object.obj().set(std::move(column_key), value.has_value ? util::Optional(value.value.int_value) : util::Optional(none)); + break; + case realm::PropertyType::Float: + object.obj().set(std::move(column_key), value.value.float_value); + break; + case realm::PropertyType::Float | realm::PropertyType::Nullable: + object.obj().set(std::move(column_key), value.has_value ? util::Optional(value.value.float_value) : util::Optional(none)); + break; + case realm::PropertyType::Double: + object.obj().set(std::move(column_key), value.value.double_value); + break; + case realm::PropertyType::Double | realm::PropertyType::Nullable: + object.obj().set(std::move(column_key), value.has_value ? util::Optional(value.value.double_value) : util::Optional(none)); + break; + case realm::PropertyType::Date: + object.obj().set(std::move(column_key), from_ticks(value.value.int_value)); + break; + case realm::PropertyType::Date | realm::PropertyType::Nullable: + object.obj().set(std::move(column_key), value.has_value ? from_ticks(value.value.int_value) : Timestamp()); + break; + case realm::PropertyType::Decimal: { + object.obj().set(std::move(column_key), realm::Decimal128(value.value.decimal_bits)); + break; + } + case realm::PropertyType::Decimal | realm::PropertyType::Nullable: { + auto decimal = value.has_value ? realm::Decimal128(value.value.decimal_bits) : Decimal128(null()); + object.obj().set(std::move(column_key), decimal); + break; + } + case realm::PropertyType::ObjectId: { + object.obj().set(std::move(column_key), to_object_id(value)); + break; + } + case realm::PropertyType::ObjectId | realm::PropertyType::Nullable: { + object.obj().set(std::move(column_key), value.has_value ? util::Optional(to_object_id(value)) : util::Optional()); + break; + } + default: + REALM_UNREACHABLE(); + } +#pragma GCC diagnostic pop + }); } REALM_EXPORT size_t object_get_string(const Object& object, size_t property_ndx, uint16_t* string_buffer, size_t buffer_size, bool& is_null, NativeException::Marshallable& ex) @@ -187,26 +288,6 @@ extern "C" { return data_size; } - REALM_EXPORT int64_t object_get_timestamp_ticks(const Object& object, size_t property_ndx, NativeException::Marshallable& ex) - { - return to_ticks(object_get(object, property_ndx, ex)); - } - - REALM_EXPORT bool object_get_nullable_timestamp_ticks(const Object& object, size_t property_ndx, int64_t& ret_value, NativeException::Marshallable& ex) - { - Timestamp field_data = object_get(object, property_ndx, ex); - if (ex.type != RealmErrorType::NoError) { - return false; - } - - if (field_data.is_null()) { - return false; - } - - ret_value = to_ticks(field_data); - return true; - } - REALM_EXPORT Results* object_get_backlinks(Object& object, size_t property_ndx, NativeException::Marshallable& ex) { return handle_errors(ex, [&] { @@ -229,24 +310,33 @@ extern "C" { { return handle_errors(ex, [&] { verify_can_get(object); - + const ObjectSchema& source_object_schema = *object.realm()->schema().find(ObjectStore::object_type_for_table_name(source_table->get_name())); const Property& source_property = source_object_schema.persisted_properties[source_property_ndx]; - + if (source_property.object_type != object.get_object_schema().name) { throw std::logic_error(util::format("'%1.%2' is not a relationship to '%3'", source_object_schema.name, source_property.name, object.get_object_schema().name)); } - + TableView backlink_view = object.obj().get_backlink_view(source_table, source_property.column_key); return new Results(object.realm(), std::move(backlink_view)); }); } - + REALM_EXPORT void object_set_link(Object& object, size_t property_ndx, const Object& target_object, NativeException::Marshallable& ex) { return object_set(object, property_ndx, target_object.obj().get_key(), ex); } + REALM_EXPORT Object* object_create_embedded(Object& parent, size_t property_ndx, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&]() { + verify_can_set(parent); + + return new Object(parent.realm(), parent.obj().create_and_set_linked_object(get_column_key(parent, property_ndx))); + }); + } + REALM_EXPORT void object_clear_link(Object& object, size_t property_ndx, NativeException::Marshallable& ex) { return object_set(object, property_ndx, null_key, ex); @@ -257,50 +347,25 @@ extern "C" { return handle_errors(ex, [&]() { verify_can_set(object); - const ColKey column_key = get_column_key(object, property_ndx); + auto column_key = get_column_key(object, property_ndx); if (!object.obj().get_table()->is_nullable(column_key)) throw std::invalid_argument("Column is not nullable"); object.obj().set_null(column_key); }); } - - REALM_EXPORT void object_set_bool(Object& object, size_t property_ndx, bool value, NativeException::Marshallable& ex) - { - return object_set(object, property_ndx, value, ex); - } - - REALM_EXPORT void object_set_int64(Object& object, size_t property_ndx, int64_t value, NativeException::Marshallable& ex) - { - return object_set(object, property_ndx, value, ex); - } - - REALM_EXPORT void object_set_float(Object& object, size_t property_ndx, float value, NativeException::Marshallable& ex) - { - return object_set(object, property_ndx, value, ex); - } - - REALM_EXPORT void object_set_double(Object& object, size_t property_ndx, double value, NativeException::Marshallable& ex) - { - return object_set(object, property_ndx, value, ex); - } REALM_EXPORT void object_set_string(Object& object, size_t property_ndx, uint16_t* value, size_t value_len, NativeException::Marshallable& ex) { Utf16StringAccessor str(value, value_len); return object_set(object, property_ndx, str, ex); } - + REALM_EXPORT void object_set_binary(Object& object, size_t property_ndx, char* value, size_t value_len, NativeException::Marshallable& ex) { return object_set(object, property_ndx, BinaryData(value, value_len), ex); } - REALM_EXPORT void object_set_timestamp_ticks(Object& object, size_t property_ndx, int64_t value, NativeException::Marshallable& ex) - { - return object_set(object, property_ndx, from_ticks(value), ex); - } - REALM_EXPORT void object_remove(Object& object, SharedRealm& realm, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { @@ -348,14 +413,13 @@ extern "C" { }, new ObjectSchema(object->get_object_schema())); }); } - + REALM_EXPORT void object_add_int64(Object& object, size_t property_ndx, int64_t value, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { verify_can_set(object); - - const ColKey column_key = get_column_key(object, property_ndx); - object.obj().add_int(column_key, value); + + object.obj().add_int(get_column_key(object, property_ndx), value); }); } diff --git a/wrappers/src/object_cs.hpp b/wrappers/src/object_cs.hpp index ac667df700..63cfad52cd 100644 --- a/wrappers/src/object_cs.hpp +++ b/wrappers/src/object_cs.hpp @@ -27,24 +27,24 @@ namespace realm { inline void verify_can_get(const Object& object) { if (object.realm()->is_closed()) throw RealmClosedException(); - + if (!object.is_valid()) throw RowDetachedException(); - + object.realm()->verify_thread(); } - + inline void verify_can_set(const Object& object) { if (object.realm()->is_closed()) throw RealmClosedException(); - + if (!object.is_valid()) throw RowDetachedException(); - + object.realm()->verify_in_write(); } - - inline ColKey get_column_key(const Object& object, const size_t property_index) { - return object.get_object_schema().persisted_properties[property_index].column_key; + + inline const ColKey get_column_key(const Object& object, const size_t property_index) { + return object.get_object_schema().persisted_properties[property_index].column_key; } } diff --git a/wrappers/src/query_cs.cpp b/wrappers/src/query_cs.cpp index 9d32c8449c..e5d18632f4 100644 --- a/wrappers/src/query_cs.cpp +++ b/wrappers/src/query_cs.cpp @@ -30,6 +30,10 @@ using namespace realm; using namespace realm::binding; +inline ColKey get_key_for_prop(Query& query, SharedRealm& realm, size_t property_index) { + return realm->schema().find(ObjectStore::object_type_for_table_name(query.get_table()->get_name()))->persisted_properties[property_index].column_key; +} + extern "C" { REALM_EXPORT void query_destroy(Query* query) @@ -44,16 +48,6 @@ REALM_EXPORT size_t query_count(Query& query, NativeException::Marshallable& ex) }); } -//convert from columnName to column_key returns -1 if the string is not a column name -//assuming that the get_table() does not return anything that must be deleted -REALM_EXPORT void query_get_column_key(Query& query, uint16_t* column_name, size_t column_name_len, ColKey& key, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - Utf16StringAccessor str(column_name, column_name_len); - key = query.get_table()->get_column_key(str); - }); -} - REALM_EXPORT void query_not(Query& query, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { @@ -82,319 +76,374 @@ REALM_EXPORT void query_or(Query& query, NativeException::Marshallable& ex) }); } -REALM_EXPORT void query_string_contains(Query& query, ColKey column_key, uint16_t* value, size_t value_len, bool case_sensitive, NativeException::Marshallable& ex) +REALM_EXPORT void query_string_contains(Query& query, SharedRealm& realm, size_t property_index, uint16_t* value, size_t value_len, bool case_sensitive, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { Utf16StringAccessor str(value, value_len); - query.contains(column_key, str, case_sensitive); + query.contains(get_key_for_prop(query, realm, property_index), str, case_sensitive); }); } -REALM_EXPORT void query_string_starts_with(Query& query, ColKey column_key, uint16_t* value, size_t value_len, bool case_sensitive, NativeException::Marshallable& ex) +REALM_EXPORT void query_string_starts_with(Query& query, SharedRealm& realm, size_t property_index, uint16_t* value, size_t value_len, bool case_sensitive, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { Utf16StringAccessor str(value, value_len); - query.begins_with(column_key, str, case_sensitive); + query.begins_with(get_key_for_prop(query, realm, property_index), str, case_sensitive); }); } -REALM_EXPORT void query_string_ends_with(Query& query, ColKey column_key, uint16_t* value, size_t value_len, bool case_sensitive, NativeException::Marshallable& ex) +REALM_EXPORT void query_string_ends_with(Query& query, SharedRealm& realm, size_t property_index, uint16_t* value, size_t value_len, bool case_sensitive, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { Utf16StringAccessor str(value, value_len); - query.ends_with(column_key, str, case_sensitive); + query.ends_with(get_key_for_prop(query, realm, property_index), str, case_sensitive); }); } -REALM_EXPORT void query_string_equal(Query& query, ColKey column_key, uint16_t* value, size_t value_len, bool case_sensitive, NativeException::Marshallable& ex) +REALM_EXPORT void query_string_equal(Query& query, SharedRealm& realm, size_t property_index, uint16_t* value, size_t value_len, bool case_sensitive, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { Utf16StringAccessor str(value, value_len); - query.equal(column_key, str, case_sensitive); + query.equal(get_key_for_prop(query, realm, property_index), str, case_sensitive); }); } -REALM_EXPORT void query_string_not_equal(Query& query, ColKey column_key, uint16_t* value, size_t value_len, bool case_sensitive, NativeException::Marshallable& ex) +REALM_EXPORT void query_string_not_equal(Query& query, SharedRealm& realm, size_t property_index, uint16_t* value, size_t value_len, bool case_sensitive, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { Utf16StringAccessor str(value, value_len); - query.not_equal(column_key, str, case_sensitive); + query.not_equal(get_key_for_prop(query, realm, property_index), str, case_sensitive); }); } -REALM_EXPORT void query_string_like(Query& query, ColKey column_key, uint16_t* value, size_t value_len, bool case_sensitive, NativeException::Marshallable& ex) +REALM_EXPORT void query_string_like(Query& query, SharedRealm& realm, size_t property_index, uint16_t* value, size_t value_len, bool case_sensitive, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { Utf16StringAccessor str(value, value_len); - query.like(column_key, str, case_sensitive); - }); -} - -REALM_EXPORT void query_bool_equal(Query& query, ColKey column_key, bool value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.equal(column_key, value); - }); -} - -REALM_EXPORT void query_bool_not_equal(Query& query, ColKey column_key, bool value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.not_equal(column_key, value); - }); -} - -REALM_EXPORT void query_int_equal(Query& query, ColKey column_key, size_t value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.equal(column_key, static_cast(value)); - }); -} - -REALM_EXPORT void query_int_not_equal(Query& query, ColKey column_key, size_t value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.not_equal(column_key, static_cast(value)); - }); -} - -REALM_EXPORT void query_int_less(Query& query, ColKey column_key, size_t value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.less(column_key, static_cast(value)); - }); -} - -REALM_EXPORT void query_int_less_equal(Query& query, ColKey column_key, size_t value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.less_equal(column_key, static_cast(value)); - }); -} - -REALM_EXPORT void query_int_greater(Query& query, ColKey column_key, size_t value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.greater(column_key, static_cast(value)); - }); -} - -REALM_EXPORT void query_int_greater_equal(Query& query, ColKey column_key, size_t value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.greater_equal(column_key, static_cast(value)); - }); -} - -REALM_EXPORT void query_long_equal(Query& query, ColKey column_key, int64_t value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.equal(column_key, value); - }); -} - -REALM_EXPORT void query_long_not_equal(Query& query, ColKey column_key, int64_t value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.not_equal(column_key, value); - }); -} - -REALM_EXPORT void query_long_less(Query& query, ColKey column_key, int64_t value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.less(column_key, value); - }); -} - -REALM_EXPORT void query_long_less_equal(Query& query, ColKey column_key, int64_t value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.less_equal(column_key, value); - }); -} - -REALM_EXPORT void query_long_greater(Query& query, ColKey column_key, int64_t value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.greater(column_key, value); + query.like(get_key_for_prop(query, realm, property_index), str, case_sensitive); }); } -REALM_EXPORT void query_long_greater_equal(Query& query, ColKey column_key, int64_t value, NativeException::Marshallable& ex) +REALM_EXPORT void query_primitive_equal(Query& query, SharedRealm& realm, size_t property_index, PrimitiveValue& primitive, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { - query.greater_equal(column_key, value); - }); -} - - REALM_EXPORT void query_float_equal(Query& query, ColKey column_key, float value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.equal(column_key, static_cast(value)); - }); -} - -REALM_EXPORT void query_float_not_equal(Query& query, ColKey column_key, float value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.not_equal(column_key, static_cast(value)); - }); -} - -REALM_EXPORT void query_float_less(Query& query, ColKey column_key, float value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.less(column_key, static_cast(value)); - }); -} - -REALM_EXPORT void query_float_less_equal(Query& query, ColKey column_key, float value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.less_equal(column_key, static_cast(value)); - }); -} - -REALM_EXPORT void query_float_greater(Query& query, ColKey column_key, float value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.greater(column_key, static_cast(value)); - }); -} - -REALM_EXPORT void query_float_greater_equal(Query& query, ColKey column_key, float value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.greater_equal(column_key, static_cast(value)); - }); -} - -REALM_EXPORT void query_double_equal(Query& query, ColKey column_key, double value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.equal(column_key, static_cast(value)); - }); -} + if (!primitive.has_value) { + throw std::runtime_error("Comparing null values should be done via query_null_equal. If you get this error, please report it to help@realm.io."); + } -REALM_EXPORT void query_double_not_equal(Query& query, ColKey column_key, double value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.not_equal(column_key, static_cast(value)); + auto col_key = get_key_for_prop(query, realm, property_index); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" + switch (primitive.type) { + case realm::PropertyType::Bool: + case realm::PropertyType::Bool | realm::PropertyType::Nullable: + query.equal(std::move(col_key), primitive.value.bool_value); + break; + case realm::PropertyType::Int: + case realm::PropertyType::Int | realm::PropertyType::Nullable: + query.equal(std::move(col_key), primitive.value.int_value); + break; + case realm::PropertyType::Float: + case realm::PropertyType::Float | realm::PropertyType::Nullable: + query.equal(std::move(col_key), primitive.value.float_value); + break; + case realm::PropertyType::Double: + case realm::PropertyType::Double | realm::PropertyType::Nullable: + query.equal(std::move(col_key), primitive.value.double_value); + break; + case realm::PropertyType::Date: + case realm::PropertyType::Date | realm::PropertyType::Nullable: + query.equal(std::move(col_key), from_ticks(primitive.value.int_value)); + break; + case realm::PropertyType::Decimal: + case realm::PropertyType::Decimal | realm::PropertyType::Nullable: + query.equal(std::move(col_key), realm::Decimal128(primitive.value.decimal_bits)); + break; + case realm::PropertyType::ObjectId: + case realm::PropertyType::ObjectId | realm::PropertyType::Nullable: + query.equal(std::move(col_key), to_object_id(primitive)); + break; + default: + REALM_UNREACHABLE(); + } +#pragma GCC diagnostic pop }); } -REALM_EXPORT void query_double_less(Query& query, ColKey column_key, double value, NativeException::Marshallable& ex) +REALM_EXPORT void query_primitive_not_equal(Query& query, SharedRealm& realm, size_t property_index, PrimitiveValue& primitive, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { - query.less(column_key, static_cast(value)); - }); -} + if (!primitive.has_value) { + throw std::runtime_error("Comparing null values should be done via query_null_not_equal. If you get this error, please report it to help@realm.io."); + } -REALM_EXPORT void query_double_less_equal(Query& query, ColKey column_key, double value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.less_equal(column_key, static_cast(value)); + auto col_key = get_key_for_prop(query, realm, property_index); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" + switch (primitive.type) { + case realm::PropertyType::Bool: + case realm::PropertyType::Bool | realm::PropertyType::Nullable: + query.not_equal(std::move(col_key), primitive.value.bool_value); + break; + case realm::PropertyType::Int: + case realm::PropertyType::Int | realm::PropertyType::Nullable: + query.not_equal(std::move(col_key), primitive.value.int_value); + break; + case realm::PropertyType::Float: + case realm::PropertyType::Float | realm::PropertyType::Nullable: + query.not_equal(std::move(col_key), primitive.value.float_value); + break; + case realm::PropertyType::Double: + case realm::PropertyType::Double | realm::PropertyType::Nullable: + query.not_equal(std::move(col_key), primitive.value.double_value); + break; + case realm::PropertyType::Date: + case realm::PropertyType::Date | realm::PropertyType::Nullable: + query.not_equal(std::move(col_key), from_ticks(primitive.value.int_value)); + break; + case realm::PropertyType::Decimal: + case realm::PropertyType::Decimal | realm::PropertyType::Nullable: + query.not_equal(std::move(col_key), realm::Decimal128(primitive.value.decimal_bits)); + break; + case realm::PropertyType::ObjectId: + case realm::PropertyType::ObjectId | realm::PropertyType::Nullable: + query.not_equal(std::move(col_key), to_object_id(primitive)); + break; + default: + REALM_UNREACHABLE(); + } +#pragma GCC diagnostic pop }); } -REALM_EXPORT void query_double_greater(Query& query, ColKey column_key, double value, NativeException::Marshallable& ex) +REALM_EXPORT void query_primitive_less(Query& query, SharedRealm& realm, size_t property_index, PrimitiveValue& primitive, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { - query.greater(column_key, static_cast(value)); - }); -} + if (!primitive.has_value) { + throw std::runtime_error("Using primitive_less with null is not supported. If you get this error, please report it to help@realm.io."); + } -REALM_EXPORT void query_double_greater_equal(Query& query, ColKey column_key, double value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.greater_equal(column_key, static_cast(value)); + auto col_key = get_key_for_prop(query, realm, property_index); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" + switch (primitive.type) { + case realm::PropertyType::Bool: + case realm::PropertyType::Bool | realm::PropertyType::Nullable: + throw std::runtime_error("Using primitive_less with bool value is not supported. If you get this error, please report it to help@realm.io"); + case realm::PropertyType::Int: + case realm::PropertyType::Int | realm::PropertyType::Nullable: + query.less(std::move(col_key), primitive.value.int_value); + break; + case realm::PropertyType::Float: + case realm::PropertyType::Float | realm::PropertyType::Nullable: + query.less(std::move(col_key), primitive.value.float_value); + break; + case realm::PropertyType::Double: + case realm::PropertyType::Double | realm::PropertyType::Nullable: + query.less(std::move(col_key), primitive.value.double_value); + break; + case realm::PropertyType::Date: + case realm::PropertyType::Date | realm::PropertyType::Nullable: + query.less(std::move(col_key), from_ticks(primitive.value.int_value)); + break; + case realm::PropertyType::Decimal: + case realm::PropertyType::Decimal | realm::PropertyType::Nullable: + query.less(std::move(col_key), realm::Decimal128(primitive.value.decimal_bits)); + break; + case realm::PropertyType::ObjectId: + case realm::PropertyType::ObjectId | realm::PropertyType::Nullable: + query.less(std::move(col_key), to_object_id(primitive)); + break; + default: + REALM_UNREACHABLE(); + } +#pragma GCC diagnostic pop }); } -REALM_EXPORT void query_timestamp_ticks_equal(Query& query, ColKey column_key, int64_t value, NativeException::Marshallable& ex) +REALM_EXPORT void query_primitive_less_equal(Query& query, SharedRealm& realm, size_t property_index, PrimitiveValue& primitive, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { - query.equal(column_key, from_ticks(value)); - }); -} + if (!primitive.has_value) { + throw std::runtime_error("Using primitive_less_equal with null is not supported. If you get this error, please report it to help@realm.io."); + } -REALM_EXPORT void query_timestamp_ticks_not_equal(Query& query, ColKey column_key, int64_t value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.not_equal(column_key, from_ticks(value)); + auto col_key = get_key_for_prop(query, realm, property_index); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" + switch (primitive.type) { + case realm::PropertyType::Bool: + case realm::PropertyType::Bool | realm::PropertyType::Nullable: + throw std::runtime_error("Using primitive_less_equal with bool value is not supported. If you get this error, please report it to help@realm.io"); + case realm::PropertyType::Int: + case realm::PropertyType::Int | realm::PropertyType::Nullable: + query.less_equal(std::move(col_key), primitive.value.int_value); + break; + case realm::PropertyType::Float: + case realm::PropertyType::Float | realm::PropertyType::Nullable: + query.less_equal(std::move(col_key), primitive.value.float_value); + break; + case realm::PropertyType::Double: + case realm::PropertyType::Double | realm::PropertyType::Nullable: + query.less_equal(std::move(col_key), primitive.value.double_value); + break; + case realm::PropertyType::Date: + case realm::PropertyType::Date | realm::PropertyType::Nullable: + query.less_equal(std::move(col_key), from_ticks(primitive.value.int_value)); + break; + case realm::PropertyType::Decimal: + case realm::PropertyType::Decimal | realm::PropertyType::Nullable: + query.less_equal(std::move(col_key), realm::Decimal128(primitive.value.decimal_bits)); + break; + case realm::PropertyType::ObjectId: + case realm::PropertyType::ObjectId | realm::PropertyType::Nullable: + query.less_equal(std::move(col_key), to_object_id(primitive)); + break; + default: + REALM_UNREACHABLE(); + } +#pragma GCC diagnostic pop }); } -REALM_EXPORT void query_timestamp_ticks_less(Query& query, ColKey column_key, int64_t value, NativeException::Marshallable& ex) +REALM_EXPORT void query_primitive_greater(Query& query, SharedRealm& realm, size_t property_index, PrimitiveValue& primitive, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { - query.less(column_key, from_ticks(value)); - }); -} + if (!primitive.has_value) { + throw std::runtime_error("Using primitive_greater with null is not supported. If you get this error, please report it to help@realm.io."); + } -REALM_EXPORT void query_timestamp_ticks_less_equal(Query& query, ColKey column_key, int64_t value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.less_equal(column_key, from_ticks(value)); + auto col_key = get_key_for_prop(query, realm, property_index); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" + switch (primitive.type) { + case realm::PropertyType::Bool: + case realm::PropertyType::Bool | realm::PropertyType::Nullable: + throw std::runtime_error("Using primitive_greater with bool value is not supported. If you get this error, please report it to help@realm.io"); + case realm::PropertyType::Int: + case realm::PropertyType::Int | realm::PropertyType::Nullable: + query.greater(std::move(col_key), primitive.value.int_value); + break; + case realm::PropertyType::Float: + case realm::PropertyType::Float | realm::PropertyType::Nullable: + query.greater(std::move(col_key), primitive.value.float_value); + break; + case realm::PropertyType::Double: + case realm::PropertyType::Double | realm::PropertyType::Nullable: + query.greater(std::move(col_key), primitive.value.double_value); + break; + case realm::PropertyType::Date: + case realm::PropertyType::Date | realm::PropertyType::Nullable: + query.greater(std::move(col_key), from_ticks(primitive.value.int_value)); + break; + case realm::PropertyType::Decimal: + case realm::PropertyType::Decimal | realm::PropertyType::Nullable: + query.greater(std::move(col_key), realm::Decimal128(primitive.value.decimal_bits)); + break; + case realm::PropertyType::ObjectId: + case realm::PropertyType::ObjectId | realm::PropertyType::Nullable: + query.greater(std::move(col_key), to_object_id(primitive)); + break; + default: + REALM_UNREACHABLE(); + } +#pragma GCC diagnostic pop }); } -REALM_EXPORT void query_timestamp_ticks_greater(Query& query, ColKey column_key, int64_t value, NativeException::Marshallable& ex) +REALM_EXPORT void query_primitive_greater_equal(Query& query, SharedRealm& realm, size_t property_index, PrimitiveValue& primitive, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { - query.greater(column_key, from_ticks(value)); - }); -} + if (!primitive.has_value) { + throw std::runtime_error("Using primitive_greater_equal with null is not supported. If you get this error, please report it to help@realm.io."); + } -REALM_EXPORT void query_timestamp_ticks_greater_equal(Query& query, ColKey column_key, int64_t value, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - query.greater_equal(column_key, from_ticks(value)); + auto col_key = get_key_for_prop(query, realm, property_index); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" + switch (primitive.type) { + case realm::PropertyType::Bool: + case realm::PropertyType::Bool | realm::PropertyType::Nullable: + throw std::runtime_error("Using primitive_greater_equal with bool value is not supported. If you get this error, please report it to help@realm.io"); + case realm::PropertyType::Int: + case realm::PropertyType::Int | realm::PropertyType::Nullable: + query.greater_equal(std::move(col_key), primitive.value.int_value); + break; + case realm::PropertyType::Float: + case realm::PropertyType::Float | realm::PropertyType::Nullable: + query.greater_equal(std::move(col_key), primitive.value.float_value); + break; + case realm::PropertyType::Double: + case realm::PropertyType::Double | realm::PropertyType::Nullable: + query.greater_equal(std::move(col_key), primitive.value.double_value); + break; + case realm::PropertyType::Date: + case realm::PropertyType::Date | realm::PropertyType::Nullable: + query.greater_equal(std::move(col_key), from_ticks(primitive.value.int_value)); + break; + case realm::PropertyType::Decimal: + case realm::PropertyType::Decimal | realm::PropertyType::Nullable: + query.greater_equal(std::move(col_key), realm::Decimal128(primitive.value.decimal_bits)); + break; + case realm::PropertyType::ObjectId: + case realm::PropertyType::ObjectId | realm::PropertyType::Nullable: + query.greater_equal(std::move(col_key), to_object_id(primitive)); + break; + default: + REALM_UNREACHABLE(); + } +#pragma GCC diagnostic pop }); } -REALM_EXPORT void query_binary_equal(Query& query, ColKey column_key, char* buffer, size_t buffer_length, NativeException::Marshallable& ex) +REALM_EXPORT void query_binary_equal(Query& query, SharedRealm& realm, size_t property_index, char* buffer, size_t buffer_length, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { - query.equal(column_key, BinaryData(buffer, buffer_length)); + query.equal(get_key_for_prop(query, realm, property_index), BinaryData(buffer, buffer_length)); }); } -REALM_EXPORT void query_binary_not_equal(Query& query, ColKey column_key, char* buffer, size_t buffer_length, NativeException::Marshallable& ex) +REALM_EXPORT void query_binary_not_equal(Query& query, SharedRealm& realm, size_t property_index, char* buffer, size_t buffer_length, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { - query.not_equal(column_key, BinaryData(buffer, buffer_length)); + query.not_equal(get_key_for_prop(query, realm, property_index), BinaryData(buffer, buffer_length)); }); } -REALM_EXPORT void query_object_equal(Query& query, ColKey column_key, Object& object, NativeException::Marshallable& ex) +REALM_EXPORT void query_object_equal(Query& query, SharedRealm& realm, size_t property_index, Object& object, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { - query.links_to(column_key, object.obj().get_key()); + query.links_to(get_key_for_prop(query, realm, property_index), object.obj().get_key()); }); } -REALM_EXPORT void query_null_equal(Query& query, ColKey column_key, NativeException::Marshallable& ex) +REALM_EXPORT void query_null_equal(Query& query, SharedRealm& realm, size_t property_index, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { - if (query.get_table()->get_column_type(column_key) == DataType::type_Link) { - query.and_query(query.get_table()->column(column_key).is_null()); + auto col_key = get_key_for_prop(query, realm, property_index); + if (query.get_table()->get_column_type(col_key) == DataType::type_Link) { + query.and_query(query.get_table()->column(col_key).is_null()); } else { - query.equal(column_key, null()); + query.equal(col_key, null()); } }); } -REALM_EXPORT void query_null_not_equal(Query& query, ColKey column_key, NativeException::Marshallable& ex) +REALM_EXPORT void query_null_not_equal(Query& query, SharedRealm& realm, size_t property_index, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { - if (query.get_table()->get_column_type(column_key) == DataType::type_Link) { - query.and_query(query.get_table()->column(column_key).is_not_null()); + auto col_key = get_key_for_prop(query, realm, property_index); + if (query.get_table()->get_column_type(col_key) == DataType::type_Link) { + query.and_query(query.get_table()->column(col_key).is_not_null()); } else { - query.not_equal(column_key, null()); + query.not_equal(col_key, null()); } }); } diff --git a/wrappers/src/realm_error_type.hpp b/wrappers/src/realm_error_type.hpp index 184f7d4db4..bdea394f71 100644 --- a/wrappers/src/realm_error_type.hpp +++ b/wrappers/src/realm_error_type.hpp @@ -73,19 +73,26 @@ namespace realm { RealmClosed = 24, ObjectManagedByAnotherRealm = 25, - - RealmFeatureUnavailable = 26, - - // deprecated: sync 1.x migration related - RealmIncompatibleSyncedFile = 27, - + RealmDotNetExceptionDuringMigration = 30, StdArgumentOutOfRange = 100, StdIndexOutOfRange = 101, - StdInvalidOperation = 102 + StdInvalidOperation = 102, + + AppClientError = 50, + + AppCustomError = 51, + + AppHttpError = 52, + + AppJsonError = 53, + + AppServiceError = 54, + + AppUnknownError = 59, }; } // namespace realm diff --git a/wrappers/src/schema_cs.cpp b/wrappers/src/schema_cs.cpp index 9b8b07d79a..88688c62e8 100644 --- a/wrappers/src/schema_cs.cpp +++ b/wrappers/src/schema_cs.cpp @@ -30,6 +30,7 @@ util::Optional create_schema(SchemaObject* objects, int objects_length, ObjectSchema o; o.name = object.name; + o.is_embedded = object.is_embedded; for (int n = object.properties_start; n < object.properties_end; n++) { SchemaProperty& property = properties[n]; diff --git a/wrappers/src/schema_cs.hpp b/wrappers/src/schema_cs.hpp index 74bea5c3a1..969f32db55 100644 --- a/wrappers/src/schema_cs.hpp +++ b/wrappers/src/schema_cs.hpp @@ -45,8 +45,9 @@ struct SchemaObject const char* name; int properties_start; int properties_end; + bool is_embedded; - static SchemaObject for_marshalling(const ObjectSchema&, std::vector&); + static SchemaObject for_marshalling(const ObjectSchema&, std::vector&, bool); }; struct SchemaForMarshaling @@ -70,10 +71,11 @@ REALM_FORCEINLINE SchemaProperty SchemaProperty::for_marshalling(const Property& }; } -REALM_FORCEINLINE SchemaObject SchemaObject::for_marshalling(const ObjectSchema& object, std::vector& properties) +REALM_FORCEINLINE SchemaObject SchemaObject::for_marshalling(const ObjectSchema& object, std::vector& properties, bool is_embedded) { SchemaObject ret; ret.name = object.name.c_str(); + ret.is_embedded = is_embedded; ret.properties_start = static_cast(properties.size()); for (const auto& property : object.persisted_properties) { diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index ba801908d5..12a77ad069 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -15,7 +15,7 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////// - + #include "shared_realm_cs.hpp" #include "error_handling.hpp" @@ -27,26 +27,28 @@ #include #include #include +#include "sync/async_open_task.hpp" #include #include #include +using SharedAsyncOpenTask = std::shared_ptr; + using namespace realm; using namespace realm::binding; -using NotifyRealmChangedDelegate = void(void* managed_state_handle); -using GetNativeSchemaDelegate = void(SchemaForMarshaling schema, void* managed_callback); -NotifyRealmChangedDelegate* notify_realm_changed = nullptr; -GetNativeSchemaDelegate* get_native_schema = nullptr; - namespace realm { namespace binding { + void (*s_open_realm_callback)(void* task_completion_source, ThreadSafeReference* ref, int32_t error_code, const char* message, size_t message_len); + void (*s_realm_changed)(void* managed_state_handle); + void (*s_get_native_schema)(SchemaForMarshaling schema, void* managed_callback); + CSharpBindingContext::CSharpBindingContext(void* managed_state_handle) : m_managed_state_handle(managed_state_handle) {} - + void CSharpBindingContext::did_change(std::vector const& observed, std::vector const& invalidated, bool version_changed) { - notify_realm_changed(m_managed_state_handle); + s_realm_changed(m_managed_state_handle); } } @@ -59,17 +61,50 @@ class TestHelper { return Realm::Internal::get_db(*realm)->has_changed(transaction); } }; + +Realm::Config get_shared_realm_config(Configuration configuration, SyncConfiguration sync_configuration, SchemaObject* objects, int objects_length, SchemaProperty* properties, uint8_t* encryption_key) +{ + Realm::Config config; + config.schema_mode = SchemaMode::Additive; + + if (objects_length > 0) { + config.schema = create_schema(objects, objects_length, properties); + } + + config.schema_version = configuration.schema_version; + config.max_number_of_active_versions = configuration.max_number_of_active_versions; + + std::string realm_url(Utf16StringAccessor(sync_configuration.url, sync_configuration.url_len)); + + config.sync_config = std::make_shared(*sync_configuration.user, realm_url); + config.sync_config->error_handler = handle_session_error; + config.sync_config->client_resync_mode = ClientResyncMode::Manual; + config.sync_config->stop_policy = sync_configuration.session_stop_policy; + config.path = Utf16StringAccessor(configuration.path, configuration.path_len); + + // by definition the key is only allowed to be 64 bytes long, enforced by C# code + if (encryption_key) { + auto& key = *reinterpret_cast*>(encryption_key); + + config.encryption_key = std::vector(key.begin(), key.end()); + config.sync_config->realm_encryption_key = key; + } + + config.cache = configuration.enable_cache; + + return config; +} } extern "C" { - - -REALM_EXPORT void shared_realm_install_callbacks(NotifyRealmChangedDelegate realm_changed, GetNativeSchemaDelegate get_schema) + +REALM_EXPORT void shared_realm_install_callbacks(decltype(s_realm_changed) realm_changed, decltype(s_get_native_schema) get_schema, decltype(s_open_realm_callback) open_callback) { - notify_realm_changed = realm_changed; - get_native_schema = get_schema; + s_realm_changed = realm_changed; + s_get_native_schema = get_schema; + s_open_realm_callback = open_callback; } - + REALM_EXPORT SharedRealm* shared_realm_open(Configuration configuration, SchemaObject* objects, int objects_length, SchemaProperty* properties, uint8_t* encryption_key, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { @@ -89,51 +124,89 @@ REALM_EXPORT SharedRealm* shared_realm_open(Configuration configuration, SchemaO } else if (configuration.delete_if_migration_needed) { config.schema_mode = SchemaMode::ResetFile; } - + if (objects_length > 0) { config.schema = create_schema(objects, objects_length, properties); } - + config.schema_version = configuration.schema_version; if (configuration.managed_migration_handle) { config.migration_function = [&configuration](SharedRealm oldRealm, SharedRealm newRealm, Schema schema) { std::vector schema_objects; std::vector schema_properties; - + for (auto& object : oldRealm->schema()) { - schema_objects.push_back(SchemaObject::for_marshalling(object, schema_properties)); + schema_objects.push_back(SchemaObject::for_marshalling(object, schema_properties, object.is_embedded)); } - + SchemaForMarshaling schema_for_marshaling { schema_objects.data(), static_cast(schema_objects.size()), - + schema_properties.data() }; - + if (!configuration.migration_callback(&oldRealm, &newRealm, schema_for_marshaling, oldRealm->schema_version(), configuration.managed_migration_handle)) { throw ManagedExceptionDuringMigration(); } }; } - + if (configuration.managed_should_compact_delegate) { config.should_compact_on_launch_function = [&configuration](uint64_t total_bytes, uint64_t used_bytes) { return configuration.should_compact_callback(configuration.managed_should_compact_delegate, total_bytes, used_bytes); }; } - + config.cache = configuration.enable_cache; auto realm = Realm::get_shared_realm(config); if (!configuration.read_only) realm->refresh(); - + return new SharedRealm{realm}; }); } +REALM_EXPORT SharedAsyncOpenTask* shared_realm_open_with_sync_async(Configuration configuration, SyncConfiguration sync_configuration, SchemaObject* objects, int objects_length, SchemaProperty* properties, uint8_t* encryption_key, void* task_completion_source, NativeException::Marshallable& ex) +{ + return handle_errors(ex, [&]() { + auto config = get_shared_realm_config(configuration, sync_configuration, objects, objects_length, properties, encryption_key); + + auto task = Realm::get_synchronized_realm(config); + task->start([task_completion_source](ThreadSafeReference ref, std::exception_ptr error) { + if (error) { + try { + std::rethrow_exception(error); + } + catch (const std::system_error& system_error) { + const std::error_code& ec = system_error.code(); + s_open_realm_callback(task_completion_source, nullptr, ec.value(), ec.message().c_str(), ec.message().length()); + } + } + else { + s_open_realm_callback(task_completion_source, new ThreadSafeReference(std::move(ref)), 0, nullptr, 0); + } + }); + + return new SharedAsyncOpenTask(task); + }); +} + +REALM_EXPORT SharedRealm* shared_realm_open_with_sync(Configuration configuration, SyncConfiguration sync_configuration, SchemaObject* objects, int objects_length, SchemaProperty* properties, uint8_t* encryption_key, NativeException::Marshallable& ex) +{ + return handle_errors(ex, [&]() { + auto config = get_shared_realm_config(configuration, sync_configuration, objects, objects_length, properties, encryption_key); + + auto realm = Realm::get_shared_realm(config); + if (!configuration.read_only) + realm->refresh(); + + return new SharedRealm(realm); + }); +} + REALM_EXPORT void shared_realm_set_managed_state_handle(SharedRealm& realm, void* managed_state_handle, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { @@ -149,12 +222,12 @@ REALM_EXPORT void* shared_realm_get_managed_state_handle(SharedRealm& realm, Nat if (realm->m_binding_context == nullptr) { return nullptr; } - + auto const& csharp_context = static_cast(realm->m_binding_context.get()); return csharp_context->get_managed_state_handle(); }); } - + REALM_EXPORT void shared_realm_destroy(SharedRealm* realm) { delete realm; @@ -171,7 +244,6 @@ REALM_EXPORT TableRef* shared_realm_get_table(SharedRealm& realm, uint16_t* obje { return handle_errors(ex, [&]() { Utf16StringAccessor object_type(object_type_buf, object_type_len); - return new TableRef(ObjectStore::table_for_object_type(realm->read_group(), object_type)); }); } @@ -231,7 +303,7 @@ REALM_EXPORT bool shared_realm_compact(SharedRealm& realm, NativeException::Mars return realm->compact(); }); } - + REALM_EXPORT Object* shared_realm_resolve_object_reference(SharedRealm& realm, ThreadSafeReference& reference, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { @@ -252,19 +324,19 @@ REALM_EXPORT Results* shared_realm_resolve_query_reference(SharedRealm& realm, T return new Results(reference.resolve(realm)); }); } - + REALM_EXPORT SharedRealm* shared_realm_resolve_realm_reference(ThreadSafeReference& reference, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { return new SharedRealm(Realm::get_shared_realm(std::move(reference))); }); } - + REALM_EXPORT void thread_safe_reference_destroy(ThreadSafeReference* reference) { delete reference; } - + REALM_EXPORT void shared_realm_write_copy(SharedRealm* realm, uint16_t* path, size_t path_len, char* encryption_key, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { @@ -274,7 +346,7 @@ REALM_EXPORT void shared_realm_write_copy(SharedRealm* realm, uint16_t* path, si realm->get()->write_copy(pathStr, BinaryData(encryption_key, encryption_key ? 64 : 0)); }); } - + } inline const ObjectSchema& find_schema(const SharedRealm& realm, ConstTableRef& table) @@ -312,14 +384,7 @@ Object* create_object_unique(const SharedRealm& realm, TableRef& table, const Ke is_new = false; } - auto result = new Object(realm, object_schema, obj); - - if (realm->is_partial() && object_schema.name == "__User") { - result->ensure_user_in_everyone_role(); - result->ensure_private_role_exists_for_user(); - } - - return result; + return new Object(realm, object_schema, obj); } extern "C" { @@ -328,18 +393,40 @@ REALM_EXPORT Object* shared_realm_create_object(SharedRealm& realm, TableRef& ta { return handle_errors(ex, [&]() { realm->verify_in_write(); - + return new Object(realm, table->create_object()); }); } -REALM_EXPORT Object* shared_realm_create_object_int_unique(const SharedRealm& realm, TableRef& table, int64_t key, bool has_value, bool is_nullable, bool try_update, bool& is_new, NativeException::Marshallable& ex) +REALM_EXPORT Object* shared_realm_create_object_primitive_unique(const SharedRealm& realm, TableRef& table, PrimitiveValue& primitive, bool try_update, bool& is_new, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { - if (is_nullable) { - return create_object_unique(realm, table, has_value ? util::some(key) : null(), try_update, is_new); - } else { - return create_object_unique(realm, table, key, try_update, is_new); + switch (primitive.type) { + case PropertyType::Int: + REALM_ASSERT(primitive.has_value); + + return create_object_unique(realm, table, primitive.value.int_value, try_update, is_new); + + case PropertyType::Int | PropertyType::Nullable: + return create_object_unique(realm, table, primitive.has_value ? util::some(primitive.value.int_value) : null(), try_update, is_new); + + case PropertyType::ObjectId: + REALM_ASSERT(primitive.has_value); + + return create_object_unique(realm, table, to_object_id(primitive), try_update, is_new); + + case PropertyType::ObjectId | PropertyType::Nullable: + // HACK: https://github.com/realm/realm-core/issues/3919 - this should eventually be + //return create_object_unique(realm, table, primitive.has_value ? util::some(to_object_id(primitive)) : null(), try_update, is_new); + + if (primitive.has_value) { + return create_object_unique(realm, table, to_object_id(primitive), try_update, is_new); + } + + return create_object_unique(realm, table, util::Optional(), try_update, is_new); + + default: + REALM_UNREACHABLE(); } }); } @@ -350,7 +437,7 @@ REALM_EXPORT Object* shared_realm_create_object_string_unique(const SharedRealm& if (key_buf == nullptr) { return create_object_unique(realm, table, StringData(), try_update, is_new); } - + Utf16StringAccessor key(key_buf, key_len); return create_object_unique(realm, table, StringData(key), try_update, is_new); }); @@ -361,12 +448,12 @@ REALM_EXPORT void shared_realm_get_schema(const SharedRealm& realm, void* manage handle_errors(ex, [&]() { std::vector schema_objects; std::vector schema_properties; - + for (auto& object : realm->schema()) { - schema_objects.push_back(SchemaObject::for_marshalling(object, schema_properties)); + schema_objects.push_back(SchemaObject::for_marshalling(object, schema_properties, object.is_embedded)); } - - get_native_schema(SchemaForMarshaling { + + s_get_native_schema(SchemaForMarshaling { schema_objects.data(), static_cast(schema_objects.size()), schema_properties.data() diff --git a/wrappers/src/shared_realm_cs.hpp b/wrappers/src/shared_realm_cs.hpp index 690a90f679..dfcd1afc8d 100644 --- a/wrappers/src/shared_realm_cs.hpp +++ b/wrappers/src/shared_realm_cs.hpp @@ -23,6 +23,18 @@ #include "schema_cs.hpp" #include "object-store/src/binding_context.hpp" #include "object_accessor.hpp" +#include "marshalling.hpp" + +#include "sync_session_cs.hpp" +#include "sync/sync_config.hpp" +#include "sync/sync_manager.hpp" +#include "sync/sync_config.hpp" +#include "sync/sync_session.hpp" + +using SharedSyncUser = std::shared_ptr; + +using namespace realm; +using namespace realm::binding; class ManagedExceptionDuringMigration : public std::runtime_error { @@ -54,6 +66,16 @@ struct Configuration uint64_t max_number_of_active_versions; }; +struct SyncConfiguration +{ + SharedSyncUser* user; + + uint16_t* url; + size_t url_len; + + SyncSessionStopPolicy session_stop_policy; +}; + namespace realm { namespace binding { diff --git a/wrappers/src/sort_descriptor_cs.cpp b/wrappers/src/sort_descriptor_cs.cpp index 46552d7109..4c9e31514d 100644 --- a/wrappers/src/sort_descriptor_cs.cpp +++ b/wrappers/src/sort_descriptor_cs.cpp @@ -43,7 +43,7 @@ REALM_EXPORT void sort_descriptor_add_clause(DescriptorOrdering& descriptor, Tab const std::string object_name(ObjectStore::object_type_for_table_name(table->get_name())); const std::vector* properties = &realm->schema().find(object_name)->persisted_properties; - for (auto i = 0; i < properties_count; ++i) { + for (size_t i = 0; i < properties_count; ++i) { const Property& property = properties->at(property_chain[i]); column_keys.push_back(property.column_key); @@ -52,7 +52,7 @@ REALM_EXPORT void sort_descriptor_add_clause(DescriptorOrdering& descriptor, Tab } } - descriptor.append_sort(SortDescriptor({column_keys}, {ascending}), SortDescriptor::MergeMode::append); + descriptor.append_sort(SortDescriptor({ column_keys }, { ascending }), SortDescriptor::MergeMode::append); }); } diff --git a/wrappers/src/subscription_cs.cpp b/wrappers/src/subscription_cs.cpp deleted file mode 100644 index 23ac6f2996..0000000000 --- a/wrappers/src/subscription_cs.cpp +++ /dev/null @@ -1,130 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include -#include "sync_manager_cs.hpp" -#include "error_handling.hpp" -#include "marshalling.hpp" -#include "realm_export_decls.hpp" -#include "sync/partial_sync.hpp" -#include "schema_cs.hpp" -#include -#include -#include "keypath_helpers.hpp" - -using namespace realm; -using namespace realm::binding; -using namespace realm::partial_sync; - -typedef void (*ManagedSubscriptionCallback)(void* managed_subscription); - -struct SubscriptionNotificationTokenContext { - SubscriptionNotificationToken token; - void* managed_subscription; - ManagedSubscriptionCallback callback; -}; - -extern "C" { - -REALM_EXPORT Subscription* realm_subscription_create(Results& results, uint16_t* name_buf, int32_t name_len, int64_t time_to_live, bool update, StringValue* inclusions, int inclusions_len, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - auto name = name_len >= 0 ? util::Optional(Utf16StringAccessor(name_buf, name_len).to_string()) : none; - auto optional_ttl = time_to_live >= 0 ? util::Optional(time_to_live) : none; - - std::vector paths; - for (auto i = 0; i < inclusions_len; i++) { - paths.emplace_back(inclusions[i].value); - } - - parser::KeyPathMapping mapping; - realm::populate_keypath_mapping(mapping, *results.get_realm()); - - auto inclusion_paths = realm::generate_include_from_keypaths(paths, *results.get_realm(), results.get_object_schema(), mapping); - - realm::partial_sync::SubscriptionOptions options; - options.user_provided_name = name; - options.time_to_live_ms = optional_ttl; - options.update = update; - options.inclusions = inclusion_paths; - - auto result = realm::partial_sync::subscribe(results, options); - return new Subscription(std::move(result)); - }); -} - -REALM_EXPORT SubscriptionState realm_subscription_get_state(Subscription* subscription, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - return subscription->state(); - }); -} - -REALM_EXPORT NativeException::Marshallable realm_subscription_get_error(Subscription* subscription) -{ - if (subscription->error()) { - try { - std::rethrow_exception(subscription->error()); - } - catch (...) { - return convert_exception().for_marshalling(); - } - } - else { - NativeException no_error = { RealmErrorType::NoError }; - return no_error.for_marshalling(); - } -} - -REALM_EXPORT SubscriptionNotificationTokenContext* realm_subscription_add_notification_callback(Subscription* subscription, void* managed_subscription, ManagedSubscriptionCallback callback, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - auto context = new SubscriptionNotificationTokenContext(); - context->managed_subscription = managed_subscription; - context->callback = callback; - context->token = subscription->add_notification_callback([context]() { - context->callback(context->managed_subscription); - }); - - return context; - }); -} - -REALM_EXPORT void* realm_subscription_destroy_notification_token(SubscriptionNotificationTokenContext* context, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - auto managed_subscription = context->managed_subscription; - delete context; - return managed_subscription; - }); -} - -REALM_EXPORT void realm_subscription_unsubscribe(Subscription* subscription, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - partial_sync::unsubscribe(*subscription); - }); -} - -REALM_EXPORT void realm_subscription_destroy(Subscription* subscription) -{ - delete subscription; -} - -} diff --git a/wrappers/src/sync_manager_cs.cpp b/wrappers/src/sync_manager_cs.cpp deleted file mode 100644 index 06a3bc7d5f..0000000000 --- a/wrappers/src/sync_manager_cs.cpp +++ /dev/null @@ -1,321 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include "sync_manager_cs.hpp" -#include "error_handling.hpp" -#include "marshalling.hpp" -#include "realm_export_decls.hpp" -#include "shared_realm_cs.hpp" -#include "shared_realm.hpp" -#include "sync/sync_manager.hpp" -#include "sync/sync_user.hpp" -#include "sync/sync_config.hpp" -#include "sync/sync_session.hpp" -#include "sync_session_cs.hpp" -#include "sync/impl/sync_metadata.hpp" -#include "sync/partial_sync.hpp" -#include "sync/async_open_task.hpp" -#include "thread_safe_reference.hpp" - -#if REALM_WINDOWS -#include -#endif - -using namespace realm; -using namespace realm::binding; - -using LogMessageDelegate = void(const char* message, size_t message_len, util::Logger::Level level); -using SharedAsyncOpenTask = std::shared_ptr; - -namespace realm { -namespace binding { - void (*s_open_realm_callback)(void* task_completion_source, ThreadSafeReference* ref, int32_t error_code, const char* message, size_t message_len); - - class SyncLogger : public util::RootLogger { - public: - SyncLogger(LogMessageDelegate* delegate) - : m_log_message_delegate(delegate) - { - } - - void do_log(util::Logger::Level level, std::string message) { - m_log_message_delegate(message.c_str(), message.length(), level); - } - private: - LogMessageDelegate* m_log_message_delegate; - }; - - class SyncLoggerFactory : public realm::SyncLoggerFactory { - public: - SyncLoggerFactory(LogMessageDelegate* delegate) - : m_log_message_delegate(delegate) - { - } - - std::unique_ptr make_logger(util::Logger::Level level) - { - auto logger = std::make_unique(m_log_message_delegate); - logger->set_level_threshold(level); - return std::unique_ptr(logger.release()); - } - private: - LogMessageDelegate* m_log_message_delegate; - }; -} - -Realm::Config get_shared_realm_config(Configuration configuration, SyncConfiguration sync_configuration, SchemaObject* objects, int objects_length, SchemaProperty* properties, uint8_t* encryption_key) -{ - Realm::Config config; - config.schema_mode = SchemaMode::Additive; - - if (objects_length > 0) { - config.schema = create_schema(objects, objects_length, properties); - } - - config.schema_version = configuration.schema_version; - config.max_number_of_active_versions = configuration.max_number_of_active_versions; - - std::string realm_url(Utf16StringAccessor(sync_configuration.url, sync_configuration.url_len)); - - config.sync_config = std::make_shared(*sync_configuration.user, realm_url); - config.sync_config->bind_session_handler = bind_session; - config.sync_config->error_handler = handle_session_error; - config.sync_config->client_resync_mode = sync_configuration.client_resync_mode; - config.path = Utf16StringAccessor(configuration.path, configuration.path_len); - - // by definition the key is only allowed to be 64 bytes long, enforced by C# code - if (encryption_key) { - auto& key = *reinterpret_cast*>(encryption_key); - - config.encryption_key = std::vector(key.begin(), key.end()); - config.sync_config->realm_encryption_key = key; - } - -#if !REALM_PLATFORM_APPLE - if (sync_configuration.trusted_ca_path) { - Utf16StringAccessor trusted_ca_path(sync_configuration.trusted_ca_path, sync_configuration.trusted_ca_path_len); - config.sync_config->ssl_trust_certificate_path = trusted_ca_path.to_string(); - } -#endif - - config.sync_config->client_validate_ssl = sync_configuration.client_validate_ssl; - config.sync_config->is_partial = sync_configuration.is_partial; - - if (sync_configuration.partial_sync_identifier) { - Utf16StringAccessor partial_sync_identifier(sync_configuration.partial_sync_identifier, sync_configuration.partial_sync_identifier_len); - config.sync_config->custom_partial_sync_identifier = partial_sync_identifier.to_string(); - } - - config.cache = configuration.enable_cache; - - return config; -} - -} - - -using SharedSyncUser = std::shared_ptr; - -extern "C" { -REALM_EXPORT void realm_install_syncmanager_callbacks(decltype(s_open_realm_callback) open_callback) -{ - s_open_realm_callback = open_callback; -} - -REALM_EXPORT void realm_syncmanager_configure(const uint16_t* base_path_buf, size_t base_path_len, - const uint16_t* user_agent_buf, size_t user_agent_len, - const SyncManager::MetadataMode* mode, const char* encryption_key_buf, bool reset_on_error, - NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - SyncClientConfig config; - - config.base_file_path = Utf16StringAccessor(base_path_buf, base_path_len); - config.user_agent_binding_info = Utf16StringAccessor(user_agent_buf, user_agent_len); - - if (mode) { - config.metadata_mode = *mode; - } else { -#if REALM_PLATFORM_APPLE && !TARGET_OS_SIMULATOR - config.metadata_mode = SyncManager::MetadataMode::Encryption; -#else - config.metadata_mode = SyncManager::MetadataMode::NoEncryption; -#endif - } - - if (encryption_key_buf) { - config.custom_encryption_key = std::vector(encryption_key_buf, encryption_key_buf + 64); - } - - config.reset_metadata_on_error = reset_on_error; - - SyncManager::shared().configure(std::move(config)); - }); -} - -REALM_EXPORT void realm_syncmanager_set_user_agent(const uint16_t* user_agent_buf, size_t user_agent_len, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - Utf16StringAccessor user_agent(user_agent_buf, user_agent_len); - SyncManager::shared().set_user_agent(user_agent); - }); -} - -REALM_EXPORT void realm_syncmanager_set_log_level(util::Logger::Level* level, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - SyncManager::shared().set_log_level(*level); - }); -} - -REALM_EXPORT void realm_syncmanager_set_log_callback(LogMessageDelegate delegate, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - SyncManager::shared().set_logger_factory(*new realm::binding::SyncLoggerFactory(delegate)); - }); -} - -REALM_EXPORT util::Logger::Level realm_syncmanager_get_log_level() -{ - return SyncManager::shared().log_level(); -} - -REALM_EXPORT SharedAsyncOpenTask* shared_realm_open_with_sync_async(Configuration configuration, SyncConfiguration sync_configuration, SchemaObject* objects, int objects_length, SchemaProperty* properties, uint8_t* encryption_key, void* task_completion_source, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - auto config = get_shared_realm_config(configuration, sync_configuration, objects, objects_length, properties, encryption_key); - - auto task = Realm::get_synchronized_realm(config); - task->start([task_completion_source](ThreadSafeReference ref, std::exception_ptr error) { - if (error) { - try { - std::rethrow_exception(error); - } catch (const std::system_error& system_error) { - const std::error_code& ec = system_error.code(); - s_open_realm_callback(task_completion_source, nullptr, ec.value(), ec.message().c_str(), ec.message().length()); - } - } else { - s_open_realm_callback(task_completion_source, new ThreadSafeReference(std::move(ref)), 0, nullptr, 0); - } - }); - - return new SharedAsyncOpenTask(task); - }); -} - -REALM_EXPORT SharedRealm* shared_realm_open_with_sync(Configuration configuration, SyncConfiguration sync_configuration, SchemaObject* objects, int objects_length, SchemaProperty* properties, uint8_t* encryption_key, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - auto config = get_shared_realm_config(configuration, sync_configuration, objects, objects_length, properties, encryption_key); - - auto realm = Realm::get_shared_realm(config); - if (!configuration.read_only) - realm->refresh(); - - return new SharedRealm(realm); - }); -} - -REALM_EXPORT size_t realm_syncmanager_get_path_for_realm(SharedSyncUser& user, uint16_t* url, size_t url_len, uint16_t* pathbuffer, size_t pathbuffer_len, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - Utf16StringAccessor realm_url(url, url_len); - auto path = SyncManager::shared().path_for_realm(*user, realm_url); - - return stringdata_to_csharpstringbuffer(path, pathbuffer, pathbuffer_len); - }); -} - -REALM_EXPORT bool realm_syncmanager_immediately_run_file_actions(uint16_t* pathbuffer, size_t pathbuffer_len, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - std::string path(Utf16StringAccessor(pathbuffer, pathbuffer_len)); - return SyncManager::shared().immediately_run_file_actions(path); - }); -} - -REALM_EXPORT void realm_syncmanager_reconnect() -{ - SyncManager::shared().reconnect(); -} - -REALM_EXPORT std::shared_ptr* realm_syncmanager_get_session(uint16_t* pathbuffer, size_t pathbuffer_len, SyncConfiguration sync_configuration, uint8_t* encryption_key, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - std::string path(Utf16StringAccessor(pathbuffer, pathbuffer_len)); - std::string url(Utf16StringAccessor(sync_configuration.url, sync_configuration.url_len)); - - SyncConfig config(*sync_configuration.user, url); - config.bind_session_handler = bind_session; - config.error_handler = handle_session_error; - if (encryption_key) { - config.realm_encryption_key = *reinterpret_cast*>(encryption_key); - } - -#if !REALM_PLATFORM_APPLE - if (sync_configuration.trusted_ca_path) { - Utf16StringAccessor trusted_ca_path(sync_configuration.trusted_ca_path, sync_configuration.trusted_ca_path_len); - config.ssl_trust_certificate_path = trusted_ca_path.to_string(); - } -#endif - - config.client_validate_ssl = sync_configuration.client_validate_ssl; - config.is_partial = sync_configuration.is_partial; - - if (sync_configuration.partial_sync_identifier) { - Utf16StringAccessor partial_sync_identifier(sync_configuration.partial_sync_identifier, sync_configuration.partial_sync_identifier_len); - config.custom_partial_sync_identifier = partial_sync_identifier.to_string(); - } - - return new std::shared_ptr(SyncManager::shared().get_session(path, config)->external_reference()); - }); -} - -REALM_EXPORT uint8_t realm_syncmanager_get_realm_privileges(SharedRealm& sharedRealm, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - return static_cast(sharedRealm->get_privileges()); - }); -} - -REALM_EXPORT uint8_t realm_syncmanager_get_class_privileges(SharedRealm& sharedRealm, uint16_t* class_buf, size_t class_len, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - Utf16StringAccessor class_name(class_buf, class_len); - return static_cast(sharedRealm->get_privileges(class_name)); - }); -} - -REALM_EXPORT uint8_t realm_syncmanager_get_object_privileges(SharedRealm& sharedRealm, Object& object, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - return static_cast(sharedRealm->get_privileges(object.obj())); - }); -} - -REALM_EXPORT void realm_syncmanager_enable_session_multiplexing(NativeException::Marshallable& ex) -{ - handle_errors(ex, [&]() { - SyncManager::shared().enable_session_multiplexing(); - }); -} - -} diff --git a/wrappers/src/sync_manager_cs.hpp b/wrappers/src/sync_manager_cs.hpp deleted file mode 100644 index b814c18051..0000000000 --- a/wrappers/src/sync_manager_cs.hpp +++ /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. -// -//////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include "realm_export_decls.hpp" -#include "sync/sync_config.hpp" - -using namespace realm; - -namespace realm { - class SyncUser; - - struct SyncConfiguration - { - std::shared_ptr* user; - - uint16_t* url; - size_t url_len; - - bool client_validate_ssl; - - uint16_t* trusted_ca_path; - size_t trusted_ca_path_len; - - bool is_partial; - uint16_t* partial_sync_identifier; - size_t partial_sync_identifier_len; - realm::ClientResyncMode client_resync_mode; - }; -} diff --git a/wrappers/src/sync_session_cs.cpp b/wrappers/src/sync_session_cs.cpp index 53aa00c4e4..b81131e788 100644 --- a/wrappers/src/sync_session_cs.cpp +++ b/wrappers/src/sync_session_cs.cpp @@ -32,56 +32,30 @@ using SharedSyncSession = std::shared_ptr; namespace realm { namespace binding { - void (*s_refresh_access_token_callback)(std::shared_ptr*); - void (*s_session_error_callback)(std::shared_ptr*, int32_t error_code, const char* message, size_t message_len, std::pair* user_info_pairs, int user_info_pairs_len); + void (*s_session_error_callback)(std::shared_ptr*, int32_t error_code, const char* message, size_t message_len, std::pair* user_info_pairs, int user_info_pairs_len, bool is_client_reset); void (*s_progress_callback)(void*, uint64_t transferred_bytes, uint64_t transferrable_bytes); void (*s_wait_callback)(void* task_completion_source, int32_t error_code, const char* message, size_t message_len); - void bind_session(const std::string&, const realm::SyncConfig& config, std::shared_ptr session) - { - s_refresh_access_token_callback(new std::shared_ptr(session)); - } - void handle_session_error(std::shared_ptr session, SyncError error) { std::vector> user_info_pairs; - + for (const auto& p : error.user_info) { user_info_pairs.push_back(std::make_pair(const_cast(p.first.c_str()), const_cast(p.second.c_str()))); } - s_session_error_callback(new std::shared_ptr(session), error.error_code.value(), error.message.c_str(), error.message.length(), user_info_pairs.data(), user_info_pairs.size()); + s_session_error_callback(new std::shared_ptr(session), error.error_code.value(), error.message.c_str(), error.message.length(), user_info_pairs.data(), user_info_pairs.size(), error.is_client_reset_requested()); } } } extern "C" { -REALM_EXPORT void realm_syncsession_refresh_access_token(SharedSyncSession& session, const uint16_t* token_buf, size_t token_len, const uint16_t* server_path_buf, size_t server_path_len, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - Utf16StringAccessor token(token_buf, token_len); - Utf16StringAccessor server_path(server_path_buf, server_path_len); - - realm::util::Uri server_url(session->config().realm_url()); - server_url.set_path(server_path); - - session->refresh_access_token(token, server_url.recompose()); - }); -} - -REALM_EXPORT SharedSyncSession* realm_syncsession_get_from_path(const uint16_t* path_buf, size_t path_len, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - Utf16StringAccessor path(path_buf, path_len); - return new SharedSyncSession(SyncManager::shared().get_existing_active_session(path)); - }); -} REALM_EXPORT std::shared_ptr* realm_syncsession_get_user(const SharedSyncSession& session) { if (session->user() == nullptr) { return nullptr; } - + return new std::shared_ptr(session->user()); } @@ -101,22 +75,14 @@ REALM_EXPORT CSharpSessionState realm_syncsession_get_state(const SharedSyncSess } }); } - -REALM_EXPORT size_t realm_syncsession_get_uri(const SharedSyncSession& session, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - std::string uri(session->full_realm_url().value_or(session->config().realm_url())); - return stringdata_to_csharpstringbuffer(uri, buffer, buffer_length); - }); -} - + REALM_EXPORT size_t realm_syncsession_get_path(const SharedSyncSession& session, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) { return handle_errors(ex, [&] { return stringdata_to_csharpstringbuffer(session->path(), buffer, buffer_length); }); } - + REALM_EXPORT SyncSession* realm_syncsession_get_raw_pointer(const SharedSyncSession& session) { return session.get(); @@ -126,33 +92,32 @@ REALM_EXPORT void realm_syncsession_destroy(SharedSyncSession* session) { delete session; } - -REALM_EXPORT void realm_install_syncsession_callbacks(decltype(s_refresh_access_token_callback) refresh_callback, decltype(s_session_error_callback) session_error_callback, decltype(s_progress_callback) progress_callback, decltype(s_wait_callback) wait_callback) + +REALM_EXPORT void realm_syncsession_install_callbacks(decltype(s_session_error_callback) session_error_callback, decltype(s_progress_callback) progress_callback, decltype(s_wait_callback) wait_callback) { - s_refresh_access_token_callback = refresh_callback; s_session_error_callback = session_error_callback; s_progress_callback = progress_callback; s_wait_callback = wait_callback; } - + enum class CSharpNotifierType : uint8_t { Upload = 0, Download = 1 }; - + REALM_EXPORT uint64_t realm_syncsession_register_progress_notifier(const SharedSyncSession& session, void* managed_state, CSharpNotifierType direction, bool is_streaming, NativeException::Marshallable& ex) { return handle_errors(ex, [&] { auto notifier_direction = direction == CSharpNotifierType::Upload ? SyncSession::NotifierType::upload : SyncSession::NotifierType::download; - + return session->register_progress_notifier([managed_state](uint64_t transferred, uint64_t transferable) { s_progress_callback(managed_state, transferred, transferable); }, notifier_direction, is_streaming); }); } - + REALM_EXPORT void realm_syncsession_unregister_progress_notifier(const SharedSyncSession& session, uint64_t token, NativeException::Marshallable& ex) { return handle_errors(ex, [&] { @@ -166,7 +131,7 @@ REALM_EXPORT void realm_syncsession_wait(const SharedSyncSession& session, void* auto waiter = [task_completion_source](std::error_code error) { s_wait_callback(task_completion_source, error.value(), error.message().c_str(), error.message().length()); }; - + if (direction == CSharpNotifierType::Upload) { session->wait_for_upload_completion(waiter); } else { @@ -174,14 +139,14 @@ REALM_EXPORT void realm_syncsession_wait(const SharedSyncSession& session, void* } }); } - + REALM_EXPORT void realm_syncsession_report_error_for_testing(const SharedSyncSession& session, int err, const uint16_t* message_buf, size_t message_len, bool is_fatal) { Utf16StringAccessor message(message_buf, message_len); std::error_code error_code(err, realm::sync::protocol_error_category()); SyncSession::OnlyForTesting::handle_error(*session, SyncError{error_code, std::move(message), is_fatal}); } - + REALM_EXPORT void realm_syncsession_stop(const SharedSyncSession& session, NativeException::Marshallable& ex) { handle_errors(ex, [&] { @@ -196,21 +161,5 @@ REALM_EXPORT void realm_syncsession_start(const SharedSyncSession& session, Nati }); } -REALM_EXPORT void realm_syncsession_set_multiplex_identifier(const SharedSyncSession& session, const uint16_t* identifier_buf, size_t identifier_len, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - Utf16StringAccessor identifier(identifier_buf, identifier_len); - session->set_multiplex_identifier(identifier); - }); -} - -REALM_EXPORT void realm_syncsession_set_url_prefix(const SharedSyncSession& session, const uint16_t* prefix_buf, size_t prefix_len, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - Utf16StringAccessor prefix(prefix_buf, prefix_len); - session->set_url_prefix(prefix); - }); -} - } diff --git a/wrappers/src/sync_session_cs.hpp b/wrappers/src/sync_session_cs.hpp index 218554c8f5..128eac7dc4 100644 --- a/wrappers/src/sync_session_cs.hpp +++ b/wrappers/src/sync_session_cs.hpp @@ -26,7 +26,6 @@ namespace realm { namespace binding { extern void (*s_progress_callback)(void*, uint64_t transferred_bytes, uint64_t transferrable_bytes); - void bind_session(const std::string&, const realm::SyncConfig& config, std::shared_ptr session); void handle_session_error(std::shared_ptr session, SyncError error); } } diff --git a/wrappers/src/sync_user_cs.cpp b/wrappers/src/sync_user_cs.cpp index baad741755..5b669d952b 100644 --- a/wrappers/src/sync_user_cs.cpp +++ b/wrappers/src/sync_user_cs.cpp @@ -23,154 +23,374 @@ #include "sync/sync_manager.hpp" #include "sync/sync_user.hpp" #include "sync/sync_session.hpp" +#include "sync/app.hpp" +#include "app_cs.hpp" using namespace realm; using namespace realm::binding; +using namespace app; using SharedSyncUser = std::shared_ptr; using SharedSyncSession = std::shared_ptr; -extern "C" { +struct UserApiKey { + PrimitiveValue id; -REALM_EXPORT SharedSyncUser* realm_get_sync_user(const uint16_t* identity_buf, size_t identity_len, - const uint16_t* auth_server_url_buf, size_t auth_server_url_len, - const uint16_t* refresh_token_buf, size_t refresh_token_len, - bool is_admin, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - Utf16StringAccessor identity(identity_buf, identity_len); - Utf16StringAccessor auth_server_url(auth_server_url_buf, auth_server_url_len); - Utf16StringAccessor refresh_token(refresh_token_buf, refresh_token_len); - - auto user = SyncManager::shared().get_user({identity, auth_server_url}, refresh_token); - user->set_is_admin(is_admin); - return new SharedSyncUser(user); - }); -} + const char* key; + size_t key_len; -REALM_EXPORT SharedSyncUser* realm_get_admintoken_user(const uint16_t* auth_server_url_buf, size_t auth_server_url_len, - const uint16_t* token_buf, size_t token_len, - NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - Utf16StringAccessor auth_server_url(auth_server_url_buf, auth_server_url_len); - Utf16StringAccessor token(token_buf, token_len); - - return new SharedSyncUser(SyncManager::shared().get_admin_token_user(auth_server_url, token)); - }); -} - -REALM_EXPORT void realm_syncuser_set_refresh_token(SharedSyncUser& user, const uint16_t* token_buf, size_t token_len, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - Utf16StringAccessor token(token_buf, token_len); - user->update_refresh_token(token); - }); -} - -REALM_EXPORT SharedSyncUser* realm_get_current_sync_user(NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() -> SharedSyncUser* { - auto ptr = SyncManager::shared().get_current_user(); - if (ptr == nullptr) { - return nullptr; - } - - return new SharedSyncUser(std::move(ptr)); - }); -} - -REALM_EXPORT size_t realm_get_logged_in_users(SharedSyncUser** buffer, size_t buffer_length, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() -> size_t { - auto users = SyncManager::shared().all_logged_in_users(); - if (users.size() > buffer_length) { - return users.size(); - } - - if (users.size() <= 0) { - return 0; - } + const char* name; + size_t name_len; + + bool disabled; +}; + +namespace realm { + namespace binding { + void (*s_api_key_callback)(void* tcs_ptr, UserApiKey* api_keys, int api_keys_len, MarshaledAppError err); - for (size_t i = 0; i < users.size(); i++) { - buffer[i] = new SharedSyncUser(users.at(i)); + inline void invoke_api_key_callback(void* tcs_ptr, std::vector keys, util::Optional err) { + if (err) { + std::string error_category = err->error_code.message(); + MarshaledAppError app_error(err->message, error_category, err->link_to_server_logs, err->http_status_code); + + s_api_key_callback(tcs_ptr, nullptr, 0, app_error); + } + else { + std::vector marshalled_keys(keys.size()); + for (auto i = 0; i < keys.size(); i++) { + auto& api_key = keys[i]; + UserApiKey marshaled_key; + marshaled_key.id.type = realm::PropertyType::ObjectId; + marshaled_key.id.has_value = true; + auto bytes = api_key.id.to_bytes(); + for (int i = 0; i < 12; i++) + { + marshaled_key.id.value.object_id_bytes[i] = bytes[i]; + } + + if (api_key.key) { + marshaled_key.key = api_key.key->c_str(); + marshaled_key.key_len = api_key.key->size(); + } + else { + marshaled_key.key = nullptr; + marshaled_key.key_len = 0; + } + + marshaled_key.name = api_key.name.c_str(); + marshaled_key.name_len = api_key.name.size(); + marshaled_key.disabled = api_key.disabled; + + marshalled_keys[i] = marshaled_key; + } + + s_api_key_callback(tcs_ptr, marshalled_keys.data(), static_cast(marshalled_keys.size()), MarshaledAppError()); + } } - - return users.size(); - }); -} - -REALM_EXPORT SharedSyncUser* realm_get_logged_in_user(const uint16_t* identity_buf, size_t identity_len, const uint16_t* auth_server_url_buf, size_t auth_server_url_len, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() -> SharedSyncUser* { - Utf16StringAccessor identity(identity_buf, identity_len); - Utf16StringAccessor auth_server_url(auth_server_url_buf, auth_server_url_len); - - if (auto user = SyncManager::shared().get_existing_logged_in_user({identity, auth_server_url})) { - new SharedSyncUser(std::move(user)); + + inline void invoke_api_key_callback(void* tcs_ptr, App::UserAPIKey key, util::Optional err) { + std::vector api_keys; + api_keys.push_back(key); + + invoke_api_key_callback(tcs_ptr, api_keys, err); } - - return nullptr; - }); -} - -REALM_EXPORT SharedSyncSession* realm_syncuser_get_session(SharedSyncUser& user, const uint16_t* path_buf, size_t path_len, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() -> SharedSyncSession* { - Utf16StringAccessor path(path_buf, path_len); - if (auto session = user->session_for_on_disk_path(path)) { - return new SharedSyncSession(std::move(session)); + + inline AuthProvider to_auth_provider(const std::string& provider) { + if (provider == IdentityProviderAnonymous) { + return AuthProvider::ANONYMOUS; + } + + if (provider == IdentityProviderFacebook) { + return AuthProvider::FACEBOOK; + } + + if (provider == IdentityProviderGoogle) { + return AuthProvider::GOOGLE; + } + + if (provider == IdentityProviderApple) { + return AuthProvider::APPLE; + } + + if (provider == IdentityProviderCustom) { + return AuthProvider::CUSTOM; + } + + if (provider == IdentityProviderUsernamePassword) { + return AuthProvider::USERNAME_PASSWORD; + } + + if (provider == IdentityProviderFunction) { + return AuthProvider::FUNCTION; + } + + if (provider == IdentityProviderUserAPIKey) { + return AuthProvider::USER_API_KEY; + } + + if (provider == IdentityProviderServerAPIKey) { + return AuthProvider::SERVER_API_KEY; + } + + return (AuthProvider)999; } - return nullptr; - }); -} + } -REALM_EXPORT void realm_syncuser_log_out(SharedSyncUser& user, NativeException::Marshallable& ex) -{ - handle_errors(ex, [&] { - user->log_out(); - }); + void to_json(nlohmann::json& j, const SyncUserIdentity& i) + { + j = nlohmann::json{ + { "Id", i.id }, + { "Provider", to_auth_provider(i.provider_type)} + }; + } } -REALM_EXPORT size_t realm_syncuser_get_identity(SharedSyncUser& user, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - std::string identity(user->identity()); - return stringdata_to_csharpstringbuffer(identity, buffer, buffer_length); - }); -} +extern "C" { + REALM_EXPORT void realm_sync_user_initialize(decltype(s_api_key_callback) api_key_callback) + { + s_api_key_callback = api_key_callback; + } -REALM_EXPORT size_t realm_syncuser_get_refresh_token(SharedSyncUser& user, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - std::string refresh_token(user->refresh_token()); - return stringdata_to_csharpstringbuffer(refresh_token, buffer, buffer_length); - }); -} + REALM_EXPORT void realm_syncuser_log_out(SharedSyncUser& user, NativeException::Marshallable& ex) + { + handle_errors(ex, [&] { + user->log_out(); + }); + } -REALM_EXPORT size_t realm_syncuser_get_server_url(SharedSyncUser& user, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - const std::string& server_url(user->server_url()); - return stringdata_to_csharpstringbuffer(server_url, buffer, buffer_length); - }); -} - -REALM_EXPORT SyncUser::State realm_syncuser_get_state(SharedSyncUser& user, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&] { - return user->state(); - }); -} - -REALM_EXPORT bool realm_syncuser_get_is_admin(SharedSyncUser& user) -{ - return user->is_admin(); -} - -REALM_EXPORT void realm_syncuser_destroy(SharedSyncUser* user) -{ - delete user; -} + REALM_EXPORT size_t realm_syncuser_get_id(SharedSyncUser& user, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&] { + std::string identity(user->identity()); + return stringdata_to_csharpstringbuffer(identity, buffer, buffer_length); + }); + } + + REALM_EXPORT size_t realm_syncuser_get_refresh_token(SharedSyncUser& user, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&] { + std::string refresh_token(user->refresh_token()); + return stringdata_to_csharpstringbuffer(refresh_token, buffer, buffer_length); + }); + } + + REALM_EXPORT size_t realm_syncuser_get_access_token(SharedSyncUser& user, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&] { + std::string access_token(user->access_token()); + return stringdata_to_csharpstringbuffer(access_token, buffer, buffer_length); + }); + } + + REALM_EXPORT size_t realm_syncuser_get_device_id(SharedSyncUser& user, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&] { + std::string device_id(user->device_id()); + return stringdata_to_csharpstringbuffer(device_id, buffer, buffer_length); + }); + } + + REALM_EXPORT SyncUser::State realm_syncuser_get_state(SharedSyncUser& user, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&] { + return user->state(); + }); + } + + REALM_EXPORT AuthProvider realm_syncuser_get_auth_provider(SharedSyncUser& user, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&] { + return to_auth_provider(user->provider_type()); + }); + } + + REALM_EXPORT size_t realm_syncuser_get_custom_data(SharedSyncUser& user, uint16_t* buffer, size_t buffer_length, bool& is_null, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&] { + if (user->custom_data()) { + is_null = false; + std::string serialized_data = bson::Bson(user->custom_data().value()).to_string(); + return stringdata_to_csharpstringbuffer(serialized_data, buffer, buffer_length); + } + + is_null = true; + return (size_t)0; + }); + } + + REALM_EXPORT void realm_syncuser_refresh_custom_data(SharedSyncUser& user, void* tcs_ptr, NativeException::Marshallable& ex) + { + handle_errors(ex, [&] { + user->refresh_custom_data(get_callback_handler(tcs_ptr)); + }); + } + + enum class UserProfileField : uint8_t { + name, + email, + picture_url, + first_name, + last_name, + gender, + birthday, + min_age, + max_age, + }; + + REALM_EXPORT size_t realm_syncuser_get_profile_data(SharedSyncUser& user, UserProfileField profile_field, uint16_t* string_buffer, size_t buffer_size, bool& is_null, NativeException::Marshallable& ex) { + return handle_errors(ex, [&]() { + util::Optional field; + + switch (profile_field) + { + case UserProfileField::name: + field = user->user_profile().name; + break; + case UserProfileField::email: + field = user->user_profile().email; + break; + case UserProfileField::picture_url: + field = user->user_profile().picture_url; + break; + case UserProfileField::first_name: + field = user->user_profile().first_name; + break; + case UserProfileField::last_name: + field = user->user_profile().last_name; + break; + case UserProfileField::gender: + field = user->user_profile().gender; + break; + case UserProfileField::birthday: + field = user->user_profile().birthday; + break; + case UserProfileField::min_age: + field = user->user_profile().min_age; + break; + case UserProfileField::max_age: + field = user->user_profile().max_age; + break; + default: + REALM_UNREACHABLE(); + } + + if ((is_null = !field)) { + return (size_t)0; + } + + return stringdata_to_csharpstringbuffer(field.value(), string_buffer, buffer_size); + }); + } + + REALM_EXPORT size_t realm_syncuser_get_serialized_identities(SharedSyncUser& user, uint16_t* string_buffer, size_t buffer_size, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&]() -> size_t { + nlohmann::json j = user->identities(); + return stringdata_to_csharpstringbuffer(j.dump(), string_buffer, buffer_size); + }); + } + + REALM_EXPORT SharedApp* realm_syncuser_get_app(SharedSyncUser& user, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&] { + if (auto shared_app = user->sync_manager()->app().lock()) { + return new SharedApp(shared_app); + } + + return (SharedApp*)nullptr; + }); + } + + REALM_EXPORT void realm_syncuser_call_function(SharedSyncUser& user, SharedApp& app, uint16_t* function_name_buf, size_t function_name_len, uint16_t* args_buf, size_t args_len, void* tcs_ptr, NativeException::Marshallable& ex) + { + handle_errors(ex, [&] { + Utf16StringAccessor function_name(function_name_buf, function_name_len); + + auto args = to_array(args_buf, args_len); + app->call_function(user, function_name, args, get_bson_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_syncuser_link_credentials(SharedSyncUser& user, SharedApp& app, Credentials credentials, void* tcs_ptr, NativeException::Marshallable& ex) { + handle_errors(ex, [&]() { + auto app_credentials = credentials.to_app_credentials(); + app->link_user(user, app_credentials, get_user_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_syncuser_push_register(SharedSyncUser& user, SharedApp& app, uint16_t* service_buf, size_t service_len, uint16_t* token_buf, size_t token_len, void* tcs_ptr, NativeException::Marshallable& ex) + { + handle_errors(ex, [&] { + Utf16StringAccessor service(service_buf, service_len); + Utf16StringAccessor token(token_buf, token_len); + app->push_notification_client(service).register_device(token, user, get_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_syncuser_push_deregister(SharedSyncUser& user, SharedApp& app, uint16_t* service_buf, size_t service_len, void* tcs_ptr, NativeException::Marshallable& ex) + { + handle_errors(ex, [&] { + Utf16StringAccessor service(service_buf, service_len); + app->push_notification_client(service).deregister_device(user, get_callback_handler(tcs_ptr)); + }); + } + +#pragma region ApiKeys + + REALM_EXPORT void realm_syncuser_api_key_create(SharedSyncUser& user, SharedApp& app, uint16_t* name_buf, size_t name_len, void* tcs_ptr, NativeException::Marshallable& ex) + { + handle_errors(ex, [&] { + Utf16StringAccessor name(name_buf, name_len); + app->provider_client().create_api_key(name, user, [tcs_ptr](App::UserAPIKey api_key, util::Optional err) { + invoke_api_key_callback(tcs_ptr, api_key, err); + }); + }); + } + + REALM_EXPORT void realm_syncuser_api_key_fetch(SharedSyncUser& user, SharedApp& app, PrimitiveValue& id, void* tcs_ptr, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&] { + app->provider_client().fetch_api_key(to_object_id(id), user, [tcs_ptr](App::UserAPIKey api_key, util::Optional err) { + invoke_api_key_callback(tcs_ptr, api_key, err); + }); + }); + } + + REALM_EXPORT void realm_syncuser_api_key_fetch_all(SharedSyncUser& user, SharedApp& app, void* tcs_ptr, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&] { + app->provider_client().fetch_api_keys(user, [tcs_ptr](std::vector api_keys, util::Optional err) { + invoke_api_key_callback(tcs_ptr, api_keys, err); + }); + }); + } + + REALM_EXPORT void realm_syncuser_api_key_delete(SharedSyncUser& user, SharedApp& app, PrimitiveValue& id, void* tcs_ptr, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&] { + app->provider_client().delete_api_key(to_object_id(id), user, get_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_syncuser_api_key_disable(SharedSyncUser& user, SharedApp& app, PrimitiveValue& id, void* tcs_ptr, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&] { + app->provider_client().disable_api_key(to_object_id(id), user, get_callback_handler(tcs_ptr)); + }); + } + + REALM_EXPORT void realm_syncuser_api_key_enable(SharedSyncUser& user, SharedApp& app, PrimitiveValue& id, void* tcs_ptr, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&] { + app->provider_client().enable_api_key(to_object_id(id), user, get_callback_handler(tcs_ptr)); + }); + } + +#pragma endregion + REALM_EXPORT void realm_syncuser_destroy(SharedSyncUser* user) + { + delete user; + } } diff --git a/wrappers/src/table_cs.cpp b/wrappers/src/table_cs.cpp index 3fcfb70b32..0285635ab5 100644 --- a/wrappers/src/table_cs.cpp +++ b/wrappers/src/table_cs.cpp @@ -49,7 +49,7 @@ Object* get_object_for_primarykey(TableRef& table, SharedRealm& realm, const T& return nullptr; return new Object(realm, object_schema, table->get_object(obj_key)); - }); + }); } extern "C" { @@ -82,20 +82,6 @@ REALM_EXPORT Results* table_create_results(TableRef& table, SharedRealm& realm, }); } -REALM_EXPORT size_t table_get_name(TableRef& table, uint16_t* string_buffer, size_t buffer_size, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - return stringdata_to_csharpstringbuffer(table->get_name(), string_buffer, buffer_size); - }); -} - -REALM_EXPORT size_t table_get_column_name(TableRef& table, const ColKey column_key, uint16_t* string_buffer, size_t buffer_size, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - return stringdata_to_csharpstringbuffer(table->get_column_name(column_key), string_buffer, buffer_size); - }); -} - REALM_EXPORT Object* table_get_object(TableRef& table, SharedRealm& realm, ObjKey object_key, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() -> Object* { @@ -110,18 +96,32 @@ REALM_EXPORT Object* table_get_object(TableRef& table, SharedRealm& realm, ObjKe }); } -REALM_EXPORT Object* table_get_object_for_int_primarykey(TableRef& table, SharedRealm& realm, int64_t value, NativeException::Marshallable& ex) +REALM_EXPORT Object* table_get_object_for_primitive_primarykey(TableRef& table, SharedRealm& realm, PrimitiveValue& primitive, NativeException::Marshallable& ex) { - return get_object_for_primarykey(table, realm, value, ex); -} - -REALM_EXPORT Object* table_get_object_for_null_primarykey(TableRef& table, SharedRealm& realm, NativeException::Marshallable& ex) -{ - return get_object_for_primarykey(table, realm, null{}, ex); + if (!primitive.has_value) { + return get_object_for_primarykey(table, realm, null{}, ex); + } + + switch (primitive.type) { + case realm::PropertyType::Int: + case realm::PropertyType::Int | realm::PropertyType::Nullable: + return get_object_for_primarykey(table, realm, primitive.value.int_value, ex); + + case realm::PropertyType::ObjectId: + case realm::PropertyType::ObjectId | realm::PropertyType::Nullable: + return get_object_for_primarykey(table, realm, to_object_id(primitive), ex); + + default: + REALM_UNREACHABLE(); + } } REALM_EXPORT Object* table_get_object_for_string_primarykey(TableRef& table, SharedRealm& realm, uint16_t* value, size_t value_len, NativeException::Marshallable& ex) { + if (value == nullptr) { + return get_object_for_primarykey(table, realm, null{}, ex); + } + Utf16StringAccessor str(value, value_len); return get_object_for_primarykey(table, realm, StringData(str), ex); } diff --git a/wrappers/src/transport_cs.cpp b/wrappers/src/transport_cs.cpp new file mode 100644 index 0000000000..ba7babd79c --- /dev/null +++ b/wrappers/src/transport_cs.cpp @@ -0,0 +1,120 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "transport_cs.hpp" +#include "marshalling.hpp" + +#include +#include "util/scheduler.hpp" +#include "realm_export_decls.hpp" +#include +#include "sync/generic_network_transport.hpp" + +using namespace realm::util; +using namespace realm::app; +using namespace realm::binding; + +namespace realm { +namespace binding { + GenericNetworkTransport::NetworkTransportFactory s_transport_factory; +} +} + +struct HttpClientRequest { + HttpMethod method; + + const char* url; + size_t url_len; + + uint64_t timeout_ms; + + std::pair* headers; + int headers_len; + + const char* body; + size_t body_len; +}; + +using ExecuteRequest = void(HttpClientRequest request, void* callback); +using ResponseFunction = std::function; + +struct HttpClientResponse { + int http_status_code; + int custom_status_code; + + uint16_t* body_buf; + size_t body_len; +}; + +struct HttpClientTransport : public GenericNetworkTransport { +public: + HttpClientTransport(ExecuteRequest* execute) + : m_execute(execute) + { } + + void send_request_to_server(const Request request, ResponseFunction completionBlock) override { + std::vector> headers; + for (auto& kvp : request.headers) { + headers.push_back(std::make_pair(const_cast(kvp.first.c_str()), const_cast(kvp.second.c_str()))); + } + + HttpClientRequest client_request = { + request.method, + request.url.c_str(), + request.url.length(), + request.timeout_ms, + headers.data(), + (int)headers.size(), + request.body.c_str(), + request.body.length() + }; + + m_execute(std::move(client_request), new ResponseFunction(completionBlock)); + } +private: + ExecuteRequest* m_execute; +}; + +extern "C" { + REALM_EXPORT void realm_http_transport_install_callbacks(ExecuteRequest* execute) + { + s_transport_factory = [=]() -> std::unique_ptr { + return std::make_unique(execute); + }; + } + + REALM_EXPORT void realm_http_transport_respond(HttpClientResponse client_response, std::pair* headers, int headers_len, void* function_ptr) + { + std::map headers_map; + for (auto i = 0; i < headers_len; i++) { + auto header = headers[i]; + headers_map.emplace(header.first, header.second); + } + + Response response = { + client_response.http_status_code, + client_response.custom_status_code, + std::move(headers_map), + Utf16StringAccessor(client_response.body_buf, client_response.body_len) + }; + + auto& func = *reinterpret_cast*>(function_ptr); + func(std::move(response)); + delete& func; + } +} diff --git a/Tests/Realm.Tests/Server/DummyNotificationHandler.cs b/wrappers/src/transport_cs.hpp similarity index 68% rename from Tests/Realm.Tests/Server/DummyNotificationHandler.cs rename to wrappers/src/transport_cs.hpp index 76a46d44ff..ea3c730bbe 100644 --- a/Tests/Realm.Tests/Server/DummyNotificationHandler.cs +++ b/wrappers/src/transport_cs.hpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2019 Realm Inc. +// 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. @@ -16,15 +16,17 @@ // //////////////////////////////////////////////////////////////////////////// -using System.Threading.Tasks; -using Realms.Server; +#ifndef TRANSPORT_CS_HPP +#define TRANSPORT_CS_HPP -namespace Realms.Tests.Server -{ - internal class DummyNotificationHandler : INotificationHandler - { - public Task HandleChangeAsync(IChangeDetails details) => Task.CompletedTask; +#include "sync/generic_network_transport.hpp" - public bool ShouldHandle(string path) => false; - } -} \ No newline at end of file +using namespace realm::app; + +namespace realm { +namespace binding { + extern GenericNetworkTransport::NetworkTransportFactory s_transport_factory; +} +} + +#endif /* defined(TRANSPORT_CS_HPP) */